libtommath: Fix possible integer overflow CVE-2023-36328
[heimdal.git] / kdc / httpkadmind.c
blob1d64e84eb8a496368f01c20f98857b957915d0c2
1 /*
2 * Copyright (c) 2020 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
37 #define _XOPEN_SOURCE_EXTENDED 1
38 #define _DEFAULT_SOURCE 1
39 #define _BSD_SOURCE 1
40 #define _GNU_SOURCE 1
42 #include <sys/socket.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/time.h>
46 #include <ctype.h>
47 #include <dlfcn.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <pthread.h>
51 #include <signal.h>
52 #include <stdarg.h>
53 #include <stddef.h>
54 #include <stdint.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60 #include <netdb.h>
61 #include <netinet/in.h>
62 #include <netinet/ip.h>
64 #include <microhttpd.h>
65 #include "kdc_locl.h"
66 #include "token_validator_plugin.h"
67 #include <getarg.h>
68 #include <roken.h>
69 #include <krb5.h>
70 #include <gssapi/gssapi.h>
71 #include <gssapi/gssapi_krb5.h>
72 #include <hx509.h>
73 #include "../lib/hx509/hx_locl.h"
74 #include <hx509-private.h>
75 #include <kadm5/admin.h>
76 #include <kadm5/private.h>
77 #include <kadm5/kadm5_err.h>
79 #define heim_pcontext krb5_context
80 #define heim_pconfig krb5_context
81 #include <heimbase-svc.h>
83 #if MHD_VERSION < 0x00097002 || defined(MHD_YES)
84 /* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
85 #ifdef MHD_YES
86 #undef MHD_YES
87 #undef MHD_NO
88 #endif
89 enum MHD_Result { MHD_NO = 0, MHD_YES = 1 };
90 #define MHD_YES 1
91 #define MHD_NO 0
92 typedef int heim_mhd_result;
93 #else
94 typedef enum MHD_Result heim_mhd_result;
95 #endif
97 #define BODYLEN_IS_STRLEN (~0)
100 * Libmicrohttpd is not the easiest API to use. It's got issues.
102 * One of the issues is how responses are handled, and the return value of the
103 * resource handler (MHD_NO -> close the connection, MHD_YES -> send response).
104 * Note that the handler could return MHD_YES without having set an HTTP
105 * response.
107 * There's memory management issues as well.
109 * Here we have to be careful about return values.
111 * Some of the functions defined here return just a krb5_error_code without
112 * having set an HTTP response on error.
113 * Others do set an HTTP response on error.
114 * The convention is to either set an HTTP response on error, or not at all,
115 * but not a mix of errors where for some the function will set a response and
116 * for others it won't.
118 * We do use some system error codes to stand in for errors here.
119 * Specifically:
121 * - EACCES -> authorization failed
122 * - EINVAL -> bad API usage
123 * - ENOSYS -> missing CSRF token but CSRF token required
125 * FIXME: We should rely only on krb5_set_error_message() and friends and make
126 * error responses only in route(), mapping krb5_error_code values to
127 * HTTP status codes. This would simplify the error handling convention
128 * here.
131 struct free_tend_list {
132 void *freeme1;
133 void *freeme2;
134 struct free_tend_list *next;
137 /* Our request description structure */
138 typedef struct kadmin_request_desc {
139 HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
141 struct MHD_Connection *connection;
142 krb5_times token_times;
144 * FIXME
146 * Currently we re-use the authz framework from bx509d, using an
147 * `hx509_request' instance (an abstraction for CSRs) to represent the
148 * request because that is what the authz plugin uses that implements the
149 * policy we want checked here.
151 * This is inappropriate in the long-term in two ways:
153 * - the policy for certificates deals in SANs and EKUs, whereas the
154 * policy for ext_keytab deals in host-based service principal names,
155 * and there is not a one-to-one mapping of service names to EKUs;
157 * - using a type from libhx509 for representing requests for things that
158 * aren't certificates is really not appropriate no matter how similar
159 * the use cases for this all might be.
161 * What we need to do is develop a library that can represent requests for
162 * credentials via naming attributes like SANs and Kerberos principal
163 * names, but more arbitrary still than what `hx509_request' supports, and
164 * then invokes a plugin.
166 * Also, we might want to develop an in-tree authorization solution that is
167 * richer than what kadmin.acl supports now, storing grants in HDB entries
168 * and/or similar places.
170 * For expediency we use `hx509_request' here for now, impedance mismatches
171 * be damned.
173 hx509_request req; /* For authz only */
174 struct free_tend_list *free_list;
175 struct MHD_PostProcessor *pp;
176 heim_array_t service_names;
177 heim_array_t hostnames;
178 heim_array_t spns;
179 krb5_principal cprinc;
180 krb5_keytab keytab;
181 krb5_storage *sp;
182 void *kadm_handle;
183 char *realm;
184 char *keytab_name;
185 char *freeme1;
186 char *enctypes;
187 char *cache_control;
188 char *csrf_token;
189 const char *method;
190 krb5_timestamp pw_end;
191 size_t post_data_size;
192 unsigned int response_set:1;
193 unsigned int materialize:1;
194 unsigned int rotate_now:1;
195 unsigned int rotate:1;
196 unsigned int revoke:1;
197 unsigned int create:1;
198 unsigned int ro:1;
199 unsigned int is_self:1;
200 char frombuf[128];
201 } *kadmin_request_desc;
203 static void
204 audit_trail(kadmin_request_desc r, krb5_error_code ret)
206 const char *retname = NULL;
209 * Get a symbolic name for some error codes.
211 * Really, libcom_err should have a primitive for this, and ours could, but
212 * we can't use a system libcom_err if we extend ours.
214 #define CASE(x) case x : retname = #x; break
215 switch (ret) {
216 case ENOSYS: retname = "ECSRFTOKENREQD"; break;
217 CASE(EINVAL);
218 CASE(ENOMEM);
219 CASE(EACCES);
220 CASE(HDB_ERR_NOT_FOUND_HERE);
221 CASE(HDB_ERR_WRONG_REALM);
222 CASE(HDB_ERR_EXISTS);
223 CASE(HDB_ERR_KVNO_NOT_FOUND);
224 CASE(HDB_ERR_NOENTRY);
225 CASE(HDB_ERR_NO_MKEY);
226 CASE(KRB5_KDC_UNREACH);
227 CASE(KADM5_FAILURE);
228 CASE(KADM5_AUTH_GET);
229 CASE(KADM5_AUTH_ADD);
230 CASE(KADM5_AUTH_MODIFY);
231 CASE(KADM5_AUTH_DELETE);
232 CASE(KADM5_AUTH_INSUFFICIENT);
233 CASE(KADM5_BAD_DB);
234 CASE(KADM5_DUP);
235 CASE(KADM5_RPC_ERROR);
236 CASE(KADM5_NO_SRV);
237 CASE(KADM5_BAD_HIST_KEY);
238 CASE(KADM5_NOT_INIT);
239 CASE(KADM5_UNK_PRINC);
240 CASE(KADM5_UNK_POLICY);
241 CASE(KADM5_BAD_MASK);
242 CASE(KADM5_BAD_CLASS);
243 CASE(KADM5_BAD_LENGTH);
244 CASE(KADM5_BAD_POLICY);
245 CASE(KADM5_BAD_PRINCIPAL);
246 CASE(KADM5_BAD_AUX_ATTR);
247 CASE(KADM5_BAD_HISTORY);
248 CASE(KADM5_BAD_MIN_PASS_LIFE);
249 CASE(KADM5_PASS_Q_TOOSHORT);
250 CASE(KADM5_PASS_Q_CLASS);
251 CASE(KADM5_PASS_Q_DICT);
252 CASE(KADM5_PASS_Q_GENERIC);
253 CASE(KADM5_PASS_REUSE);
254 CASE(KADM5_PASS_TOOSOON);
255 CASE(KADM5_POLICY_REF);
256 CASE(KADM5_INIT);
257 CASE(KADM5_BAD_PASSWORD);
258 CASE(KADM5_PROTECT_PRINCIPAL);
259 CASE(KADM5_BAD_SERVER_HANDLE);
260 CASE(KADM5_BAD_STRUCT_VERSION);
261 CASE(KADM5_OLD_STRUCT_VERSION);
262 CASE(KADM5_NEW_STRUCT_VERSION);
263 CASE(KADM5_BAD_API_VERSION);
264 CASE(KADM5_OLD_LIB_API_VERSION);
265 CASE(KADM5_OLD_SERVER_API_VERSION);
266 CASE(KADM5_NEW_LIB_API_VERSION);
267 CASE(KADM5_NEW_SERVER_API_VERSION);
268 CASE(KADM5_SECURE_PRINC_MISSING);
269 CASE(KADM5_NO_RENAME_SALT);
270 CASE(KADM5_BAD_CLIENT_PARAMS);
271 CASE(KADM5_BAD_SERVER_PARAMS);
272 CASE(KADM5_AUTH_LIST);
273 CASE(KADM5_AUTH_CHANGEPW);
274 CASE(KADM5_BAD_TL_TYPE);
275 CASE(KADM5_MISSING_CONF_PARAMS);
276 CASE(KADM5_BAD_SERVER_NAME);
277 CASE(KADM5_KS_TUPLE_NOSUPP);
278 CASE(KADM5_SETKEY3_ETYPE_MISMATCH);
279 CASE(KADM5_DECRYPT_USAGE_NOSUPP);
280 CASE(KADM5_POLICY_OP_NOSUPP);
281 CASE(KADM5_KEEPOLD_NOSUPP);
282 CASE(KADM5_AUTH_GET_KEYS);
283 CASE(KADM5_ALREADY_LOCKED);
284 CASE(KADM5_NOT_LOCKED);
285 CASE(KADM5_LOG_CORRUPT);
286 CASE(KADM5_LOG_NEEDS_UPGRADE);
287 CASE(KADM5_BAD_SERVER_HOOK);
288 CASE(KADM5_SERVER_HOOK_NOT_FOUND);
289 CASE(KADM5_OLD_SERVER_HOOK_VERSION);
290 CASE(KADM5_NEW_SERVER_HOOK_VERSION);
291 CASE(KADM5_READ_ONLY);
292 case 0:
293 retname = "SUCCESS";
294 break;
295 default:
296 retname = NULL;
297 break;
299 heim_audit_trail((heim_svc_req_desc)r, ret, retname);
302 static krb5_log_facility *logfac;
303 static pthread_key_t k5ctx;
305 static krb5_error_code
306 get_krb5_context(krb5_context *contextp)
308 krb5_error_code ret;
310 if ((*contextp = pthread_getspecific(k5ctx)))
311 return 0;
313 ret = krb5_init_context(contextp);
314 /* XXX krb5_set_log_dest(), warn_dest, debug_dest */
315 if (ret == 0)
316 (void) pthread_setspecific(k5ctx, *contextp);
317 return ret;
320 typedef enum {
321 CSRF_PROT_UNSPEC = 0,
322 CSRF_PROT_GET_WITH_HEADER = 1,
323 CSRF_PROT_GET_WITH_TOKEN = 2,
324 CSRF_PROT_POST_WITH_HEADER = 8,
325 CSRF_PROT_POST_WITH_TOKEN = 16,
326 } csrf_protection_type;
328 static csrf_protection_type csrf_prot_type = CSRF_PROT_UNSPEC;
329 static int port = -1;
330 static int help_flag;
331 static int allow_GET_flag = -1;
332 static int daemonize;
333 static int daemon_child_fd = -1;
334 static int local_hdb;
335 static int local_hdb_read_only;
336 static int read_only;
337 static int verbose_counter;
338 static int version_flag;
339 static int reverse_proxied_flag;
340 static int thread_per_client_flag;
341 struct getarg_strings audiences;
342 static getarg_strings csrf_prot_type_strs;
343 static const char *csrf_header = "X-CSRF";
344 static const char *cert_file;
345 static const char *priv_key_file;
346 static const char *cache_dir;
347 static const char *realm;
348 static const char *hdb;
349 static const char *primary_server_URI;
350 static const char *kadmin_server;
351 static const char *writable_kadmin_server;
352 static const char *stash_file;
353 static const char *kadmin_client_name = "httpkadmind/admin";
354 static const char *kadmin_client_keytab;
355 static struct getarg_strings auth_types;
357 #define set_conf(c, f, v, b) \
358 if (v) { \
359 if (((c).f = strdup(v)) == NULL) \
360 goto enomem; \
361 conf.mask |= b; \
365 * Does NOT set an HTTP response, naturally, as it doesn't even have access to
366 * the connection.
368 static krb5_error_code
369 get_kadm_handle(krb5_context context,
370 const char *want_realm,
371 int want_write,
372 void **kadm_handle)
374 kadm5_config_params conf;
375 krb5_error_code ret;
378 * If the caller wants to write and we are configured to redirect in that
379 * case, then trigger a redirect by returning KADM5_READ_ONLY.
381 if (want_write && local_hdb_read_only && primary_server_URI)
382 return KADM5_READ_ONLY;
383 if (want_write && read_only)
384 return KADM5_READ_ONLY;
387 * Configure kadm5 connection.
389 * Note that all of these are optional, and will be found in krb5.conf or,
390 * in some cases, in DNS, as needed.
392 memset(&conf, 0, sizeof(conf));
393 conf.realm = NULL;
394 conf.dbname = NULL;
395 conf.stash_file = NULL;
396 conf.admin_server = NULL;
397 conf.readonly_admin_server = NULL;
398 set_conf(conf, realm, want_realm, KADM5_CONFIG_REALM);
399 set_conf(conf, dbname, hdb, KADM5_CONFIG_DBNAME);
400 set_conf(conf, stash_file, stash_file, KADM5_CONFIG_STASH_FILE);
403 * If we have a local HDB we'll use it if we can. If the local HDB is
404 * read-only and the caller wants to write, then we won't use the local
405 * HDB, naturally.
407 if (local_hdb && (!local_hdb_read_only || !want_write)) {
408 ret = kadm5_s_init_with_password_ctx(context,
409 kadmin_client_name,
410 NULL, /* password */
411 NULL, /* service_name */
412 &conf,
413 0, /* struct_version */
414 0, /* api_version */
415 kadm_handle);
416 goto out;
420 * Remote connection. This will connect to a read-only kadmind if
421 * possible, and if so, reconnect to a writable kadmind as needed.
423 * Note that kadmin_client_keytab can be an HDB: or HDBGET: keytab.
425 if (writable_kadmin_server)
426 set_conf(conf, admin_server, writable_kadmin_server, KADM5_CONFIG_ADMIN_SERVER);
427 if (kadmin_server)
428 set_conf(conf, readonly_admin_server, kadmin_server,
429 KADM5_CONFIG_READONLY_ADMIN_SERVER);
430 ret = kadm5_c_init_with_skey_ctx(context,
431 kadmin_client_name,
432 kadmin_client_keytab,
433 KADM5_ADMIN_SERVICE,
434 &conf,
435 0, /* struct_version */
436 0, /* api_version */
437 kadm_handle);
438 goto out;
440 enomem:
441 ret = krb5_enomem(context);
443 out:
444 free(conf.readonly_admin_server);
445 free(conf.admin_server);
446 free(conf.stash_file);
447 free(conf.dbname);
448 free(conf.realm);
449 return ret;
452 static krb5_error_code resp(kadmin_request_desc, int, krb5_error_code,
453 enum MHD_ResponseMemoryMode, const char *,
454 const void *, size_t, const char *);
455 static krb5_error_code bad_req(kadmin_request_desc, krb5_error_code, int,
456 const char *, ...)
457 HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
459 static krb5_error_code bad_enomem(kadmin_request_desc, krb5_error_code);
460 static krb5_error_code bad_400(kadmin_request_desc, krb5_error_code, const char *);
461 static krb5_error_code bad_401(kadmin_request_desc, const char *);
462 static krb5_error_code bad_403(kadmin_request_desc, krb5_error_code, const char *);
463 static krb5_error_code bad_404(kadmin_request_desc, krb5_error_code, const char *);
464 static krb5_error_code bad_405(kadmin_request_desc, const char *);
465 /*static krb5_error_code bad_500(kadmin_request_desc, krb5_error_code, const char *);*/
466 static krb5_error_code bad_503(kadmin_request_desc, krb5_error_code, const char *);
468 static int
469 validate_token(kadmin_request_desc r)
471 krb5_error_code ret;
472 const char *token;
473 const char *host;
474 char token_type[64]; /* Plenty */
475 char *p;
476 krb5_data tok;
477 size_t host_len, brk, i;
479 memset(&r->token_times, 0, sizeof(r->token_times));
480 host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
481 MHD_HTTP_HEADER_HOST);
482 if (host == NULL)
483 return bad_400(r, EINVAL, "Host header is missing");
485 /* Exclude port number here (IPv6-safe because of the below) */
486 host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
488 token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
489 MHD_HTTP_HEADER_AUTHORIZATION);
490 if (token == NULL)
491 return bad_401(r, "Authorization token is missing");
492 brk = strcspn(token, " \t");
493 if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
494 return bad_401(r, "Authorization token is missing");
495 memcpy(token_type, token, brk);
496 token_type[brk] = '\0';
497 token += brk + 1;
498 tok.length = strlen(token);
499 tok.data = (void *)(uintptr_t)token;
501 for (i = 0; i < audiences.num_strings; i++)
502 if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
503 audiences.strings[i][host_len] == '\0')
504 break;
505 if (i == audiences.num_strings)
506 return bad_403(r, EINVAL, "Host: value is not accepted here");
508 r->sname = strdup(host); /* No need to check for ENOMEM here */
510 ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
511 (const char **)&audiences.strings[i], 1,
512 &r->cprinc, &r->token_times);
513 if (ret)
514 return bad_403(r, ret, "Token validation failed");
515 if (r->cprinc == NULL)
516 return bad_403(r, ret,
517 "Could not extract a principal name from token");
518 ret = krb5_unparse_name(r->context, r->cprinc, &r->cname);
519 if (ret)
520 return bad_503(r, ret,
521 "Could not extract a principal name from token");
522 return 0;
525 static void
526 k5_free_context(void *ctx)
528 krb5_free_context(ctx);
531 #ifndef HAVE_UNLINKAT
532 static int
533 unlink1file(const char *dname, const char *name)
535 char p[PATH_MAX];
537 if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
538 strlcat(p, "/", sizeof(p)) < sizeof(p) &&
539 strlcat(p, name, sizeof(p)) < sizeof(p))
540 return unlink(p);
541 return ERANGE;
543 #endif
545 static void
546 rm_cache_dir(void)
548 struct dirent *e;
549 DIR *d;
552 * This works, but not on Win32:
554 * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
556 * We make no directories in `cache_dir', so we need not recurse.
558 if ((d = opendir(cache_dir)) == NULL)
559 return;
561 while ((e = readdir(d))) {
562 #ifdef HAVE_UNLINKAT
564 * Because unlinkat() takes a directory FD, implementing one for
565 * libroken is tricky at best. Instead we might want to implement an
566 * rm_dash_rf() function in lib/roken.
568 (void) unlinkat(dirfd(d), e->d_name, 0);
569 #else
570 (void) unlink1file(cache_dir, e->d_name);
571 #endif
573 (void) closedir(d);
574 (void) rmdir(cache_dir);
578 * Work around older libmicrohttpd not strduping response header values when
579 * set.
581 static HEIMDAL_THREAD_LOCAL struct redirect_uri {
582 char uri[4096];
583 size_t len;
584 size_t first_param;
585 int valid;
586 } redirect_uri;
588 static void
589 redirect_uri_appends(struct redirect_uri *redirect,
590 const char *s)
592 size_t sz, len;
593 char *p;
595 if (!redirect->valid || redirect->len >= sizeof(redirect->uri) - 1) {
596 redirect->valid = 0;
597 return;
599 /* Optimize strlcpy by using redirect->uri + redirect->len */
600 p = redirect->uri + redirect->len;
601 sz = sizeof(redirect->uri) - redirect->len;
602 if ((len = strlcpy(p, s, sz)) >= sz)
603 redirect->valid = 0;
604 else
605 redirect->len += len;
608 static heim_mhd_result
609 make_redirect_uri_param_cb(void *d,
610 enum MHD_ValueKind kind,
611 const char *key,
612 const char *val)
614 struct redirect_uri *redirect = d;
616 redirect_uri_appends(redirect, redirect->first_param ? "?" : "&");
617 redirect_uri_appends(redirect, key);
618 if (val) {
619 redirect_uri_appends(redirect, "=");
620 redirect_uri_appends(redirect, val);
622 redirect->first_param = 0;
623 return MHD_YES;
626 static const char *
627 make_redirect_uri(kadmin_request_desc r, const char *base)
629 redirect_uri.len = 0;
630 redirect_uri.uri[0] = '\0';
631 redirect_uri.valid = 1;
632 redirect_uri.first_param = 1;
634 redirect_uri_appends(&redirect_uri, base); /* Redirect to primary URI base */
635 redirect_uri_appends(&redirect_uri, r->reqtype); /* URI local-part */
636 (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
637 make_redirect_uri_param_cb,
638 &redirect_uri);
639 return redirect_uri.valid ? redirect_uri.uri : NULL;
644 * XXX Shouldn't be a body, but a status message. The body should be
645 * configurable to be from a file. MHD doesn't give us a way to set the
646 * response status message though, just the body.
648 * Calls audit_trail().
650 * Returns -1 if something terrible happened, which should ultimately cause
651 * route() to return MHD_NO, which should cause libmicrohttpd to close the
652 * connection to the user-agent.
654 * Returns 0 in all other cases.
656 static krb5_error_code
657 resp(kadmin_request_desc r,
658 int http_status_code,
659 krb5_error_code ret,
660 enum MHD_ResponseMemoryMode rmmode,
661 const char *content_type,
662 const void *body,
663 size_t bodylen,
664 const char *token)
666 struct MHD_Response *response;
667 int mret = MHD_YES;
669 if (r->response_set) {
670 krb5_log_msg(r->context, logfac, 1, NULL,
671 "Internal error; attempted to set a second response");
672 return 0;
675 (void) gettimeofday(&r->tv_end, NULL);
676 audit_trail(r, ret);
678 if (body && bodylen == BODYLEN_IS_STRLEN)
679 bodylen = strlen(body);
681 response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
682 rmmode);
683 if (response == NULL)
684 return -1;
685 mret = MHD_add_response_header(response, MHD_HTTP_HEADER_AGE, "0");
686 if (mret == MHD_YES && http_status_code == MHD_HTTP_OK) {
687 krb5_timestamp now;
689 free(r->cache_control);
690 r->cache_control = NULL;
691 krb5_timeofday(r->context, &now);
692 if (r->pw_end && r->pw_end > now) {
693 if (asprintf(&r->cache_control, "no-store, max-age=%lld",
694 (long long)r->pw_end - now) == -1 ||
695 r->cache_control == NULL)
696 /* Soft handling of ENOMEM here */
697 mret = MHD_add_response_header(response,
698 MHD_HTTP_HEADER_CACHE_CONTROL,
699 "no-store, max-age=3600");
700 else
701 mret = MHD_add_response_header(response,
702 MHD_HTTP_HEADER_CACHE_CONTROL,
703 r->cache_control);
705 } else
706 mret = MHD_add_response_header(response,
707 MHD_HTTP_HEADER_CACHE_CONTROL,
708 "no-store, max-age=0");
709 } else {
710 /* Shouldn't happen */
711 mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
712 "no-store, max-age=0");
714 if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
715 size_t i;
717 if (auth_types.num_strings < 1)
718 http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
719 else
720 for (i = 0; mret == MHD_YES && i < auth_types.num_strings; i++)
721 mret = MHD_add_response_header(response,
722 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
723 auth_types.strings[i]);
724 } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
725 const char *redir = make_redirect_uri(r, primary_server_URI);
727 if (redir)
728 mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
729 redir);
730 else
731 /* XXX Find a way to set a new response body; log */
732 http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
735 if (mret == MHD_YES && r->csrf_token)
736 mret = MHD_add_response_header(response,
737 "X-CSRF-Token",
738 r->csrf_token);
740 if (mret == MHD_YES && content_type) {
741 mret = MHD_add_response_header(response,
742 MHD_HTTP_HEADER_CONTENT_TYPE,
743 content_type);
745 if (mret != MHD_NO)
746 mret = MHD_queue_response(r->connection, http_status_code, response);
747 MHD_destroy_response(response);
748 r->response_set = 1;
749 return mret == MHD_NO ? -1 : 0;
752 static krb5_error_code
753 bad_reqv(kadmin_request_desc r,
754 krb5_error_code code,
755 int http_status_code,
756 const char *fmt,
757 va_list ap)
759 krb5_error_code ret;
760 krb5_context context = NULL;
761 const char *k5msg = NULL;
762 const char *emsg = NULL;
763 char *formatted = NULL;
764 char *msg = NULL;
766 context = r->context;
767 if (r->hcontext && r->kv)
768 heim_audit_setkv_number((heim_svc_req_desc)r, "http-status-code",
769 http_status_code);
770 (void) gettimeofday(&r->tv_end, NULL);
771 if (code == ENOMEM) {
772 if (context)
773 krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
774 return resp(r, http_status_code, code, MHD_RESPMEM_PERSISTENT,
775 NULL, fmt, BODYLEN_IS_STRLEN, NULL);
778 if (code) {
779 if (context)
780 emsg = k5msg = krb5_get_error_message(context, code);
781 else
782 emsg = strerror(code);
785 ret = vasprintf(&formatted, fmt, ap) == -1;
786 if (code) {
787 if (ret > -1 && formatted)
788 ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
789 } else {
790 msg = formatted;
791 formatted = NULL;
793 if (r->hcontext)
794 heim_audit_addreason((heim_svc_req_desc)r, "%s", formatted);
795 krb5_free_error_message(context, k5msg);
797 if (ret == -1 || msg == NULL) {
798 if (context)
799 krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
800 return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, ENOMEM,
801 MHD_RESPMEM_PERSISTENT, NULL,
802 "Out of memory", BODYLEN_IS_STRLEN, NULL);
805 ret = resp(r, http_status_code, code, MHD_RESPMEM_MUST_COPY,
806 NULL, msg, BODYLEN_IS_STRLEN, NULL);
807 free(formatted);
808 free(msg);
809 return ret == -1 ? -1 : code;
812 static krb5_error_code
813 bad_req(kadmin_request_desc r,
814 krb5_error_code code,
815 int http_status_code,
816 const char *fmt,
817 ...)
819 krb5_error_code ret;
820 va_list ap;
822 va_start(ap, fmt);
823 ret = bad_reqv(r, code, http_status_code, fmt, ap);
824 va_end(ap);
825 return ret;
828 static krb5_error_code
829 bad_enomem(kadmin_request_desc r, krb5_error_code ret)
831 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
832 "Out of memory");
835 static krb5_error_code
836 bad_400(kadmin_request_desc r, int ret, const char *reason)
838 return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
841 static krb5_error_code
842 bad_401(kadmin_request_desc r, const char *reason)
844 return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
847 static krb5_error_code
848 bad_403(kadmin_request_desc r, krb5_error_code ret, const char *reason)
850 return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
853 static krb5_error_code
854 bad_404(kadmin_request_desc r, krb5_error_code ret, const char *name)
856 return bad_req(r, ret, MHD_HTTP_NOT_FOUND,
857 "Resource not found: %s", name);
860 static krb5_error_code
861 bad_405(kadmin_request_desc r, const char *method)
863 return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
864 "Method not supported: %s", method);
867 static krb5_error_code
868 bad_413(kadmin_request_desc r)
870 return bad_req(r, E2BIG, MHD_HTTP_METHOD_NOT_ALLOWED,
871 "POST request body too large");
874 static krb5_error_code
875 bad_method_want_POST(kadmin_request_desc r)
877 return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
878 "Use POST for making changes to principals");
881 #if 0
882 static krb5_error_code
883 bad_500(kadmin_request_desc r,
884 krb5_error_code ret,
885 const char *reason)
887 return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
888 "Internal error: %s", reason);
890 #endif
892 static krb5_error_code
893 bad_503(kadmin_request_desc r,
894 krb5_error_code ret,
895 const char *reason)
897 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
898 "Service unavailable: %s", reason);
901 static krb5_error_code
902 good_ext_keytab(kadmin_request_desc r)
904 krb5_error_code ret;
905 size_t bodylen;
906 void *body;
907 char *p;
909 if (!r->keytab_name || !(p = strchr(r->keytab_name, ':')))
910 return bad_503(r, EINVAL, "Internal error (no keytab produced)");
911 p++;
912 if (strncmp(p, cache_dir, strlen(cache_dir)) != 0)
913 return bad_503(r, EINVAL, "Internal error");
914 ret = rk_undumpdata(p, &body, &bodylen);
915 if (ret)
916 return bad_503(r, ret, "Could not recover keytab from temp file");
918 ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
919 "application/octet-stream", body, bodylen, NULL);
920 free(body);
921 return ret;
924 static krb5_error_code
925 check_service_name(kadmin_request_desc r, const char *name)
927 if (name == NULL || name[0] == '\0' ||
928 strchr(name, '/') || strchr(name, '\\') || strchr(name, '@') ||
929 strcmp(name, "krbtgt") == 0 ||
930 strcmp(name, "iprop") == 0 ||
931 strcmp(name, "kadmin") == 0 ||
932 strcmp(name, "hprop") == 0 ||
933 strcmp(name, "WELLKNOWN") == 0 ||
934 strcmp(name, "K") == 0) {
935 krb5_set_error_message(r->context, EACCES,
936 "No one is allowed to fetch keys for "
937 "Heimdal service %s", name);
938 return EACCES;
940 if (strcmp(name, "root") != 0 &&
941 strcmp(name, "host") != 0 &&
942 strcmp(name, "exceed") != 0)
943 return 0;
944 if (krb5_config_get_bool_default(r->context, NULL, FALSE,
945 "ext_keytab",
946 "csr_authorizer_handles_svc_names",
947 NULL))
948 return 0;
949 krb5_set_error_message(r->context, EACCES,
950 "No one is allowed to fetch keys for "
951 "service \"%s\" because of authorizer "
952 "limitations", name);
953 return EACCES;
956 static heim_mhd_result
957 param_cb(void *d,
958 enum MHD_ValueKind kind,
959 const char *key,
960 const char *val)
962 kadmin_request_desc r = d;
963 krb5_error_code ret = 0;
964 heim_string_t s = NULL;
967 * Multi-valued params:
969 * - spn=<service>/<hostname>
970 * - dNSName=<hostname>
971 * - service=<service>
973 * Single-valued params:
975 * - realm=<REALM>
976 * - materialize=true -- create a concrete princ where it's virtual
977 * - enctypes=... -- key-salt types
978 * - revoke=true -- delete old keys (concrete princs only)
979 * - rotate=true -- change keys (no-op for virtual princs)
980 * - create=true -- create a concrete princ
981 * - ro=true -- perform no writes
984 if (strcmp(key, "realm") == 0 && val) {
985 if (!r->realm && !(r->realm = strdup(val)))
986 ret = krb5_enomem(r->context);
987 } else if (strcmp(key, "materialize") == 0 ||
988 strcmp(key, "revoke") == 0 ||
989 strcmp(key, "rotate") == 0 ||
990 strcmp(key, "create") == 0 ||
991 strcmp(key, "ro") == 0) {
992 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
993 "requested_option", "%s", key);
994 if (!val || strcmp(val, "true") != 0)
995 krb5_set_error_message(r->context, ret = EINVAL,
996 "get-keys \"%s\" q-param accepts "
997 "only \"true\"", key);
998 else if (strcmp(key, "materialize") == 0)
999 r->materialize = 1;
1000 else if (strcmp(key, "revoke") == 0)
1001 r->revoke = 1;
1002 else if (strcmp(key, "rotate") == 0)
1003 r->rotate = 1;
1004 else if (strcmp(key, "create") == 0)
1005 r->create = 1;
1006 else if (strcmp(key, "ro") == 0)
1007 r->ro = 1;
1008 } else if (strcmp(key, "dNSName") == 0 && val) {
1009 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1010 "requested_dNSName", "%s", val);
1011 if (r->is_self) {
1012 krb5_set_error_message(r->context, ret = EACCES,
1013 "only one service may be requested for self");
1014 } else if (strchr(val, '.') == NULL) {
1015 krb5_set_error_message(r->context, ret = EACCES,
1016 "dNSName must have at least one '.' in it");
1017 } else {
1018 s = heim_string_create(val);
1019 if (!s)
1020 ret = krb5_enomem(r->context);
1021 else
1022 ret = heim_array_append_value(r->hostnames, s);
1024 if (ret == 0)
1025 ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
1026 } else if (strcmp(key, "service") == 0 && val) {
1027 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1028 "requested_service", "%s", val);
1029 if (r->is_self)
1030 krb5_set_error_message(r->context, ret = EACCES,
1031 "use \"spn\" for self");
1032 else
1033 ret = check_service_name(r, val);
1034 if (ret == 0) {
1035 s = heim_string_create(val);
1036 if (!s)
1037 ret = krb5_enomem(r->context);
1038 else
1039 ret = heim_array_append_value(r->service_names, s);
1041 } else if (strcmp(key, "enctypes") == 0 && val) {
1042 r->enctypes = strdup(val);
1043 if (!(r->enctypes = strdup(val)))
1044 ret = krb5_enomem(r->context);
1045 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1046 "requested_enctypes", "%s", val);
1047 } else if (r->is_self && strcmp(key, "spn") == 0 && val) {
1048 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1049 "requested_spn", "%s", val);
1050 krb5_set_error_message(r->context, ret = EACCES,
1051 "only one service may be requested for self");
1052 } else if (strcmp(key, "spn") == 0 && val) {
1053 krb5_principal p = NULL;
1054 const char *hostname = "";
1056 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1057 "requested_spn", "%s", val);
1059 ret = krb5_parse_name_flags(r->context, val,
1060 KRB5_PRINCIPAL_PARSE_NO_DEF_REALM, &p);
1061 if (ret == 0 && krb5_principal_get_realm(r->context, p) == NULL)
1062 ret = krb5_principal_set_realm(r->context, p,
1063 r->realm ? r->realm : realm);
1066 * The SPN has to have two components.
1068 * TODO: Support more components? Support AD-style NetBIOS computer
1069 * account names?
1071 if (ret == 0 && krb5_principal_get_num_comp(r->context, p) != 2)
1072 ret = ENOTSUP;
1075 * Allow only certain service names. Except that when
1076 * the SPN == the requestor's principal name then allow the "host"
1077 * service name.
1079 if (ret == 0) {
1080 const char *service =
1081 krb5_principal_get_comp_string(r->context, p, 0);
1083 if (strcmp(service, "host") == 0 &&
1084 krb5_principal_compare(r->context, p, r->cprinc) &&
1085 !r->is_self &&
1086 heim_array_get_length(r->hostnames) == 0 &&
1087 heim_array_get_length(r->spns) == 0) {
1088 r->is_self = 1;
1089 } else
1090 ret = check_service_name(r, service);
1092 if (ret == 0 && !krb5_principal_compare(r->context, p, r->cprinc))
1093 ret = check_service_name(r,
1094 krb5_principal_get_comp_string(r->context,
1095 p, 0));
1096 if (ret == 0) {
1097 hostname = krb5_principal_get_comp_string(r->context, p, 1);
1098 if (!hostname || !strchr(hostname, '.'))
1099 krb5_set_error_message(r->context, ret = ENOTSUP,
1100 "Only host-based service names supported");
1102 if (ret == 0 && r->realm)
1103 ret = krb5_principal_set_realm(r->context, p, r->realm);
1104 else if (ret == 0 && realm)
1105 ret = krb5_principal_set_realm(r->context, p, realm);
1106 if (ret == 0)
1107 ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req,
1108 hostname);
1109 if (ret == 0 && !(s = heim_string_create(val)))
1110 ret = krb5_enomem(r->context);
1111 if (ret == 0)
1112 ret = heim_array_append_value(r->spns, s);
1113 krb5_free_principal(r->context, p);
1115 #if 0
1116 /* The authorizer probably doesn't know what to do with this */
1117 ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req, val);
1118 #endif
1119 } else {
1120 /* Produce error for unknown params */
1121 heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
1122 krb5_set_error_message(r->context, ret = ENOTSUP,
1123 "Query parameter %s not supported", key);
1125 if (ret && !r->error_code)
1126 r->error_code = ret;
1127 heim_release(s);
1128 return ret ? MHD_NO /* Stop iterating */ : MHD_YES;
1131 static krb5_error_code
1132 authorize_req(kadmin_request_desc r)
1134 krb5_error_code ret;
1136 r->is_self = 0;
1137 ret = hx509_request_init(r->context->hx509ctx, &r->req);
1138 if (ret)
1139 return bad_enomem(r, ret);
1140 (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
1141 param_cb, r);
1142 ret = r->error_code;
1143 if (ret == EACCES)
1144 return bad_403(r, ret, "Not authorized to requested principal(s)");
1145 if (ret)
1146 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
1147 "Could not handle query parameters");
1148 if (r->is_self)
1149 ret = 0;
1150 else
1151 ret = kdc_authorize_csr(r->context, "ext_keytab", r->req, r->cprinc);
1152 if (ret == EACCES || ret == EINVAL || ret == ENOTSUP ||
1153 ret == KRB5KDC_ERR_POLICY)
1154 return bad_403(r, ret, "Not authorized to requested principal(s)");
1155 if (ret)
1156 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
1157 "Error checking authorization");
1158 return ret;
1161 static krb5_error_code
1162 make_keytab(kadmin_request_desc r)
1164 krb5_error_code ret = 0;
1165 int fd = -1;
1167 r->keytab_name = NULL;
1168 if (asprintf(&r->keytab_name, "FILE:%s/kt-XXXXXX", cache_dir) == -1 ||
1169 r->keytab_name == NULL)
1170 ret = krb5_enomem(r->context);
1171 if (ret == 0)
1172 fd = mkstemp(r->keytab_name + sizeof("FILE:") - 1);
1173 if (ret == 0 && fd == -1)
1174 ret = errno;
1175 if (ret == 0)
1176 ret = krb5_kt_resolve(r->context, r->keytab_name, &r->keytab);
1177 if (fd != -1)
1178 (void) close(fd);
1179 return ret;
1182 static krb5_error_code
1183 write_keytab(kadmin_request_desc r,
1184 kadm5_principal_ent_rec *princ,
1185 const char *unparsed)
1187 krb5_error_code ret = 0;
1188 krb5_keytab_entry key;
1189 size_t i;
1191 if (princ->n_key_data <= 0)
1192 return 0;
1194 if (kadm5_some_keys_are_bogus(princ->n_key_data, &princ->key_data[0])) {
1195 krb5_warn(r->context, ret,
1196 "httpkadmind running with insufficient kadmin privilege "
1197 "for extracting keys for %s", unparsed);
1198 krb5_log_msg(r->context, logfac, 1, NULL,
1199 "httpkadmind running with insufficient kadmin privilege "
1200 "for extracting keys for %s", unparsed);
1201 return EACCES;
1204 memset(&key, 0, sizeof(key));
1205 for (i = 0; ret == 0 && i < princ->n_key_data; i++) {
1206 krb5_key_data *kd = &princ->key_data[i];
1208 key.principal = princ->principal;
1209 key.vno = kd->key_data_kvno;
1210 key.keyblock.keytype = kd->key_data_type[0];
1211 key.keyblock.keyvalue.length = kd->key_data_length[0];
1212 key.keyblock.keyvalue.data = kd->key_data_contents[0];
1215 * FIXME kadm5 doesn't give us set_time here. If it gave us the
1216 * KeyRotation metadata, we could compute it. But this might be a
1217 * concrete principal with concrete keys, in which case we can't.
1219 * To fix this we need to extend the protocol and the API.
1221 key.timestamp = time(NULL);
1223 ret = krb5_kt_add_entry(r->context, r->keytab, &key);
1225 if (ret)
1226 krb5_warn(r->context, ret,
1227 "Failed to write keytab entries for %s", unparsed);
1229 return ret;
1232 static void
1233 random_password(krb5_context context, char *buf, size_t buflen)
1235 static const char chars[] =
1236 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,";
1237 char p[32];
1238 size_t i;
1239 char b;
1241 buflen--;
1242 for (i = 0; i < buflen; i++) {
1243 if (i % sizeof(p) == 0)
1244 krb5_generate_random_block(p, sizeof(p));
1245 b = p[i % sizeof(p)];
1246 buf[i] = chars[b % (sizeof(chars) - 1)];
1248 buf[i] = '\0';
1251 static krb5_error_code
1252 make_kstuple(krb5_context context,
1253 kadm5_principal_ent_rec *p,
1254 krb5_key_salt_tuple **kstuple,
1255 size_t *n_kstuple)
1257 size_t i;
1259 *kstuple = 0;
1260 *n_kstuple = 0;
1262 if (p->n_key_data < 1)
1263 return 0;
1264 *kstuple = calloc(p->n_key_data, sizeof (**kstuple));
1265 for (i = 0; *kstuple && i < p->n_key_data; i++) {
1266 if (p->key_data[i].key_data_kvno == p->kvno) {
1267 (*kstuple)[i].ks_enctype = p->key_data[i].key_data_type[0];
1268 (*kstuple)[i].ks_salttype = p->key_data[i].key_data_type[1];
1269 (*n_kstuple)++;
1272 return *kstuple ? 0 :krb5_enomem(context);
1275 /* Copied from kadmin/util.c */
1276 struct units kdb_attrs[] = {
1277 { "auth-data-reqd", KRB5_KDB_AUTH_DATA_REQUIRED },
1278 { "no-auth-data-reqd", KRB5_KDB_NO_AUTH_DATA_REQUIRED },
1279 { "disallow-client", KRB5_KDB_DISALLOW_CLIENT },
1280 { "virtual", KRB5_KDB_VIRTUAL },
1281 { "virtual-keys", KRB5_KDB_VIRTUAL_KEYS },
1282 { "allow-digest", KRB5_KDB_ALLOW_DIGEST },
1283 { "allow-kerberos4", KRB5_KDB_ALLOW_KERBEROS4 },
1284 { "trusted-for-delegation", KRB5_KDB_TRUSTED_FOR_DELEGATION },
1285 { "ok-as-delegate", KRB5_KDB_OK_AS_DELEGATE },
1286 { "new-princ", KRB5_KDB_NEW_PRINC },
1287 { "support-desmd5", KRB5_KDB_SUPPORT_DESMD5 },
1288 { "pwchange-service", KRB5_KDB_PWCHANGE_SERVICE },
1289 { "disallow-svr", KRB5_KDB_DISALLOW_SVR },
1290 { "requires-pw-change", KRB5_KDB_REQUIRES_PWCHANGE },
1291 { "requires-hw-auth", KRB5_KDB_REQUIRES_HW_AUTH },
1292 { "requires-pre-auth", KRB5_KDB_REQUIRES_PRE_AUTH },
1293 { "disallow-all-tix", KRB5_KDB_DISALLOW_ALL_TIX },
1294 { "disallow-dup-skey", KRB5_KDB_DISALLOW_DUP_SKEY },
1295 { "disallow-proxiable", KRB5_KDB_DISALLOW_PROXIABLE },
1296 { "disallow-renewable", KRB5_KDB_DISALLOW_RENEWABLE },
1297 { "disallow-tgt-based", KRB5_KDB_DISALLOW_TGT_BASED },
1298 { "disallow-forwardable", KRB5_KDB_DISALLOW_FORWARDABLE },
1299 { "disallow-postdated", KRB5_KDB_DISALLOW_POSTDATED },
1300 { NULL, 0 }
1304 * Determine the default/allowed attributes for some new principal.
1306 static krb5_flags
1307 create_attributes(kadmin_request_desc r, krb5_const_principal p)
1309 krb5_error_code ret;
1310 const char *srealm = krb5_principal_get_realm(r->context, p);
1311 const char *svc;
1312 const char *hn;
1314 /* Has to be a host-based service principal (for now) */
1315 if (krb5_principal_get_num_comp(r->context, p) != 2)
1316 return 0;
1318 hn = krb5_principal_get_comp_string(r->context, p, 1);
1319 svc = krb5_principal_get_comp_string(r->context, p, 0);
1321 while (hn && strchr(hn, '.') != NULL) {
1322 kadm5_principal_ent_rec nsprinc;
1323 krb5_principal nsp;
1324 uint64_t a = 0;
1325 const char *as;
1327 /* Try finding a virtual host-based service principal namespace */
1328 memset(&nsprinc, 0, sizeof(nsprinc));
1329 ret = krb5_make_principal(r->context, &nsp, srealm,
1330 KRB5_WELLKNOWN_NAME, HDB_WK_NAMESPACE,
1331 svc, hn, NULL);
1332 if (ret == 0)
1333 ret = kadm5_get_principal(r->kadm_handle, nsp, &nsprinc,
1334 KADM5_PRINCIPAL | KADM5_ATTRIBUTES);
1335 krb5_free_principal(r->context, nsp);
1336 if (ret == 0) {
1337 /* Found one; use it even if disabled, but drop that attribute */
1338 a = nsprinc.attributes & ~KRB5_KDB_DISALLOW_ALL_TIX;
1339 kadm5_free_principal_ent(r->kadm_handle, &nsprinc);
1340 return a;
1343 /* Fallback on krb5.conf */
1344 as = krb5_config_get_string(r->context, NULL, "ext_keytab",
1345 "new_hostbased_service_principal_attributes",
1346 svc, hn, NULL);
1347 if (as) {
1348 a = parse_flags(as, kdb_attrs, 0);
1349 if (a == (uint64_t)-1) {
1350 krb5_warnx(r->context, "Invalid value for [ext_keytab] "
1351 "new_hostbased_service_principal_attributes");
1352 return 0;
1354 return a;
1357 hn = strchr(hn + 1, '.');
1360 return 0;
1364 * Get keys for one principal.
1366 * Does NOT set an HTTP response.
1368 static krb5_error_code
1369 get_keys1(kadmin_request_desc r, const char *pname)
1371 kadm5_principal_ent_rec princ;
1372 krb5_key_salt_tuple *kstuple = NULL;
1373 krb5_error_code ret = 0;
1374 krb5_principal p = NULL;
1375 uint32_t mask =
1376 KADM5_PRINCIPAL | KADM5_KVNO | KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
1377 KADM5_PW_EXPIRATION | KADM5_ATTRIBUTES | KADM5_KEY_DATA |
1378 KADM5_TL_DATA;
1379 uint32_t create_mask = mask & ~(KADM5_KEY_DATA | KADM5_TL_DATA);
1380 size_t nkstuple = 0;
1381 int change = 0;
1382 int refetch = 0;
1383 int freeit = 0;
1385 memset(&princ, 0, sizeof(princ));
1386 princ.key_data = NULL;
1387 princ.tl_data = NULL;
1389 ret = krb5_parse_name(r->context, pname, &p);
1390 if (ret == 0 && r->realm)
1391 ret = krb5_principal_set_realm(r->context, p, r->realm);
1392 else if (ret == 0 && realm)
1393 ret = krb5_principal_set_realm(r->context, p, realm);
1394 if (ret == 0 && r->enctypes)
1395 ret = krb5_string_to_keysalts2(r->context, r->enctypes,
1396 &nkstuple, &kstuple);
1397 if (ret == 0)
1398 ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1399 if (ret == 0) {
1400 freeit = 1;
1403 * If princ is virtual and we're not asked to materialize, ignore
1404 * requests to rotate.
1406 if (!r->materialize &&
1407 (princ.attributes & (KRB5_KDB_VIRTUAL_KEYS | KRB5_KDB_VIRTUAL))) {
1408 r->rotate = 0;
1409 r->revoke = 0;
1413 change = !r->ro && (r->rotate || r->revoke);
1415 /* Handle create / materialize options */
1416 if (ret == KADM5_UNK_PRINC && r->create) {
1417 char pw[128];
1419 memset(&princ, 0, sizeof(princ));
1420 princ.attributes = create_attributes(r, p);
1422 if (read_only)
1423 ret = KADM5_READ_ONLY;
1424 else
1425 ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1426 if (ret == 0 && local_hdb && local_hdb_read_only) {
1427 /* Make sure we can write */
1428 kadm5_destroy(r->kadm_handle);
1429 r->kadm_handle = NULL;
1430 ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1431 &r->kadm_handle);
1434 * Some software is allergic to kvno 1, assuming that kvno 1 implies
1435 * half-baked service principal. We've some vague recollection of
1436 * something similar for kvno 2, so let's start at 3.
1438 princ.kvno = 3;
1439 princ.tl_data = NULL;
1440 princ.key_data = NULL;
1441 princ.max_life = 24 * 3600; /* XXX Make configurable */
1442 princ.max_renewable_life = princ.max_life; /* XXX Make configurable */
1444 random_password(r->context, pw, sizeof(pw));
1445 princ.principal = p; /* Borrow */
1446 if (ret == 0)
1447 ret = kadm5_create_principal_3(r->kadm_handle, &princ, create_mask,
1448 nkstuple, kstuple, pw);
1449 princ.principal = NULL; /* Return */
1450 refetch = 1;
1451 freeit = 1;
1452 } else if (ret == 0 && r->materialize &&
1453 (princ.attributes & KRB5_KDB_VIRTUAL)) {
1455 if (read_only)
1456 ret = KADM5_READ_ONLY;
1457 else
1458 ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1459 if (ret == 0 && local_hdb && local_hdb_read_only) {
1460 /* Make sure we can write */
1461 kadm5_destroy(r->kadm_handle);
1462 r->kadm_handle = NULL;
1463 ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1464 &r->kadm_handle);
1466 princ.attributes |= KRB5_KDB_MATERIALIZE;
1467 princ.attributes &= ~KRB5_KDB_VIRTUAL;
1469 * XXX If there are TL data which should be re-encoded and sent as
1470 * KRB5_TL_EXTENSION, then this call will fail with KADM5_BAD_TL_TYPE.
1472 * We should either drop those TLs, re-encode them, or make
1473 * perform_tl_data() handle them. (New extensions should generally go
1474 * as KRB5_TL_EXTENSION so that non-critical ones can be set on
1475 * principals via old kadmind programs that don't support them.)
1477 * What we really want is a kadm5 utility function to convert some TLs
1478 * to KRB5_TL_EXTENSION and drop all others.
1480 if (ret == 0)
1481 ret = kadm5_create_principal(r->kadm_handle, &princ, mask, "");
1482 refetch = 1;
1483 } /* else create/materialize q-params are superfluous */
1485 /* Handle rotate / revoke options */
1486 if (ret == 0 && change) {
1487 krb5_keyblock *k = NULL;
1488 size_t i;
1489 int n_k = 0;
1490 int keepold = r->revoke ? 0 : 1;
1492 if (read_only)
1493 ret = KADM5_READ_ONLY;
1494 else
1495 ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1496 if (ret == 0 && local_hdb && local_hdb_read_only) {
1497 /* Make sure we can write */
1498 kadm5_destroy(r->kadm_handle);
1499 r->kadm_handle = NULL;
1500 ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1501 &r->kadm_handle);
1504 /* Use requested enctypes or same ones as princ already had keys for */
1505 if (ret == 0 && kstuple == NULL)
1506 ret = make_kstuple(r->context, &princ, &kstuple, &nkstuple);
1508 /* Set new keys */
1509 if (ret == 0)
1510 ret = kadm5_randkey_principal_3(r->kadm_handle, p, keepold,
1511 nkstuple, kstuple, &k, &n_k);
1512 refetch = 1;
1513 for (i = 0; n_k > 0 && i < n_k; i++)
1514 krb5_free_keyblock_contents(r->context, &k[i]);
1515 free(kstuple);
1516 free(k);
1519 if (ret == 0 && refetch) {
1520 /* Refetch changed principal */
1521 if (freeit)
1522 kadm5_free_principal_ent(r->kadm_handle, &princ);
1523 freeit = 0;
1524 ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1525 if (ret == 0)
1526 freeit = 1;
1529 if (ret == 0)
1530 ret = write_keytab(r, &princ, pname);
1532 if (ret == 0) {
1534 * We will use the principal's password expiration to work out the
1535 * value for the max-age Cache-Control.
1537 * Virtual service principals will have their `pw_expiration' set to a
1538 * time when the client should refetch keys.
1540 * Concrete service principals will generally not have a non-zero
1541 * `pw_expiration', but if we have a new_service_key_delay, then we'll
1542 * use half of it as the max-age Cache-Control.
1544 if (princ.pw_expiration == 0) {
1545 krb5_timestamp nskd =
1546 krb5_config_get_time_default(r->context, NULL, 0, "hdb",
1547 "new_service_key_delay", NULL);
1548 if (nskd)
1549 princ.pw_expiration = time(NULL) + (nskd >> 1);
1553 * This service can be used to fetch more than one principal's keys, so
1554 * the max-age Cache-Control should be derived from the soonest-
1555 * "expiring" principal.
1557 if (r->pw_end == 0 ||
1558 (princ.pw_expiration < r->pw_end && princ.pw_expiration > time(NULL)))
1559 r->pw_end = princ.pw_expiration;
1561 if (freeit)
1562 kadm5_free_principal_ent(r->kadm_handle, &princ);
1563 krb5_free_principal(r->context, p);
1564 return ret;
1567 static krb5_error_code check_csrf(kadmin_request_desc);
1570 * Calls get_keys1() to extract each requested principal's keys.
1572 * When this returns a response will have been set.
1574 static krb5_error_code
1575 get_keysN(kadmin_request_desc r)
1577 krb5_error_code ret;
1578 size_t nhosts;
1579 size_t nsvcs;
1580 size_t nspns;
1581 size_t i, k;
1583 /* Parses and validates the request, then checks authorization */
1584 ret = authorize_req(r);
1585 if (ret)
1586 return ret; /* authorize_req() calls bad_req() on error */
1589 * If we have a r->kadm_handle already it's because we validated a CSRF
1590 * token. It may not be a handle to a realm we wanted though.
1592 if (r->kadm_handle)
1593 kadm5_destroy(r->kadm_handle);
1594 r->kadm_handle = NULL;
1595 ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
1596 0 /* want_write */, &r->kadm_handle);
1597 if (ret)
1598 return bad_404(r, ret, "Could not connect to realm");
1600 nhosts = heim_array_get_length(r->hostnames);
1601 nsvcs = heim_array_get_length(r->service_names);
1602 nspns = heim_array_get_length(r->spns);
1603 if (!nhosts && !nspns)
1604 return bad_403(r, EINVAL, "No service principals requested");
1606 if (nhosts && !nsvcs) {
1607 heim_string_t s;
1609 if ((s = heim_string_create("HTTP")) == NULL)
1610 ret = krb5_enomem(r->context);
1611 if (ret == 0)
1612 ret = heim_array_append_value(r->service_names, s);
1613 heim_release(s);
1614 nsvcs = 1;
1615 if (ret)
1616 return bad_503(r, ret, "Out of memory");
1619 if (nspns + nsvcs * nhosts >
1620 krb5_config_get_int_default(r->context, NULL, 400,
1621 "ext_keytab", "get_keys_max_spns", NULL))
1622 return bad_403(r, EINVAL, "Requested keys for too many principals");
1624 ret = make_keytab(r);
1625 for (i = 0; ret == 0 && i < nsvcs; i++) {
1626 const char *svc =
1627 heim_string_get_utf8(
1628 heim_array_get_value(r->service_names, i));
1630 for (k = 0; ret == 0 && k < nhosts; k++) {
1631 krb5_principal p = NULL;
1632 const char *hostname =
1633 heim_string_get_utf8(
1634 heim_array_get_value(r->hostnames, k));
1635 char *spn = NULL;
1637 ret = krb5_make_principal(r->context, &p,
1638 r->realm ? r->realm : realm,
1639 svc, hostname, NULL);
1640 if (ret == 0)
1641 ret = krb5_unparse_name(r->context, p, &spn);
1642 if (ret == 0)
1643 ret = get_keys1(r, spn);
1644 krb5_free_principal(r->context, p);
1645 free(spn);
1648 for (i = 0; ret == 0 && i < nspns; i++) {
1649 ret = get_keys1(r,
1650 heim_string_get_utf8(heim_array_get_value(r->spns,
1651 i)));
1653 switch (ret) {
1654 case -1:
1655 /* Can't happen */
1656 krb5_log_msg(r->context, logfac, 1, NULL,
1657 "Failed to extract keys for unknown reasons");
1658 if (r->response_set)
1659 return MHD_YES;
1660 return bad_503(r, ret, "Could not get keys");
1661 case ENOSYS:
1662 /* Our convention */
1663 return bad_method_want_POST(r);
1664 case KADM5_READ_ONLY:
1665 if (primary_server_URI) {
1666 krb5_log_msg(r->context, logfac, 1, NULL,
1667 "Redirect %s to primary server", r->cname);
1668 return resp(r, MHD_HTTP_TEMPORARY_REDIRECT, KADM5_READ_ONLY,
1669 MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
1670 } else {
1671 krb5_log_msg(r->context, logfac, 1, NULL, "HDB is read-only");
1672 return bad_403(r, ret, "HDB is read-only");
1674 case 0:
1675 krb5_log_msg(r->context, logfac, 1, NULL, "Sent keytab to %s",
1676 r->cname);
1677 return good_ext_keytab(r);
1678 default:
1679 return bad_503(r, ret, "Could not get keys");
1683 /* Copied from kdc/connect.c */
1684 static void
1685 addr_to_string(krb5_context context,
1686 struct sockaddr *addr,
1687 char *str,
1688 size_t len)
1690 krb5_error_code ret;
1691 krb5_address a;
1693 ret = krb5_sockaddr2address(context, addr, &a);
1694 if (ret == 0) {
1695 ret = krb5_print_address(&a, str, len, &len);
1696 krb5_free_address(context, &a);
1698 if (ret)
1699 snprintf(str, len, "<family=%d>", addr->sa_family);
1702 static void clean_req_desc(kadmin_request_desc);
1704 static krb5_error_code
1705 set_req_desc(struct MHD_Connection *connection,
1706 const char *method,
1707 const char *url,
1708 kadmin_request_desc *rp)
1710 const union MHD_ConnectionInfo *ci;
1711 kadmin_request_desc r;
1712 const char *token;
1713 krb5_error_code ret;
1715 *rp = NULL;
1716 if ((r = calloc(1, sizeof(*r))) == NULL)
1717 return ENOMEM;
1719 (void) gettimeofday(&r->tv_start, NULL);
1720 if ((ret = get_krb5_context(&r->context))) {
1721 free(r);
1722 return ret;
1724 /* HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS fields */
1725 r->request.data = "<HTTP-REQUEST>";
1726 r->request.length = sizeof("<HTTP-REQUEST>");
1727 r->from = r->frombuf;
1728 r->free_list = NULL;
1729 r->config = NULL;
1730 r->logf = logfac;
1731 r->reqtype = url;
1732 r->reason = NULL;
1733 r->reply = NULL;
1734 r->sname = NULL;
1735 r->cname = NULL;
1736 r->addr = NULL;
1737 r->kv = heim_dict_create(10);
1738 r->pp = NULL;
1739 r->attributes = heim_dict_create(1);
1740 /* Our fields */
1741 r->connection = connection;
1742 r->kadm_handle = NULL;
1743 r->hcontext = r->context->hcontext;
1744 r->service_names = heim_array_create();
1745 r->hostnames = heim_array_create();
1746 r->spns = heim_array_create();
1747 r->keytab_name = NULL;
1748 r->enctypes = NULL;
1749 r->cache_control = NULL;
1750 r->freeme1 = NULL;
1751 r->method = method;
1752 r->cprinc = NULL;
1753 r->req = NULL;
1754 r->sp = NULL;
1755 ci = MHD_get_connection_info(connection,
1756 MHD_CONNECTION_INFO_CLIENT_ADDRESS);
1757 if (ci) {
1758 r->addr = ci->client_addr;
1759 addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
1762 if (r->kv) {
1763 heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
1764 heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
1766 token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
1767 MHD_HTTP_HEADER_AUTHORIZATION);
1768 if (token && r->kv) {
1769 const char *token_end;
1771 if ((token_end = strchr(token, ' ')) == NULL ||
1772 (token_end - token) > INT_MAX || (token_end - token) < 2)
1773 heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
1774 else
1775 heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
1776 (int)(token_end - token), token);
1780 if (ret == 0 && r->kv == NULL) {
1781 krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
1782 ret = r->error_code = ENOMEM;
1784 if (ret == 0)
1785 *rp = r;
1786 else
1787 clean_req_desc(r);
1788 return ret;
1791 static void
1792 clean_req_desc(kadmin_request_desc r)
1794 if (!r)
1795 return;
1797 if (r->keytab)
1798 krb5_kt_destroy(r->context, r->keytab);
1799 else if (r->keytab_name && strchr(r->keytab_name, ':'))
1800 (void) unlink(strchr(r->keytab_name, ':') + 1);
1801 if (r->kadm_handle)
1802 kadm5_destroy(r->kadm_handle);
1803 if (r->pp)
1804 MHD_destroy_post_processor(r->pp);
1805 hx509_request_free(&r->req);
1806 heim_release(r->service_names);
1807 heim_release(r->attributes);
1808 heim_release(r->hostnames);
1809 heim_release(r->reason);
1810 heim_release(r->spns);
1811 heim_release(r->kv);
1812 krb5_free_principal(r->context, r->cprinc);
1813 free(r->cache_control);
1814 free(r->keytab_name);
1815 free(r->csrf_token);
1816 free(r->enctypes);
1817 free(r->freeme1);
1818 free(r->cname);
1819 free(r->sname);
1820 free(r->realm);
1821 free(r);
1824 static void
1825 cleanup_req(void *cls,
1826 struct MHD_Connection *connection,
1827 void **con_cls,
1828 enum MHD_RequestTerminationCode toe)
1830 kadmin_request_desc r = *con_cls;
1832 (void)cls;
1833 (void)connection;
1834 (void)toe;
1835 clean_req_desc(r);
1836 *con_cls = NULL;
1839 /* Implements GETs of /get-keys */
1840 static krb5_error_code
1841 get_keys(kadmin_request_desc r)
1843 if (r->cname == NULL || r->cprinc == NULL)
1844 return bad_401(r, "Could not extract principal name from token");
1845 return get_keysN(r); /* Sets an HTTP response */
1848 /* Implements GETs of /get-config */
1849 static krb5_error_code
1850 get_config(kadmin_request_desc r)
1853 kadm5_principal_ent_rec princ;
1854 krb5_error_code ret;
1855 krb5_principal p = NULL;
1856 uint32_t mask = KADM5_PRINCIPAL | KADM5_TL_DATA;
1857 krb5_tl_data *tl_next;
1858 const char *pname;
1859 /* Default configuration for principals that have none set: */
1860 size_t bodylen = sizeof("include /etc/krb5.conf\n") - 1;
1861 void *body = "include /etc/krb5.conf\n";
1862 int freeit = 0;
1864 if (r->cname == NULL || r->cprinc == NULL)
1865 return bad_401(r, "Could not extract principal name from token");
1867 * No authorization needed -- configs are public. Though we do require
1868 * authentication (above).
1871 ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
1872 0 /* want_write */, &r->kadm_handle);
1873 if (ret)
1874 return bad_503(r, ret, "Could not access KDC database");
1876 memset(&princ, 0, sizeof(princ));
1877 princ.key_data = NULL;
1878 princ.tl_data = NULL;
1880 pname = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1881 "princ");
1882 if (pname == NULL)
1883 pname = r->cname;
1884 ret = krb5_parse_name(r->context, pname, &p);
1885 if (ret == 0) {
1886 ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1887 if (ret == 0) {
1888 freeit = 1;
1889 for (tl_next = princ.tl_data; tl_next; tl_next = tl_next->tl_data_next) {
1890 if (tl_next->tl_data_type != KRB5_TL_KRB5_CONFIG)
1891 continue;
1892 bodylen = tl_next->tl_data_length;
1893 body = tl_next->tl_data_contents;
1894 break;
1896 } else {
1897 r->error_code = ret;
1898 return bad_404(r, ret, "/get-config");
1902 if (ret == 0) {
1903 krb5_log_msg(r->context, logfac, 1, NULL,
1904 "Returned krb5.conf contents to %s", r->cname);
1905 ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
1906 "application/text", body, bodylen, NULL);
1907 } else {
1908 ret = bad_503(r, ret, "Could not retrieve principal configuration");
1910 if (freeit)
1911 kadm5_free_principal_ent(r->kadm_handle, &princ);
1912 krb5_free_principal(r->context, p);
1913 return ret;
1916 static krb5_error_code
1917 mac_csrf_token(kadmin_request_desc r, krb5_storage *sp)
1919 kadm5_principal_ent_rec princ;
1920 krb5_error_code ret;
1921 krb5_principal p = NULL;
1922 krb5_data data;
1923 char mac[EVP_MAX_MD_SIZE];
1924 unsigned int maclen = sizeof(mac);
1925 HMAC_CTX *ctx = NULL;
1926 size_t i = 0;
1927 int freeit = 0;
1929 memset(&princ, 0, sizeof(princ));
1930 ret = krb5_storage_to_data(sp, &data);
1931 if (r->kadm_handle == NULL)
1932 ret = get_kadm_handle(r->context,
1933 r->realm ? r->realm : realm,
1934 0 /* want_write */,
1935 &r->kadm_handle);
1936 if (ret == 0)
1937 ret = krb5_make_principal(r->context, &p,
1938 r->realm ? r->realm : realm,
1939 "WELLKNOWN", "CSRFTOKEN", NULL);
1940 if (ret == 0)
1941 ret = kadm5_get_principal(r->kadm_handle, p, &princ,
1942 KADM5_PRINCIPAL | KADM5_KVNO |
1943 KADM5_KEY_DATA);
1944 if (ret == 0)
1945 freeit = 1;
1946 if (ret == 0 && princ.n_key_data < 1)
1947 ret = KADM5_UNK_PRINC;
1948 if (ret == 0)
1949 for (i = 0; i < princ.n_key_data; i++)
1950 if (princ.key_data[i].key_data_kvno == princ.kvno)
1951 break;
1952 if (ret == 0 && i == princ.n_key_data)
1953 i = 0; /* Weird, but can't happen */
1955 if (ret == 0 && (ctx = HMAC_CTX_new()) == NULL)
1956 ret = krb5_enomem(r->context);
1957 /* HMAC the token body and the client principal name */
1958 if (ret == 0) {
1959 if (HMAC_Init_ex(ctx, princ.key_data[i].key_data_contents[0],
1960 princ.key_data[i].key_data_length[0], EVP_sha256(),
1961 NULL) == 0) {
1962 HMAC_CTX_cleanup(ctx);
1963 ret = krb5_enomem(r->context);
1964 } else {
1965 HMAC_Update(ctx, data.data, data.length);
1966 HMAC_Update(ctx, r->cname, strlen(r->cname));
1967 HMAC_Final(ctx, mac, &maclen);
1968 HMAC_CTX_cleanup(ctx);
1969 krb5_data_free(&data);
1970 data.length = maclen;
1971 data.data = mac;
1972 if (krb5_storage_write(sp, mac, maclen) != maclen)
1973 ret = krb5_enomem(r->context);
1976 krb5_free_principal(r->context, p);
1977 if (freeit)
1978 kadm5_free_principal_ent(r->kadm_handle, &princ);
1979 if (ctx)
1980 HMAC_CTX_free(ctx);
1981 return ret;
1984 static krb5_error_code
1985 make_csrf_token(kadmin_request_desc r,
1986 const char *given,
1987 char **token,
1988 int64_t *age)
1990 krb5_error_code ret = 0;
1991 unsigned char given_decoded[128];
1992 krb5_storage *sp = NULL;
1993 krb5_data data;
1994 ssize_t dlen = -1;
1995 uint64_t nonce;
1996 int64_t t = 0;
1999 *age = 0;
2000 data.data = NULL;
2001 data.length = 0;
2002 if (given) {
2003 size_t len = strlen(given);
2005 if (len >= sizeof(given_decoded))
2006 ret = ERANGE;
2007 if (ret == 0 && (dlen = rk_base64_decode(given, &given_decoded)) <= 0)
2008 ret = errno;
2009 if (ret == 0 &&
2010 (sp = krb5_storage_from_mem(given_decoded, dlen)) == NULL)
2011 ret = krb5_enomem(r->context);
2012 if (ret == 0)
2013 ret = krb5_ret_int64(sp, &t);
2014 if (ret == 0)
2015 ret = krb5_ret_uint64(sp, &nonce);
2016 krb5_storage_free(sp);
2017 sp = NULL;
2018 if (ret == 0)
2019 *age = time(NULL) - t;
2020 } else {
2021 t = time(NULL);
2022 krb5_generate_random_block((void *)&nonce, sizeof(nonce));
2025 if (ret == 0 && (sp = krb5_storage_emem()) == NULL)
2026 ret = krb5_enomem(r->context);
2027 if (ret == 0)
2028 ret = krb5_store_int64(sp, t);
2029 if (ret == 0)
2030 ret = krb5_store_uint64(sp, nonce);
2031 if (ret == 0)
2032 ret = mac_csrf_token(r, sp);
2033 if (ret == 0)
2034 ret = krb5_storage_to_data(sp, &data);
2035 if (ret == 0 && data.length > INT_MAX)
2036 ret = ERANGE;
2037 if (ret == 0 &&
2038 rk_base64_encode(data.data, data.length, token) < 0)
2039 ret = errno;
2040 krb5_storage_free(sp);
2041 krb5_data_free(&data);
2042 return ret;
2046 * Returns system or krb5_error_code on error, but also calls resp() or bad_*()
2047 * on error.
2049 static krb5_error_code
2050 check_csrf(kadmin_request_desc r)
2052 krb5_error_code ret;
2053 const char *given;
2054 int64_t age;
2055 size_t givenlen, expectedlen;
2057 if ((((csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
2058 strcmp(r->method, "GET") == 0) ||
2059 ((csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
2060 strcmp(r->method, "POST") == 0)) &&
2061 MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
2062 csrf_header) == NULL) {
2063 ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
2064 "Request must have header \"%s\"", csrf_header);
2065 return ret == -1 ? MHD_NO : MHD_YES;
2068 if (strcmp(r->method, "GET") == 0 &&
2069 !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
2070 return 0;
2071 if (strcmp(r->method, "POST") == 0 &&
2072 !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
2073 return 0;
2075 given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
2076 "X-CSRF-Token");
2077 ret = make_csrf_token(r, given, &r->csrf_token, &age);
2078 if (ret)
2079 return bad_503(r, ret, "Could not create a CSRF token");
2081 * If CSRF token needed but missing, call resp() directly, bypassing
2082 * bad_403(), to return a 403 with an expected CSRF token in the response.
2084 if (given == NULL) {
2085 (void) resp(r, MHD_HTTP_FORBIDDEN, ENOSYS, MHD_RESPMEM_PERSISTENT,
2086 NULL, "CSRF token needed; copy the X-CSRF-Token: response "
2087 "header to your next POST", BODYLEN_IS_STRLEN, NULL);
2088 return ENOSYS;
2091 /* Validate the CSRF token for this request */
2092 givenlen = strlen(given);
2093 expectedlen = strlen(r->csrf_token);
2094 if (givenlen != expectedlen || ct_memcmp(given, r->csrf_token, givenlen)) {
2095 (void) bad_403(r, EACCES, "Invalid CSRF token");
2096 return EACCES;
2098 if (age > 300) { /* XXX */
2099 (void) bad_403(r, EACCES, "CSRF token too old");
2100 return EACCES;
2102 return 0;
2105 static krb5_error_code
2106 health(const char *method, kadmin_request_desc r)
2108 if (strcmp(method, "HEAD") == 0) {
2109 return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
2110 NULL);
2112 return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL,
2113 "To determine the health of the service, use the /get-config "
2114 "end-point.\n", BODYLEN_IS_STRLEN, NULL);
2118 static heim_mhd_result
2119 ip(void *cls,
2120 enum MHD_ValueKind kind,
2121 const char *key,
2122 const char *content_name,
2123 const char *content_type,
2124 const char *transfer_encoding,
2125 const char *val,
2126 uint64_t off,
2127 size_t size)
2129 kadmin_request_desc r = cls;
2130 struct free_tend_list *ftl = calloc(1, sizeof(*ftl));
2131 char *keydup = strdup(key);
2132 char *valdup = strndup(val, size);
2134 (void)content_name; /* MIME attachment name */
2135 (void)content_type;
2136 (void)transfer_encoding;
2137 (void)off; /* Offset in POST data */
2139 /* We're going to MHD_set_connection_value(), but we need copies */
2140 if (ftl == NULL || keydup == NULL || valdup == NULL) {
2141 free(ftl);
2142 free(keydup);
2143 free(valdup);
2144 return MHD_NO;
2146 ftl->freeme1 = keydup;
2147 ftl->freeme2 = valdup;
2148 ftl->next = r->free_list;
2149 r->free_list = ftl;
2151 return MHD_set_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
2152 keydup, valdup);
2155 typedef krb5_error_code (*handler)(struct kadmin_request_desc *);
2157 struct route {
2158 const char *local_part;
2159 handler h;
2160 } routes[] = {
2161 { "/get-keys", get_keys },
2162 { "/get-config", get_config },
2165 static heim_mhd_result
2166 route(void *cls,
2167 struct MHD_Connection *connection,
2168 const char *url,
2169 const char *method,
2170 const char *version,
2171 const char *upload_data,
2172 size_t *upload_data_size,
2173 void **ctx)
2175 struct kadmin_request_desc *r = *ctx;
2176 size_t i;
2177 int ret;
2179 if (r == NULL) {
2181 * This is the first call, right after headers were read.
2183 * We must return quickly so that any 100-Continue might be sent with
2184 * celerity. We want to make sure to send any 401s early, so we check
2185 * WWW-Authenticate now, not later.
2187 * We'll get called again to really do the processing. If we're
2188 * handling a POST then we'll also get called with upload_data != NULL,
2189 * possibly multiple times.
2191 if ((ret = set_req_desc(connection, method, url, &r)))
2192 return MHD_NO;
2193 *ctx = r;
2196 * All requests other than /health require authentication and CSRF
2197 * protection.
2199 if (strcmp(url, "/health") == 0)
2200 return MHD_YES;
2202 /* Authenticate and do CSRF protection */
2203 ret = validate_token(r);
2204 if (ret == 0)
2205 ret = check_csrf(r);
2208 * As this is the initial call to this handler, we must return now.
2210 * If authentication or CSRF protection failed then we'll already have
2211 * enqueued a 401, 403, or 5xx response and then we're done.
2213 * If both authentication and CSRF protection succeeded then no
2214 * response has been queued up and we'll get called again to finally
2215 * process the request, then this entire if block will not be executed.
2217 return ret == -1 ? MHD_NO : MHD_YES;
2220 /* Validate HTTP method */
2221 if (strcmp(method, "GET") != 0 &&
2222 strcmp(method, "POST") != 0 &&
2223 strcmp(method, "HEAD") != 0) {
2224 return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
2227 if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
2228 (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
2229 /* /health end-point -- no authentication, no CSRF, no nothing */
2230 return health(method, r) == -1 ? MHD_NO : MHD_YES;
2233 if (strcmp(method, "POST") == 0 && *upload_data_size != 0) {
2235 * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
2236 * (as if they had been URI query parameters).
2238 * We have to do this before we can MHD_queue_response() as MHD will
2239 * not consume the rest of the request body on its own, so it's an
2240 * error to MHD_queue_response() before we've done this, and if we do
2241 * then MHD just closes the connection.
2243 * 4KB should be more than enough buffer space for all the keys we
2244 * expect.
2246 if (r->pp == NULL)
2247 r->pp = MHD_create_post_processor(connection, 4096, ip, r);
2248 if (r->pp == NULL) {
2249 ret = bad_503(r, errno ? errno : ENOMEM,
2250 "Could not consume POST data");
2251 return ret == -1 ? MHD_NO : MHD_YES;
2253 if (r->post_data_size + *upload_data_size > 1UL<<17) {
2254 return bad_413(r) == -1 ? MHD_NO : MHD_YES;
2256 r->post_data_size += *upload_data_size;
2257 if (MHD_post_process(r->pp, upload_data,
2258 *upload_data_size) == MHD_NO) {
2259 ret = bad_503(r, errno ? errno : ENOMEM,
2260 "Could not consume POST data");
2261 return ret == -1 ? MHD_NO : MHD_YES;
2263 *upload_data_size = 0;
2264 return MHD_YES;
2268 * Either this is a HEAD, a GET, or a POST whose request body has now been
2269 * received completely and processed.
2272 /* Allow GET? */
2273 if (strcmp(method, "GET") == 0 && !allow_GET_flag) {
2274 /* No */
2275 return bad_405(r, method) == -1 ? MHD_NO : MHD_YES;
2278 for (i = 0; i < sizeof(routes)/sizeof(routes[0]); i++) {
2279 if (strcmp(url, routes[i].local_part) != 0)
2280 continue;
2281 if (MHD_lookup_connection_value(r->connection,
2282 MHD_HEADER_KIND,
2283 "Referer") != NULL) {
2284 ret = bad_req(r, EACCES, MHD_HTTP_FORBIDDEN,
2285 "GET from browser not allowed");
2286 return ret == -1 ? MHD_NO : MHD_YES;
2288 if (strcmp(method, "HEAD") == 0)
2289 ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
2290 NULL);
2291 else
2292 ret = routes[i].h(r);
2293 return ret == -1 ? MHD_NO : MHD_YES;
2296 ret = bad_404(r, ENOENT, url);
2297 return ret == -1 ? MHD_NO : MHD_YES;
2300 static struct getargs args[] = {
2301 { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
2302 { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
2303 { NULL, 'H', arg_strings, &audiences,
2304 "expected token audience(s) of the service", "HOSTNAME" },
2305 { "allow-GET", 0, arg_negative_flag,
2306 &allow_GET_flag, NULL, NULL },
2307 { "csrf-header", 0, arg_string, &csrf_header,
2308 "required request header", "HEADER-NAME" },
2309 { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
2310 { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
2311 { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
2312 "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
2313 { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
2314 { "temp-dir", 0, arg_string, &cache_dir,
2315 "cache directory", "DIRECTORY" },
2316 { "cert", 0, arg_string, &cert_file,
2317 "certificate file path (PEM)", "HX509-STORE" },
2318 { "private-key", 0, arg_string, &priv_key_file,
2319 "private key file path (PEM)", "HX509-STORE" },
2320 { "thread-per-client", 't', arg_flag, &thread_per_client_flag, "thread per-client", NULL },
2321 { "realm", 0, arg_string, &realm, "realm", "REALM" },
2322 { "hdb", 0, arg_string, &hdb, "HDB filename", "PATH" },
2323 { "read-only-admin-server", 0, arg_string, &kadmin_server,
2324 "Name of read-only kadmin server", "HOST[:PORT]" },
2325 { "writable-admin-server", 0, arg_string, &writable_kadmin_server,
2326 "Name of writable kadmin server", "HOST[:PORT]" },
2327 { "primary-server-uri", 0, arg_string, &primary_server_URI,
2328 "Name of primary httpkadmind server for HTTP redirects", "URL" },
2329 { "local", 'l', arg_flag, &local_hdb,
2330 "Use a local HDB as read-only", NULL },
2331 { "local-read-only", 0, arg_flag, &local_hdb_read_only,
2332 "Use a local HDB as read-only", NULL },
2333 { "read-only", 0, arg_flag, &read_only, "Allow no writes", NULL },
2334 { "stash-file", 0, arg_string, &stash_file,
2335 "Stash file for HDB", "PATH" },
2336 { "kadmin-client-name", 0, arg_string, &kadmin_client_name,
2337 "Client name for remote kadmind", "PRINCIPAL" },
2338 { "kadmin-client-keytab", 0, arg_string, &kadmin_client_keytab,
2339 "Keytab with client credentials for remote kadmind", "KEYTAB" },
2340 { "token-authentication-type", 'T', arg_strings, &auth_types,
2341 "Token authentication type(s) supported", "HTTP-AUTH-TYPE" },
2342 { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
2345 static int
2346 usage(int e)
2348 arg_printusage(args, sizeof(args) / sizeof(args[0]), "httpkadmind",
2349 "\nServes an HTTP API for getting (and rotating) service "
2350 "principal keys, and other kadmin-like operations\n");
2351 exit(e);
2354 static int sigpipe[2] = { -1, -1 };
2356 static void
2357 sighandler(int sig)
2359 char c = sig;
2360 while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
2364 static void
2365 my_openlog(krb5_context context,
2366 const char *svc,
2367 krb5_log_facility **fac)
2369 char **s = NULL, **p;
2371 krb5_initlog(context, "httpkadmind", fac);
2372 s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
2373 if (s == NULL)
2374 s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
2375 if (s) {
2376 for(p = s; *p; p++)
2377 krb5_addlog_dest(context, *fac, *p);
2378 krb5_config_free_strings(s);
2379 } else {
2380 char *ss;
2381 if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
2382 KDC_LOG_FILE) < 0)
2383 err(1, "out of memory");
2384 krb5_addlog_dest(context, *fac, ss);
2385 free(ss);
2387 krb5_set_warn_dest(context, *fac);
2390 static const char *sysplugin_dirs[] = {
2391 #ifdef _WIN32
2392 "$ORIGIN",
2393 #else
2394 "$ORIGIN/../lib/plugin/kdc",
2395 #endif
2396 #ifdef __APPLE__
2397 LIBDIR "/plugin/kdc",
2398 #endif
2399 NULL
2402 static void
2403 load_plugins(krb5_context context)
2405 const char * const *dirs = sysplugin_dirs;
2406 #ifndef _WIN32
2407 char **cfdirs;
2409 cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
2410 if (cfdirs)
2411 dirs = (const char * const *)cfdirs;
2412 #endif
2414 /* XXX kdc? */
2415 _krb5_load_plugins(context, "kdc", (const char **)dirs);
2417 #ifndef _WIN32
2418 krb5_config_free_strings(cfdirs);
2419 #endif
2422 static void
2423 get_csrf_prot_type(krb5_context context)
2425 char * const *strs = csrf_prot_type_strs.strings;
2426 size_t n = csrf_prot_type_strs.num_strings;
2427 size_t i;
2428 char **freeme = NULL;
2430 if (csrf_header == NULL)
2431 csrf_header = krb5_config_get_string(context, NULL, "bx509d",
2432 "csrf_protection_csrf_header",
2433 NULL);
2435 if (n == 0) {
2436 char * const *p;
2438 strs = freeme = krb5_config_get_strings(context, NULL, "bx509d",
2439 "csrf_protection_type", NULL);
2440 for (p = strs; p && p; p++)
2441 n++;
2444 for (i = 0; i < n; i++) {
2445 if (strcmp(strs[i], "GET-with-header") == 0)
2446 csrf_prot_type |= CSRF_PROT_GET_WITH_HEADER;
2447 else if (strcmp(strs[i], "GET-with-token") == 0)
2448 csrf_prot_type |= CSRF_PROT_GET_WITH_TOKEN;
2449 else if (strcmp(strs[i], "POST-with-header") == 0)
2450 csrf_prot_type |= CSRF_PROT_POST_WITH_HEADER;
2451 else if (strcmp(strs[i], "POST-with-token") == 0)
2452 csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
2454 free(freeme);
2457 * For GETs we default to no CSRF protection as our GETable resources are
2458 * safe and idempotent and we count on the browser not to make the
2459 * responses available to cross-site requests.
2461 * But, really, we don't want browsers even making these requests since, if
2462 * the browsers behave correctly, then there's no point, and if they don't
2463 * behave correctly then that could be catastrophic. Of course, there's no
2464 * guarantee that a browser won't have other catastrophic bugs, but still,
2465 * we should probably change this default in the future:
2467 * if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
2468 * !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
2469 * csrf_prot_type |= <whatever-the-new-default-should-be>;
2473 * For POSTs we default to CSRF protection with anti-CSRF tokens even
2474 * though out POSTable resources are safe and idempotent when POSTed and we
2475 * could count on the browser not to make the responses available to
2476 * cross-site requests.
2478 if (!(csrf_prot_type & CSRF_PROT_POST_WITH_HEADER) &&
2479 !(csrf_prot_type & CSRF_PROT_POST_WITH_TOKEN))
2480 csrf_prot_type |= CSRF_PROT_POST_WITH_TOKEN;
2484 main(int argc, char **argv)
2486 unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
2487 struct sockaddr_in sin;
2488 struct MHD_Daemon *previous = NULL;
2489 struct MHD_Daemon *current = NULL;
2490 struct sigaction sa;
2491 krb5_context context = NULL;
2492 MHD_socket sock = MHD_INVALID_SOCKET;
2493 void *kadm_handle;
2494 char *priv_key_pem = NULL;
2495 char *cert_pem = NULL;
2496 char sig;
2497 int optidx = 0;
2498 int ret;
2500 setprogname("httpkadmind");
2501 if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
2502 usage(1);
2503 if (help_flag)
2504 usage(0);
2505 if (version_flag) {
2506 print_version(NULL);
2507 exit(0);
2509 if (argc > optidx) /* Add option to set a URI local part prefix? */
2510 usage(1);
2511 if (port < 0)
2512 errx(1, "Port number must be given");
2514 if (writable_kadmin_server == NULL && kadmin_server == NULL &&
2515 !local_hdb && !local_hdb_read_only)
2516 errx(1, "One of --local or --local-read-only must be given, or a "
2517 "remote kadmind must be given");
2519 if (audiences.num_strings == 0) {
2520 char localhost[MAXHOSTNAMELEN];
2522 ret = gethostname(localhost, sizeof(localhost));
2523 if (ret == -1)
2524 errx(1, "Could not determine local hostname; use --audience");
2526 if ((audiences.strings =
2527 calloc(1, sizeof(audiences.strings[0]))) == NULL ||
2528 (audiences.strings[0] = strdup(localhost)) == NULL)
2529 err(1, "Out of memory");
2530 audiences.num_strings = 1;
2533 if (daemonize && daemon_child_fd == -1)
2534 daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
2535 daemonize = 0;
2537 argc -= optidx;
2538 argv += optidx;
2539 if (argc != 0)
2540 usage(1);
2542 if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
2543 err(1, "Could not create thread-specific storage");
2545 if ((errno = get_krb5_context(&context)))
2546 err(1, "Could not init krb5 context (config file issue?)");
2548 get_csrf_prot_type(context);
2550 if (!realm) {
2551 char *s;
2553 ret = krb5_get_default_realm(context, &s);
2554 if (ret)
2555 krb5_err(context, 1, ret, "Could not determine default realm");
2556 realm = s;
2559 if ((errno = get_kadm_handle(context, realm, 0 /* want_write */,
2560 &kadm_handle)))
2561 err(1, "Could not connect to HDB");
2562 kadm5_destroy(kadm_handle);
2563 kadm_handle = NULL;
2565 my_openlog(context, "httpkadmind", &logfac);
2566 load_plugins(context);
2568 if (cache_dir == NULL) {
2569 char *s = NULL;
2571 if (asprintf(&s, "%s/httpkadmind-XXXXXX",
2572 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2573 s == NULL ||
2574 (cache_dir = mkdtemp(s)) == NULL)
2575 err(1, "could not create temporary cache directory");
2576 if (verbose_counter)
2577 fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
2578 atexit(rm_cache_dir);
2579 setenv("TMPDIR", cache_dir, 1);
2582 again:
2583 if (cert_file && !priv_key_file)
2584 priv_key_file = cert_file;
2586 if (cert_file) {
2587 hx509_cursor cursor = NULL;
2588 hx509_certs certs = NULL;
2589 hx509_cert cert = NULL;
2590 time_t min_cert_life = 0;
2591 size_t len;
2592 void *s;
2594 ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
2595 if (ret == 0)
2596 ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
2597 while (ret == 0 &&
2598 (ret = hx509_certs_next_cert(context->hx509ctx, certs,
2599 cursor, &cert)) == 0 && cert) {
2600 time_t notAfter = 0;
2602 if (!hx509_cert_have_private_key_only(cert) &&
2603 (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
2604 errx(1, "One or more certificates in %s are expired",
2605 cert_file);
2606 if (notAfter) {
2607 notAfter -= time(NULL);
2608 if (notAfter < 600)
2609 warnx("One or more certificates in %s expire soon",
2610 cert_file);
2611 /* Reload 5 minutes prior to expiration */
2612 if (notAfter < min_cert_life || min_cert_life < 1)
2613 min_cert_life = notAfter;
2615 hx509_cert_free(cert);
2617 if (certs)
2618 (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
2619 if (min_cert_life > 4)
2620 alarm(min_cert_life >> 1);
2621 hx509_certs_free(&certs);
2622 if (ret)
2623 hx509_err(context->hx509ctx, 1, ret,
2624 "could not read certificate from %s", cert_file);
2626 if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
2627 (cert_pem = strndup(s, len)) == NULL)
2628 err(1, "could not read certificate from %s", cert_file);
2629 if (strlen(cert_pem) != len)
2630 err(1, "NULs in certificate file contents: %s", cert_file);
2631 free(s);
2634 if (priv_key_file) {
2635 size_t len;
2636 void *s;
2638 if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
2639 (priv_key_pem = strndup(s, len)) == NULL)
2640 err(1, "could not read private key from %s", priv_key_file);
2641 if (strlen(priv_key_pem) != len)
2642 err(1, "NULs in private key file contents: %s", priv_key_file);
2643 free(s);
2646 if (verbose_counter > 1)
2647 flags |= MHD_USE_DEBUG;
2648 if (thread_per_client_flag)
2649 flags |= MHD_USE_THREAD_PER_CONNECTION;
2652 if (pipe(sigpipe) == -1)
2653 err(1, "Could not set up key/cert reloading");
2654 memset(&sa, 0, sizeof(sa));
2655 sa.sa_handler = sighandler;
2656 if (reverse_proxied_flag) {
2658 * We won't use TLS in the reverse proxy case, so no need to reload
2659 * certs. But we'll still read them if given, and alarm() will get
2660 * called.
2662 * XXX We should be able to re-read krb5.conf and such on SIGHUP.
2664 (void) signal(SIGHUP, SIG_IGN);
2665 (void) signal(SIGUSR1, SIG_IGN);
2666 (void) signal(SIGALRM, SIG_IGN);
2667 } else {
2668 (void) sigaction(SIGHUP, &sa, NULL); /* Reload key & cert */
2669 (void) sigaction(SIGUSR1, &sa, NULL); /* Reload key & cert */
2670 (void) sigaction(SIGALRM, &sa, NULL); /* Reload key & cert */
2672 (void) sigaction(SIGINT, &sa, NULL); /* Graceful shutdown */
2673 (void) sigaction(SIGTERM, &sa, NULL); /* Graceful shutdown */
2674 (void) signal(SIGPIPE, SIG_IGN);
2676 if (previous)
2677 sock = MHD_quiesce_daemon(previous);
2679 if (reverse_proxied_flag) {
2681 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
2682 * them.
2684 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2685 sin.sin_family = AF_INET;
2686 sin.sin_port = htons(port);
2687 current = MHD_start_daemon(flags, port,
2689 * This is a connection access callback. We
2690 * don't use it.
2692 NULL, NULL,
2693 /* This is our request handler */
2694 route, (char *)NULL,
2695 MHD_OPTION_SOCK_ADDR, &sin,
2696 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2697 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2698 /* This is our request cleanup handler */
2699 MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
2700 MHD_OPTION_END);
2701 } else if (sock != MHD_INVALID_SOCKET) {
2703 * Certificate/key rollover: reuse the listen socket returned by
2704 * MHD_quiesce_daemon().
2706 current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2707 NULL, NULL,
2708 route, (char *)NULL,
2709 MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2710 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2711 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2712 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2713 MHD_OPTION_LISTEN_SOCKET, sock,
2714 MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
2715 MHD_OPTION_END);
2716 sock = MHD_INVALID_SOCKET;
2717 } else {
2718 current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2719 NULL, NULL,
2720 route, (char *)NULL,
2721 MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2722 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2723 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2724 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2725 MHD_OPTION_NOTIFY_COMPLETED, cleanup_req, NULL,
2726 MHD_OPTION_END);
2728 if (current == NULL)
2729 err(1, "Could not start kadmin REST service");
2731 if (previous) {
2732 MHD_stop_daemon(previous);
2733 previous = NULL;
2736 if (verbose_counter)
2737 fprintf(stderr, "Ready!\n");
2738 if (daemon_child_fd != -1)
2739 roken_detach_finish(NULL, daemon_child_fd);
2741 /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
2742 while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
2743 errno == EINTR)
2746 free(priv_key_pem);
2747 free(cert_pem);
2748 priv_key_pem = NULL;
2749 cert_pem = NULL;
2751 if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
2752 /* Reload certs and restart service gracefully */
2753 previous = current;
2754 current = NULL;
2755 goto again;
2758 MHD_stop_daemon(current);
2759 _krb5_unload_plugins(context, "kdc");
2760 pthread_key_delete(k5ctx);
2761 return 0;