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 #if MHD_VERSION < 0x00097002 || defined(MHD_YES)
84 /* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
89 enum MHD_Result
{ MHD_NO
= 0, MHD_YES
= 1 };
92 typedef int heim_mhd_result
;
94 typedef enum MHD_Result heim_mhd_result
;
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
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.
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
131 struct free_tend_list
{
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
;
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
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
;
179 krb5_principal cprinc
;
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;
199 unsigned int is_self
:1;
201 } *kadmin_request_desc
;
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
216 case ENOSYS
: retname
= "ECSRFTOKENREQD"; break;
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
);
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
);
235 CASE(KADM5_RPC_ERROR
);
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
);
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
);
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
)
310 if ((*contextp
= pthread_getspecific(k5ctx
)))
313 ret
= krb5_init_context(contextp
);
314 /* XXX krb5_set_log_dest(), warn_dest, debug_dest */
316 (void) pthread_setspecific(k5ctx
, *contextp
);
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) \
359 if (((c).f = strdup(v)) == NULL) \
365 * Does NOT set an HTTP response, naturally, as it doesn't even have access to
368 static krb5_error_code
369 get_kadm_handle(krb5_context context
,
370 const char *want_realm
,
374 kadm5_config_params conf
;
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
));
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
407 if (local_hdb
&& (!local_hdb_read_only
|| !want_write
)) {
408 ret
= kadm5_s_init_with_password_ctx(context
,
411 NULL
, /* service_name */
413 0, /* struct_version */
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
);
428 set_conf(conf
, readonly_admin_server
, kadmin_server
,
429 KADM5_CONFIG_READONLY_ADMIN_SERVER
);
430 ret
= kadm5_c_init_with_skey_ctx(context
,
432 kadmin_client_keytab
,
435 0, /* struct_version */
441 ret
= krb5_enomem(context
);
444 free(conf
.readonly_admin_server
);
445 free(conf
.admin_server
);
446 free(conf
.stash_file
);
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,
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 *);
469 validate_token(kadmin_request_desc r
)
474 char token_type
[64]; /* Plenty */
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
);
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
);
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';
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')
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
);
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
);
520 return bad_503(r
, ret
,
521 "Could not extract a principal name from token");
526 k5_free_context(void *ctx
)
528 krb5_free_context(ctx
);
531 #ifndef HAVE_UNLINKAT
533 unlink1file(const char *dname
, const char *name
)
537 if (strlcpy(p
, dname
, sizeof(p
)) < sizeof(p
) &&
538 strlcat(p
, "/", sizeof(p
)) < sizeof(p
) &&
539 strlcat(p
, name
, sizeof(p
)) < sizeof(p
))
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
)
561 while ((e
= readdir(d
))) {
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);
570 (void) unlink1file(cache_dir
, e
->d_name
);
574 (void) rmdir(cache_dir
);
578 * Work around older libmicrohttpd not strduping response header values when
581 static HEIMDAL_THREAD_LOCAL
struct redirect_uri
{
589 redirect_uri_appends(struct redirect_uri
*redirect
,
595 if (!redirect
->valid
|| redirect
->len
>= sizeof(redirect
->uri
) - 1) {
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
)
605 redirect
->len
+= len
;
608 static heim_mhd_result
609 make_redirect_uri_param_cb(void *d
,
610 enum MHD_ValueKind kind
,
614 struct redirect_uri
*redirect
= d
;
616 redirect_uri_appends(redirect
, redirect
->first_param
? "?" : "&");
617 redirect_uri_appends(redirect
, key
);
619 redirect_uri_appends(redirect
, "=");
620 redirect_uri_appends(redirect
, val
);
622 redirect
->first_param
= 0;
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
,
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
,
660 enum MHD_ResponseMemoryMode rmmode
,
661 const char *content_type
,
666 struct MHD_Response
*response
;
669 if (r
->response_set
) {
670 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
671 "Internal error; attempted to set a second response");
675 (void) gettimeofday(&r
->tv_end
, NULL
);
678 if (body
&& bodylen
== BODYLEN_IS_STRLEN
)
679 bodylen
= strlen(body
);
681 response
= MHD_create_response_from_buffer(bodylen
, rk_UNCONST(body
),
683 if (response
== NULL
)
685 mret
= MHD_add_response_header(response
, MHD_HTTP_HEADER_AGE
, "0");
686 if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_OK
) {
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");
701 mret
= MHD_add_response_header(response
,
702 MHD_HTTP_HEADER_CACHE_CONTROL
,
706 mret
= MHD_add_response_header(response
,
707 MHD_HTTP_HEADER_CACHE_CONTROL
,
708 "no-store, max-age=0");
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
) {
717 if (auth_types
.num_strings
< 1)
718 http_status_code
= MHD_HTTP_SERVICE_UNAVAILABLE
;
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
);
728 mret
= MHD_add_response_header(response
, MHD_HTTP_HEADER_LOCATION
,
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
,
740 if (mret
== MHD_YES
&& content_type
) {
741 mret
= MHD_add_response_header(response
,
742 MHD_HTTP_HEADER_CONTENT_TYPE
,
746 mret
= MHD_queue_response(r
->connection
, http_status_code
, response
);
747 MHD_destroy_response(response
);
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
,
760 krb5_context context
= NULL
;
761 const char *k5msg
= NULL
;
762 const char *emsg
= NULL
;
763 char *formatted
= NULL
;
766 context
= r
->context
;
767 if (r
->hcontext
&& r
->kv
)
768 heim_audit_setkv_number((heim_svc_req_desc
)r
, "http-status-code",
770 (void) gettimeofday(&r
->tv_end
, NULL
);
771 if (code
== ENOMEM
) {
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
);
780 emsg
= k5msg
= krb5_get_error_message(context
, code
);
782 emsg
= strerror(code
);
785 ret
= vasprintf(&formatted
, fmt
, ap
) == -1;
787 if (ret
> -1 && formatted
)
788 ret
= asprintf(&msg
, "%s: %s (%d)", formatted
, emsg
, (int)code
);
794 heim_audit_addreason((heim_svc_req_desc
)r
, "%s", formatted
);
795 krb5_free_error_message(context
, k5msg
);
797 if (ret
== -1 || msg
== NULL
) {
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
);
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
,
823 ret
= bad_reqv(r
, code
, http_status_code
, fmt
, ap
);
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
,
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");
882 static krb5_error_code
883 bad_500(kadmin_request_desc r
,
887 return bad_req(r
, ret
, MHD_HTTP_INTERNAL_SERVER_ERROR
,
888 "Internal error: %s", reason
);
892 static krb5_error_code
893 bad_503(kadmin_request_desc r
,
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
)
909 if (!r
->keytab_name
|| !(p
= strchr(r
->keytab_name
, ':')))
910 return bad_503(r
, EINVAL
, "Internal error (no keytab produced)");
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
);
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
);
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
);
940 if (strcmp(name
, "root") != 0 &&
941 strcmp(name
, "host") != 0 &&
942 strcmp(name
, "exceed") != 0)
944 if (krb5_config_get_bool_default(r
->context
, NULL
, FALSE
,
946 "csr_authorizer_handles_svc_names",
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
);
956 static heim_mhd_result
958 enum MHD_ValueKind kind
,
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:
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)
1000 else if (strcmp(key
, "revoke") == 0)
1002 else if (strcmp(key
, "rotate") == 0)
1004 else if (strcmp(key
, "create") == 0)
1006 else if (strcmp(key
, "ro") == 0)
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
);
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");
1018 s
= heim_string_create(val
);
1020 ret
= krb5_enomem(r
->context
);
1022 ret
= heim_array_append_value(r
->hostnames
, s
);
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
);
1030 krb5_set_error_message(r
->context
, ret
= EACCES
,
1031 "use \"spn\" for self");
1033 ret
= check_service_name(r
, val
);
1035 s
= heim_string_create(val
);
1037 ret
= krb5_enomem(r
->context
);
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
1071 if (ret
== 0 && krb5_principal_get_num_comp(r
->context
, p
) != 2)
1075 * Allow only certain service names. Except that when
1076 * the SPN == the requestor's principal name then allow the "host"
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
) &&
1086 heim_array_get_length(r
->hostnames
) == 0 &&
1087 heim_array_get_length(r
->spns
) == 0) {
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
,
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
);
1107 ret
= hx509_request_add_dns_name(r
->context
->hx509ctx
, r
->req
,
1109 if (ret
== 0 && !(s
= heim_string_create(val
)))
1110 ret
= krb5_enomem(r
->context
);
1112 ret
= heim_array_append_value(r
->spns
, s
);
1113 krb5_free_principal(r
->context
, p
);
1116 /* The authorizer probably doesn't know what to do with this */
1117 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
, val
);
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
;
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
;
1137 ret
= hx509_request_init(r
->context
->hx509ctx
, &r
->req
);
1139 return bad_enomem(r
, ret
);
1140 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1142 ret
= r
->error_code
;
1144 return bad_403(r
, ret
, "Not authorized to requested principal(s)");
1146 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
1147 "Could not handle query parameters");
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)");
1156 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
1157 "Error checking authorization");
1161 static krb5_error_code
1162 make_keytab(kadmin_request_desc r
)
1164 krb5_error_code ret
= 0;
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
);
1172 fd
= mkstemp(r
->keytab_name
+ sizeof("FILE:") - 1);
1173 if (ret
== 0 && fd
== -1)
1176 ret
= krb5_kt_resolve(r
->context
, r
->keytab_name
, &r
->keytab
);
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
;
1191 if (princ
->n_key_data
<= 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
);
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
);
1226 krb5_warn(r
->context
, ret
,
1227 "Failed to write keytab entries for %s", unparsed
);
1233 random_password(krb5_context context
, char *buf
, size_t buflen
)
1235 static const char chars
[] =
1236 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,";
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)];
1251 static krb5_error_code
1252 make_kstuple(krb5_context context
,
1253 kadm5_principal_ent_rec
*p
,
1254 krb5_key_salt_tuple
**kstuple
,
1262 if (p
->n_key_data
< 1)
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];
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
},
1304 * Determine the default/allowed attributes for some new principal.
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
);
1314 /* Has to be a host-based service principal (for now) */
1315 if (krb5_principal_get_num_comp(r
->context
, p
) != 2)
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
;
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
,
1333 ret
= kadm5_get_principal(r
->kadm_handle
, nsp
, &nsprinc
,
1334 KADM5_PRINCIPAL
| KADM5_ATTRIBUTES
);
1335 krb5_free_principal(r
->context
, nsp
);
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
);
1343 /* Fallback on krb5.conf */
1344 as
= krb5_config_get_string(r
->context
, NULL
, "ext_keytab",
1345 "new_hostbased_service_principal_attributes",
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");
1357 hn
= strchr(hn
+ 1, '.');
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
;
1376 KADM5_PRINCIPAL
| KADM5_KVNO
| KADM5_MAX_LIFE
| KADM5_MAX_RLIFE
|
1377 KADM5_PW_EXPIRATION
| KADM5_ATTRIBUTES
| KADM5_KEY_DATA
|
1379 uint32_t create_mask
= mask
& ~(KADM5_KEY_DATA
| KADM5_TL_DATA
);
1380 size_t nkstuple
= 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
);
1398 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
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
))) {
1413 change
= !r
->ro
&& (r
->rotate
|| r
->revoke
);
1415 /* Handle create / materialize options */
1416 if (ret
== KADM5_UNK_PRINC
&& r
->create
) {
1419 memset(&princ
, 0, sizeof(princ
));
1420 princ
.attributes
= create_attributes(r
, p
);
1423 ret
= KADM5_READ_ONLY
;
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 */,
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.
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 */
1447 ret
= kadm5_create_principal_3(r
->kadm_handle
, &princ
, create_mask
,
1448 nkstuple
, kstuple
, pw
);
1449 princ
.principal
= NULL
; /* Return */
1452 } else if (ret
== 0 && r
->materialize
&&
1453 (princ
.attributes
& KRB5_KDB_VIRTUAL
)) {
1456 ret
= KADM5_READ_ONLY
;
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 */,
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.
1481 ret
= kadm5_create_principal(r
->kadm_handle
, &princ
, mask
, "");
1483 } /* else create/materialize q-params are superfluous */
1485 /* Handle rotate / revoke options */
1486 if (ret
== 0 && change
) {
1487 krb5_keyblock
*k
= NULL
;
1490 int keepold
= r
->revoke
? 0 : 1;
1493 ret
= KADM5_READ_ONLY
;
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 */,
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
);
1510 ret
= kadm5_randkey_principal_3(r
->kadm_handle
, p
, keepold
,
1511 nkstuple
, kstuple
, &k
, &n_k
);
1513 for (i
= 0; n_k
> 0 && i
< n_k
; i
++)
1514 krb5_free_keyblock_contents(r
->context
, &k
[i
]);
1519 if (ret
== 0 && refetch
) {
1520 /* Refetch changed principal */
1522 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1524 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
1530 ret
= write_keytab(r
, &princ
, pname
);
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
);
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
;
1562 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1563 krb5_free_principal(r
->context
, p
);
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
;
1583 /* Parses and validates the request, then checks authorization */
1584 ret
= authorize_req(r
);
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.
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
);
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
) {
1609 if ((s
= heim_string_create("HTTP")) == NULL
)
1610 ret
= krb5_enomem(r
->context
);
1612 ret
= heim_array_append_value(r
->service_names
, s
);
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
++) {
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
));
1637 ret
= krb5_make_principal(r
->context
, &p
,
1638 r
->realm
? r
->realm
: realm
,
1639 svc
, hostname
, NULL
);
1641 ret
= krb5_unparse_name(r
->context
, p
, &spn
);
1643 ret
= get_keys1(r
, spn
);
1644 krb5_free_principal(r
->context
, p
);
1648 for (i
= 0; ret
== 0 && i
< nspns
; i
++) {
1650 heim_string_get_utf8(heim_array_get_value(r
->spns
,
1656 krb5_log_msg(r
->context
, logfac
, 1, NULL
,
1657 "Failed to extract keys for unknown reasons");
1658 if (r
->response_set
)
1660 return bad_503(r
, ret
, "Could not get keys");
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
);
1671 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "HDB is read-only");
1672 return bad_403(r
, ret
, "HDB is read-only");
1675 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Sent keytab to %s",
1677 return good_ext_keytab(r
);
1679 return bad_503(r
, ret
, "Could not get keys");
1683 /* Copied from kdc/connect.c */
1685 addr_to_string(krb5_context context
,
1686 struct sockaddr
*addr
,
1690 krb5_error_code ret
;
1693 ret
= krb5_sockaddr2address(context
, addr
, &a
);
1695 ret
= krb5_print_address(&a
, str
, len
, &len
);
1696 krb5_free_address(context
, &a
);
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
,
1708 kadmin_request_desc
*rp
)
1710 const union MHD_ConnectionInfo
*ci
;
1711 kadmin_request_desc r
;
1713 krb5_error_code ret
;
1716 if ((r
= calloc(1, sizeof(*r
))) == NULL
)
1719 (void) gettimeofday(&r
->tv_start
, NULL
);
1720 if ((ret
= get_krb5_context(&r
->context
))) {
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
;
1737 r
->kv
= heim_dict_create(10);
1739 r
->attributes
= heim_dict_create(1);
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
;
1749 r
->cache_control
= NULL
;
1755 ci
= MHD_get_connection_info(connection
,
1756 MHD_CONNECTION_INFO_CLIENT_ADDRESS
);
1758 r
->addr
= ci
->client_addr
;
1759 addr_to_string(r
->context
, r
->addr
, r
->frombuf
, sizeof(r
->frombuf
));
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>");
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
;
1792 clean_req_desc(kadmin_request_desc r
)
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);
1802 kadm5_destroy(r
->kadm_handle
);
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
);
1825 cleanup_req(void *cls
,
1826 struct MHD_Connection
*connection
,
1828 enum MHD_RequestTerminationCode toe
)
1830 kadmin_request_desc r
= *con_cls
;
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
;
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";
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
);
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
,
1884 ret
= krb5_parse_name(r
->context
, pname
, &p
);
1886 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
, mask
);
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
)
1892 bodylen
= tl_next
->tl_data_length
;
1893 body
= tl_next
->tl_data_contents
;
1897 r
->error_code
= ret
;
1898 return bad_404(r
, ret
, "/get-config");
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
);
1908 ret
= bad_503(r
, ret
, "Could not retrieve principal configuration");
1911 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1912 krb5_free_principal(r
->context
, p
);
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
;
1923 char mac
[EVP_MAX_MD_SIZE
];
1924 unsigned int maclen
= sizeof(mac
);
1925 HMAC_CTX
*ctx
= NULL
;
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
,
1937 ret
= krb5_make_principal(r
->context
, &p
,
1938 r
->realm
? r
->realm
: realm
,
1939 "WELLKNOWN", "CSRFTOKEN", NULL
);
1941 ret
= kadm5_get_principal(r
->kadm_handle
, p
, &princ
,
1942 KADM5_PRINCIPAL
| KADM5_KVNO
|
1946 if (ret
== 0 && princ
.n_key_data
< 1)
1947 ret
= KADM5_UNK_PRINC
;
1949 for (i
= 0; i
< princ
.n_key_data
; i
++)
1950 if (princ
.key_data
[i
].key_data_kvno
== princ
.kvno
)
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 */
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(),
1962 HMAC_CTX_cleanup(ctx
);
1963 ret
= krb5_enomem(r
->context
);
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
;
1972 if (krb5_storage_write(sp
, mac
, maclen
) != maclen
)
1973 ret
= krb5_enomem(r
->context
);
1976 krb5_free_principal(r
->context
, p
);
1978 kadm5_free_principal_ent(r
->kadm_handle
, &princ
);
1984 static krb5_error_code
1985 make_csrf_token(kadmin_request_desc r
,
1990 krb5_error_code ret
= 0;
1991 unsigned char given_decoded
[128];
1992 krb5_storage
*sp
= NULL
;
2003 size_t len
= strlen(given
);
2005 if (len
>= sizeof(given_decoded
))
2007 if (ret
== 0 && (dlen
= rk_base64_decode(given
, &given_decoded
)) <= 0)
2010 (sp
= krb5_storage_from_mem(given_decoded
, dlen
)) == NULL
)
2011 ret
= krb5_enomem(r
->context
);
2013 ret
= krb5_ret_int64(sp
, &t
);
2015 ret
= krb5_ret_uint64(sp
, &nonce
);
2016 krb5_storage_free(sp
);
2019 *age
= time(NULL
) - t
;
2022 krb5_generate_random_block((void *)&nonce
, sizeof(nonce
));
2025 if (ret
== 0 && (sp
= krb5_storage_emem()) == NULL
)
2026 ret
= krb5_enomem(r
->context
);
2028 ret
= krb5_store_int64(sp
, t
);
2030 ret
= krb5_store_uint64(sp
, nonce
);
2032 ret
= mac_csrf_token(r
, sp
);
2034 ret
= krb5_storage_to_data(sp
, &data
);
2035 if (ret
== 0 && data
.length
> INT_MAX
)
2038 rk_base64_encode(data
.data
, data
.length
, token
) < 0)
2040 krb5_storage_free(sp
);
2041 krb5_data_free(&data
);
2046 * Returns system or krb5_error_code on error, but also calls resp() or bad_*()
2049 static krb5_error_code
2050 check_csrf(kadmin_request_desc r
)
2052 krb5_error_code ret
;
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
))
2071 if (strcmp(r
->method
, "POST") == 0 &&
2072 !(csrf_prot_type
& CSRF_PROT_POST_WITH_TOKEN
))
2075 given
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
2077 ret
= make_csrf_token(r
, given
, &r
->csrf_token
, &age
);
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
);
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");
2098 if (age
> 300) { /* XXX */
2099 (void) bad_403(r
, EACCES
, "CSRF token too old");
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,
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
2120 enum MHD_ValueKind kind
,
2122 const char *content_name
,
2123 const char *content_type
,
2124 const char *transfer_encoding
,
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 */
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
) {
2146 ftl
->freeme1
= keydup
;
2147 ftl
->freeme2
= valdup
;
2148 ftl
->next
= r
->free_list
;
2151 return MHD_set_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
2155 typedef krb5_error_code (*handler
)(struct kadmin_request_desc
*);
2158 const char *local_part
;
2161 { "/get-keys", get_keys
},
2162 { "/get-config", get_config
},
2165 static heim_mhd_result
2167 struct MHD_Connection
*connection
,
2170 const char *version
,
2171 const char *upload_data
,
2172 size_t *upload_data_size
,
2175 struct kadmin_request_desc
*r
= *ctx
;
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
)))
2196 * All requests other than /health require authentication and CSRF
2199 if (strcmp(url
, "/health") == 0)
2202 /* Authenticate and do CSRF protection */
2203 ret
= validate_token(r
);
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
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;
2268 * Either this is a HEAD, a GET, or a POST whose request body has now been
2269 * received completely and processed.
2273 if (strcmp(method
, "GET") == 0 && !allow_GET_flag
) {
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)
2281 if (MHD_lookup_connection_value(r
->connection
,
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,
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" }
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");
2354 static int sigpipe
[2] = { -1, -1 };
2360 while (write(sigpipe
[1], &c
, sizeof(c
)) == -1 && errno
== EINTR
)
2365 my_openlog(krb5_context context
,
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
);
2374 s
= krb5_config_get_strings(context
, NULL
, "logging", svc
, NULL
);
2377 krb5_addlog_dest(context
, *fac
, *p
);
2378 krb5_config_free_strings(s
);
2381 if (asprintf(&ss
, "0-1/FILE:%s/%s", hdb_db_dir(context
),
2383 err(1, "out of memory");
2384 krb5_addlog_dest(context
, *fac
, ss
);
2387 krb5_set_warn_dest(context
, *fac
);
2390 static const char *sysplugin_dirs
[] = {
2394 "$ORIGIN/../lib/plugin/kdc",
2397 LIBDIR
"/plugin/kdc",
2403 load_plugins(krb5_context context
)
2405 const char * const *dirs
= sysplugin_dirs
;
2409 cfdirs
= krb5_config_get_strings(context
, NULL
, "kdc", "plugin_dir", NULL
);
2411 dirs
= (const char * const *)cfdirs
;
2415 _krb5_load_plugins(context
, "kdc", (const char **)dirs
);
2418 krb5_config_free_strings(cfdirs
);
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
;
2428 char **freeme
= NULL
;
2430 if (csrf_header
== NULL
)
2431 csrf_header
= krb5_config_get_string(context
, NULL
, "bx509d",
2432 "csrf_protection_csrf_header",
2438 strs
= freeme
= krb5_config_get_strings(context
, NULL
, "bx509d",
2439 "csrf_protection_type", NULL
);
2440 for (p
= strs
; p
&& p
; p
++)
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
;
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
;
2494 char *priv_key_pem
= NULL
;
2495 char *cert_pem
= NULL
;
2500 setprogname("httpkadmind");
2501 if (getarg(args
, sizeof(args
) / sizeof(args
[0]), argc
, argv
, &optidx
))
2506 print_version(NULL
);
2509 if (argc
> optidx
) /* Add option to set a URI local part prefix? */
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
));
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");
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
);
2553 ret
= krb5_get_default_realm(context
, &s
);
2555 krb5_err(context
, 1, ret
, "Could not determine default realm");
2559 if ((errno
= get_kadm_handle(context
, realm
, 0 /* want_write */,
2561 err(1, "Could not connect to HDB");
2562 kadm5_destroy(kadm_handle
);
2565 my_openlog(context
, "httpkadmind", &logfac
);
2566 load_plugins(context
);
2568 if (cache_dir
== NULL
) {
2571 if (asprintf(&s
, "%s/httpkadmind-XXXXXX",
2572 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
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);
2583 if (cert_file
&& !priv_key_file
)
2584 priv_key_file
= cert_file
;
2587 hx509_cursor cursor
= NULL
;
2588 hx509_certs certs
= NULL
;
2589 hx509_cert cert
= NULL
;
2590 time_t min_cert_life
= 0;
2594 ret
= hx509_certs_init(context
->hx509ctx
, cert_file
, 0, NULL
, &certs
);
2596 ret
= hx509_certs_start_seq(context
->hx509ctx
, certs
, &cursor
);
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",
2607 notAfter
-= time(NULL
);
2609 warnx("One or more certificates in %s expire soon",
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
);
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
);
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
);
2634 if (priv_key_file
) {
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
);
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
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
);
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
);
2677 sock
= MHD_quiesce_daemon(previous
);
2679 if (reverse_proxied_flag
) {
2681 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
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
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
,
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
,
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
,
2716 sock
= MHD_INVALID_SOCKET
;
2718 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
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
,
2728 if (current
== NULL
)
2729 err(1, "Could not start kadmin REST service");
2732 MHD_stop_daemon(previous
);
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 &&
2748 priv_key_pem
= NULL
;
2751 if (ret
== 1 && (sig
== SIGHUP
|| sig
== SIGUSR1
|| sig
== SIGALRM
)) {
2752 /* Reload certs and restart service gracefully */
2758 MHD_stop_daemon(current
);
2759 _krb5_unload_plugins(context
, "kdc");
2760 pthread_key_delete(k5ctx
);