s4:torture: Adapt KDC canon test to Heimdal upstream changes
[samba.git] / source4 / heimdal / kdc / httpkadmind.c
1 /*
2  * Copyright (c) 2020 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 /*
35  */
36
37 #define _XOPEN_SOURCE_EXTENDED  1
38 #define _DEFAULT_SOURCE  1
39 #define _BSD_SOURCE  1
40 #define _GNU_SOURCE  1
41
42 #include <sys/socket.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/time.h>
46 #include <ctype.h>
47 #include <dlfcn.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <pthread.h>
51 #include <signal.h>
52 #include <stdarg.h>
53 #include <stddef.h>
54 #include <stdint.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60 #include <netdb.h>
61 #include <netinet/in.h>
62 #include <netinet/ip.h>
63
64 #include <microhttpd.h>
65 #include "kdc_locl.h"
66 #include "token_validator_plugin.h"
67 #include <getarg.h>
68 #include <roken.h>
69 #include <krb5.h>
70 #include <gssapi/gssapi.h>
71 #include <gssapi/gssapi_krb5.h>
72 #include <hx509.h>
73 #include "../lib/hx509/hx_locl.h"
74 #include <hx509-private.h>
75 #include <kadm5/admin.h>
76 #include <kadm5/private.h>
77 #include <kadm5/kadm5_err.h>
78
79 #define heim_pcontext krb5_context
80 #define heim_pconfig krb5_context
81 #include <heimbase-svc.h>
82
83 #define BODYLEN_IS_STRLEN (~0)
84
85 /*
86  * Libmicrohttpd is not the easiest API to use.  It's got issues.
87  *
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
91  * response.
92  *
93  * There's memory management issues as well.
94  *
95  * Here we have to be careful about return values.
96  *
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.
103  *
104  * We do use some system error codes to stand in for errors here.
105  * Specifically:
106  *
107  *  - EACCES -> authorization failed
108  *  - EINVAL -> bad API usage
109  *  - ENOSYS -> missing CSRF token but CSRF token required
110  *
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
114  *        here.
115  */
116
117 /* Our request description structure */
118 typedef struct kadmin_request_desc {
119     HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
120
121     struct MHD_Connection *connection;
122     krb5_times token_times;
123     /*
124      * FIXME
125      *
126      * Currently we re-use the authz framework from bx509d, using an
127      * `hx509_request' instance (an abstraction for CSRs) to represent the
128      * request because that is what the authz plugin uses that implements the
129      * policy we want checked here.
130      *
131      * This is inappropriate in the long-term in two ways:
132      *
133      *  - the policy for certificates deals in SANs and EKUs, whereas the
134      *    policy for ext_keytab deals in host-based service principal names,
135      *    and there is not a one-to-one mapping of service names to EKUs;
136      *
137      *  - using a type from libhx509 for representing requests for things that
138      *    aren't certificates is really not appropriate no matter how similar
139      *    the use cases for this all might be.
140      *
141      * What we need to do is develop a library that can represent requests for
142      * credentials via naming attributes like SANs and Kerberos principal
143      * names, but more arbitrary still than what `hx509_request' supports, and
144      * then invokes a plugin.
145      *
146      * Also, we might want to develop an in-tree authorization solution that is
147      * richer than what kadmin.acl supports now, storing grants in HDB entries
148      * and/or similar places.
149      *
150      * For expediency we use `hx509_request' here for now, impedance mismatches
151      * be damned.
152      */
153     hx509_request req;          /* For authz only */
154     heim_array_t service_names;
155     heim_array_t hostnames;
156     heim_array_t spns;
157     krb5_principal cprinc;
158     krb5_keytab keytab;
159     krb5_storage *sp;
160     void *kadm_handle;
161     char *realm;
162     char *keytab_name;
163     char *freeme1;
164     char *enctypes;
165     const char *method;
166     unsigned int response_set:1;
167     unsigned int materialize:1;
168     unsigned int rotate_now:1;
169     unsigned int rotate:1;
170     unsigned int revoke:1;
171     unsigned int create:1;
172     unsigned int ro:1;
173     unsigned int is_self:1;
174     char frombuf[128];
175 } *kadmin_request_desc;
176
177 static void
178 audit_trail(kadmin_request_desc r, krb5_error_code ret)
179 {
180     const char *retname = NULL;
181
182     /*
183      * Get a symbolic name for some error codes.
184      *
185      * Really, libcom_err should have a primitive for this, and ours could, but
186      * we can't use a system libcom_err if we extend ours.
187      */
188 #define CASE(x) case x : retname = #x; break
189     switch (ret) {
190     case ENOSYS: retname = "ECSRFTOKENREQD"; break;
191     CASE(EINVAL);
192     CASE(ENOMEM);
193     CASE(EACCES);
194     CASE(HDB_ERR_NOT_FOUND_HERE);
195     CASE(HDB_ERR_WRONG_REALM);
196     CASE(HDB_ERR_EXISTS);
197     CASE(HDB_ERR_KVNO_NOT_FOUND);
198     CASE(HDB_ERR_NOENTRY);
199     CASE(HDB_ERR_NO_MKEY);
200     CASE(KRB5_KDC_UNREACH);
201     CASE(KADM5_FAILURE);
202     CASE(KADM5_AUTH_GET);
203     CASE(KADM5_AUTH_ADD);
204     CASE(KADM5_AUTH_MODIFY);
205     CASE(KADM5_AUTH_DELETE);
206     CASE(KADM5_AUTH_INSUFFICIENT);
207     CASE(KADM5_BAD_DB);
208     CASE(KADM5_DUP);
209     CASE(KADM5_RPC_ERROR);
210     CASE(KADM5_NO_SRV);
211     CASE(KADM5_BAD_HIST_KEY);
212     CASE(KADM5_NOT_INIT);
213     CASE(KADM5_UNK_PRINC);
214     CASE(KADM5_UNK_POLICY);
215     CASE(KADM5_BAD_MASK);
216     CASE(KADM5_BAD_CLASS);
217     CASE(KADM5_BAD_LENGTH);
218     CASE(KADM5_BAD_POLICY);
219     CASE(KADM5_BAD_PRINCIPAL);
220     CASE(KADM5_BAD_AUX_ATTR);
221     CASE(KADM5_BAD_HISTORY);
222     CASE(KADM5_BAD_MIN_PASS_LIFE);
223     CASE(KADM5_PASS_Q_TOOSHORT);
224     CASE(KADM5_PASS_Q_CLASS);
225     CASE(KADM5_PASS_Q_DICT);
226     CASE(KADM5_PASS_Q_GENERIC);
227     CASE(KADM5_PASS_REUSE);
228     CASE(KADM5_PASS_TOOSOON);
229     CASE(KADM5_POLICY_REF);
230     CASE(KADM5_INIT);
231     CASE(KADM5_BAD_PASSWORD);
232     CASE(KADM5_PROTECT_PRINCIPAL);
233     CASE(KADM5_BAD_SERVER_HANDLE);
234     CASE(KADM5_BAD_STRUCT_VERSION);
235     CASE(KADM5_OLD_STRUCT_VERSION);
236     CASE(KADM5_NEW_STRUCT_VERSION);
237     CASE(KADM5_BAD_API_VERSION);
238     CASE(KADM5_OLD_LIB_API_VERSION);
239     CASE(KADM5_OLD_SERVER_API_VERSION);
240     CASE(KADM5_NEW_LIB_API_VERSION);
241     CASE(KADM5_NEW_SERVER_API_VERSION);
242     CASE(KADM5_SECURE_PRINC_MISSING);
243     CASE(KADM5_NO_RENAME_SALT);
244     CASE(KADM5_BAD_CLIENT_PARAMS);
245     CASE(KADM5_BAD_SERVER_PARAMS);
246     CASE(KADM5_AUTH_LIST);
247     CASE(KADM5_AUTH_CHANGEPW);
248     CASE(KADM5_BAD_TL_TYPE);
249     CASE(KADM5_MISSING_CONF_PARAMS);
250     CASE(KADM5_BAD_SERVER_NAME);
251     CASE(KADM5_KS_TUPLE_NOSUPP);
252     CASE(KADM5_SETKEY3_ETYPE_MISMATCH);
253     CASE(KADM5_DECRYPT_USAGE_NOSUPP);
254     CASE(KADM5_POLICY_OP_NOSUPP);
255     CASE(KADM5_KEEPOLD_NOSUPP);
256     CASE(KADM5_AUTH_GET_KEYS);
257     CASE(KADM5_ALREADY_LOCKED);
258     CASE(KADM5_NOT_LOCKED);
259     CASE(KADM5_LOG_CORRUPT);
260     CASE(KADM5_LOG_NEEDS_UPGRADE);
261     CASE(KADM5_BAD_SERVER_HOOK);
262     CASE(KADM5_SERVER_HOOK_NOT_FOUND);
263     CASE(KADM5_OLD_SERVER_HOOK_VERSION);
264     CASE(KADM5_NEW_SERVER_HOOK_VERSION);
265     CASE(KADM5_READ_ONLY);
266     case 0:
267         retname = "SUCCESS";
268         break;
269     default:
270         retname = NULL;
271         break;
272     }
273     heim_audit_trail((heim_svc_req_desc)r, ret, retname);
274 }
275
276 static krb5_log_facility *logfac;
277 static pthread_key_t k5ctx;
278
279 static krb5_error_code
280 get_krb5_context(krb5_context *contextp)
281 {
282     krb5_error_code ret;
283
284     if ((*contextp = pthread_getspecific(k5ctx)))
285         return 0;
286
287     ret = krb5_init_context(contextp);
288     /* XXX krb5_set_log_dest(), warn_dest, debug_dest */
289     if (ret == 0)
290         (void) pthread_setspecific(k5ctx, *contextp);
291     return ret;
292 }
293
294 static int port = -1;
295 static int help_flag;
296 static int daemonize;
297 static int daemon_child_fd = -1;
298 static int local_hdb;
299 static int local_hdb_read_only;
300 static int read_only;
301 static int verbose_counter;
302 static int version_flag;
303 static int reverse_proxied_flag;
304 static int thread_per_client_flag;
305 struct getarg_strings audiences;
306 static const char *cert_file;
307 static const char *priv_key_file;
308 static const char *cache_dir;
309 static const char *realm;
310 static const char *hdb;
311 static const char *primary_server_URI;
312 static const char *kadmin_server;
313 static const char *writable_kadmin_server;
314 static const char *stash_file;
315 static const char *kadmin_client_name = "httpkadmind/admin";
316 static const char *kadmin_client_keytab;
317 static struct getarg_strings auth_types;
318
319 #define set_conf(c, f, v, b) \
320     if (v) { \
321         if (((c).f = strdup(v)) == NULL) \
322             goto enomem; \
323         conf.mask |= b; \
324     }
325
326 /*
327  * Does NOT set an HTTP response, naturally, as it doesn't even have access to
328  * the connection.
329  */
330 static krb5_error_code
331 get_kadm_handle(krb5_context context,
332                 const char *want_realm,
333                 int want_write,
334                 void **kadm_handle)
335 {
336     kadm5_config_params conf;
337     krb5_error_code ret;
338
339     /*
340      * If the caller wants to write and we are configured to redirect in that
341      * case, then trigger a redirect by returning KADM5_READ_ONLY.
342      */
343     if (want_write && local_hdb_read_only && primary_server_URI)
344         return KADM5_READ_ONLY;
345     if (want_write && read_only)
346         return KADM5_READ_ONLY;
347
348     /*
349      * Configure kadm5 connection.
350      *
351      * Note that all of these are optional, and will be found in krb5.conf or,
352      * in some cases, in DNS, as needed.
353      */
354     memset(&conf, 0, sizeof(conf));
355     conf.realm = NULL;
356     conf.dbname = NULL;
357     conf.stash_file = NULL;
358     conf.admin_server = NULL;
359     conf.readonly_admin_server = NULL;
360     set_conf(conf, realm, want_realm, KADM5_CONFIG_REALM);
361     set_conf(conf, dbname, hdb, KADM5_CONFIG_DBNAME);
362     set_conf(conf, stash_file, stash_file, KADM5_CONFIG_STASH_FILE);
363     set_conf(conf, admin_server, writable_kadmin_server, KADM5_CONFIG_ADMIN_SERVER);
364     set_conf(conf, readonly_admin_server, kadmin_server,
365              KADM5_CONFIG_READONLY_ADMIN_SERVER);
366
367     /*
368      * If we have a local HDB we'll use it if we can.  If the local HDB is
369      * read-only and the caller wants to write, then we won't use the local
370      * HDB, naturally.
371      */
372     if (local_hdb && (!local_hdb_read_only || !want_write)) {
373         ret = kadm5_s_init_with_password_ctx(context,
374                                              kadmin_client_name,
375                                              NULL, /* password */
376                                              NULL, /* service_name */
377                                              &conf,
378                                              0,    /* struct_version */
379                                              0,    /* api_version */
380                                              kadm_handle);
381         goto out;
382     }
383
384     /*
385      * Remote connection.  This will connect to a read-only kadmind if
386      * possible, and if so, reconnect to a writable kadmind as needed.
387      *
388      * Note that kadmin_client_keytab can be an HDB: or HDBGET: keytab.
389      */
390     ret = kadm5_c_init_with_skey_ctx(context,
391                                      kadmin_client_name,
392                                      kadmin_client_keytab,
393                                      KADM5_ADMIN_SERVICE,
394                                      &conf,
395                                      0, /* struct_version */
396                                      0, /* api_version */
397                                      kadm_handle);
398     goto out;
399
400 enomem:
401     ret = krb5_enomem(context);
402
403 out:
404     free(conf.readonly_admin_server);
405     free(conf.admin_server);
406     free(conf.stash_file);
407     free(conf.dbname);
408     free(conf.realm);
409     return ret;
410 }
411
412 static krb5_error_code resp(kadmin_request_desc, int, krb5_error_code,
413                             enum MHD_ResponseMemoryMode, const char *,
414                             const void *, size_t, const char *, const char *);
415 static krb5_error_code bad_req(kadmin_request_desc, krb5_error_code, int,
416                                const char *, ...)
417                                HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
418
419 static krb5_error_code bad_enomem(kadmin_request_desc, krb5_error_code);
420 static krb5_error_code bad_400(kadmin_request_desc, krb5_error_code, const char *);
421 static krb5_error_code bad_401(kadmin_request_desc, const char *);
422 static krb5_error_code bad_403(kadmin_request_desc, krb5_error_code, const char *);
423 static krb5_error_code bad_404(kadmin_request_desc, const char *);
424 static krb5_error_code bad_405(kadmin_request_desc, const char *);
425 /*static krb5_error_code bad_500(kadmin_request_desc, krb5_error_code, const char *);*/
426 static krb5_error_code bad_503(kadmin_request_desc, krb5_error_code, const char *);
427
428 static int
429 validate_token(kadmin_request_desc r)
430 {
431     krb5_error_code ret;
432     const char *token;
433     const char *host;
434     char token_type[64]; /* Plenty */
435     char *p;
436     krb5_data tok;
437     size_t host_len, brk, i;
438
439     memset(&r->token_times, 0, sizeof(r->token_times));
440     host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
441                                        MHD_HTTP_HEADER_HOST);
442     if (host == NULL)
443         return bad_400(r, EINVAL, "Host header is missing");
444
445     /* Exclude port number here (IPv6-safe because of the below) */
446     host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
447
448     token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
449                                         MHD_HTTP_HEADER_AUTHORIZATION);
450     if (token == NULL)
451         return bad_401(r, "Authorization token is missing");
452     brk = strcspn(token, " \t");
453     if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
454         return bad_401(r, "Authorization token is missing");
455     memcpy(token_type, token, brk);
456     token_type[brk] = '\0';
457     token += brk + 1;
458     tok.length = strlen(token);
459     tok.data = (void *)(uintptr_t)token;
460
461     for (i = 0; i < audiences.num_strings; i++)
462         if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
463             audiences.strings[i][host_len] == '\0')
464             break;
465     if (i == audiences.num_strings)
466         return bad_403(r, EINVAL, "Host: value is not accepted here");
467
468     r->sname = strdup(host); /* No need to check for ENOMEM here */
469
470     ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
471                              (const char **)&audiences.strings[i], 1,
472                              &r->cprinc, &r->token_times);
473     if (ret)
474         return bad_403(r, ret, "Token validation failed");
475     if (r->cprinc == NULL)
476         return bad_403(r, ret,
477                        "Could not extract a principal name from token");
478     ret = krb5_unparse_name(r->context, r->cprinc, &r->cname);
479     if (ret)
480         return bad_503(r, ret,
481                        "Could not extract a principal name from token");
482     return 0;
483 }
484
485 static void
486 k5_free_context(void *ctx)
487 {
488     krb5_free_context(ctx);
489 }
490
491 #ifndef HAVE_UNLINKAT
492 static int
493 unlink1file(const char *dname, const char *name)
494 {
495     char p[PATH_MAX];
496
497     if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
498         strlcat(p, "/", sizeof(p)) < sizeof(p) &&
499         strlcat(p, name, sizeof(p)) < sizeof(p))
500         return unlink(p);
501     return ERANGE;
502 }
503 #endif
504
505 static void
506 rm_cache_dir(void)
507 {
508     struct dirent *e;
509     DIR *d;
510
511     /*
512      * This works, but not on Win32:
513      *
514      *  (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
515      *
516      * We make no directories in `cache_dir', so we need not recurse.
517      */
518     if ((d = opendir(cache_dir)) == NULL)
519         return;
520
521     while ((e = readdir(d))) {
522 #ifdef HAVE_UNLINKAT
523         /*
524          * Because unlinkat() takes a directory FD, implementing one for
525          * libroken is tricky at best.  Instead we might want to implement an
526          * rm_dash_rf() function in lib/roken.
527          */
528         (void) unlinkat(dirfd(d), e->d_name, 0);
529 #else
530         (void) unlink1file(cache_dir, e->d_name);
531 #endif
532     }
533     (void) closedir(d);
534     (void) rmdir(cache_dir);
535 }
536
537 /*
538  * Work around older libmicrohttpd not strduping response header values when
539  * set.
540  */
541 static HEIMDAL_THREAD_LOCAL struct redirect_uri {
542     char uri[4096];
543     size_t len;
544     size_t first_param;
545     int valid;
546 } redirect_uri;
547
548 static void
549 redirect_uri_appends(struct redirect_uri *redirect,
550                      const char *s)
551 {
552     size_t sz, len;
553     char *p;
554
555     if (!redirect->valid || redirect->len >= sizeof(redirect->uri) - 1) {
556         redirect->valid = 0;
557         return;
558     }
559     /* Optimize strlcpy by using redirect->uri + redirect->len */
560     p = redirect->uri + redirect->len;
561     sz = sizeof(redirect->uri) - redirect->len;
562     if ((len = strlcpy(p, s, sz)) >= sz)
563         redirect->valid = 0;
564     else
565         redirect->len += len;
566 }
567
568 static int
569 make_redirect_uri_param_cb(void *d,
570                            enum MHD_ValueKind kind,
571                            const char *key,
572                            const char *val)
573 {
574     struct redirect_uri *redirect = d;
575
576     redirect_uri_appends(redirect, redirect->first_param ? "?" : "&");
577     redirect_uri_appends(redirect, key);
578     if (val) {
579         redirect_uri_appends(redirect, "=");
580         redirect_uri_appends(redirect, val);
581     }
582     redirect->first_param = 0;
583     return MHD_YES;
584 }
585
586 static const char *
587 make_redirect_uri(kadmin_request_desc r, const char *base)
588 {
589     redirect_uri.len = 0;
590     redirect_uri.uri[0] = '\0';
591     redirect_uri.valid = 1;
592     redirect_uri.first_param = 1;
593
594     redirect_uri_appends(&redirect_uri, base); /* Redirect to primary URI base */
595     redirect_uri_appends(&redirect_uri, r->reqtype); /* URI local-part */
596     (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
597                                      make_redirect_uri_param_cb,
598                                      &redirect_uri);
599     return redirect_uri.valid ? redirect_uri.uri : NULL;
600 }
601
602
603 /*
604  * XXX Shouldn't be a body, but a status message.  The body should be
605  * configurable to be from a file.  MHD doesn't give us a way to set the
606  * response status message though, just the body.
607  *
608  * Calls audit_trail().
609  *
610  * Returns -1 if something terrible happened, which should ultimately cause
611  * route() to return MHD_NO, which should cause libmicrohttpd to close the
612  * connection to the user-agent.
613  *
614  * Returns 0 in all other cases.
615  */
616 static krb5_error_code
617 resp(kadmin_request_desc r,
618      int http_status_code,
619      krb5_error_code ret,
620      enum MHD_ResponseMemoryMode rmmode,
621      const char *content_type,
622      const void *body,
623      size_t bodylen,
624      const char *token,
625      const char *csrf)
626 {
627     struct MHD_Response *response;
628     int mret = MHD_YES;
629
630     if (r->response_set) {
631         krb5_log_msg(r->context, logfac, 1, NULL,
632                      "Internal error; attempted to set a second response");
633         return 0;
634     }
635
636     (void) gettimeofday(&r->tv_end, NULL);
637     audit_trail(r, ret);
638
639     if (body && bodylen == BODYLEN_IS_STRLEN)
640         bodylen = strlen(body);
641
642     response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
643                                                rmmode);
644     if (response == NULL)
645         return -1;
646     mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
647                                    "no-store, max-age=0");
648     if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
649         size_t i;
650
651         if (auth_types.num_strings < 1)
652             http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
653         else
654             for (i = 0; mret == MHD_YES && i < auth_types.num_strings; i++)
655                 mret = MHD_add_response_header(response,
656                                                MHD_HTTP_HEADER_WWW_AUTHENTICATE,
657                                                auth_types.strings[i]);
658     } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
659         const char *redir = make_redirect_uri(r, primary_server_URI);
660
661         if (redir)
662             mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
663                                            redir);
664         else
665             /* XXX Find a way to set a new response body; log */
666             http_status_code = MHD_HTTP_SERVICE_UNAVAILABLE;
667     }
668
669     if (mret == MHD_YES && csrf)
670         mret = MHD_add_response_header(response,
671                                        "X-CSRF-Token",
672                                        csrf);
673
674     if (mret == MHD_YES && content_type) {
675         mret = MHD_add_response_header(response,
676                                        MHD_HTTP_HEADER_CONTENT_TYPE,
677                                        content_type);
678     }
679     if (mret != MHD_NO)
680         mret = MHD_queue_response(r->connection, http_status_code, response);
681     MHD_destroy_response(response);
682     r->response_set = 1;
683     return mret == MHD_NO ? -1 : 0;
684 }
685
686 static krb5_error_code
687 bad_reqv(kadmin_request_desc r,
688          krb5_error_code code,
689          int http_status_code,
690          const char *fmt,
691          va_list ap)
692 {
693     krb5_error_code ret;
694     krb5_context context = NULL;
695     const char *k5msg = NULL;
696     const char *emsg = NULL;
697     char *formatted = NULL;
698     char *msg = NULL;
699
700     if (r && r->context)
701         context = r->context;
702     if (r && r->hcontext && r->kv)
703         heim_audit_addkv((heim_svc_req_desc)r, 0, "http-status-code", "%d",
704                          http_status_code);
705     (void) gettimeofday(&r->tv_end, NULL);
706     if (code == ENOMEM) {
707         if (context)
708             krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
709         return resp(r, http_status_code, code, MHD_RESPMEM_PERSISTENT,
710                     NULL, fmt, BODYLEN_IS_STRLEN, NULL, NULL);
711     }
712
713     if (code) {
714         if (context)
715             emsg = k5msg = krb5_get_error_message(context, code);
716         else
717             emsg = strerror(code);
718     }
719
720     ret = vasprintf(&formatted, fmt, ap) == -1;
721     if (code) {
722         if (ret > -1 && formatted)
723             ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
724     } else {
725         msg = formatted;
726         formatted = NULL;
727     }
728     if (r && r->hcontext)
729         heim_audit_addreason((heim_svc_req_desc)r, "%s", formatted);
730     krb5_free_error_message(context, k5msg);
731
732     if (ret == -1 || msg == NULL) {
733         if (context)
734             krb5_log_msg(context, logfac, 1, NULL, "Out of memory");
735         return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, ENOMEM,
736                     MHD_RESPMEM_PERSISTENT, NULL,
737                     "Out of memory", BODYLEN_IS_STRLEN, NULL, NULL);
738     }
739
740     ret = resp(r, http_status_code, code, MHD_RESPMEM_MUST_COPY,
741                NULL, msg, BODYLEN_IS_STRLEN, NULL, NULL);
742     free(formatted);
743     free(msg);
744     return ret == -1 ? -1 : code;
745 }
746
747 static krb5_error_code
748 bad_req(kadmin_request_desc r,
749         krb5_error_code code,
750         int http_status_code,
751         const char *fmt,
752         ...)
753 {
754     krb5_error_code ret;
755     va_list ap;
756
757     va_start(ap, fmt);
758     ret = bad_reqv(r, code, http_status_code, fmt, ap);
759     va_end(ap);
760     return ret;
761 }
762
763 static krb5_error_code
764 bad_enomem(kadmin_request_desc r, krb5_error_code ret)
765 {
766     return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
767                    "Out of memory");
768 }
769
770 static krb5_error_code
771 bad_400(kadmin_request_desc r, int ret, const char *reason)
772 {
773     return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
774 }
775
776 static krb5_error_code
777 bad_401(kadmin_request_desc r, const char *reason)
778 {
779     return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
780 }
781
782 static krb5_error_code
783 bad_403(kadmin_request_desc r, krb5_error_code ret, const char *reason)
784 {
785     return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
786 }
787
788 static krb5_error_code
789 bad_404(kadmin_request_desc r, const char *name)
790 {
791     return bad_req(r, ENOENT, MHD_HTTP_NOT_FOUND,
792                    "Resource not found: %s", name);
793 }
794
795 static krb5_error_code
796 bad_405(kadmin_request_desc r, const char *method)
797 {
798     return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
799                    "Method not supported: %s", method);
800 }
801
802 static krb5_error_code
803 bad_method_want_POST(kadmin_request_desc r)
804 {
805     return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
806                    "Use POST for making changes to principals");
807 }
808
809 #if 0
810 static krb5_error_code
811 bad_500(kadmin_request_desc r,
812         krb5_error_code ret,
813         const char *reason)
814 {
815     return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
816                    "Internal error: %s", reason);
817 }
818 #endif
819
820 static krb5_error_code
821 bad_503(kadmin_request_desc r,
822         krb5_error_code ret,
823         const char *reason)
824 {
825     return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
826                    "Service unavailable: %s", reason);
827 }
828
829 static krb5_error_code
830 good_ext_keytab(kadmin_request_desc r)
831 {
832     krb5_error_code ret;
833     size_t bodylen;
834     void *body;
835     char *p;
836
837     if (!r->keytab_name || !(p = strchr(r->keytab_name, ':')))
838         return bad_503(r, EINVAL, "Internal error (no keytab produced)");
839     p++;
840     if (strncmp(p, cache_dir, strlen(cache_dir)) != 0)
841         return bad_503(r, EINVAL, "Internal error");
842     ret = rk_undumpdata(p, &body, &bodylen);
843     if (ret)
844         return bad_503(r, ret, "Could not recover keytab from temp file");
845
846     ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
847                "application/octet-stream", body, bodylen, NULL, NULL);
848     free(body);
849     return ret;
850 }
851
852 static krb5_error_code
853 check_service_name(kadmin_request_desc r, const char *name)
854 {
855     if (name == NULL || name[0] == '\0' ||
856         strchr(name, '/') || strchr(name, '\\') || strchr(name, '@') ||
857         strcmp(name, "krbtgt") == 0 ||
858         strcmp(name, "iprop") == 0 ||
859         strcmp(name, "kadmin") == 0 ||
860         strcmp(name, "hprop") == 0 ||
861         strcmp(name, "WELLKNOWN") == 0 ||
862         strcmp(name, "K") == 0) {
863         krb5_set_error_message(r->context, EACCES,
864                                "No one is allowed to fetch keys for "
865                                "Heimdal service %s", name);
866         return EACCES;
867     }
868     if (strcmp(name, "root") != 0 &&
869         strcmp(name, "host") != 0 &&
870         strcmp(name, "exceed") != 0)
871         return 0;
872     if (krb5_config_get_bool_default(r->context, NULL, FALSE,
873                                      "ext_keytab",
874                                      "csr_authorizer_handles_svc_names",
875                                      NULL))
876         return 0;
877     krb5_set_error_message(r->context, EACCES,
878                            "No one is allowed to fetch keys for "
879                            "service \"%s\" because of authorizer "
880                            "limitations", name);
881     return EACCES;
882 }
883
884 static int
885 param_cb(void *d,
886          enum MHD_ValueKind kind,
887          const char *key,
888          const char *val)
889 {
890     kadmin_request_desc r = d;
891     krb5_error_code ret = 0;
892     heim_string_t s = NULL;
893
894     /*
895      * Multi-valued params:
896      *
897      *  - spn=<service>/<hostname>
898      *  - dNSName=<hostname>
899      *  - service=<service>
900      *
901      * Single-valued params:
902      *
903      *  - realm=<REALM>
904      *  - materialize=true  -- create a concrete princ where it's virtual
905      *  - enctypes=...      -- key-salt types
906      *  - revoke=true       -- delete old keys (concrete princs only)
907      *  - rotate=true       -- change keys (no-op for virtual princs)
908      *  - create=true       -- create a concrete princ
909      *  - ro=true           -- perform no writes
910      */
911
912     if (strcmp(key, "realm") == 0 && val) {
913         if (!r->realm && !(r->realm = strdup(val)))
914             ret = krb5_enomem(r->context);
915     } else if (strcmp(key, "materialize") == 0  ||
916                strcmp(key, "revoke") == 0       ||
917                strcmp(key, "rotate") == 0       ||
918                strcmp(key, "create") == 0       ||
919                strcmp(key, "ro") == 0) {
920         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
921                          "requested_option", "%s", key);
922         if (!val || strcmp(val, "true") != 0)
923             krb5_set_error_message(r->context, ret = EINVAL,
924                                    "get-keys \"%s\" q-param accepts "
925                                    "only \"true\"", key);
926         else if (strcmp(key, "materialize") == 0)
927             r->materialize = 1;
928         else if (strcmp(key, "revoke") == 0)
929             r->revoke = 1;
930         else if (strcmp(key, "rotate") == 0)
931             r->rotate = 1;
932         else if (strcmp(key, "create") == 0)
933             r->create = 1;
934         else if (strcmp(key, "ro") == 0)
935             r->ro = 1;
936     } else if (strcmp(key, "dNSName") == 0 && val) {
937         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
938                          "requested_dNSName", "%s", val);
939         if (r->is_self) {
940             krb5_set_error_message(r->context, ret = EACCES,
941                                    "only one service may be requested for self");
942         } else if (strchr(val, '.') == NULL) {
943             krb5_set_error_message(r->context, ret = EACCES,
944                                    "dNSName must have at least one '.' in it");
945         } else {
946             s = heim_string_create(val);
947             if (!s)
948                 ret = krb5_enomem(r->context);
949             else
950                 ret = heim_array_append_value(r->hostnames, s);
951         }
952         if (ret == 0)
953             ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
954     } else if (strcmp(key, "service") == 0 && val) {
955         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
956                          "requested_service", "%s", val);
957         if (r->is_self)
958             krb5_set_error_message(r->context, ret = EACCES,
959                                    "use \"spn\" for self");
960         else
961             ret = check_service_name(r, val);
962         if (ret == 0) {
963             s = heim_string_create(val);
964             if (!s)
965                 ret = krb5_enomem(r->context);
966             else
967                 ret = heim_array_append_value(r->service_names, s);
968         }
969     } else if (strcmp(key, "enctypes") == 0 && val) {
970         r->enctypes = strdup(val);
971         if (!(r->enctypes = strdup(val)))
972             ret = krb5_enomem(r->context);
973         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
974                          "requested_enctypes", "%s", val);
975     } else if (r->is_self && strcmp(key, "spn") == 0 && val) {
976         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
977                          "requested_spn", "%s", val);
978         krb5_set_error_message(r->context, ret = EACCES,
979                                "only one service may be requested for self");
980     } else if (strcmp(key, "spn") == 0 && val) {
981         krb5_principal p = NULL;
982         const char *hostname = "";
983
984         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
985                          "requested_spn", "%s", val);
986
987         ret = krb5_parse_name_flags(r->context, val,
988                                     KRB5_PRINCIPAL_PARSE_NO_DEF_REALM, &p);
989         if (ret == 0 && krb5_principal_get_realm(r->context, p) == NULL)
990             ret = krb5_principal_set_realm(r->context, p,
991                                            r->realm ? r->realm : realm);
992
993         /*
994          * The SPN has to have two components.
995          *
996          * TODO: Support more components?  Support AD-style NetBIOS computer
997          *       account names?
998          */
999         if (ret == 0 && krb5_principal_get_num_comp(r->context, p) != 2)
1000             ret = ENOTSUP;
1001
1002         /*
1003          * Allow only certain service names.  Except that when
1004          * the SPN == the requestor's principal name then allow the "host"
1005          * service name.
1006          */
1007         if (ret == 0) {
1008             const char *service =
1009                 krb5_principal_get_comp_string(r->context, p, 0);
1010
1011             if (strcmp(service, "host") == 0 &&
1012                 krb5_principal_compare(r->context, p, r->cprinc) &&
1013                 !r->is_self &&
1014                 heim_array_get_length(r->hostnames) == 0 &&
1015                 heim_array_get_length(r->spns) == 0) {
1016                 r->is_self = 1;
1017             } else
1018                 ret = check_service_name(r, service);
1019         }
1020         if (ret == 0 && !krb5_principal_compare(r->context, p, r->cprinc))
1021             ret = check_service_name(r,
1022                                      krb5_principal_get_comp_string(r->context,
1023                                                                     p, 0));
1024         if (ret == 0) {
1025             hostname = krb5_principal_get_comp_string(r->context, p, 1);
1026             if (!hostname || !strchr(hostname, '.'))
1027                 krb5_set_error_message(r->context, ret = ENOTSUP,
1028                                        "Only host-based service names supported");
1029         }
1030         if (ret == 0 && r->realm)
1031             ret = krb5_principal_set_realm(r->context, p, r->realm);
1032         else if (ret == 0 && realm)
1033             ret = krb5_principal_set_realm(r->context, p, realm);
1034         if (ret == 0)
1035             ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req,
1036                                              hostname);
1037         if (ret == 0 && !(s = heim_string_create(val)))
1038             ret = krb5_enomem(r->context);
1039         if (ret == 0)
1040             ret = heim_array_append_value(r->spns, s);
1041         krb5_free_principal(r->context, p);
1042
1043 #if 0
1044         /* The authorizer probably doesn't know what to do with this */
1045         ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req, val);
1046 #endif
1047     } else {
1048         /* Produce error for unknown params */
1049         heim_audit_addkv((heim_svc_req_desc)r, 0, "requested_unknown", "true");
1050         krb5_set_error_message(r->context, ret = ENOTSUP,
1051                                "Query parameter %s not supported", key);
1052     }
1053     if (ret && !r->ret)
1054         r->ret = ret;
1055     heim_release(s);
1056     return ret ? MHD_NO /* Stop iterating */ : MHD_YES;
1057 }
1058
1059 static krb5_error_code
1060 authorize_req(kadmin_request_desc r)
1061 {
1062     krb5_error_code ret;
1063
1064     r->is_self = 0;
1065     ret = hx509_request_init(r->context->hx509ctx, &r->req);
1066     if (ret)
1067         return bad_enomem(r, ret);
1068     (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
1069                                      param_cb, r);
1070     ret = r->ret;
1071     if (ret == EACCES)
1072         return bad_403(r, ret, "Not authorized to requested principal(s)");
1073     if (ret)
1074         return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
1075                        "Could not handle query parameters");
1076     if (r->is_self)
1077         ret = 0;
1078     else
1079         ret = kdc_authorize_csr(r->context, "ext_keytab", r->req, r->cprinc);
1080     if (ret == EACCES || ret == EINVAL || ret == ENOTSUP ||
1081         ret == KRB5KDC_ERR_POLICY)
1082         return bad_403(r, ret, "Not authorized to requested principal(s)");
1083     if (ret)
1084         return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
1085                        "Error checking authorization");
1086     return ret;
1087 }
1088
1089 static krb5_error_code
1090 make_keytab(kadmin_request_desc r)
1091 {
1092     krb5_error_code ret = 0;
1093     int fd = -1;
1094
1095     r->keytab_name = NULL;
1096     if (asprintf(&r->keytab_name, "FILE:%s/kt-XXXXXX", cache_dir) == -1 ||
1097         r->keytab_name == NULL)
1098         ret = krb5_enomem(r->context);
1099     if (ret == 0)
1100         fd = mkstemp(r->keytab_name + sizeof("FILE:") - 1);
1101     if (ret == 0 && fd == -1)
1102         ret = errno;
1103     if (ret == 0)
1104         ret = krb5_kt_resolve(r->context, r->keytab_name, &r->keytab);
1105     if (fd != -1)
1106         (void) close(fd);
1107     return ret;
1108 }
1109
1110 static krb5_error_code
1111 write_keytab(kadmin_request_desc r,
1112              kadm5_principal_ent_rec *princ,
1113              const char *unparsed)
1114 {
1115     krb5_error_code ret = 0;
1116     krb5_keytab_entry key;
1117     size_t i;
1118
1119     if (princ->n_key_data <= 0)
1120         return 0;
1121
1122     if (kadm5_some_keys_are_bogus(princ->n_key_data, &princ->key_data[0])) {
1123         krb5_warn(r->context, ret,
1124                   "httpkadmind running with insufficient kadmin privilege "
1125                   "for extracting keys for %s", unparsed);
1126         krb5_log_msg(r->context, logfac, 1, NULL,
1127                   "httpkadmind running with insufficient kadmin privilege "
1128                   "for extracting keys for %s", unparsed);
1129         return EACCES;
1130     }
1131
1132     memset(&key, 0, sizeof(key));
1133     for (i = 0; ret == 0 && i < princ->n_key_data; i++) {
1134         krb5_key_data *kd = &princ->key_data[i];
1135
1136         key.principal = princ->principal;
1137         key.vno = kd->key_data_kvno;
1138         key.keyblock.keytype = kd->key_data_type[0];
1139         key.keyblock.keyvalue.length = kd->key_data_length[0];
1140         key.keyblock.keyvalue.data = kd->key_data_contents[0];
1141
1142         /*
1143          * FIXME kadm5 doesn't give us set_time here.  If it gave us the
1144          * KeyRotation metadata, we could compute it.  But this might be a
1145          * concrete principal with concrete keys, in which case we can't.
1146          *
1147          * To fix this we need to extend the protocol and the API.
1148          */
1149         key.timestamp = time(NULL);
1150
1151         ret = krb5_kt_add_entry(r->context, r->keytab, &key);
1152     }
1153     if (ret)
1154         krb5_warn(r->context, ret,
1155                   "Failed to write keytab entries for %s", unparsed);
1156
1157     return ret;
1158 }
1159
1160 static void
1161 random_password(krb5_context context, char *buf, size_t buflen)
1162 {
1163     static const char chars[] =
1164         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,";
1165     char p[32];
1166     size_t i;
1167     char b;
1168
1169     buflen--;
1170     for (i = 0; i < buflen; i++) {
1171         if (i % sizeof(p) == 0)
1172             krb5_generate_random_block(p, sizeof(p));
1173         b = p[i % sizeof(p)];
1174         buf[i] = chars[b % (sizeof(chars) - 1)];
1175     }
1176     buf[i] = '\0';
1177 }
1178
1179 static krb5_error_code
1180 make_kstuple(krb5_context context,
1181              kadm5_principal_ent_rec *p,
1182              krb5_key_salt_tuple **kstuple,
1183              size_t *n_kstuple)
1184 {
1185     size_t i;
1186
1187     *kstuple = 0;
1188     *n_kstuple = 0;
1189
1190     if (p->n_key_data < 1)
1191         return 0;
1192     *kstuple = calloc(p->n_key_data, sizeof (*kstuple));
1193     for (i = 0; *kstuple && i < p->n_key_data; i++) {
1194         if (p->key_data[i].key_data_kvno == p->kvno) {
1195             (*kstuple)[i].ks_enctype = p->key_data[i].key_data_type[0];
1196             (*kstuple)[i].ks_salttype = p->key_data[i].key_data_type[1];
1197             (*n_kstuple)++;
1198         }
1199     }
1200     return *kstuple ? 0 :krb5_enomem(context);
1201 }
1202
1203 /*
1204  * Get keys for one principal.
1205  *
1206  * Does NOT set an HTTP response.
1207  */
1208 static krb5_error_code
1209 get_keys1(kadmin_request_desc r, const char *pname)
1210 {
1211     kadm5_principal_ent_rec princ;
1212     krb5_key_salt_tuple *kstuple = NULL;
1213     krb5_error_code ret = 0;
1214     krb5_principal p = NULL;
1215     uint32_t mask =
1216         KADM5_PRINCIPAL | KADM5_KVNO | KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
1217         KADM5_ATTRIBUTES | KADM5_KEY_DATA | KADM5_TL_DATA;
1218     uint32_t create_mask = mask & ~(KADM5_KEY_DATA | KADM5_TL_DATA);
1219     size_t nkstuple = 0;
1220     int change = 0;
1221     int refetch = 0;
1222     int freeit = 0;
1223
1224     memset(&princ, 0, sizeof(princ));
1225     princ.key_data = NULL;
1226     princ.tl_data = NULL;
1227
1228     ret = krb5_parse_name(r->context, pname, &p);
1229     if (ret == 0 && r->realm)
1230         ret = krb5_principal_set_realm(r->context, p, r->realm);
1231     else if (ret == 0 && realm)
1232         ret = krb5_principal_set_realm(r->context, p, realm);
1233     if (ret == 0 && r->enctypes)
1234         ret = krb5_string_to_keysalts2(r->context, r->enctypes,
1235                                        &nkstuple, &kstuple);
1236     if (ret == 0)
1237         ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1238     if (ret == 0) {
1239         freeit = 1;
1240
1241         /*
1242          * If princ is virtual and we're not asked to materialize, ignore
1243          * requests to rotate.
1244          */
1245         if (!r->materialize &&
1246             (princ.attributes & (KRB5_KDB_VIRTUAL_KEYS | KRB5_KDB_VIRTUAL))) {
1247             r->rotate = 0;
1248             r->revoke = 0;
1249         }
1250     }
1251
1252     change = !r->ro && (r->rotate || r->revoke);
1253
1254     /* Handle create / materialize options */
1255     if (ret == KADM5_UNK_PRINC && r->create) {
1256         char pw[128];
1257
1258         if (read_only)
1259             ret = KADM5_READ_ONLY;
1260         else
1261             ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1262         if (ret == 0 && local_hdb && local_hdb_read_only) {
1263             /* Make sure we can write */
1264             kadm5_destroy(r->kadm_handle);
1265             r->kadm_handle = NULL;
1266             ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1267                                   &r->kadm_handle);
1268         }
1269         memset(&princ, 0, sizeof(princ));
1270         /*
1271          * Some software is allergic to kvno 1, assuming that kvno 1 implies
1272          * half-baked service principal.  We've some vague recollection of
1273          * something similar for kvno 2, so let's start at 3.
1274          */
1275         princ.kvno = 3;
1276         princ.tl_data = NULL;
1277         princ.key_data = NULL;
1278         princ.max_life = 24 * 3600;                /* XXX Make configurable */
1279         princ.max_renewable_life = princ.max_life; /* XXX Make configurable */
1280
1281         random_password(r->context, pw, sizeof(pw));
1282         princ.principal = p;     /* Borrow */
1283         if (ret == 0)
1284             ret = kadm5_create_principal_3(r->kadm_handle, &princ, create_mask,
1285                                            nkstuple, kstuple, pw);
1286         princ.principal = NULL;  /* Return */
1287         refetch = 1;
1288         freeit = 1;
1289     } else if (ret == 0 && r->materialize &&
1290                (princ.attributes & KRB5_KDB_VIRTUAL)) {
1291
1292 #ifndef MATERIALIZE_NOTYET
1293         ret = ENOTSUP;
1294 #else
1295         if (read_only)
1296             ret = KADM5_READ_ONLY;
1297         else
1298             ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1299         if (ret == 0 && local_hdb && local_hdb_read_only) {
1300             /* Make sure we can write */
1301             kadm5_destroy(r->kadm_handle);
1302             r->kadm_handle = NULL;
1303             ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1304                                   &r->kadm_handle);
1305         }
1306         princ.attributes |= KRB5_KDB_MATERIALIZE;
1307         princ.attributes &= ~KRB5_KDB_VIRTUAL;
1308         /*
1309          * XXX If there are TL data which should be re-encoded and sent as
1310          * KRB5_TL_EXTENSION, then this call will fail with KADM5_BAD_TL_TYPE.
1311          *
1312          * We should either drop those TLs, re-encode them, or make
1313          * perform_tl_data() handle them.  (New extensions should generally go
1314          * as KRB5_TL_EXTENSION so that non-critical ones can be set on
1315          * principals via old kadmind programs that don't support them.)
1316          *
1317          * What we really want is a kadm5 utility function to convert some TLs
1318          * to KRB5_TL_EXTENSION and drop all others.
1319          */
1320         if (ret == 0)
1321             ret = kadm5_create_principal(r->kadm_handle, &princ, mask, "");
1322         refetch = 1;
1323 #endif
1324     } /* else create/materialize q-params are superfluous */
1325
1326     /* Handle rotate / revoke options */
1327     if (ret == 0 && change) {
1328         krb5_keyblock *k = NULL;
1329         size_t i;
1330         int n_k = 0;
1331         int keepold = r->revoke ? 0 : 1;
1332
1333         if (read_only)
1334             ret = KADM5_READ_ONLY;
1335         else
1336             ret = strcmp(r->method, "POST") == 0 ? 0 : ENOSYS; /* XXX */
1337         if (ret == 0 && local_hdb && local_hdb_read_only) {
1338             /* Make sure we can write */
1339             kadm5_destroy(r->kadm_handle);
1340             r->kadm_handle = NULL;
1341             ret = get_kadm_handle(r->context, r->realm, 1 /* want_write */,
1342                                   &r->kadm_handle);
1343         }
1344
1345         /* Use requested enctypes or same ones as princ already had keys for */
1346         if (ret == 0 && kstuple == NULL)
1347             ret = make_kstuple(r->context, &princ, &kstuple, &nkstuple);
1348
1349         /* Set new keys */
1350         if (ret == 0)
1351             ret = kadm5_randkey_principal_3(r->kadm_handle, p, keepold,
1352                                             nkstuple, kstuple, &k, &n_k);
1353         refetch = 1;
1354         for (i = 0; n_k > 0 && i < n_k; i++)
1355             krb5_free_keyblock_contents(r->context, &k[i]);
1356         free(kstuple);
1357         free(k);
1358     }
1359
1360     if (ret == 0 && refetch) {
1361         /* Refetch changed principal */
1362         if (freeit)
1363             kadm5_free_principal_ent(r->kadm_handle, &princ);
1364         freeit = 0;
1365         ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1366         if (ret == 0)
1367             freeit = 1;
1368     }
1369
1370     if (ret == 0)
1371         ret = write_keytab(r, &princ, pname);
1372     if (freeit)
1373         kadm5_free_principal_ent(r->kadm_handle, &princ);
1374     krb5_free_principal(r->context, p);
1375     return ret;
1376 }
1377
1378 static krb5_error_code check_csrf(kadmin_request_desc);
1379
1380 /*
1381  * Calls get_keys1() to extract each requested principal's keys.
1382  *
1383  * When this returns a response will have been set.
1384  */
1385 static krb5_error_code
1386 get_keysN(kadmin_request_desc r, const char *method)
1387 {
1388     krb5_error_code ret;
1389     size_t nhosts;
1390     size_t nsvcs;
1391     size_t nspns;
1392     size_t i, k;
1393
1394     /* Parses and validates the request, then checks authorization */
1395     ret = authorize_req(r);
1396     if (ret)
1397         return ret; /* authorize_req() calls bad_req() on error */
1398
1399     ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
1400                           0 /* want_write */, &r->kadm_handle);
1401
1402     if (strcmp(method, "POST") == 0 && (ret = check_csrf(r)))
1403         return ret; /* check_csrf() calls bad_req() on error */
1404
1405     nhosts = heim_array_get_length(r->hostnames);
1406     nsvcs = heim_array_get_length(r->service_names);
1407     nspns = heim_array_get_length(r->spns);
1408     if (!nhosts && !nspns)
1409         return bad_403(r, EINVAL, "No service principals requested");
1410
1411     if (nhosts && !nsvcs) {
1412         heim_string_t s;
1413
1414         if ((s = heim_string_create("HTTP")) == NULL)
1415             ret = krb5_enomem(r->context);
1416         if (ret == 0)
1417             ret = heim_array_append_value(r->service_names, s);
1418         heim_release(s);
1419         nsvcs = 1;
1420     }
1421
1422     /* FIXME: Make this configurable */
1423     if (nspns + nsvcs * nhosts > 40)
1424         return bad_403(r, EINVAL, "Requested keys for too many principals");
1425
1426     ret = make_keytab(r);
1427     for (i = 0; ret == 0 && i < nsvcs; i++) {
1428         const char *svc =
1429             heim_string_get_utf8(
1430                 heim_array_get_value(r->service_names, i));
1431
1432         for (k = 0; ret == 0 && k < nhosts; k++) {
1433             krb5_principal p = NULL;
1434             const char *hostname =
1435                 heim_string_get_utf8(
1436                     heim_array_get_value(r->hostnames, k));
1437             char *spn = NULL;
1438
1439             ret = krb5_make_principal(r->context, &p,
1440                                       r->realm ? r->realm : realm,
1441                                       svc, hostname, NULL);
1442             if (ret == 0)
1443                 ret = krb5_unparse_name(r->context, p, &spn);
1444             if (ret == 0)
1445                 ret = get_keys1(r, spn);
1446             krb5_free_principal(r->context, p);
1447             free(spn);
1448         }
1449     }
1450     for (i = 0; ret == 0 && i < nspns; i++) {
1451         ret = get_keys1(r,
1452                         heim_string_get_utf8(heim_array_get_value(r->spns,
1453                                                                   i)));
1454     }
1455     switch (ret) {
1456     case -1:
1457         /* Can't happen */
1458         krb5_log_msg(r->context, logfac, 1, NULL,
1459                      "Failed to extract keys for unknown reasons");
1460         if (r->response_set)
1461             return MHD_YES;
1462         return bad_503(r, ret, "Could not get keys");
1463     case ENOSYS:
1464         /* Our convention */
1465         return bad_method_want_POST(r);
1466     case KADM5_READ_ONLY:
1467         if (primary_server_URI) {
1468             krb5_log_msg(r->context, logfac, 1, NULL,
1469                          "Redirect %s to primary server", r->cname);
1470             return resp(r, MHD_HTTP_TEMPORARY_REDIRECT, KADM5_READ_ONLY,
1471                         MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL, NULL);
1472         } else {
1473             krb5_log_msg(r->context, logfac, 1, NULL, "HDB is read-only");
1474             return bad_403(r, ret, "HDB is read-only");
1475         }
1476     case 0:
1477         krb5_log_msg(r->context, logfac, 1, NULL, "Sent keytab to %s",
1478                      r->cname);
1479         return good_ext_keytab(r);
1480     default:
1481         return bad_503(r, ret, "Could not get keys");
1482     }
1483 }
1484
1485 /* Copied from kdc/connect.c */
1486 static void
1487 addr_to_string(krb5_context context,
1488                struct sockaddr *addr,
1489                char *str,
1490                size_t len)
1491 {
1492     krb5_error_code ret;
1493     krb5_address a;
1494
1495     ret = krb5_sockaddr2address(context, addr, &a);
1496     if (ret == 0) {
1497         ret = krb5_print_address(&a, str, len, &len);
1498         krb5_free_address(context, &a);
1499     }
1500     if (ret)
1501         snprintf(str, len, "<family=%d>", addr->sa_family);
1502 }
1503
1504 static krb5_error_code
1505 set_req_desc(struct MHD_Connection *connection,
1506              const char *method,
1507              const char *url,
1508              kadmin_request_desc r)
1509 {
1510     const union MHD_ConnectionInfo *ci;
1511     const char *token;
1512     krb5_error_code ret;
1513
1514     memset(r, 0, sizeof(*r));
1515     (void) gettimeofday(&r->tv_start, NULL);
1516
1517     if ((ret = get_krb5_context(&r->context)))
1518         return ret;
1519     /* HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS fields */
1520     r->request.data = "<HTTP-REQUEST>";
1521     r->request.length = sizeof("<HTTP-REQUEST>");
1522     r->from = r->frombuf;
1523     r->config = NULL;
1524     r->logf = logfac;
1525     r->reqtype = url;
1526     r->reason = NULL;
1527     r->reply = NULL;
1528     r->sname = NULL;
1529     r->cname = NULL;
1530     r->addr = NULL;
1531     r->kv = heim_array_create();
1532     /* Our fields */
1533     r->connection = connection;
1534     r->kadm_handle = NULL;
1535     r->hcontext = r->context->hcontext;
1536     r->service_names = heim_array_create();
1537     r->hostnames = heim_array_create();
1538     r->spns = heim_array_create();
1539     r->keytab_name = NULL;
1540     r->enctypes = NULL;
1541     r->freeme1 = NULL;
1542     r->method = method;
1543     r->cprinc = NULL;
1544     r->req = NULL;
1545     r->sp = NULL;
1546     ci = MHD_get_connection_info(connection,
1547                                  MHD_CONNECTION_INFO_CLIENT_ADDRESS);
1548     if (ci) {
1549         r->addr = ci->client_addr;
1550         addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
1551     }
1552
1553     if (r->kv) {
1554         heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
1555         heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
1556     }
1557     token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
1558                                         MHD_HTTP_HEADER_AUTHORIZATION);
1559     if (token && r->kv) {
1560         const char *token_end;
1561
1562         if ((token_end = strchr(token, ' ')) == NULL ||
1563             (token_end - token) > INT_MAX || (token_end - token) < 2)
1564             heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
1565         else
1566             heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
1567                              (int)(token_end - token), token);
1568
1569     }
1570
1571     if (ret == 0 && r->kv == NULL) {
1572         krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
1573         ret = r->ret = ENOMEM;
1574     }
1575     return ret;
1576 }
1577
1578 static void
1579 clean_req_desc(kadmin_request_desc r)
1580 {
1581     if (!r)
1582         return;
1583
1584     if (r->keytab)
1585         krb5_kt_destroy(r->context, r->keytab);
1586     else if (r->keytab_name && strchr(r->keytab_name, ':'))
1587         (void) unlink(strchr(r->keytab_name, ':') + 1);
1588     if (r->kadm_handle)
1589         kadm5_destroy(r->kadm_handle);
1590     hx509_request_free(&r->req);
1591     heim_release(r->service_names);
1592     heim_release(r->hostnames);
1593     heim_release(r->reason);
1594     heim_release(r->spns);
1595     heim_release(r->kv);
1596     krb5_free_principal(r->context, r->cprinc);
1597     free(r->keytab_name);
1598     free(r->enctypes);
1599     free(r->freeme1);
1600     free(r->cname);
1601     free(r->sname);
1602 }
1603
1604 /* Implements GETs of /get-keys */
1605 static krb5_error_code
1606 get_keys(kadmin_request_desc r, const char *method)
1607 {
1608     krb5_error_code ret;
1609
1610     if ((ret = validate_token(r)))
1611         return ret; /* validate_token() calls bad_req() */
1612     if (r->cname == NULL || r->cprinc == NULL)
1613         return bad_403(r, EINVAL,
1614                        "Could not extract principal name from token");
1615     return get_keysN(r, method); /* Sets an HTTP response */
1616 }
1617
1618 /* Implements GETs of /get-config */
1619 static krb5_error_code
1620 get_config(kadmin_request_desc r)
1621 {
1622
1623     kadm5_principal_ent_rec princ;
1624     krb5_error_code ret;
1625     krb5_principal p = NULL;
1626     uint32_t mask = KADM5_PRINCIPAL | KADM5_TL_DATA;
1627     krb5_tl_data *tl_next;
1628     const char *pname;
1629     /* Default configuration for principals that have none set: */
1630     size_t bodylen = sizeof("include /etc/krb5.conf\n") - 1;
1631     void *body = "include /etc/krb5.conf\n";
1632     int freeit = 0;
1633
1634     if ((ret = validate_token(r)))
1635         return ret; /* validate_token() calls bad_req() */
1636     if (r->cname == NULL || r->cprinc == NULL)
1637         return bad_403(r, EINVAL,
1638                        "Could not extract principal name from token");
1639     /*
1640      * No authorization needed -- configs are public.  Though we do require
1641      * authentication (above).
1642      */
1643
1644     ret = get_kadm_handle(r->context, r->realm ? r->realm : realm,
1645                           0 /* want_write */, &r->kadm_handle);
1646
1647     memset(&princ, 0, sizeof(princ));
1648     princ.key_data = NULL;
1649     princ.tl_data = NULL;
1650
1651     pname = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1652                                         "princ");
1653     if (pname == NULL)
1654         pname = r->cname;
1655     ret = krb5_parse_name(r->context, pname, &p);
1656     if (ret == 0) {
1657         ret = kadm5_get_principal(r->kadm_handle, p, &princ, mask);
1658         if (ret == 0) {
1659             freeit = 1;
1660             for (tl_next = princ.tl_data; tl_next; tl_next = tl_next->tl_data_next) {
1661                 if (tl_next->tl_data_type != KRB5_TL_KRB5_CONFIG)
1662                     continue;
1663                 bodylen = tl_next->tl_data_length;
1664                 body = tl_next->tl_data_contents;
1665                 break;
1666             }
1667         } else {
1668             r->ret = ret;
1669             return bad_404(r, "/get-config");
1670         }
1671     }
1672
1673     if (ret == 0) {
1674         krb5_log_msg(r->context, logfac, 1, NULL,
1675                      "Returned krb5.conf contents to %s", r->cname);
1676         ret = resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_MUST_COPY,
1677                    "application/text", body, bodylen, NULL, NULL);
1678     } else {
1679         ret = bad_503(r, ret, "Could not retrieve principal configuration");
1680     }
1681     if (freeit)
1682         kadm5_free_principal_ent(r->kadm_handle, &princ);
1683     krb5_free_principal(r->context, p);
1684     return ret;
1685 }
1686
1687 static krb5_error_code
1688 mac_csrf_token(kadmin_request_desc r, krb5_storage *sp)
1689 {
1690     kadm5_principal_ent_rec princ;
1691     krb5_error_code ret;
1692     krb5_principal p = NULL;
1693     krb5_data data;
1694     char mac[EVP_MAX_MD_SIZE];
1695     unsigned int maclen = sizeof(mac);
1696     HMAC_CTX *ctx = NULL;
1697     size_t i = 0;
1698     int freeit = 0;
1699
1700     memset(&princ, 0, sizeof(princ));
1701     ret = krb5_storage_to_data(sp, &data);
1702     if (r->kadm_handle == NULL)
1703         ret = get_kadm_handle(r->context, r->realm, 0 /* want_write */,
1704                               &r->kadm_handle);
1705     if (ret == 0)
1706         ret = krb5_make_principal(r->context, &p,
1707                                   r->realm ? r->realm : realm,
1708                                   "WELLKNOWN", "CSRFTOKEN", NULL);
1709     if (ret == 0)
1710         ret = kadm5_get_principal(r->kadm_handle, p, &princ, 
1711                                   KADM5_PRINCIPAL | KADM5_KVNO |
1712                                   KADM5_KEY_DATA);
1713     if (ret == 0)
1714         freeit = 1;
1715     if (ret == 0 && princ.n_key_data < 1)
1716         ret = KADM5_UNK_PRINC;
1717     if (ret == 0)
1718         for (i = 0; i < princ.n_key_data; i++)
1719             if (princ.key_data[i].key_data_kvno == princ.kvno)
1720                 break;
1721     if (ret == 0 && i == princ.n_key_data)
1722         i = 0; /* Weird, but can't happen */
1723
1724     if (ret == 0 && (ctx = HMAC_CTX_new()) == NULL)
1725             ret = krb5_enomem(r->context);
1726     /* HMAC the token body and the client principal name */
1727     if (ret == 0) {
1728         HMAC_Init_ex(ctx, princ.key_data[i].key_data_contents[0], princ.key_data[i].key_data_length[0], EVP_sha256(), NULL);
1729         HMAC_Update(ctx, data.data, data.length);
1730         HMAC_Update(ctx, r->cname, strlen(r->cname));
1731         HMAC_Final(ctx, mac, &maclen);
1732         krb5_data_free(&data);
1733         data.length = maclen;
1734         data.data = mac;
1735         if (krb5_storage_write(sp, mac, maclen) != maclen)
1736             ret = krb5_enomem(r->context);
1737     }
1738     krb5_free_principal(r->context, p);
1739     if (freeit)
1740         kadm5_free_principal_ent(r->kadm_handle, &princ);
1741     if (ctx)
1742         HMAC_CTX_free(ctx);
1743     return ret;
1744 }
1745
1746 static krb5_error_code
1747 make_csrf_token(kadmin_request_desc r,
1748                 const char *given,
1749                 char **token,
1750                 int64_t *age)
1751 {
1752     static HEIMDAL_THREAD_LOCAL char tokenbuf[128]; /* See below, be sad */
1753     krb5_error_code ret = 0;
1754     unsigned char given_decoded[128];
1755     krb5_storage *sp = NULL;
1756     krb5_data data;
1757     ssize_t dlen = -1;
1758     uint64_t nonce;
1759     int64_t t = 0;
1760
1761
1762     *age = 0;
1763     data.data = NULL;
1764     data.length = 0;
1765     if (given) {
1766         size_t len = strlen(given);
1767
1768         if (len >= sizeof(given_decoded))
1769             ret = ERANGE;
1770         if (ret == 0 && (dlen = rk_base64_decode(given, &given_decoded)) <= 0)
1771             ret = errno;
1772         if (ret == 0 &&
1773             (sp = krb5_storage_from_mem(given_decoded, dlen)) == NULL)
1774             ret = krb5_enomem(r->context);
1775         if (ret == 0)
1776             ret = krb5_ret_int64(sp, &t);
1777         if (ret == 0)
1778             ret = krb5_ret_uint64(sp, &nonce);
1779         krb5_storage_free(sp);
1780         sp = NULL;
1781         if (ret == 0)
1782             *age = time(NULL) - t;
1783     } else {
1784         t = time(NULL);
1785         krb5_generate_random_block((void *)&nonce, sizeof(nonce));
1786     }
1787
1788     if (ret == 0 && (sp = krb5_storage_emem()) == NULL)
1789         ret = krb5_enomem(r->context);
1790     if (ret == 0)
1791         ret = krb5_store_int64(sp, t);
1792     if (ret == 0)
1793         ret = krb5_store_uint64(sp, nonce);
1794     if (ret == 0)
1795         ret = mac_csrf_token(r, sp);
1796     if (ret == 0)
1797         ret = krb5_storage_to_data(sp, &data);
1798     if (ret == 0 && data.length > INT_MAX)
1799         ret = ERANGE;
1800     if (ret == 0 &&
1801         (dlen = rk_base64_encode(data.data, data.length, token)) < 0)
1802         ret = errno;
1803     if (ret == 0 && dlen >= sizeof(tokenbuf))
1804         ret = ERANGE;
1805     if (ret == 0) {
1806         /*
1807          * Work around for older versions of libmicrohttpd do not strdup()ing
1808          * response header values.
1809          */
1810         memcpy(tokenbuf, *token, dlen);
1811         free(*token);
1812         *token = tokenbuf;
1813     }
1814     krb5_storage_free(sp);
1815     krb5_data_free(&data);
1816     return ret;
1817 }
1818
1819 /*
1820  * Returns system or krb5_error_code on error, but also calls resp() or bad_*()
1821  * on error.
1822  */
1823 static krb5_error_code
1824 check_csrf(kadmin_request_desc r)
1825 {
1826     krb5_error_code ret;
1827     const char *given;
1828     int64_t age;
1829     size_t givenlen, expectedlen;
1830     char *expected = NULL;
1831
1832     given = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
1833                                         "X-CSRF-Token");
1834     ret = make_csrf_token(r, given, &expected, &age);
1835     if (ret)
1836         return bad_503(r, ret, "Could not create a CSRF token");
1837     /*
1838      * If CSRF token needed but missing, call resp() directly, bypassing
1839      * bad_403(), to return a 403 with an expected CSRF token in the response.
1840      */
1841     if (given == NULL) {
1842         (void) resp(r, MHD_HTTP_FORBIDDEN, ENOSYS, MHD_RESPMEM_PERSISTENT,
1843                     NULL, "CSRF token needed; copy the X-CSRF-Token: response "
1844                     "header to your next POST", BODYLEN_IS_STRLEN, NULL,
1845                     expected);
1846         return ENOSYS;
1847     }
1848
1849     /* Validate the CSRF token for this request */
1850     givenlen = strlen(given);
1851     expectedlen = strlen(expected);
1852     if (givenlen != expectedlen || ct_memcmp(given, expected, givenlen)) {
1853         (void) bad_403(r, EACCES, "Invalid CSRF token");
1854         return EACCES;
1855     }
1856     if (age > 300) { /* XXX */
1857         (void) bad_403(r, EACCES, "CSRF token too old");
1858         return EACCES;
1859     }
1860     return 0;
1861 }
1862
1863 static krb5_error_code
1864 health(const char *method, kadmin_request_desc r)
1865 {
1866     if (strcmp(method, "HEAD") == 0) {
1867         return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL, "", 0,
1868                     NULL, NULL);
1869     }
1870     return resp(r, MHD_HTTP_OK, 0, MHD_RESPMEM_PERSISTENT, NULL,
1871                 "To determine the health of the service, use the /get-config "
1872                 "end-point.\n", BODYLEN_IS_STRLEN, NULL, NULL);
1873
1874 }
1875
1876 /* Implements the entirety of this REST service */
1877 static int
1878 route(void *cls,
1879       struct MHD_Connection *connection,
1880       const char *url,
1881       const char *method,
1882       const char *version,
1883       const char *upload_data,
1884       size_t *upload_data_size,
1885       void **ctx)
1886 {
1887     static int aptr = 0;
1888     struct kadmin_request_desc r;
1889     int ret;
1890
1891     if (*ctx == NULL) {
1892         /*
1893          * This is the first call, right after headers were read.
1894          *
1895          * We must return quickly so that any 100-Continue might be sent with
1896          * celerity.
1897          *
1898          * We'll get called again to really do the processing.  If we handled
1899          * POSTs then we'd also get called with upload_data != NULL between the
1900          * first and last calls.  We need to keep no state between the first
1901          * and last calls, but we do need to distinguish first and last call,
1902          * so we use the ctx argument for this.
1903          */
1904         *ctx = &aptr;
1905         return MHD_YES;
1906     }
1907
1908     /*
1909      * Note that because we attempt to connect to the HDB in set_req_desc(),
1910      * this early 503 if we fail to serves to do all of what /health should do.
1911      */
1912     if ((ret = set_req_desc(connection, method, url, &r)))
1913         return bad_503(&r, ret, "Could not initialize request state");
1914     if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
1915         (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0)) {
1916         ret = health(method, &r);
1917     } else if (strcmp(method, "GET") != 0 && strcmp(method, "POST") != 0) {
1918         ret = bad_405(&r, method);
1919     } else if (strcmp(url, "/get-keys") == 0) {
1920         ret = get_keys(&r, method);
1921     } else if (strcmp(url, "/get-config") == 0) {
1922         if (strcmp(method, "GET") != 0)
1923             ret = bad_405(&r, method);
1924         else
1925             ret = get_config(&r);
1926     } else {
1927         ret = bad_404(&r, url);
1928     }
1929
1930     clean_req_desc(&r);
1931     return ret == -1 ? MHD_NO : MHD_YES;
1932 }
1933
1934 static struct getargs args[] = {
1935     { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
1936     { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
1937     { NULL, 'H', arg_strings, &audiences,
1938         "expected token audience(s) of the service", "HOSTNAME" },
1939     { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
1940     { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
1941     { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
1942         "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
1943     { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
1944     { "temp-dir", 0, arg_string, &cache_dir,
1945         "cache directory", "DIRECTORY" },
1946     { "cert", 0, arg_string, &cert_file,
1947         "certificate file path (PEM)", "HX509-STORE" },
1948     { "private-key", 0, arg_string, &priv_key_file,
1949         "private key file path (PEM)", "HX509-STORE" },
1950     { "thread-per-client", 't', arg_flag, &thread_per_client_flag, "thread per-client", NULL },
1951     { "realm", 0, arg_string, &realm, "realm", "REALM" },
1952     { "hdb", 0, arg_string, &hdb, "HDB filename", "PATH" },
1953     { "read-only-admin-server", 0, arg_string, &kadmin_server,
1954         "Name of read-only kadmin server", "HOST[:PORT]" },
1955     { "writable-admin-server", 0, arg_string, &writable_kadmin_server,
1956         "Name of writable kadmin server", "HOST[:PORT]" },
1957     { "primary-server-uri", 0, arg_string, &primary_server_URI,
1958         "Name of primary httpkadmind server for HTTP redirects", "URL" },
1959     { "local", 'l', arg_flag, &local_hdb,
1960         "Use a local HDB as read-only", NULL },
1961     { "local-read-only", 0, arg_flag, &local_hdb_read_only,
1962         "Use a local HDB as read-only", NULL },
1963     { "read-only", 0, arg_flag, &read_only, "Allow no writes", NULL },
1964     { "stash-file", 0, arg_string, &stash_file,
1965         "Stash file for HDB", "PATH" },
1966     { "kadmin-client-name", 0, arg_string, &kadmin_client_name,
1967         "Client name for remote kadmind", "PRINCIPAL" },
1968     { "kadmin-client-keytab", 0, arg_string, &kadmin_client_keytab,
1969         "Keytab with client credentials for remote kadmind", "KEYTAB" },
1970     { "token-authentication-type", 'T', arg_strings, &auth_types,
1971         "Token authentication type(s) supported", "HTTP-AUTH-TYPE" },
1972     { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
1973 };
1974
1975 static int
1976 usage(int e)
1977 {
1978     arg_printusage(args, sizeof(args) / sizeof(args[0]), "httpkadmind",
1979         "\nServes an HTTP API for getting (and rotating) service "
1980         "principal keys, and other kadmin-like operations\n");
1981     exit(e);
1982 }
1983
1984 static int sigpipe[2] = { -1, -1 };
1985
1986 static void
1987 sighandler(int sig)
1988 {
1989     char c = sig;
1990     while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
1991         ;
1992 }
1993
1994 static void
1995 my_openlog(krb5_context context,
1996            const char *svc,
1997            krb5_log_facility **fac)
1998 {
1999     char **s = NULL, **p;
2000
2001     krb5_initlog(context, "httpkadmind", fac);
2002     s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
2003     if (s == NULL)
2004         s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
2005     if (s) {
2006         for(p = s; *p; p++)
2007             krb5_addlog_dest(context, *fac, *p);
2008         krb5_config_free_strings(s);
2009     } else {
2010         char *ss;
2011         if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
2012             KDC_LOG_FILE) < 0)
2013             err(1, "out of memory");
2014         krb5_addlog_dest(context, *fac, ss);
2015         free(ss);
2016     }
2017     krb5_set_warn_dest(context, *fac);
2018 }
2019
2020 static const char *sysplugin_dirs[] =  {
2021 #ifdef _WIN32
2022     "$ORIGIN",
2023 #else
2024     "$ORIGIN/../lib/plugin/kdc",
2025 #endif
2026 #ifdef __APPLE__
2027     LIBDIR "/plugin/kdc",
2028 #endif
2029     NULL
2030 };
2031
2032 static void
2033 load_plugins(krb5_context context)
2034 {
2035     const char * const *dirs = sysplugin_dirs;
2036 #ifndef _WIN32
2037     char **cfdirs;
2038
2039     cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
2040     if (cfdirs)
2041         dirs = (const char * const *)cfdirs;
2042 #endif
2043
2044     /* XXX kdc? */
2045     _krb5_load_plugins(context, "kdc", (const char **)dirs);
2046
2047 #ifndef _WIN32
2048     krb5_config_free_strings(cfdirs);
2049 #endif
2050 }
2051
2052 int
2053 main(int argc, char **argv)
2054 {
2055     unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
2056     struct sockaddr_in sin;
2057     struct MHD_Daemon *previous = NULL;
2058     struct MHD_Daemon *current = NULL;
2059     struct sigaction sa;
2060     krb5_context context = NULL;
2061     MHD_socket sock = MHD_INVALID_SOCKET;
2062     void *kadm_handle;
2063     char *priv_key_pem = NULL;
2064     char *cert_pem = NULL;
2065     char sig;
2066     int optidx = 0;
2067     int ret;
2068
2069     setprogname("httpkadmind");
2070     if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
2071         usage(1);
2072     if (help_flag)
2073         usage(0);
2074     if (version_flag) {
2075         print_version(NULL);
2076         exit(0);
2077     }
2078     if (argc > optidx) /* Add option to set a URI local part prefix? */
2079         usage(1);
2080     if (port < 0)
2081         errx(1, "Port number must be given");
2082
2083     if (audiences.num_strings == 0) {
2084         char localhost[MAXHOSTNAMELEN];
2085
2086         ret = gethostname(localhost, sizeof(localhost));
2087         if (ret == -1)
2088             errx(1, "Could not determine local hostname; use --audience");
2089
2090         if ((audiences.strings =
2091                  calloc(1, sizeof(audiences.strings[0]))) == NULL ||
2092             (audiences.strings[0] = strdup(localhost)) == NULL)
2093             err(1, "Out of memory");
2094         audiences.num_strings = 1;
2095     }
2096
2097     if (daemonize && daemon_child_fd == -1)
2098         daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
2099     daemonize = 0;
2100
2101     argc -= optidx;
2102     argv += optidx;
2103
2104     if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
2105         err(1, "Could not create thread-specific storage");
2106
2107     if ((errno = get_krb5_context(&context)))
2108         err(1, "Could not init krb5 context (config file issue?)");
2109
2110     if (!realm) {
2111         char *s;
2112
2113         ret = krb5_get_default_realm(context, &s);
2114         if (ret)
2115             krb5_err(context, 1, ret, "Could not determine default realm");
2116         realm = s;
2117     }
2118
2119     if ((errno = get_kadm_handle(context, realm, 0 /* want_write */,
2120                                  &kadm_handle)))
2121         err(1, "Could not connect to HDB");
2122     kadm5_destroy(kadm_handle);
2123
2124     my_openlog(context, "httpkadmind", &logfac);
2125     load_plugins(context);
2126
2127     if (cache_dir == NULL) {
2128         char *s = NULL;
2129
2130         if (asprintf(&s, "%s/httpkadmind-XXXXXX",
2131                      getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2132             s == NULL ||
2133             (cache_dir = mkdtemp(s)) == NULL)
2134             err(1, "could not create temporary cache directory");
2135         if (verbose_counter)
2136             fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
2137         atexit(rm_cache_dir);
2138         setenv("TMPDIR", cache_dir, 1);
2139     }
2140
2141 again:
2142     if (cert_file && !priv_key_file)
2143         priv_key_file = cert_file;
2144
2145     if (cert_file) {
2146         hx509_cursor cursor = NULL;
2147         hx509_certs certs = NULL;
2148         hx509_cert cert = NULL;
2149         time_t min_cert_life = 0;
2150         size_t len;
2151         void *s;
2152
2153         ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
2154         if (ret == 0)
2155             ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
2156         while (ret == 0 &&
2157                (ret = hx509_certs_next_cert(context->hx509ctx, certs,
2158                                             cursor, &cert)) == 0 && cert) {
2159             time_t notAfter = 0;
2160
2161             if (!hx509_cert_have_private_key_only(cert) &&
2162                 (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
2163                 errx(1, "One or more certificates in %s are expired",
2164                      cert_file);
2165             if (notAfter) {
2166                 notAfter -= time(NULL);
2167                 if (notAfter < 600)
2168                     warnx("One or more certificates in %s expire soon",
2169                           cert_file);
2170                 /* Reload 5 minutes prior to expiration */
2171                 if (notAfter < min_cert_life || min_cert_life < 1)
2172                     min_cert_life = notAfter;
2173             }
2174             hx509_cert_free(cert);
2175         }
2176         if (certs)
2177             (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
2178         if (min_cert_life > 4)
2179             alarm(min_cert_life >> 1);
2180         hx509_certs_free(&certs);
2181         if (ret)
2182             hx509_err(context->hx509ctx, 1, ret,
2183                       "could not read certificate from %s", cert_file);
2184
2185         if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
2186             (cert_pem = strndup(s, len)) == NULL)
2187             err(1, "could not read certificate from %s", cert_file);
2188         if (strlen(cert_pem) != len)
2189             err(1, "NULs in certificate file contents: %s", cert_file);
2190         free(s);
2191     }
2192
2193     if (priv_key_file) {
2194         size_t len;
2195         void *s;
2196
2197         if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
2198             (priv_key_pem = strndup(s, len)) == NULL)
2199             err(1, "could not read private key from %s", priv_key_file);
2200         if (strlen(priv_key_pem) != len)
2201             err(1, "NULs in private key file contents: %s", priv_key_file);
2202         free(s);
2203     }
2204
2205     if (verbose_counter > 1)
2206         flags |= MHD_USE_DEBUG;
2207     if (thread_per_client_flag)
2208         flags |= MHD_USE_THREAD_PER_CONNECTION;
2209
2210
2211     if (pipe(sigpipe) == -1)
2212         err(1, "Could not set up key/cert reloading");
2213     memset(&sa, 0, sizeof(sa));
2214     sa.sa_handler = sighandler;
2215     if (reverse_proxied_flag) {
2216         /*
2217          * We won't use TLS in the reverse proxy case, so no need to reload
2218          * certs.  But we'll still read them if given, and alarm() will get
2219          * called.
2220          *
2221          * XXX We should be able to re-read krb5.conf and such on SIGHUP.
2222          */
2223         (void) signal(SIGHUP, SIG_IGN);
2224         (void) signal(SIGUSR1, SIG_IGN);
2225         (void) signal(SIGALRM, SIG_IGN);
2226     } else {
2227         (void) sigaction(SIGHUP, &sa, NULL);    /* Reload key & cert */
2228         (void) sigaction(SIGUSR1, &sa, NULL);   /* Reload key & cert */
2229         (void) sigaction(SIGALRM, &sa, NULL);   /* Reload key & cert */
2230     }
2231     (void) sigaction(SIGINT, &sa, NULL);    /* Graceful shutdown */
2232     (void) sigaction(SIGTERM, &sa, NULL);   /* Graceful shutdown */
2233     (void) signal(SIGPIPE, SIG_IGN);
2234
2235     if (previous)
2236         sock = MHD_quiesce_daemon(previous);
2237
2238     if (reverse_proxied_flag) {
2239         /*
2240          * XXX IPv6 too.  Create the sockets and tell MHD_start_daemon() about
2241          * them.
2242          */
2243         sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2244         sin.sin_family = AF_INET;
2245         sin.sin_port = htons(port);
2246         current = MHD_start_daemon(flags, port,
2247                                    NULL, NULL,
2248                                    route, (char *)NULL,
2249                                    MHD_OPTION_SOCK_ADDR, &sin,
2250                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2251                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2252                                    MHD_OPTION_END);
2253     } else if (sock != MHD_INVALID_SOCKET) {
2254         /*
2255          * Certificate/key rollover: reuse the listen socket returned by
2256          * MHD_quiesce_daemon().
2257          */
2258         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2259                                    NULL, NULL,
2260                                    route, (char *)NULL,
2261                                    MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2262                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2263                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2264                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2265                                    MHD_OPTION_LISTEN_SOCKET, sock,
2266                                    MHD_OPTION_END);
2267         sock = MHD_INVALID_SOCKET;
2268     } else {
2269         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2270                                    NULL, NULL,
2271                                    route, (char *)NULL,
2272                                    MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2273                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2274                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2275                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2276                                    MHD_OPTION_END);
2277     }
2278     if (current == NULL)
2279         err(1, "Could not start kadmin REST service");
2280
2281     if (previous) {
2282         MHD_stop_daemon(previous);
2283         previous = NULL;
2284     }
2285
2286     if (verbose_counter)
2287         fprintf(stderr, "Ready!\n");
2288     if (daemon_child_fd != -1)
2289         roken_detach_finish(NULL, daemon_child_fd);
2290
2291     /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
2292     while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
2293            errno == EINTR)
2294         ;
2295
2296     free(priv_key_pem);
2297     free(cert_pem);
2298     priv_key_pem = NULL;
2299     cert_pem = NULL;
2300
2301     if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
2302         /* Reload certs and restart service gracefully */
2303         previous = current;
2304         current = NULL;
2305         goto again;
2306     }
2307
2308     MHD_stop_daemon(current);
2309     _krb5_unload_plugins(context, "kdc");
2310     pthread_key_delete(k5ctx);
2311     return 0;
2312 }