2 * Copyright (c) 2020 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
37 #define _XOPEN_SOURCE_EXTENDED 1
38 #define _DEFAULT_SOURCE 1
42 #include <sys/socket.h>
43 #include <sys/types.h>
61 #include <netinet/in.h>
62 #include <netinet/ip.h>
64 #include <microhttpd.h>
66 #include "token_validator_plugin.h"
70 #include <gssapi/gssapi.h>
71 #include <gssapi/gssapi_krb5.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 #define BODYLEN_IS_STRLEN (~0)
86 * Libmicrohttpd is not the easiest API to use. It's got issues.
88 * One of the issues is how responses are handled, and the return value of the
89 * resource handler (MHD_NO -> close the connection, MHD_YES -> send response).
90 * Note that the handler could return MHD_YES without having set an HTTP
93 * There's memory management issues as well.
95 * Here we have to be careful about return values.
97 * Some of the functions defined here return just a krb5_error_code without
98 * having set an HTTP response on error.
99 * Others do set an HTTP response on error.
100 * The convention is to either set an HTTP response on error, or not at all,
101 * but not a mix of errors where for some the function will set a response and
102 * for others it won't.
104 * We do use some system error codes to stand in for errors here.
107 * - EACCES -> authorization failed
108 * - EINVAL -> bad API usage
109 * - ENOSYS -> missing CSRF token but CSRF token required
111 * FIXME: We should rely only on krb5_set_error_message() and friends and make
112 * error responses only in route(), mapping krb5_error_code values to
113 * HTTP status codes. This would simplify the error handling convention
117 /* Our request description structure */
118 typedef struct kadmin_request_desc
{
119 HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS
;
121 struct MHD_Connection
*connection
;
123 krb5_times token_times
;
127 * Currently we re-use the authz framework from bx509d, using an
128 * `hx509_request' instance (an abstraction for CSRs) to represent the
129 * request because that is what the authz plugin uses that implements the
130 * policy we want checked here.
132 * This is inappropriate in the long-term in two ways:
134 * - the policy for certificates deals in SANs and EKUs, whereas the
135 * policy for ext_keytab deals in host-based service principal names,
136 * and there is not a one-to-one mapping of service names to EKUs;
138 * - using a type from libhx509 for representing requests for things that
139 * aren't certificates is really not appropriate no matter how similar
140 * the use cases for this all might be.
142 * What we need to do is develop a library that can represent requests for
143 * credentials via naming attributes like SANs and Kerberos principal
144 * names, but more arbitrary still than what `hx509_request' supports, and
145 * then invokes a plugin.
147 * Also, we might want to develop an in-tree authorization solution that is
148 * richer than what kadmin.acl supports now, storing grants in HDB entries
149 * and/or similar places.
151 * For expediency we use `hx509_request' here for now, impedance mismatches
154 hx509_request req
; /* For authz only */
155 heim_array_t service_names
;
156 heim_array_t hostnames
;
158 krb5_principal cprinc
;
167 unsigned int response_set
:1;
168 unsigned int materialize
:1;
169 unsigned int rotate_now
:1;
170 unsigned int rotate
:1;
171 unsigned int revoke
:1;
172 unsigned int create
:1;
174 unsigned int is_self
:1;
176 } *kadmin_request_desc
;
179 audit_trail(kadmin_request_desc r
, krb5_error_code ret
)
181 const char *retname
= NULL
;
184 * Get a symbolic name for some error codes.
186 * Really, libcom_err should have a primitive for this, and ours could, but
187 * we can't use a system libcom_err if we extend ours.
189 #define CASE(x) case x : retname = #x; break
191 case ENOSYS
: retname
= "ECSRFTOKENREQD"; break;
195 CASE(HDB_ERR_NOT_FOUND_HERE
);
196 CASE(HDB_ERR_WRONG_REALM
);
197 CASE(HDB_ERR_EXISTS
);
198 CASE(HDB_ERR_KVNO_NOT_FOUND
);
199 CASE(HDB_ERR_NOENTRY
);
200 CASE(HDB_ERR_NO_MKEY
);
201 CASE(KRB5_KDC_UNREACH
);
203 CASE(KADM5_AUTH_GET
);
204 CASE(KADM5_AUTH_ADD
);
205 CASE(KADM5_AUTH_MODIFY
);
206 CASE(KADM5_AUTH_DELETE
);
207 CASE(KADM5_AUTH_INSUFFICIENT
);
210 CASE(KADM5_RPC_ERROR
);
212 CASE(KADM5_BAD_HIST_KEY
);
213 CASE(KADM5_NOT_INIT
);
214 CASE(KADM5_UNK_PRINC
);
215 CASE(KADM5_UNK_POLICY
);
216 CASE(KADM5_BAD_MASK
);
217 CASE(KADM5_BAD_CLASS
);
218 CASE(KADM5_BAD_LENGTH
);
219 CASE(KADM5_BAD_POLICY
);
220 CASE(KADM5_BAD_PRINCIPAL
);
221 CASE(KADM5_BAD_AUX_ATTR
);
222 CASE(KADM5_BAD_HISTORY
);
223 CASE(KADM5_BAD_MIN_PASS_LIFE
);
224 CASE(KADM5_PASS_Q_TOOSHORT
);
225 CASE(KADM5_PASS_Q_CLASS
);
226 CASE(KADM5_PASS_Q_DICT
);
227 CASE(KADM5_PASS_Q_GENERIC
);
228 CASE(KADM5_PASS_REUSE
);
229 CASE(KADM5_PASS_TOOSOON
);
230 CASE(KADM5_POLICY_REF
);
232 CASE(KADM5_BAD_PASSWORD
);
233 CASE(KADM5_PROTECT_PRINCIPAL
);
234 CASE(KADM5_BAD_SERVER_HANDLE
);
235 CASE(KADM5_BAD_STRUCT_VERSION
);
236 CASE(KADM5_OLD_STRUCT_VERSION
);
237 CASE(KADM5_NEW_STRUCT_VERSION
);
238 CASE(KADM5_BAD_API_VERSION
);
239 CASE(KADM5_OLD_LIB_API_VERSION
);
240 CASE(KADM5_OLD_SERVER_API_VERSION
);
241 CASE(KADM5_NEW_LIB_API_VERSION
);
242 CASE(KADM5_NEW_SERVER_API_VERSION
);
243 CASE(KADM5_SECURE_PRINC_MISSING
);
244 CASE(KADM5_NO_RENAME_SALT
);
245 CASE(KADM5_BAD_CLIENT_PARAMS
);
246 CASE(KADM5_BAD_SERVER_PARAMS
);
247 CASE(KADM5_AUTH_LIST
);
248 CASE(KADM5_AUTH_CHANGEPW
);
249 CASE(KADM5_BAD_TL_TYPE
);
250 CASE(KADM5_MISSING_CONF_PARAMS
);
251 CASE(KADM5_BAD_SERVER_NAME
);
252 CASE(KADM5_KS_TUPLE_NOSUPP
);
253 CASE(KADM5_SETKEY3_ETYPE_MISMATCH
);
254 CASE(KADM5_DECRYPT_USAGE_NOSUPP
);
255 CASE(KADM5_POLICY_OP_NOSUPP
);
256 CASE(KADM5_KEEPOLD_NOSUPP
);
257 CASE(KADM5_AUTH_GET_KEYS
);
258 CASE(KADM5_ALREADY_LOCKED
);
259 CASE(KADM5_NOT_LOCKED
);
260 CASE(KADM5_LOG_CORRUPT
);
261 CASE(KADM5_LOG_NEEDS_UPGRADE
);
262 CASE(KADM5_BAD_SERVER_HOOK
);
263 CASE(KADM5_SERVER_HOOK_NOT_FOUND
);
264 CASE(KADM5_OLD_SERVER_HOOK_VERSION
);
265 CASE(KADM5_NEW_SERVER_HOOK_VERSION
);
266 CASE(KADM5_READ_ONLY
);
274 heim_audit_trail((heim_svc_req_desc
)r
, ret
, retname
);
277 static krb5_log_facility
*logfac
;
278 static pthread_key_t k5ctx
;
280 static krb5_error_code
281 get_krb5_context(krb5_context
*contextp
)
285 if ((*contextp
= pthread_getspecific(k5ctx
)))
288 ret
= krb5_init_context(contextp
);
289 /* XXX krb5_set_log_dest(), warn_dest, debug_dest */
291 (void) pthread_setspecific(k5ctx
, *contextp
);
295 static int port
= -1;
296 static int help_flag
;
297 static int daemonize
;
298 static int daemon_child_fd
= -1;
299 static int local_hdb
;
300 static int local_hdb_read_only
;
301 static int read_only
;
302 static int verbose_counter
;
303 static int version_flag
;
304 static int reverse_proxied_flag
;
305 static int thread_per_client_flag
;
306 struct getarg_strings audiences
;
307 static const char *cert_file
;
308 static const char *priv_key_file
;
309 static const char *cache_dir
;
310 static const char *realm
;
311 static const char *hdb
;
312 static const char *primary_server_URI
;
313 static const char *kadmin_server
;
314 static const char *writable_kadmin_server
;
315 static const char *stash_file
;
316 static const char *kadmin_client_name
= "httpkadmind/admin";
317 static const char *kadmin_client_keytab
;
318 static struct getarg_strings auth_types
;
320 #define set_conf(c, f, v, b) \
322 if (((c).f = strdup(v)) == NULL) \
328 * Does NOT set an HTTP response, naturally, as it doesn't even have access to
331 static krb5_error_code
332 get_kadm_handle(krb5_context context
,
333 const char *want_realm
,
337 kadm5_config_params conf
;
341 * If the caller wants to write and we are configured to redirect in that
342 * case, then trigger a redirect by returning KADM5_READ_ONLY.
344 if (want_write
&& local_hdb_read_only
&& primary_server_URI
)
345 return KADM5_READ_ONLY
;
346 if (want_write
&& read_only
)
347 return KADM5_READ_ONLY
;
350 * Configure kadm5 connection.
352 * Note that all of these are optional, and will be found in krb5.conf or,
353 * in some cases, in DNS, as needed.
355 memset(&conf
, 0, sizeof(conf
));
358 conf
.stash_file
= NULL
;
359 conf
.admin_server
= NULL
;
360 conf
.readonly_admin_server
= NULL
;
361 set_conf(conf
, realm
, want_realm
, KADM5_CONFIG_REALM
);
362 set_conf(conf
, dbname
, hdb
, KADM5_CONFIG_DBNAME
);
363 set_conf(conf
, stash_file
, stash_file
, KADM5_CONFIG_STASH_FILE
);
364 set_conf(conf
, admin_server
, writable_kadmin_server
, KADM5_CONFIG_ADMIN_SERVER
);
365 set_conf(conf
, readonly_admin_server
, kadmin_server
,
366 KADM5_CONFIG_READONLY_ADMIN_SERVER
);
369 * If we have a local HDB we'll use it if we can. If the local HDB is
370 * read-only and the caller wants to write, then we won't use the local
373 if (local_hdb
&& (!local_hdb_read_only
|| !want_write
)) {
374 ret
= kadm5_s_init_with_password_ctx(context
,
377 NULL
, /* service_name */
379 0, /* struct_version */
386 * Remote connection. This will connect to a read-only kadmind if
387 * possible, and if so, reconnect to a writable kadmind as needed.
389 * Note that kadmin_client_keytab can be an HDB: or HDBGET: keytab.
391 ret
= kadm5_c_init_with_skey_ctx(context
,
393 kadmin_client_keytab
,
396 0, /* struct_version */
402 ret
= krb5_enomem(context
);
405 free(conf
.readonly_admin_server
);
406 free(conf
.admin_server
);
407 free(conf
.stash_file
);
413 static krb5_error_code
resp(kadmin_request_desc
, int, krb5_error_code
,
414 enum MHD_ResponseMemoryMode
, const char *,
415 const void *, size_t, const char *, const char *);
416 static krb5_error_code
bad_req(kadmin_request_desc
, krb5_error_code
, int,
418 HEIMDAL_PRINTF_ATTRIBUTE((__printf__
, 4, 5));
420 static krb5_error_code
bad_enomem(kadmin_request_desc
, krb5_error_code
);
421 static krb5_error_code
bad_400(kadmin_request_desc
, krb5_error_code
, const char *);
422 static krb5_error_code
bad_401(kadmin_request_desc
, const char *);
423 static krb5_error_code
bad_403(kadmin_request_desc
, krb5_error_code
, const char *);
424 static krb5_error_code
bad_404(kadmin_request_desc
, const char *);
425 static krb5_error_code
bad_405(kadmin_request_desc
, const char *);
426 /*static krb5_error_code bad_500(kadmin_request_desc, krb5_error_code, const char *);*/
427 static krb5_error_code
bad_503(kadmin_request_desc
, krb5_error_code
, const char *);
430 validate_token(kadmin_request_desc r
)
435 char token_type
[64]; /* Plenty */
438 size_t host_len
, brk
, i
;
440 memset(&r
->token_times
, 0, sizeof(r
->token_times
));
441 host
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
442 MHD_HTTP_HEADER_HOST
);
444 return bad_400(r
, EINVAL
, "Host header is missing");
446 /* Exclude port number here (IPv6-safe because of the below) */
447 host_len
= ((p
= strchr(host
, ':'))) ? p
- host
: strlen(host
);
449 token
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
450 MHD_HTTP_HEADER_AUTHORIZATION
);
452 return bad_401(r
, "Authorization token is missing");
453 brk
= strcspn(token
, " \t");
454 if (token
[brk
] == '\0' || brk
> sizeof(token_type
) - 1)
455 return bad_401(r
, "Authorization token is missing");
456 memcpy(token_type
, token
, brk
);
457 token_type
[brk
] = '\0';
459 tok
.length
= strlen(token
);
460 tok
.data
= (void *)(uintptr_t)token
;
462 for (i
= 0; i
< audiences
.num_strings
; i
++)
463 if (strncasecmp(host
, audiences
.strings
[i
], host_len
) == 0 &&
464 audiences
.strings
[i
][host_len
] == '\0')
466 if (i
== audiences
.num_strings
)
467 return bad_403(r
, EINVAL
, "Host: value is not accepted here");
469 r
->sname
= strdup(host
); /* No need to check for ENOMEM here */
471 ret
= kdc_validate_token(r
->context
, NULL
/* realm */, token_type
, &tok
,
472 (const char **)&audiences
.strings
[i
], 1,
473 &r
->cprinc
, &r
->token_times
);
475 return bad_403(r
, ret
, "Token validation failed");
476 if (r
->cprinc
== NULL
)
477 return bad_403(r
, ret
,
478 "Could not extract a principal name from token");
479 ret
= krb5_unparse_name(r
->context
, r
->cprinc
, &r
->cname
);
481 return bad_503(r
, ret
,
482 "Could not extract a principal name from token");
487 k5_free_context(void *ctx
)
489 krb5_free_context(ctx
);
492 #ifndef HAVE_UNLINKAT
494 unlink1file(const char *dname
, const char *name
)
498 if (strlcpy(p
, dname
, sizeof(p
)) < sizeof(p
) &&
499 strlcat(p
, "/", sizeof(p
)) < sizeof(p
) &&
500 strlcat(p
, name
, sizeof(p
)) < sizeof(p
))
513 * This works, but not on Win32:
515 * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
517 * We make no directories in `cache_dir', so we need not recurse.
519 if ((d
= opendir(cache_dir
)) == NULL
)
522 while ((e
= readdir(d
))) {
525 * Because unlinkat() takes a directory FD, implementing one for
526 * libroken is tricky at best. Instead we might want to implement an
527 * rm_dash_rf() function in lib/roken.
529 (void) unlinkat(dirfd(d
), e
->d_name
, 0);
531 (void) unlink1file(cache_dir
, e
->d_name
);
535 (void) rmdir(cache_dir
);
539 * Work around older libmicrohttpd not strduping response header values when
542 static HEIMDAL_THREAD_LOCAL
struct redirect_uri
{
550 redirect_uri_appends(struct redirect_uri
*redirect
,
556 if (!redirect
->valid
|| redirect
->len
>= sizeof(redirect
->uri
) - 1) {
560 /* Optimize strlcpy by using redirect->uri + redirect->len */
561 p
= redirect
->uri
+ redirect
->len
;
562 sz
= sizeof(redirect
->uri
) - redirect
->len
;
563 if ((len
= strlcpy(p
, s
, sz
)) >= sz
)
566 redirect
->len
+= len
;
570 make_redirect_uri_param_cb(void *d
,
571 enum MHD_ValueKind kind
,
575 struct redirect_uri
*redirect
= d
;
577 redirect_uri_appends(redirect
, redirect
->first_param
? "?" : "&");
578 redirect_uri_appends(redirect
, key
);
580 redirect_uri_appends(redirect
, "=");
581 redirect_uri_appends(redirect
, val
);
583 redirect
->first_param
= 0;
588 make_redirect_uri(kadmin_request_desc r
, const char *base
)
590 redirect_uri
.len
= 0;
591 redirect_uri
.uri
[0] = '\0';
592 redirect_uri
.valid
= 1;
593 redirect_uri
.first_param
= 1;
595 redirect_uri_appends(&redirect_uri
, base
); /* Redirect to primary URI base */
596 redirect_uri_appends(&redirect_uri
, r
->reqtype
); /* URI local-part */
597 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
598 make_redirect_uri_param_cb
,
600 return redirect_uri
.valid
? redirect_uri
.uri
: NULL
;
605 * XXX Shouldn't be a body, but a status message. The body should be
606 * configurable to be from a file. MHD doesn't give us a way to set the
607 * response status message though, just the body.
609 * Calls audit_trail().
611 * Returns -1 if something terrible happened, which should ultimately cause
612 * route() to return MHD_NO, which should cause libmicrohttpd to close the
613 * connection to the user-agent.
615 * Returns 0 in all other cases.
617 static krb5_error_code
618 resp(kadmin_request_desc r
,
619 int http_status_code
,
621 enum MHD_ResponseMemoryMode rmmode
,
622 const char *content_type
,
628 struct MHD_Response
*response
;
631 if (r
->response_set
) {
632 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
633 "Internal error; attempted to set a second response");
637 (void) gettimeofday(&r
->tv_end
, NULL
);
640 if (body
&& bodylen
== BODYLEN_IS_STRLEN
)
641 bodylen
= strlen(body
);
643 response
= MHD_create_response_from_buffer(bodylen
, rk_UNCONST(body
),
645 if (response
== NULL
)
647 mret
= MHD_add_response_header(response
, MHD_HTTP_HEADER_CACHE_CONTROL
,
648 "no-store, max-age=0");
649 if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_UNAUTHORIZED
) {
652 if (auth_types
.num_strings
< 1)
653 http_status_code
= MHD_HTTP_SERVICE_UNAVAILABLE
;
655 for (i
= 0; mret
== MHD_YES
&& i
< auth_types
.num_strings
; i
++)
656 mret
= MHD_add_response_header(response
,
657 MHD_HTTP_HEADER_WWW_AUTHENTICATE
,
658 auth_types
.strings
[i
]);
659 } else if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_TEMPORARY_REDIRECT
) {
660 const char *redir
= make_redirect_uri(r
, primary_server_URI
);
663 mret
= MHD_add_response_header(response
, MHD_HTTP_HEADER_LOCATION
,
666 /* XXX Find a way to set a new response body; log */
667 http_status_code
= MHD_HTTP_SERVICE_UNAVAILABLE
;
670 if (mret
== MHD_YES
&& csrf
)
671 mret
= MHD_add_response_header(response
,
675 if (mret
== MHD_YES
&& content_type
) {
676 mret
= MHD_add_response_header(response
,
677 MHD_HTTP_HEADER_CONTENT_TYPE
,
681 mret
= MHD_queue_response(r
->connection
, http_status_code
, response
);
682 MHD_destroy_response(response
);
684 return mret
== MHD_NO
? -1 : 0;
687 static krb5_error_code
688 bad_reqv(kadmin_request_desc r
,
689 krb5_error_code code
,
690 int http_status_code
,
695 krb5_context context
= NULL
;
696 const char *k5msg
= NULL
;
697 const char *emsg
= NULL
;
698 char *formatted
= NULL
;
702 context
= r
->context
;
703 if (r
&& r
->hcontext
&& r
->kv
)
704 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "http-status-code", "%d",
706 (void) gettimeofday(&r
->tv_end
, NULL
);
707 if (code
== ENOMEM
) {
709 krb5_log_msg(context
, logfac
, 1, NULL
, "Out of memory");
710 return resp(r
, http_status_code
, code
, MHD_RESPMEM_PERSISTENT
,
711 NULL
, fmt
, BODYLEN_IS_STRLEN
, NULL
, NULL
);
716 emsg
= k5msg
= krb5_get_error_message(context
, code
);
718 emsg
= strerror(code
);
721 ret
= vasprintf(&formatted
, fmt
, ap
) == -1;
723 if (ret
> -1 && formatted
)
724 ret
= asprintf(&msg
, "%s: %s (%d)", formatted
, emsg
, (int)code
);
729 if (r
&& r
->hcontext
)
730 heim_audit_addreason((heim_svc_req_desc
)r
, "%s", formatted
);
731 krb5_free_error_message(context
, k5msg
);
733 if (ret
== -1 || msg
== NULL
) {
735 krb5_log_msg(context
, logfac
, 1, NULL
, "Out of memory");
736 return resp(r
, MHD_HTTP_SERVICE_UNAVAILABLE
, ENOMEM
,
737 MHD_RESPMEM_PERSISTENT
, NULL
,
738 "Out of memory", BODYLEN_IS_STRLEN
, NULL
, NULL
);
741 ret
= resp(r
, http_status_code
, code
, MHD_RESPMEM_MUST_COPY
,
742 NULL
, msg
, BODYLEN_IS_STRLEN
, NULL
, NULL
);
745 return ret
== -1 ? -1 : code
;
748 static krb5_error_code
749 bad_req(kadmin_request_desc r
,
750 krb5_error_code code
,
751 int http_status_code
,
759 ret
= bad_reqv(r
, code
, http_status_code
, fmt
, ap
);
764 static krb5_error_code
765 bad_enomem(kadmin_request_desc r
, krb5_error_code ret
)
767 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
771 static krb5_error_code
772 bad_400(kadmin_request_desc r
, int ret
, const char *reason
)
774 return bad_req(r
, ret
, MHD_HTTP_BAD_REQUEST
, "%s", reason
);
777 static krb5_error_code
778 bad_401(kadmin_request_desc r
, const char *reason
)
780 return bad_req(r
, EACCES
, MHD_HTTP_UNAUTHORIZED
, "%s", reason
);
783 static krb5_error_code
784 bad_403(kadmin_request_desc r
, krb5_error_code ret
, const char *reason
)
786 return bad_req(r
, ret
, MHD_HTTP_FORBIDDEN
, "%s", reason
);
789 static krb5_error_code
790 bad_404(kadmin_request_desc r
, const char *name
)
792 return bad_req(r
, ENOENT
, MHD_HTTP_NOT_FOUND
,
793 "Resource not found: %s", name
);
796 static krb5_error_code
797 bad_405(kadmin_request_desc r
, const char *method
)
799 return bad_req(r
, EPERM
, MHD_HTTP_METHOD_NOT_ALLOWED
,
800 "Method not supported: %s", method
);
803 static krb5_error_code
804 bad_method_want_POST(kadmin_request_desc r
)
806 return bad_req(r
, EPERM
, MHD_HTTP_METHOD_NOT_ALLOWED
,
807 "Use POST for making changes to principals");
811 static krb5_error_code
812 bad_500(kadmin_request_desc r
,
816 return bad_req(r
, ret
, MHD_HTTP_INTERNAL_SERVER_ERROR
,
817 "Internal error: %s", reason
);
821 static krb5_error_code
822 bad_503(kadmin_request_desc r
,
826 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
827 "Service unavailable: %s", reason
);
830 static krb5_error_code
831 good_ext_keytab(kadmin_request_desc r
)
838 if (!r
->keytab_name
|| !(p
= strchr(r
->keytab_name
, ':')))
839 return bad_503(r
, EINVAL
, "Internal error (no keytab produced)");
841 if (strncmp(p
, cache_dir
, strlen(cache_dir
)) != 0)
842 return bad_503(r
, EINVAL
, "Internal error");
843 ret
= rk_undumpdata(p
, &body
, &bodylen
);
845 return bad_503(r
, ret
, "Could not recover keytab from temp file");
847 ret
= resp(r
, MHD_HTTP_OK
, 0, MHD_RESPMEM_MUST_COPY
,
848 "application/octet-stream", body
, bodylen
, NULL
, NULL
);
853 static krb5_error_code
854 check_service_name(kadmin_request_desc r
, const char *name
)
856 if (name
== NULL
|| name
[0] == '\0' ||
857 strchr(name
, '/') || strchr(name
, '\\') || strchr(name
, '@') ||
858 strcmp(name
, "krbtgt") == 0 ||
859 strcmp(name
, "iprop") == 0 ||
860 strcmp(name
, "kadmin") == 0 ||
861 strcmp(name
, "hprop") == 0 ||
862 strcmp(name
, "WELLKNOWN") == 0 ||
863 strcmp(name
, "K") == 0) {
864 krb5_set_error_message(r
->context
, EACCES
,
865 "No one is allowed to fetch keys for "
866 "Heimdal service %s", name
);
869 if (strcmp(name
, "root") != 0 &&
870 strcmp(name
, "host") != 0 &&
871 strcmp(name
, "exceed") != 0)
873 if (krb5_config_get_bool_default(r
->context
, NULL
, FALSE
,
875 "csr_authorizer_handles_svc_names",
878 krb5_set_error_message(r
->context
, EACCES
,
879 "No one is allowed to fetch keys for "
880 "service \"%s\" because of authorizer "
881 "limitations", name
);
887 enum MHD_ValueKind kind
,
891 kadmin_request_desc r
= d
;
892 krb5_error_code ret
= 0;
893 heim_string_t s
= NULL
;
896 * Multi-valued params:
898 * - spn=<service>/<hostname>
899 * - dNSName=<hostname>
900 * - service=<service>
902 * Single-valued params:
905 * - materialize=true -- create a concrete princ where it's virtual
906 * - enctypes=... -- key-salt types
907 * - revoke=true -- delete old keys (concrete princs only)
908 * - rotate=true -- change keys (no-op for virtual princs)
909 * - create=true -- create a concrete princ
910 * - ro=true -- perform no writes
913 if (strcmp(key
, "realm") == 0 && val
) {
914 if (!r
->realm
&& !(r
->realm
= strdup(val
)))
915 ret
= krb5_enomem(r
->context
);
916 } else if (strcmp(key
, "materialize") == 0 ||
917 strcmp(key
, "revoke") == 0 ||
918 strcmp(key
, "rotate") == 0 ||
919 strcmp(key
, "create") == 0 ||
920 strcmp(key
, "ro") == 0) {
921 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
922 "requested_option", "%s", key
);
923 if (!val
|| strcmp(val
, "true") != 0)
924 krb5_set_error_message(r
->context
, ret
= EINVAL
,
925 "get-keys \"%s\" q-param accepts "
926 "only \"true\"", key
);
927 else if (strcmp(key
, "materialize") == 0)
929 else if (strcmp(key
, "revoke") == 0)
931 else if (strcmp(key
, "rotate") == 0)
933 else if (strcmp(key
, "create") == 0)
935 else if (strcmp(key
, "ro") == 0)
937 } else if (strcmp(key
, "dNSName") == 0 && val
) {
938 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
939 "requested_dNSName", "%s", val
);
941 krb5_set_error_message(r
->context
, ret
= EACCES
,
942 "only one service may be requested for self");
943 } else if (strchr(val
, '.') == NULL
) {
944 krb5_set_error_message(r
->context
, ret
= EACCES
,
945 "dNSName must have at least one '.' in it");
947 s
= heim_string_create(val
);
949 ret
= krb5_enomem(r
->context
);
951 ret
= heim_array_append_value(r
->hostnames
, s
);
954 ret
= hx509_request_add_dns_name(r
->context
->hx509ctx
, r
->req
, val
);
955 } else if (strcmp(key
, "service") == 0 && val
) {
956 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
957 "requested_service", "%s", val
);
959 krb5_set_error_message(r
->context
, ret
= EACCES
,
960 "use \"spn\" for self");
962 ret
= check_service_name(r
, val
);
964 s
= heim_string_create(val
);
966 ret
= krb5_enomem(r
->context
);
968 ret
= heim_array_append_value(r
->service_names
, s
);
970 } else if (strcmp(key
, "enctypes") == 0 && val
) {
971 r
->enctypes
= strdup(val
);
972 if (!(r
->enctypes
= strdup(val
)))
973 ret
= krb5_enomem(r
->context
);
974 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
975 "requested_enctypes", "%s", val
);
976 } else if (r
->is_self
&& strcmp(key
, "spn") == 0 && val
) {
977 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
978 "requested_spn", "%s", val
);
979 krb5_set_error_message(r
->context
, ret
= EACCES
,
980 "only one service may be requested for self");
981 } else if (strcmp(key
, "spn") == 0 && val
) {
982 krb5_principal p
= NULL
;
983 const char *hostname
= "";
985 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
986 "requested_spn", "%s", val
);
988 ret
= krb5_parse_name_flags(r
->context
, val
,
989 KRB5_PRINCIPAL_PARSE_NO_DEF_REALM
, &p
);
990 if (ret
== 0 && krb5_principal_get_realm(r
->context
, p
) == NULL
)
991 ret
= krb5_principal_set_realm(r
->context
, p
,
992 r
->realm
? r
->realm
: realm
);
995 * The SPN has to have two components.
997 * TODO: Support more components? Support AD-style NetBIOS computer
1000 if (ret
== 0 && krb5_principal_get_num_comp(r
->context
, p
) != 2)
1004 * Allow only certain service names. Except that when
1005 * the SPN == the requestor's principal name then allow the "host"
1009 const char *service
=
1010 krb5_principal_get_comp_string(r
->context
, p
, 0);
1012 if (strcmp(service
, "host") == 0 &&
1013 krb5_principal_compare(r
->context
, p
, r
->cprinc
) &&
1015 heim_array_get_length(r
->hostnames
) == 0 &&
1016 heim_array_get_length(r
->spns
) == 0) {
1019 ret
= check_service_name(r
, service
);
1021 if (ret
== 0 && !krb5_principal_compare(r
->context
, p
, r
->cprinc
))
1022 ret
= check_service_name(r
,
1023 krb5_principal_get_comp_string(r
->context
,
1026 hostname
= krb5_principal_get_comp_string(r
->context
, p
, 1);
1027 if (!hostname
|| !strchr(hostname
, '.'))
1028 krb5_set_error_message(r
->context
, ret
= ENOTSUP
,
1029 "Only host-based service names supported");
1031 if (ret
== 0 && r
->realm
)
1032 ret
= krb5_principal_set_realm(r
->context
, p
, r
->realm
);
1033 else if (ret
== 0 && realm
)
1034 ret
= krb5_principal_set_realm(r
->context
, p
, realm
);
1036 ret
= hx509_request_add_dns_name(r
->context
->hx509ctx
, r
->req
,
1038 if (ret
== 0 && !(s
= heim_string_create(val
)))
1039 ret
= krb5_enomem(r
->context
);
1041 ret
= heim_array_append_value(r
->spns
, s
);
1042 krb5_free_principal(r
->context
, p
);
1045 /* The authorizer probably doesn't know what to do with this */
1046 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
, val
);
1049 /* Produce error for unknown params */
1050 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "requested_unknown", "true");
1051 krb5_set_error_message(r
->context
, ret
= ENOTSUP
,
1052 "Query parameter %s not supported", key
);
1057 return ret
? MHD_NO
/* Stop iterating */ : MHD_YES
;
1060 static krb5_error_code
1061 authorize_req(kadmin_request_desc r
)
1063 krb5_error_code ret
;
1066 ret
= hx509_request_init(r
->context
->hx509ctx
, &r
->req
);
1068 return bad_enomem(r
, ret
);
1069 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1073 return bad_403(r
, ret
, "Not authorized to requested principal(s)");
1075 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
1076 "Could not handle query parameters");
1080 ret
= kdc_authorize_csr(r
->context
, "ext_keytab", r
->req
, r
->cprinc
);
1081 if (ret
== EACCES
|| ret
== EINVAL
|| ret
== ENOTSUP
||
1082 ret
== KRB5KDC_ERR_POLICY
)
1083 return bad_403(r
, ret
, "Not authorized to requested principal(s)");
1085 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
1086 "Error checking authorization");
1090 static krb5_error_code
1091 make_keytab(kadmin_request_desc r
)
1093 krb5_error_code ret
= 0;
1096 r
->keytab_name
= NULL
;
1097 if (asprintf(&r
->keytab_name
, "FILE:%s/kt-XXXXXX", cache_dir
) == -1 ||
1098 r
->keytab_name
== NULL
)
1099 ret
= krb5_enomem(r
->context
);
1101 fd
= mkstemp(r
->keytab_name
+ sizeof("FILE:") - 1);
1102 if (ret
== 0 && fd
== -1)
1105 ret
= krb5_kt_resolve(r
->context
, r
->keytab_name
, &r
->keytab
);
1111 static krb5_error_code
1112 write_keytab(kadmin_request_desc r
,
1113 kadm5_principal_ent_rec
*princ
,
1114 const char *unparsed
)
1116 krb5_error_code ret
= 0;
1117 krb5_keytab_entry key
;
1120 if (princ
->n_key_data
<= 0)
1123 if (kadm5_some_keys_are_bogus(princ
->n_key_data
, &princ
->key_data
[0])) {
1124 krb5_warn(r
->context
, ret
,
1125 "httpkadmind running with insufficient kadmin privilege "
1126 "for extracting keys for %s", unparsed
);
1127 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
1128 "httpkadmind running with insufficient kadmin privilege "
1129 "for extracting keys for %s", unparsed
);
1133 memset(&key
, 0, sizeof(key
));
1134 for (i
= 0; ret
== 0 && i
< princ
->n_key_data
; i
++) {
1135 krb5_key_data
*kd
= &princ
->key_data
[i
];
1137 key
.principal
= princ
->principal
;
1138 key
.vno
= kd
->key_data_kvno
;
1139 key
.keyblock
.keytype
= kd
->key_data_type
[0];
1140 key
.keyblock
.keyvalue
.length
= kd
->key_data_length
[0];
1141 key
.keyblock
.keyvalue
.data
= kd
->key_data_contents
[0];
1144 * FIXME kadm5 doesn't give us set_time here. If it gave us the
1145 * KeyRotation metadata, we could compute it. But this might be a
1146 * concrete principal with concrete keys, in which case we can't.
1148 * To fix this we need to extend the protocol and the API.
1150 key
.timestamp
= time(NULL
);
1152 ret
= krb5_kt_add_entry(r
->context
, r
->keytab
, &key
);
1155 krb5_warn(r
->context
, ret
,
1156 "Failed to write keytab entries for %s", unparsed
);
1162 random_password(krb5_context context
, char *buf
, size_t buflen
)
1164 static const char chars
[] =
1165 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,";
1171 for (i
= 0; i
< buflen
; i
++) {
1172 if (i
% sizeof(p
) == 0)
1173 krb5_generate_random_block(p
, sizeof(p
));
1174 b
= p
[i
% sizeof(p
)];
1175 buf
[i
] = chars
[b
% (sizeof(chars
) - 1)];
1180 static krb5_error_code
1181 make_kstuple(krb5_context context
,
1182 kadm5_principal_ent_rec
*p
,
1183 krb5_key_salt_tuple
**kstuple
,
1191 if (p
->n_key_data
< 1)
1193 *kstuple
= calloc(p
->n_key_data
, sizeof (*kstuple
));
1194 for (i
= 0; *kstuple
&& i
< p
->n_key_data
; i
++) {
1195 if (p
->key_data
[i
].key_data_kvno
== p
->kvno
) {
1196 (*kstuple
)[i
].ks_enctype
= p
->key_data
[i
].key_data_type
[0];
1197 (*kstuple
)[i
].ks_salttype
= p
->key_data
[i
].key_data_type
[1];
1201 return *kstuple
? 0 :krb5_enomem(context
);
1205 * Get keys for one principal.
1207 * Does NOT set an HTTP response.
1209 static krb5_error_code
1210 get_keys1(kadmin_request_desc r
, const char *pname
)
1212 kadm5_principal_ent_rec princ
;
1213 krb5_key_salt_tuple
*kstuple
= NULL
;
1214 krb5_error_code ret
= 0;
1215 krb5_principal p
= NULL
;
1217 KADM5_PRINCIPAL
| KADM5_KVNO
| KADM5_MAX_LIFE
| KADM5_MAX_RLIFE
|
1218 KADM5_ATTRIBUTES
| KADM5_KEY_DATA
| KADM5_TL_DATA
;
1219 uint32_t create_mask
= mask
& ~(KADM5_KEY_DATA
| KADM5_TL_DATA
);
1220 size_t nkstuple
= 0;
1225 memset(&princ
, 0, sizeof(princ
));
1226 princ
.key_data
= NULL
;
1227 princ
.tl_data
= NULL
;
1229 ret
= krb5_parse_name(r
->context
, pname
, &p
);
1230 if (ret
== 0 && r
->realm
)
1231 ret
= krb5_principal_set_realm(r
->context
, p
, r
->realm
);
1232 else if (ret
== 0 && realm
)
1233 ret
= krb5_principal_set_realm(r
->context
, p
, realm
);
1234 if (ret
== 0 && r
->enctypes
)
1235 ret
= krb5_string_to_keysalts2(r
->context
, r
->enctypes
,
1236 &nkstuple
, &kstuple
);
1238 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
1243 * If princ is virtual and we're not asked to materialize, ignore
1244 * requests to rotate.
1246 if (!r
->materialize
&&
1247 (princ
.attributes
& (KRB5_KDB_VIRTUAL_KEYS
| KRB5_KDB_VIRTUAL
))) {
1253 change
= !r
->ro
&& (r
->rotate
|| r
->revoke
);
1255 /* Handle create / materialize options */
1256 if (ret
== KADM5_UNK_PRINC
&& r
->create
) {
1260 ret
= KADM5_READ_ONLY
;
1262 ret
= strcmp(r
->method
, "POST") == 0 ? 0 : ENOSYS
; /* XXX */
1263 if (ret
== 0 && local_hdb
&& local_hdb_read_only
) {
1264 /* Make sure we can write */
1265 kadm5_destroy(r
->kadm_handle
);
1266 r
->kadm_handle
= NULL
;
1267 ret
= get_kadm_handle(r
->context
, r
->realm
, 1 /* want_write */,
1270 memset(&princ
, 0, sizeof(princ
));
1272 * Some software is allergic to kvno 1, assuming that kvno 1 implies
1273 * half-baked service principal. We've some vague recollection of
1274 * something similar for kvno 2, so let's start at 3.
1277 princ
.tl_data
= NULL
;
1278 princ
.key_data
= NULL
;
1279 princ
.max_life
= 24 * 3600; /* XXX Make configurable */
1280 princ
.max_renewable_life
= princ
.max_life
; /* XXX Make configurable */
1282 random_password(r
->context
, pw
, sizeof(pw
));
1283 princ
.principal
= p
; /* Borrow */
1285 ret
= kadm5_create_principal_3(r
->kadm_handle
, &princ
, create_mask
,
1286 nkstuple
, kstuple
, pw
);
1287 princ
.principal
= NULL
; /* Return */
1290 } else if (ret
== 0 && r
->materialize
&&
1291 (princ
.attributes
& KRB5_KDB_VIRTUAL
)) {
1293 #ifndef MATERIALIZE_NOTYET
1297 ret
= KADM5_READ_ONLY
;
1299 ret
= strcmp(r
->method
, "POST") == 0 ? 0 : ENOSYS
; /* XXX */
1300 if (ret
== 0 && local_hdb
&& local_hdb_read_only
) {
1301 /* Make sure we can write */
1302 kadm5_destroy(r
->kadm_handle
);
1303 r
->kadm_handle
= NULL
;
1304 ret
= get_kadm_handle(r
->context
, r
->realm
, 1 /* want_write */,
1307 princ
.attributes
|= KRB5_KDB_MATERIALIZE
;
1308 princ
.attributes
&= ~KRB5_KDB_VIRTUAL
;
1310 * XXX If there are TL data which should be re-encoded and sent as
1311 * KRB5_TL_EXTENSION, then this call will fail with KADM5_BAD_TL_TYPE.
1313 * We should either drop those TLs, re-encode them, or make
1314 * perform_tl_data() handle them. (New extensions should generally go
1315 * as KRB5_TL_EXTENSION so that non-critical ones can be set on
1316 * principals via old kadmind programs that don't support them.)
1318 * What we really want is a kadm5 utility function to convert some TLs
1319 * to KRB5_TL_EXTENSION and drop all others.
1322 ret
= kadm5_create_principal(r
->kadm_handle
, &princ
, mask
, "");
1325 } /* else create/materialize q-params are superfluous */
1327 /* Handle rotate / revoke options */
1328 if (ret
== 0 && change
) {
1329 krb5_keyblock
*k
= NULL
;
1332 int keepold
= r
->revoke
? 0 : 1;
1335 ret
= KADM5_READ_ONLY
;
1337 ret
= strcmp(r
->method
, "POST") == 0 ? 0 : ENOSYS
; /* XXX */
1338 if (ret
== 0 && local_hdb
&& local_hdb_read_only
) {
1339 /* Make sure we can write */
1340 kadm5_destroy(r
->kadm_handle
);
1341 r
->kadm_handle
= NULL
;
1342 ret
= get_kadm_handle(r
->context
, r
->realm
, 1 /* want_write */,
1346 /* Use requested enctypes or same ones as princ already had keys for */
1347 if (ret
== 0 && kstuple
== NULL
)
1348 ret
= make_kstuple(r
->context
, &princ
, &kstuple
, &nkstuple
);
1352 ret
= kadm5_randkey_principal_3(r
->kadm_handle
, p
, keepold
,
1353 nkstuple
, kstuple
, &k
, &n_k
);
1355 for (i
= 0; n_k
> 0 && i
< n_k
; i
++)
1356 krb5_free_keyblock_contents(r
->context
, &k
[i
]);
1361 if (ret
== 0 && refetch
) {
1362 /* Refetch changed principal */
1364 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1366 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
1372 ret
= write_keytab(r
, &princ
, pname
);
1374 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1375 krb5_free_principal(r
->context
, p
);
1379 static krb5_error_code
check_csrf(kadmin_request_desc
);
1382 * Calls get_keys1() to extract each requested principal's keys.
1384 * When this returns a response will have been set.
1386 static krb5_error_code
1387 get_keysN(kadmin_request_desc r
, const char *method
)
1389 krb5_error_code ret
;
1395 /* Parses and validates the request, then checks authorization */
1396 ret
= authorize_req(r
);
1398 return ret
; /* authorize_req() calls bad_req() on error */
1400 ret
= get_kadm_handle(r
->context
, r
->realm
? r
->realm
: realm
,
1401 0 /* want_write */, &r
->kadm_handle
);
1403 if (strcmp(method
, "POST") == 0 && (ret
= check_csrf(r
)))
1404 return ret
; /* check_csrf() calls bad_req() on error */
1406 nhosts
= heim_array_get_length(r
->hostnames
);
1407 nsvcs
= heim_array_get_length(r
->service_names
);
1408 nspns
= heim_array_get_length(r
->spns
);
1409 if (!nhosts
&& !nspns
)
1410 return bad_403(r
, EINVAL
, "No service principals requested");
1412 if (nhosts
&& !nsvcs
) {
1415 if ((s
= heim_string_create("HTTP")) == NULL
)
1416 ret
= krb5_enomem(r
->context
);
1418 ret
= heim_array_append_value(r
->service_names
, s
);
1423 /* FIXME: Make this configurable */
1424 if (nspns
+ nsvcs
* nhosts
> 40)
1425 return bad_403(r
, EINVAL
, "Requested keys for too many principals");
1427 ret
= make_keytab(r
);
1428 for (i
= 0; ret
== 0 && i
< nsvcs
; i
++) {
1430 heim_string_get_utf8(
1431 heim_array_get_value(r
->service_names
, i
));
1433 for (k
= 0; ret
== 0 && k
< nhosts
; k
++) {
1434 krb5_principal p
= NULL
;
1435 const char *hostname
=
1436 heim_string_get_utf8(
1437 heim_array_get_value(r
->hostnames
, k
));
1440 ret
= krb5_make_principal(r
->context
, &p
,
1441 r
->realm
? r
->realm
: realm
,
1442 svc
, hostname
, NULL
);
1444 ret
= krb5_unparse_name(r
->context
, p
, &spn
);
1446 ret
= get_keys1(r
, spn
);
1447 krb5_free_principal(r
->context
, p
);
1451 for (i
= 0; ret
== 0 && i
< nspns
; i
++) {
1453 heim_string_get_utf8(heim_array_get_value(r
->spns
,
1459 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
1460 "Failed to extract keys for unknown reasons");
1461 if (r
->response_set
)
1463 return bad_503(r
, ret
, "Could not get keys");
1465 /* Our convention */
1466 return bad_method_want_POST(r
);
1467 case KADM5_READ_ONLY
:
1468 if (primary_server_URI
) {
1469 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
1470 "Redirect %s to primary server", r
->cname
);
1471 return resp(r
, MHD_HTTP_TEMPORARY_REDIRECT
, KADM5_READ_ONLY
,
1472 MHD_RESPMEM_PERSISTENT
, NULL
, "", 0, NULL
, NULL
);
1474 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "HDB is read-only");
1475 return bad_403(r
, ret
, "HDB is read-only");
1478 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Sent keytab to %s",
1480 return good_ext_keytab(r
);
1482 return bad_503(r
, ret
, "Could not get keys");
1486 /* Copied from kdc/connect.c */
1488 addr_to_string(krb5_context context
,
1489 struct sockaddr
*addr
,
1493 krb5_error_code ret
;
1496 ret
= krb5_sockaddr2address(context
, addr
, &a
);
1498 ret
= krb5_print_address(&a
, str
, len
, &len
);
1499 krb5_free_address(context
, &a
);
1502 snprintf(str
, len
, "<family=%d>", addr
->sa_family
);
1505 static krb5_error_code
1506 set_req_desc(struct MHD_Connection
*connection
,
1509 kadmin_request_desc r
)
1511 const union MHD_ConnectionInfo
*ci
;
1513 krb5_error_code ret
;
1515 memset(r
, 0, sizeof(*r
));
1516 (void) gettimeofday(&r
->tv_start
, NULL
);
1518 if ((ret
= get_krb5_context(&r
->context
)))
1520 /* HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS fields */
1521 r
->request
.data
= "<HTTP-REQUEST>";
1522 r
->request
.length
= sizeof("<HTTP-REQUEST>");
1523 r
->from
= r
->frombuf
;
1532 r
->kv
= heim_array_create();
1534 r
->connection
= connection
;
1535 r
->kadm_handle
= NULL
;
1536 r
->hcontext
= r
->context
->hcontext
;
1537 r
->service_names
= heim_array_create();
1538 r
->hostnames
= heim_array_create();
1539 r
->spns
= heim_array_create();
1540 r
->keytab_name
= NULL
;
1547 ci
= MHD_get_connection_info(connection
,
1548 MHD_CONNECTION_INFO_CLIENT_ADDRESS
);
1550 r
->addr
= ci
->client_addr
;
1551 addr_to_string(r
->context
, r
->addr
, r
->frombuf
, sizeof(r
->frombuf
));
1555 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "method", "GET");
1556 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "endpoint", "%s", r
->reqtype
);
1558 token
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
1559 MHD_HTTP_HEADER_AUTHORIZATION
);
1560 if (token
&& r
->kv
) {
1561 const char *token_end
;
1563 if ((token_end
= strchr(token
, ' ')) == NULL
||
1564 (token_end
- token
) > INT_MAX
|| (token_end
- token
) < 2)
1565 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "auth", "<unknown>");
1567 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "auth", "%.*s",
1568 (int)(token_end
- token
), token
);
1572 if (ret
== 0 && r
->kv
== NULL
) {
1573 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Out of memory");
1574 ret
= r
->ret
= ENOMEM
;
1580 clean_req_desc(kadmin_request_desc r
)
1586 krb5_kt_destroy(r
->context
, r
->keytab
);
1587 else if (r
->keytab_name
&& strchr(r
->keytab_name
, ':'))
1588 (void) unlink(strchr(r
->keytab_name
, ':') + 1);
1590 kadm5_destroy(r
->kadm_handle
);
1591 hx509_request_free(&r
->req
);
1592 heim_release(r
->service_names
);
1593 heim_release(r
->hostnames
);
1594 heim_release(r
->reason
);
1595 heim_release(r
->spns
);
1596 heim_release(r
->kv
);
1597 krb5_free_principal(r
->context
, r
->cprinc
);
1598 free(r
->keytab_name
);
1605 /* Implements GETs of /get-keys */
1606 static krb5_error_code
1607 get_keys(kadmin_request_desc r
, const char *method
)
1609 krb5_error_code ret
;
1611 if ((ret
= validate_token(r
)))
1612 return ret
; /* validate_token() calls bad_req() */
1613 if (r
->cname
== NULL
|| r
->cprinc
== NULL
)
1614 return bad_403(r
, EINVAL
,
1615 "Could not extract principal name from token");
1616 return get_keysN(r
, method
); /* Sets an HTTP response */
1619 /* Implements GETs of /get-config */
1620 static krb5_error_code
1621 get_config(kadmin_request_desc r
)
1624 kadm5_principal_ent_rec princ
;
1625 krb5_error_code ret
;
1626 krb5_principal p
= NULL
;
1627 uint32_t mask
= KADM5_PRINCIPAL
| KADM5_TL_DATA
;
1628 krb5_tl_data
*tl_next
;
1630 /* Default configuration for principals that have none set: */
1631 size_t bodylen
= sizeof("include /etc/krb5.conf\n") - 1;
1632 void *body
= "include /etc/krb5.conf\n";
1635 if ((ret
= validate_token(r
)))
1636 return ret
; /* validate_token() calls bad_req() */
1637 if (r
->cname
== NULL
|| r
->cprinc
== NULL
)
1638 return bad_403(r
, EINVAL
,
1639 "Could not extract principal name from token");
1641 * No authorization needed -- configs are public. Though we do require
1642 * authentication (above).
1645 ret
= get_kadm_handle(r
->context
, r
->realm
? r
->realm
: realm
,
1646 0 /* want_write */, &r
->kadm_handle
);
1648 memset(&princ
, 0, sizeof(princ
));
1649 princ
.key_data
= NULL
;
1650 princ
.tl_data
= NULL
;
1652 pname
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1656 ret
= krb5_parse_name(r
->context
, pname
, &p
);
1658 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
1661 for (tl_next
= princ
.tl_data
; tl_next
; tl_next
= tl_next
->tl_data_next
) {
1662 if (tl_next
->tl_data_type
!= KRB5_TL_KRB5_CONFIG
)
1664 bodylen
= tl_next
->tl_data_length
;
1665 body
= tl_next
->tl_data_contents
;
1670 return bad_404(r
, "/get-config");
1675 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
1676 "Returned krb5.conf contents to %s", r
->cname
);
1677 ret
= resp(r
, MHD_HTTP_OK
, 0, MHD_RESPMEM_MUST_COPY
,
1678 "application/text", body
, bodylen
, NULL
, NULL
);
1680 ret
= bad_503(r
, ret
, "Could not retrieve principal configuration");
1683 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1684 krb5_free_principal(r
->context
, p
);
1688 static krb5_error_code
1689 mac_csrf_token(kadmin_request_desc r
, krb5_storage
*sp
)
1691 kadm5_principal_ent_rec princ
;
1692 krb5_error_code ret
;
1693 krb5_principal p
= NULL
;
1695 char mac
[EVP_MAX_MD_SIZE
];
1696 unsigned int maclen
= sizeof(mac
);
1697 HMAC_CTX
*ctx
= NULL
;
1701 memset(&princ
, 0, sizeof(princ
));
1702 ret
= krb5_storage_to_data(sp
, &data
);
1703 if (r
->kadm_handle
== NULL
)
1704 ret
= get_kadm_handle(r
->context
, r
->realm
, 0 /* want_write */,
1707 ret
= krb5_make_principal(r
->context
, &p
,
1708 r
->realm
? r
->realm
: realm
,
1709 "WELLKNOWN", "CSRFTOKEN", NULL
);
1711 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
,
1712 KADM5_PRINCIPAL
| KADM5_KVNO
|
1716 if (ret
== 0 && princ
.n_key_data
< 1)
1717 ret
= KADM5_UNK_PRINC
;
1719 for (i
= 0; i
< princ
.n_key_data
; i
++)
1720 if (princ
.key_data
[i
].key_data_kvno
== princ
.kvno
)
1722 if (ret
== 0 && i
== princ
.n_key_data
)
1723 i
= 0; /* Weird, but can't happen */
1725 if (ret
== 0 && (ctx
= HMAC_CTX_new()) == NULL
)
1726 ret
= krb5_enomem(r
->context
);
1727 /* HMAC the token body and the client principal name */
1729 HMAC_Init_ex(ctx
, princ
.key_data
[i
].key_data_contents
[0], princ
.key_data
[i
].key_data_length
[0], EVP_sha256(), NULL
);
1730 HMAC_Update(ctx
, data
.data
, data
.length
);
1731 HMAC_Update(ctx
, r
->cname
, strlen(r
->cname
));
1732 HMAC_Final(ctx
, mac
, &maclen
);
1733 krb5_data_free(&data
);
1734 data
.length
= maclen
;
1736 if (krb5_storage_write(sp
, mac
, maclen
) != maclen
)
1737 ret
= krb5_enomem(r
->context
);
1739 krb5_free_principal(r
->context
, p
);
1741 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1747 static krb5_error_code
1748 make_csrf_token(kadmin_request_desc r
,
1753 static HEIMDAL_THREAD_LOCAL
char tokenbuf
[128]; /* See below, be sad */
1754 krb5_error_code ret
= 0;
1755 unsigned char given_decoded
[128];
1756 krb5_storage
*sp
= NULL
;
1767 size_t len
= strlen(given
);
1769 if (len
>= sizeof(given_decoded
))
1771 if (ret
== 0 && (dlen
= rk_base64_decode(given
, &given_decoded
)) <= 0)
1774 (sp
= krb5_storage_from_mem(given_decoded
, dlen
)) == NULL
)
1775 ret
= krb5_enomem(r
->context
);
1777 ret
= krb5_ret_int64(sp
, &t
);
1779 ret
= krb5_ret_uint64(sp
, &nonce
);
1780 krb5_storage_free(sp
);
1783 *age
= time(NULL
) - t
;
1786 krb5_generate_random_block((void *)&nonce
, sizeof(nonce
));
1789 if (ret
== 0 && (sp
= krb5_storage_emem()) == NULL
)
1790 ret
= krb5_enomem(r
->context
);
1792 ret
= krb5_store_int64(sp
, t
);
1794 ret
= krb5_store_uint64(sp
, nonce
);
1796 ret
= mac_csrf_token(r
, sp
);
1798 ret
= krb5_storage_to_data(sp
, &data
);
1799 if (ret
== 0 && data
.length
> INT_MAX
)
1802 (dlen
= rk_base64_encode(data
.data
, data
.length
, token
)) < 0)
1804 if (ret
== 0 && dlen
>= sizeof(tokenbuf
))
1808 * Work around for older versions of libmicrohttpd do not strdup()ing
1809 * response header values.
1811 memcpy(tokenbuf
, *token
, dlen
);
1815 krb5_storage_free(sp
);
1816 krb5_data_free(&data
);
1821 * Returns system or krb5_error_code on error, but also calls resp() or bad_*()
1824 static krb5_error_code
1825 check_csrf(kadmin_request_desc r
)
1827 krb5_error_code ret
;
1830 size_t givenlen
, expectedlen
;
1831 char *expected
= NULL
;
1833 given
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
1835 ret
= make_csrf_token(r
, given
, &expected
, &age
);
1837 return bad_503(r
, ret
, "Could not create a CSRF token");
1839 * If CSRF token needed but missing, call resp() directly, bypassing
1840 * bad_403(), to return a 403 with an expected CSRF token in the response.
1842 if (given
== NULL
) {
1843 (void) resp(r
, MHD_HTTP_FORBIDDEN
, ENOSYS
, MHD_RESPMEM_PERSISTENT
,
1844 NULL
, "CSRF token needed; copy the X-CSRF-Token: response "
1845 "header to your next POST", BODYLEN_IS_STRLEN
, NULL
,
1850 /* Validate the CSRF token for this request */
1851 givenlen
= strlen(given
);
1852 expectedlen
= strlen(expected
);
1853 if (givenlen
!= expectedlen
|| ct_memcmp(given
, expected
, givenlen
)) {
1854 (void) bad_403(r
, EACCES
, "Invalid CSRF token");
1857 if (age
> 300) { /* XXX */
1858 (void) bad_403(r
, EACCES
, "CSRF token too old");
1864 static krb5_error_code
1865 health(const char *method
, kadmin_request_desc r
)
1867 if (strcmp(method
, "HEAD") == 0) {
1868 return resp(r
, MHD_HTTP_OK
, 0, MHD_RESPMEM_PERSISTENT
, NULL
, "", 0,
1871 return resp(r
, MHD_HTTP_OK
, 0, MHD_RESPMEM_PERSISTENT
, NULL
,
1872 "To determine the health of the service, use the /get-config "
1873 "end-point.\n", BODYLEN_IS_STRLEN
, NULL
, NULL
);
1877 /* Implements the entirety of this REST service */
1880 struct MHD_Connection
*connection
,
1883 const char *version
,
1884 const char *upload_data
,
1885 size_t *upload_data_size
,
1888 static int aptr
= 0;
1889 struct kadmin_request_desc r
;
1894 * This is the first call, right after headers were read.
1896 * We must return quickly so that any 100-Continue might be sent with
1899 * We'll get called again to really do the processing. If we handled
1900 * POSTs then we'd also get called with upload_data != NULL between the
1901 * first and last calls. We need to keep no state between the first
1902 * and last calls, but we do need to distinguish first and last call,
1903 * so we use the ctx argument for this.
1910 * Note that because we attempt to connect to the HDB in set_req_desc(),
1911 * this early 503 if we fail to serves to do all of what /health should do.
1913 if ((ret
= set_req_desc(connection
, method
, url
, &r
)))
1914 return bad_503(&r
, ret
, "Could not initialize request state");
1915 if ((strcmp(method
, "HEAD") == 0 || strcmp(method
, "GET") == 0) &&
1916 (strcmp(url
, "/health") == 0 || strcmp(url
, "/") == 0)) {
1917 ret
= health(method
, &r
);
1918 } else if (strcmp(method
, "GET") != 0 && strcmp(method
, "POST") != 0) {
1919 ret
= bad_405(&r
, method
);
1920 } else if (strcmp(url
, "/get-keys") == 0) {
1921 ret
= get_keys(&r
, method
);
1922 } else if (strcmp(url
, "/get-config") == 0) {
1923 if (strcmp(method
, "GET") != 0)
1924 ret
= bad_405(&r
, method
);
1926 ret
= get_config(&r
);
1928 ret
= bad_404(&r
, url
);
1932 return ret
== -1 ? MHD_NO
: MHD_YES
;
1935 static struct getargs args
[] = {
1936 { "help", 'h', arg_flag
, &help_flag
, "Print usage message", NULL
},
1937 { "version", '\0', arg_flag
, &version_flag
, "Print version", NULL
},
1938 { NULL
, 'H', arg_strings
, &audiences
,
1939 "expected token audience(s) of the service", "HOSTNAME" },
1940 { "daemon", 'd', arg_flag
, &daemonize
, "daemonize", "daemonize" },
1941 { "daemon-child", 0, arg_flag
, &daemon_child_fd
, NULL
, NULL
}, /* priv */
1942 { "reverse-proxied", 0, arg_flag
, &reverse_proxied_flag
,
1943 "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
1944 { NULL
, 'p', arg_integer
, &port
, "PORT", "port number (default: 443)" },
1945 { "temp-dir", 0, arg_string
, &cache_dir
,
1946 "cache directory", "DIRECTORY" },
1947 { "cert", 0, arg_string
, &cert_file
,
1948 "certificate file path (PEM)", "HX509-STORE" },
1949 { "private-key", 0, arg_string
, &priv_key_file
,
1950 "private key file path (PEM)", "HX509-STORE" },
1951 { "thread-per-client", 't', arg_flag
, &thread_per_client_flag
, "thread per-client", NULL
},
1952 { "realm", 0, arg_string
, &realm
, "realm", "REALM" },
1953 { "hdb", 0, arg_string
, &hdb
, "HDB filename", "PATH" },
1954 { "read-only-admin-server", 0, arg_string
, &kadmin_server
,
1955 "Name of read-only kadmin server", "HOST[:PORT]" },
1956 { "writable-admin-server", 0, arg_string
, &writable_kadmin_server
,
1957 "Name of writable kadmin server", "HOST[:PORT]" },
1958 { "primary-server-uri", 0, arg_string
, &primary_server_URI
,
1959 "Name of primary httpkadmind server for HTTP redirects", "URL" },
1960 { "local", 'l', arg_flag
, &local_hdb
,
1961 "Use a local HDB as read-only", NULL
},
1962 { "local-read-only", 0, arg_flag
, &local_hdb_read_only
,
1963 "Use a local HDB as read-only", NULL
},
1964 { "read-only", 0, arg_flag
, &read_only
, "Allow no writes", NULL
},
1965 { "stash-file", 0, arg_string
, &stash_file
,
1966 "Stash file for HDB", "PATH" },
1967 { "kadmin-client-name", 0, arg_string
, &kadmin_client_name
,
1968 "Client name for remote kadmind", "PRINCIPAL" },
1969 { "kadmin-client-keytab", 0, arg_string
, &kadmin_client_keytab
,
1970 "Keytab with client credentials for remote kadmind", "KEYTAB" },
1971 { "token-authentication-type", 'T', arg_strings
, &auth_types
,
1972 "Token authentication type(s) supported", "HTTP-AUTH-TYPE" },
1973 { "verbose", 'v', arg_counter
, &verbose_counter
, "verbose", "run verbosely" }
1979 arg_printusage(args
, sizeof(args
) / sizeof(args
[0]), "httpkadmind",
1980 "\nServes an HTTP API for getting (and rotating) service "
1981 "principal keys, and other kadmin-like operations\n");
1985 static int sigpipe
[2] = { -1, -1 };
1991 while (write(sigpipe
[1], &c
, sizeof(c
)) == -1 && errno
== EINTR
)
1996 my_openlog(krb5_context context
,
1998 krb5_log_facility
**fac
)
2000 char **s
= NULL
, **p
;
2002 krb5_initlog(context
, "httpkadmind", fac
);
2003 s
= krb5_config_get_strings(context
, NULL
, svc
, "logging", NULL
);
2005 s
= krb5_config_get_strings(context
, NULL
, "logging", svc
, NULL
);
2008 krb5_addlog_dest(context
, *fac
, *p
);
2009 krb5_config_free_strings(s
);
2012 if (asprintf(&ss
, "0-1/FILE:%s/%s", hdb_db_dir(context
),
2014 err(1, "out of memory");
2015 krb5_addlog_dest(context
, *fac
, ss
);
2018 krb5_set_warn_dest(context
, *fac
);
2021 static const char *sysplugin_dirs
[] = {
2025 "$ORIGIN/../lib/plugin/kdc",
2028 LIBDIR
"/plugin/kdc",
2034 load_plugins(krb5_context context
)
2036 const char * const *dirs
= sysplugin_dirs
;
2040 cfdirs
= krb5_config_get_strings(context
, NULL
, "kdc", "plugin_dir", NULL
);
2042 dirs
= (const char * const *)cfdirs
;
2046 _krb5_load_plugins(context
, "kdc", (const char **)dirs
);
2049 krb5_config_free_strings(cfdirs
);
2054 main(int argc
, char **argv
)
2056 unsigned int flags
= MHD_USE_THREAD_PER_CONNECTION
; /* XXX */
2057 struct sockaddr_in sin
;
2058 struct MHD_Daemon
*previous
= NULL
;
2059 struct MHD_Daemon
*current
= NULL
;
2060 struct sigaction sa
;
2061 krb5_context context
= NULL
;
2062 MHD_socket sock
= MHD_INVALID_SOCKET
;
2064 char *priv_key_pem
= NULL
;
2065 char *cert_pem
= NULL
;
2070 setprogname("httpkadmind");
2071 if (getarg(args
, sizeof(args
) / sizeof(args
[0]), argc
, argv
, &optidx
))
2076 print_version(NULL
);
2079 if (argc
> optidx
) /* Add option to set a URI local part prefix? */
2082 errx(1, "Port number must be given");
2084 if (audiences
.num_strings
== 0) {
2085 char localhost
[MAXHOSTNAMELEN
];
2087 ret
= gethostname(localhost
, sizeof(localhost
));
2089 errx(1, "Could not determine local hostname; use --audience");
2091 if ((audiences
.strings
=
2092 calloc(1, sizeof(audiences
.strings
[0]))) == NULL
||
2093 (audiences
.strings
[0] = strdup(localhost
)) == NULL
)
2094 err(1, "Out of memory");
2095 audiences
.num_strings
= 1;
2098 if (daemonize
&& daemon_child_fd
== -1)
2099 daemon_child_fd
= roken_detach_prep(argc
, argv
, "--daemon-child");
2105 if ((errno
= pthread_key_create(&k5ctx
, k5_free_context
)))
2106 err(1, "Could not create thread-specific storage");
2108 if ((errno
= get_krb5_context(&context
)))
2109 err(1, "Could not init krb5 context (config file issue?)");
2114 ret
= krb5_get_default_realm(context
, &s
);
2116 krb5_err(context
, 1, ret
, "Could not determine default realm");
2120 if ((errno
= get_kadm_handle(context
, realm
, 0 /* want_write */,
2122 err(1, "Could not connect to HDB");
2123 kadm5_destroy(kadm_handle
);
2125 my_openlog(context
, "httpkadmind", &logfac
);
2126 load_plugins(context
);
2128 if (cache_dir
== NULL
) {
2131 if (asprintf(&s
, "%s/httpkadmind-XXXXXX",
2132 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2134 (cache_dir
= mkdtemp(s
)) == NULL
)
2135 err(1, "could not create temporary cache directory");
2136 if (verbose_counter
)
2137 fprintf(stderr
, "Note: using %s as cache directory\n", cache_dir
);
2138 atexit(rm_cache_dir
);
2139 setenv("TMPDIR", cache_dir
, 1);
2143 if (cert_file
&& !priv_key_file
)
2144 priv_key_file
= cert_file
;
2147 hx509_cursor cursor
= NULL
;
2148 hx509_certs certs
= NULL
;
2149 hx509_cert cert
= NULL
;
2150 time_t min_cert_life
= 0;
2154 ret
= hx509_certs_init(context
->hx509ctx
, cert_file
, 0, NULL
, &certs
);
2156 ret
= hx509_certs_start_seq(context
->hx509ctx
, certs
, &cursor
);
2158 (ret
= hx509_certs_next_cert(context
->hx509ctx
, certs
,
2159 cursor
, &cert
)) == 0 && cert
) {
2160 time_t notAfter
= 0;
2162 if (!hx509_cert_have_private_key_only(cert
) &&
2163 (notAfter
= hx509_cert_get_notAfter(cert
)) <= time(NULL
) + 30)
2164 errx(1, "One or more certificates in %s are expired",
2167 notAfter
-= time(NULL
);
2169 warnx("One or more certificates in %s expire soon",
2171 /* Reload 5 minutes prior to expiration */
2172 if (notAfter
< min_cert_life
|| min_cert_life
< 1)
2173 min_cert_life
= notAfter
;
2175 hx509_cert_free(cert
);
2178 (void) hx509_certs_end_seq(context
->hx509ctx
, certs
, cursor
);
2179 if (min_cert_life
> 4)
2180 alarm(min_cert_life
>> 1);
2181 hx509_certs_free(&certs
);
2183 hx509_err(context
->hx509ctx
, 1, ret
,
2184 "could not read certificate from %s", cert_file
);
2186 if ((errno
= rk_undumpdata(cert_file
, &s
, &len
)) ||
2187 (cert_pem
= strndup(s
, len
)) == NULL
)
2188 err(1, "could not read certificate from %s", cert_file
);
2189 if (strlen(cert_pem
) != len
)
2190 err(1, "NULs in certificate file contents: %s", cert_file
);
2194 if (priv_key_file
) {
2198 if ((errno
= rk_undumpdata(priv_key_file
, &s
, &len
)) ||
2199 (priv_key_pem
= strndup(s
, len
)) == NULL
)
2200 err(1, "could not read private key from %s", priv_key_file
);
2201 if (strlen(priv_key_pem
) != len
)
2202 err(1, "NULs in private key file contents: %s", priv_key_file
);
2206 if (verbose_counter
> 1)
2207 flags
|= MHD_USE_DEBUG
;
2208 if (thread_per_client_flag
)
2209 flags
|= MHD_USE_THREAD_PER_CONNECTION
;
2212 if (pipe(sigpipe
) == -1)
2213 err(1, "Could not set up key/cert reloading");
2214 memset(&sa
, 0, sizeof(sa
));
2215 sa
.sa_handler
= sighandler
;
2216 if (reverse_proxied_flag
) {
2218 * We won't use TLS in the reverse proxy case, so no need to reload
2219 * certs. But we'll still read them if given, and alarm() will get
2222 * XXX We should be able to re-read krb5.conf and such on SIGHUP.
2224 (void) signal(SIGHUP
, SIG_IGN
);
2225 (void) signal(SIGUSR1
, SIG_IGN
);
2226 (void) signal(SIGALRM
, SIG_IGN
);
2228 (void) sigaction(SIGHUP
, &sa
, NULL
); /* Reload key & cert */
2229 (void) sigaction(SIGUSR1
, &sa
, NULL
); /* Reload key & cert */
2230 (void) sigaction(SIGALRM
, &sa
, NULL
); /* Reload key & cert */
2232 (void) sigaction(SIGINT
, &sa
, NULL
); /* Graceful shutdown */
2233 (void) sigaction(SIGTERM
, &sa
, NULL
); /* Graceful shutdown */
2234 (void) signal(SIGPIPE
, SIG_IGN
);
2237 sock
= MHD_quiesce_daemon(previous
);
2239 if (reverse_proxied_flag
) {
2241 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
2244 sin
.sin_addr
.s_addr
= htonl(INADDR_LOOPBACK
);
2245 sin
.sin_family
= AF_INET
;
2246 sin
.sin_port
= htons(port
);
2247 current
= MHD_start_daemon(flags
, port
,
2249 route
, (char *)NULL
,
2250 MHD_OPTION_SOCK_ADDR
, &sin
,
2251 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
2252 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
2254 } else if (sock
!= MHD_INVALID_SOCKET
) {
2256 * Certificate/key rollover: reuse the listen socket returned by
2257 * MHD_quiesce_daemon().
2259 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
2261 route
, (char *)NULL
,
2262 MHD_OPTION_HTTPS_MEM_KEY
, priv_key_pem
,
2263 MHD_OPTION_HTTPS_MEM_CERT
, cert_pem
,
2264 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
2265 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
2266 MHD_OPTION_LISTEN_SOCKET
, sock
,
2268 sock
= MHD_INVALID_SOCKET
;
2270 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
2272 route
, (char *)NULL
,
2273 MHD_OPTION_HTTPS_MEM_KEY
, priv_key_pem
,
2274 MHD_OPTION_HTTPS_MEM_CERT
, cert_pem
,
2275 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
2276 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
2279 if (current
== NULL
)
2280 err(1, "Could not start kadmin REST service");
2283 MHD_stop_daemon(previous
);
2287 if (verbose_counter
)
2288 fprintf(stderr
, "Ready!\n");
2289 if (daemon_child_fd
!= -1)
2290 roken_detach_finish(NULL
, daemon_child_fd
);
2292 /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
2293 while ((ret
= read(sigpipe
[0], &sig
, sizeof(sig
))) == -1 &&
2299 priv_key_pem
= NULL
;
2302 if (ret
== 1 && (sig
== SIGHUP
|| sig
== SIGUSR1
|| sig
== SIGALRM
)) {
2303 /* Reload certs and restart service gracefully */
2309 MHD_stop_daemon(current
);
2310 _krb5_unload_plugins(context
, "kdc");
2311 pthread_key_delete(k5ctx
);