s4:torture: Adapt KDC canon test to Heimdal upstream changes
[samba.git] / source4 / heimdal / kdc / bx509d.c
1 /*
2  * Copyright (c) 2019 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  * This file implements a RESTful HTTPS API to an online CA, as well as an
36  * HTTP/Negotiate token issuer.
37  *
38  * Users are authenticated with bearer tokens.
39  *
40  * This is essentially a RESTful online CA sharing code with the KDC's kx509
41  * online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
42  *
43  * To get a key certified:
44  *
45  *  GET /bx509?csr=<base64-encoded-PKCS#10-CSR>
46  *
47  * To get an HTTP/Negotiate token:
48  *
49  *  GET /bnegotiate?target=<acceptor-principal>
50  *
51  * which, if authorized, produces a Negotiate token (base64-encoded, as
52  * expected, with the "Negotiate " prefix, ready to be put in an Authorization:
53  * header).
54  *
55  * TBD:
56  *  - rewrite to not use libmicrohttpd but an alternative more appropriate to
57  *    Heimdal's license (though libmicrohttpd will do)
58  *  - /bx509 should include the certificate chain
59  *  - /bx509 should support HTTP/Negotiate
60  *  - there should be an end-point for fetching an issuer's chain
61  *  - maybe add /bkrb5 which returns a KRB-CRED with the user's TGT
62  *
63  * NOTES:
64  *  - We use krb5_error_code values as much as possible.  Where we need to use
65  *    MHD_NO because we got that from an mhd function and cannot respond with
66  *    an HTTP response, we use (krb5_error_code)-1, and later map that to
67  *    MHD_NO.
68  *
69  *    (MHD_NO is an ENOMEM-cannot-even-make-a-static-503-response level event.)
70  */
71
72 #define _XOPEN_SOURCE_EXTENDED  1
73 #define _DEFAULT_SOURCE  1
74 #define _BSD_SOURCE  1
75 #define _GNU_SOURCE  1
76
77 #include <sys/socket.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <sys/time.h>
81 #include <ctype.h>
82 #include <dlfcn.h>
83 #include <errno.h>
84 #include <fcntl.h>
85 #include <pthread.h>
86 #include <signal.h>
87 #include <stdarg.h>
88 #include <stddef.h>
89 #include <stdint.h>
90 #include <stdio.h>
91 #include <stdlib.h>
92 #include <string.h>
93 #include <time.h>
94 #include <unistd.h>
95 #include <netdb.h>
96 #include <netinet/in.h>
97 #include <netinet/ip.h>
98
99 #include <microhttpd.h>
100 #include "kdc_locl.h"
101 #include "token_validator_plugin.h"
102 #include <getarg.h>
103 #include <roken.h>
104 #include <krb5.h>
105 #include <gssapi/gssapi.h>
106 #include <gssapi/gssapi_krb5.h>
107 #include <hx509.h>
108 #include "../lib/hx509/hx_locl.h"
109 #include <hx509-private.h>
110
111 #define heim_pcontext krb5_context
112 #define heim_pconfig krb5_context
113 #include <heimbase-svc.h>
114
115 typedef struct bx509_request_desc {
116     HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
117
118     struct MHD_Connection *connection;
119     krb5_times token_times;
120     time_t req_life;
121     hx509_request req;
122     const char *for_cname;
123     const char *target;
124     const char *redir;
125     char *pkix_store;
126     char *ccname;
127     char *freeme1;
128     krb5_addresses tgt_addresses; /* For /get-tgt */
129     char frombuf[128];
130 } *bx509_request_desc;
131
132 static void
133 audit_trail(bx509_request_desc r, krb5_error_code ret)
134 {
135     const char *retname = NULL;
136
137     /* Get a symbolic name for some error codes */
138 #define CASE(x) case x : retname = #x; break
139     switch (ret) {
140     CASE(ENOMEM);
141     CASE(EACCES);
142     CASE(HDB_ERR_NOT_FOUND_HERE);
143     CASE(HDB_ERR_WRONG_REALM);
144     CASE(HDB_ERR_EXISTS);
145     CASE(HDB_ERR_KVNO_NOT_FOUND);
146     CASE(HDB_ERR_NOENTRY);
147     CASE(HDB_ERR_NO_MKEY);
148     CASE(KRB5KDC_ERR_BADOPTION);
149     CASE(KRB5KDC_ERR_CANNOT_POSTDATE);
150     CASE(KRB5KDC_ERR_CLIENT_NOTYET);
151     CASE(KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
152     CASE(KRB5KDC_ERR_ETYPE_NOSUPP);
153     CASE(KRB5KDC_ERR_KEY_EXPIRED);
154     CASE(KRB5KDC_ERR_NAME_EXP);
155     CASE(KRB5KDC_ERR_NEVER_VALID);
156     CASE(KRB5KDC_ERR_NONE);
157     CASE(KRB5KDC_ERR_NULL_KEY);
158     CASE(KRB5KDC_ERR_PADATA_TYPE_NOSUPP);
159     CASE(KRB5KDC_ERR_POLICY);
160     CASE(KRB5KDC_ERR_PREAUTH_FAILED);
161     CASE(KRB5KDC_ERR_PREAUTH_REQUIRED);
162     CASE(KRB5KDC_ERR_SERVER_NOMATCH);
163     CASE(KRB5KDC_ERR_SERVICE_EXP);
164     CASE(KRB5KDC_ERR_SERVICE_NOTYET);
165     CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
166     CASE(KRB5KDC_ERR_TRTYPE_NOSUPP);
167     CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG);
168     /* XXX Add relevant error codes */
169     case 0:
170         retname = "SUCCESS";
171         break;
172     default:
173         retname = NULL;
174         break;
175     }
176
177     /* Let's save a few bytes */
178     if (retname && strncmp("KRB5KDC_", retname, sizeof("KRB5KDC_") - 1) == 0)
179         retname += sizeof("KRB5KDC_") - 1;
180 #undef PREFIX
181     heim_audit_trail((heim_svc_req_desc)r, ret, retname);
182 }
183
184 static krb5_log_facility *logfac;
185 static pthread_key_t k5ctx;
186
187 static krb5_error_code
188 get_krb5_context(krb5_context *contextp)
189 {
190     krb5_error_code ret;
191
192     if ((*contextp = pthread_getspecific(k5ctx)))
193         return 0;
194     if ((ret = krb5_init_context(contextp)))
195         return *contextp = NULL, ret;
196     (void) pthread_setspecific(k5ctx, *contextp);
197     return *contextp ? 0 : ENOMEM;
198 }
199
200 static int port = -1;
201 static int help_flag;
202 static int daemonize;
203 static int daemon_child_fd = -1;
204 static int verbose_counter;
205 static int version_flag;
206 static int reverse_proxied_flag;
207 static int thread_per_client_flag;
208 struct getarg_strings audiences;
209 static const char *cert_file;
210 static const char *priv_key_file;
211 static const char *cache_dir;
212 static char *impersonation_key_fn;
213
214 static krb5_error_code resp(struct bx509_request_desc *, int,
215                             enum MHD_ResponseMemoryMode, const char *,
216                             const void *, size_t, const char *);
217 static krb5_error_code bad_req(struct bx509_request_desc *, krb5_error_code, int,
218                                const char *, ...)
219                                HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
220
221 static krb5_error_code bad_enomem(struct bx509_request_desc *, krb5_error_code);
222 static krb5_error_code bad_400(struct bx509_request_desc *, krb5_error_code, char *);
223 static krb5_error_code bad_401(struct bx509_request_desc *, char *);
224 static krb5_error_code bad_403(struct bx509_request_desc *, krb5_error_code, char *);
225 static krb5_error_code bad_404(struct bx509_request_desc *, const char *);
226 static krb5_error_code bad_405(struct bx509_request_desc *, const char *);
227 static krb5_error_code bad_500(struct bx509_request_desc *, krb5_error_code, const char *);
228 static krb5_error_code bad_503(struct bx509_request_desc *, krb5_error_code, const char *);
229
230 static int
231 validate_token(struct bx509_request_desc *r)
232 {
233     krb5_error_code ret;
234     krb5_principal cprinc = NULL;
235     const char *token;
236     const char *host;
237     char token_type[64]; /* Plenty */
238     char *p;
239     krb5_data tok;
240     size_t host_len, brk, i;
241
242     memset(&r->token_times, 0, sizeof(r->token_times));
243     host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
244                                        MHD_HTTP_HEADER_HOST);
245     if (host == NULL)
246         return bad_400(r, EINVAL, "Host header is missing");
247
248     /* Exclude port number here (IPv6-safe because of the below) */
249     host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
250
251     token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
252                                         MHD_HTTP_HEADER_AUTHORIZATION);
253     if (token == NULL)
254         return bad_401(r, "Authorization token is missing");
255     brk = strcspn(token, " \t");
256     if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
257         return bad_401(r, "Authorization token is missing");
258     memcpy(token_type, token, brk);
259     token_type[brk] = '\0';
260     token += brk + 1;
261     tok.length = strlen(token);
262     tok.data = (void *)(uintptr_t)token;
263
264     for (i = 0; i < audiences.num_strings; i++)
265         if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
266             audiences.strings[i][host_len] == '\0')
267             break;
268     if (i == audiences.num_strings)
269         return bad_403(r, EINVAL, "Host: value is not accepted here");
270
271     r->sname = strdup(host); /* No need to check for ENOMEM here */
272
273     ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
274                              (const char **)&audiences.strings[i], 1,
275                              &cprinc, &r->token_times);
276     if (ret)
277         return bad_403(r, ret, "Token validation failed");
278     if (cprinc == NULL)
279         return bad_403(r, ret, "Could not extract a principal name "
280                        "from token");
281     ret = krb5_unparse_name(r->context, cprinc, &r->cname);
282     krb5_free_principal(r->context, cprinc);
283     if (ret)
284         return bad_503(r, ret, "Could not parse principal name");
285     return ret;
286 }
287
288 static void
289 generate_key(hx509_context context,
290              const char *key_name,
291              const char *gen_type,
292              unsigned long gen_bits,
293              char **fn)
294 {
295     struct hx509_generate_private_context *key_gen_ctx = NULL;
296     hx509_private_key key = NULL;
297     hx509_certs certs = NULL;
298     hx509_cert cert = NULL;
299     int ret;
300
301     if (strcmp(gen_type, "rsa") != 0)
302         errx(1, "Only RSA keys are supported at this time");
303
304     if (asprintf(fn, "PEM-FILE:%s/.%s_priv_key.pem",
305                  cache_dir, key_name) == -1 ||
306         *fn == NULL)
307         err(1, "Could not set up private key for %s", key_name);
308
309     ret = _hx509_generate_private_key_init(context,
310                                            ASN1_OID_ID_PKCS1_RSAENCRYPTION,
311                                            &key_gen_ctx);
312     if (ret == 0)
313         ret = _hx509_generate_private_key_bits(context, key_gen_ctx, gen_bits);
314     if (ret == 0)
315         ret = _hx509_generate_private_key(context, key_gen_ctx, &key);
316     if (ret == 0)
317         cert = hx509_cert_init_private_key(context, key, NULL);
318     if (ret == 0)
319         ret = hx509_certs_init(context, *fn,
320                                HX509_CERTS_CREATE | HX509_CERTS_UNPROTECT_ALL,
321                                NULL, &certs);
322     if (ret == 0)
323         ret = hx509_certs_add(context, certs, cert);
324     if (ret == 0)
325         ret = hx509_certs_store(context, certs, 0, NULL);
326     if (ret)
327         hx509_err(context, 1, ret, "Could not generate and save private key "
328                   "for %s", key_name);
329
330     _hx509_generate_private_key_free(&key_gen_ctx);
331     hx509_private_key_free(&key);
332     hx509_certs_free(&certs);
333     hx509_cert_free(cert);
334 }
335
336 static void
337 k5_free_context(void *ctx)
338 {
339     krb5_free_context(ctx);
340 }
341
342 #ifndef HAVE_UNLINKAT
343 static int
344 unlink1file(const char *dname, const char *name)
345 {
346     char p[PATH_MAX];
347
348     if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
349         strlcat(p, "/", sizeof(p)) < sizeof(p) &&
350         strlcat(p, name, sizeof(p)) < sizeof(p))
351         return unlink(p);
352     return ERANGE;
353 }
354 #endif
355
356 static void
357 rm_cache_dir(void)
358 {
359     struct dirent *e;
360     DIR *d;
361
362     /*
363      * This works, but not on Win32:
364      *
365      *  (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
366      *
367      * We make no directories in `cache_dir', so we need not recurse.
368      */
369     if ((d = opendir(cache_dir)) == NULL)
370         return;
371
372     while ((e = readdir(d))) {
373 #ifdef HAVE_UNLINKAT
374         /*
375          * Because unlinkat() takes a directory FD, implementing one for
376          * libroken is tricky at best.  Instead we might want to implement an
377          * rm_dash_rf() function in lib/roken.
378          */
379         (void) unlinkat(dirfd(d), e->d_name, 0);
380 #else
381         (void) unlink1file(cache_dir, e->d_name);
382 #endif
383     }
384     (void) closedir(d);
385     (void) rmdir(cache_dir);
386 }
387
388 static krb5_error_code
389 mk_pkix_store(char **pkix_store)
390 {
391     char *s = NULL;
392     int ret = ENOMEM;
393     int fd;
394
395     *pkix_store = NULL;
396     if (asprintf(&s, "PEM-FILE:%s/pkix-XXXXXX", cache_dir) == -1 ||
397         s == NULL) {
398         free(s);
399         return ret;
400     }
401     /*
402      * This way of using mkstemp() isn't safer than mktemp(), but we want to
403      * quiet the warning that we'd get if we used mktemp().
404      */
405     if ((fd = mkstemp(s + sizeof("PEM-FILE:") - 1)) == -1) {
406         free(s);
407         return errno;
408     }
409     (void) close(fd);
410     *pkix_store = s;
411     return 0;
412 }
413
414 /*
415  * XXX Shouldn't be a body, but a status message.  The body should be
416  * configurable to be from a file.  MHD doesn't give us a way to set the
417  * response status message though, just the body.
418  */
419 static krb5_error_code
420 resp(struct bx509_request_desc *r,
421      int http_status_code,
422      enum MHD_ResponseMemoryMode rmmode,
423      const char *content_type,
424      const void *body,
425      size_t bodylen,
426      const char *token)
427 {
428     struct MHD_Response *response;
429     int mret = MHD_YES;
430
431     (void) gettimeofday(&r->tv_end, NULL);
432     if (http_status_code == MHD_HTTP_OK ||
433         http_status_code == MHD_HTTP_TEMPORARY_REDIRECT)
434         audit_trail(r, 0);
435
436     response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
437                                                rmmode);
438     if (response == NULL)
439         return -1;
440     mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
441                                    "no-store, max-age=0");
442     if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
443         mret = MHD_add_response_header(response,
444                                        MHD_HTTP_HEADER_WWW_AUTHENTICATE,
445                                        "Bearer");
446         if (mret == MHD_YES)
447             mret = MHD_add_response_header(response,
448                                            MHD_HTTP_HEADER_WWW_AUTHENTICATE,
449                                            "Negotiate");
450     } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
451         const char *redir;
452
453         /* XXX Move this */
454         redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
455                                             "redirect");
456         mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
457                                        redir);
458         if (mret != MHD_NO && token)
459             mret = MHD_add_response_header(response,
460                                            MHD_HTTP_HEADER_AUTHORIZATION,
461                                            token);
462     }
463     if (mret == MHD_YES && content_type) {
464         mret = MHD_add_response_header(response,
465                                        MHD_HTTP_HEADER_CONTENT_TYPE,
466                                        content_type);
467     }
468     if (mret == MHD_YES)
469         mret = MHD_queue_response(r->connection, http_status_code, response);
470     MHD_destroy_response(response);
471     return mret == MHD_NO ? -1 : 0;
472 }
473
474 static krb5_error_code
475 bad_reqv(struct bx509_request_desc *r,
476          krb5_error_code code,
477          int http_status_code,
478          const char *fmt,
479          va_list ap)
480 {
481     krb5_error_code ret;
482     krb5_context context = NULL;
483     const char *k5msg = NULL;
484     const char *emsg = NULL;
485     char *formatted = NULL;
486     char *msg = NULL;
487
488     heim_audit_addkv((heim_svc_req_desc)r, 0, "http-status-code", "%d",
489                      http_status_code);
490     (void) gettimeofday(&r->tv_end, NULL);
491     if (code == ENOMEM) {
492         if (r->context)
493             krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
494         audit_trail(r, code);
495         return resp(r, http_status_code, MHD_RESPMEM_PERSISTENT,
496                     NULL, fmt, strlen(fmt), NULL);
497     }
498
499     if (code) {
500         if (r->context)
501             emsg = k5msg = krb5_get_error_message(r->context, code);
502         else
503             emsg = strerror(code);
504     }
505
506     ret = vasprintf(&formatted, fmt, ap) == -1;
507     if (code) {
508         if (ret > -1 && formatted)
509             ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
510     } else {
511         msg = formatted;
512         formatted = NULL;
513     }
514     heim_audit_addreason((heim_svc_req_desc)r, "%s", formatted);
515     audit_trail(r, code);
516     krb5_free_error_message(context, k5msg);
517
518     if (ret == -1 || msg == NULL) {
519         if (context)
520             krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
521         return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, MHD_RESPMEM_PERSISTENT,
522                     NULL, "Out of memory", sizeof("Out of memory") - 1, NULL);
523     }
524
525     ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY,
526                NULL, msg, strlen(msg), NULL);
527     free(formatted);
528     free(msg);
529     return ret == -1 ? -1 : code;
530 }
531
532 static krb5_error_code
533 bad_req(struct bx509_request_desc *r,
534         krb5_error_code code,
535         int http_status_code,
536         const char *fmt,
537         ...)
538 {
539     krb5_error_code ret;
540     va_list ap;
541
542     va_start(ap, fmt);
543     ret = bad_reqv(r, code, http_status_code, fmt, ap);
544     va_end(ap);
545     return ret;
546 }
547
548 static krb5_error_code
549 bad_enomem(struct bx509_request_desc *r, krb5_error_code ret)
550 {
551     return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
552                    "Out of memory");
553 }
554
555 static krb5_error_code
556 bad_400(struct bx509_request_desc *r, int ret, char *reason)
557 {
558     return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
559 }
560
561 static krb5_error_code
562 bad_401(struct bx509_request_desc *r, char *reason)
563 {
564     return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
565 }
566
567 static krb5_error_code
568 bad_403(struct bx509_request_desc *r, krb5_error_code ret, char *reason)
569 {
570     return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
571 }
572
573 static krb5_error_code
574 bad_404(struct bx509_request_desc *r, const char *name)
575 {
576     return bad_req(r, ENOENT, MHD_HTTP_NOT_FOUND,
577                    "Resource not found: %s", name);
578 }
579
580 static krb5_error_code
581 bad_405(struct bx509_request_desc *r, const char *method)
582 {
583     return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
584                    "Method not supported: %s", method);
585 }
586
587 static krb5_error_code
588 bad_500(struct bx509_request_desc *r,
589         krb5_error_code ret,
590         const char *reason)
591 {
592     return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
593                    "Internal error: %s", reason);
594 }
595
596 static krb5_error_code
597 bad_503(struct bx509_request_desc *r,
598         krb5_error_code ret,
599         const char *reason)
600 {
601     return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
602                    "Service unavailable: %s", reason);
603 }
604
605 static krb5_error_code
606 good_bx509(struct bx509_request_desc *r)
607 {
608     krb5_error_code ret;
609     size_t bodylen;
610     void *body;
611
612     ret = rk_undumpdata(strchr(r->pkix_store, ':') + 1, &body, &bodylen);
613     if (ret)
614         return bad_503(r, ret, "Could not recover issued certificate "
615                        "from PKIX store");
616
617     (void) gettimeofday(&r->tv_end, NULL);
618     ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY, "application/x-pem-file",
619                body, bodylen, NULL);
620     free(body);
621     return ret;
622 }
623
624 static int
625 bx509_param_cb(void *d,
626                enum MHD_ValueKind kind,
627                const char *key,
628                const char *val)
629 {
630     struct bx509_request_desc *r = d;
631     heim_oid oid = { 0, 0 };
632
633     if (strcmp(key, "eku") == 0 && val) {
634         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "requested_eku",
635                          "%s", val);
636         r->ret = der_parse_heim_oid(val, ".", &oid);
637         if (r->ret == 0)
638             r->ret = hx509_request_add_eku(r->context->hx509ctx, r->req, &oid);
639         der_free_oid(&oid);
640     } else if (strcmp(key, "dNSName") == 0 && val) {
641         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
642                          "requested_dNSName", "%s", val);
643         r->ret = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
644     } else if (strcmp(key, "rfc822Name") == 0 && val) {
645         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
646                          "requested_rfc822Name", "%s", val);
647         r->ret = hx509_request_add_email(r->context->hx509ctx, r->req, val);
648     } else if (strcmp(key, "xMPPName") == 0 && val) {
649         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
650                          "requested_xMPPName", "%s", val);
651         r->ret = hx509_request_add_xmpp_name(r->context->hx509ctx, r->req,
652                                              val);
653     } else if (strcmp(key, "krb5PrincipalName") == 0 && val) {
654         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
655                          "requested_krb5PrincipalName", "%s", val);
656         r->ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
657                                           val);
658     } else if (strcmp(key, "ms-upn") == 0 && val) {
659         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
660                          "requested_ms_upn", "%s", val);
661         r->ret = hx509_request_add_ms_upn_name(r->context->hx509ctx, r->req,
662                                                val);
663     } else if (strcmp(key, "registeredID") == 0 && val) {
664         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
665                          "requested_registered_id", "%s", val);
666         r->ret = der_parse_heim_oid(val, ".", &oid);
667         if (r->ret == 0)
668             r->ret = hx509_request_add_registered(r->context->hx509ctx, r->req,
669                                                   &oid);
670         der_free_oid(&oid);
671     } else if (strcmp(key, "csr") == 0 && val) {
672         heim_audit_addkv((heim_svc_req_desc)r, 0, "requested_csr", "true");
673         r->ret = 0; /* Handled upstairs */
674     } else if (strcmp(key, "lifetime") == 0 && val) {
675         r->req_life = parse_time(val, "day");
676     } else {
677         /* Produce error for unknown params */
678         heim_audit_addkv((heim_svc_req_desc)r, 0, "requested_unknown", "true");
679         krb5_set_error_message(r->context, r->ret = ENOTSUP,
680                                "Query parameter %s not supported", key);
681     }
682     return r->ret == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
683 }
684
685 static krb5_error_code
686 authorize_CSR(struct bx509_request_desc *r,
687               krb5_data *csr,
688               krb5_const_principal p)
689 {
690     krb5_error_code ret;
691
692     ret = hx509_request_parse_der(r->context->hx509ctx, csr, &r->req);
693     if (ret)
694         return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
695                        "Could not parse CSR");
696     r->ret = 0;
697     (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
698                                      bx509_param_cb, r);
699     ret = r->ret;
700     if (ret)
701         return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
702                        "Could not handle query parameters");
703
704     ret = kdc_authorize_csr(r->context, "bx509", r->req, p);
705     if (ret)
706         return bad_403(r, ret, "Not authorized to requested certificate");
707     return ret;
708 }
709
710 /*
711  * hx509_certs_iter_f() callback to assign a private key to the first cert in a
712  * store.
713  */
714 static int HX509_LIB_CALL
715 set_priv_key(hx509_context context, void *d, hx509_cert c)
716 {
717     (void) _hx509_cert_assign_key(c, (hx509_private_key)d);
718     return -1; /* stop iteration */
719 }
720
721 static krb5_error_code
722 store_certs(hx509_context context,
723             const char *store,
724             hx509_certs store_these,
725             hx509_private_key key)
726 {
727     krb5_error_code ret;
728     hx509_certs certs = NULL;
729
730     ret = hx509_certs_init(context, store, HX509_CERTS_CREATE, NULL,
731                            &certs);
732     if (ret == 0) {
733         if (key)
734             (void) hx509_certs_iter_f(context, store_these, set_priv_key, key);
735         hx509_certs_merge(context, certs, store_these);
736     }
737     if (ret == 0)
738         hx509_certs_store(context, certs, 0, NULL);
739     hx509_certs_free(&certs);
740     return ret;
741 }
742
743 /* Setup a CSR for bx509() */
744 static krb5_error_code
745 do_CA(struct bx509_request_desc *r, const char *csr)
746 {
747     krb5_error_code ret = 0;
748     krb5_principal p;
749     hx509_certs certs = NULL;
750     krb5_data d;
751     ssize_t bytes;
752     char *csr2, *q;
753
754     /*
755      * Work around bug where microhttpd decodes %2b to + then + to space.  That
756      * bug does not affect other base64 special characters that get URI
757      * %-encoded.
758      */
759     if ((csr2 = strdup(csr)) == NULL)
760         return bad_enomem(r, ENOMEM);
761     for (q = strchr(csr2, ' '); q; q = strchr(q + 1, ' '))
762         *q = '+';
763
764     ret = krb5_parse_name(r->context, r->cname, &p);
765     if (ret) {
766         free(csr2);
767         return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
768                        "Could not parse principal name");
769     }
770
771     /* Set CSR */
772     if ((d.data = malloc(strlen(csr2))) == NULL) {
773         krb5_free_principal(r->context, p);
774         return bad_enomem(r, ENOMEM);
775     }
776
777     bytes = rk_base64_decode(csr2, d.data);
778     free(csr2);
779     if (bytes < 0)
780         ret = errno;
781     else
782         d.length = bytes;
783     if (ret) {
784         krb5_free_principal(r->context, p);
785         free(d.data);
786         return bad_500(r, ret, "Invalid base64 encoding of CSR");
787     }
788
789     /*
790      * Parses and validates the CSR, adds external extension requests from
791      * query parameters, then checks authorization.
792      */
793     ret = authorize_CSR(r, &d, p);
794     free(d.data);
795     d.data = 0;
796     d.length = 0;
797     if (ret) {
798         krb5_free_principal(r->context, p);
799         return ret; /* authorize_CSR() calls bad_req() */
800     }
801
802     /* Issue the certificate */
803     ret = kdc_issue_certificate(r->context, "bx509", logfac, r->req, p,
804                                 &r->token_times, r->req_life,
805                                 1 /* send_chain */, &certs);
806     krb5_free_principal(r->context, p);
807     if (ret) {
808         if (ret == KRB5KDC_ERR_POLICY || ret == EACCES)
809             return bad_403(r, ret,
810                            "Certificate request denied for policy reasons");
811         return bad_500(r, ret, "Certificate issuance failed");
812     }
813
814     /* Setup PKIX store */
815     if ((ret = mk_pkix_store(&r->pkix_store)))
816         return bad_500(r, ret,
817                        "Could not create PEM store for issued certificate");
818
819     ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, NULL);
820     hx509_certs_free(&certs);
821     if (ret) {
822         (void) unlink(strchr(r->pkix_store, ':') + 1);
823         return bad_500(r, ret,
824                        "Failed convert issued certificate and chain to PEM");
825     }
826     return 0;
827 }
828
829 /* Copied from kdc/connect.c */
830 static void
831 addr_to_string(krb5_context context,
832                struct sockaddr *addr,
833                char *str,
834                size_t len)
835 {
836     krb5_error_code ret;
837     krb5_address a;
838
839     ret = krb5_sockaddr2address(context, addr, &a);
840     if (ret == 0) {
841         ret = krb5_print_address(&a, str, len, &len);
842         krb5_free_address(context, &a);
843     }
844     if (ret)
845         snprintf(str, len, "<family=%d>", addr->sa_family);
846 }
847
848 static krb5_error_code
849 set_req_desc(struct MHD_Connection *connection,
850              const char *url,
851              struct bx509_request_desc *r)
852 {
853     const union MHD_ConnectionInfo *ci;
854     const char *token;
855     krb5_error_code ret;
856
857     memset(r, 0, sizeof(*r));
858     (void) gettimeofday(&r->tv_start, NULL);
859
860     ret = get_krb5_context(&r->context);
861     r->connection = connection;
862     r->request.data = "<HTTP-REQUEST>";
863     r->request.length = sizeof("<HTTP-REQUEST>");
864     r->from = r->frombuf;
865     r->tgt_addresses.len = 0;
866     r->tgt_addresses.val = 0;
867     r->hcontext = r->context->hcontext;
868     r->config = NULL;
869     r->logf = logfac;
870     r->reqtype = url;
871     r->target = r->redir = NULL;
872     r->pkix_store = NULL;
873     r->for_cname = NULL;
874     r->freeme1 = NULL;
875     r->reason = NULL;
876     r->ccname = NULL;
877     r->reply = NULL;
878     r->sname = NULL;
879     r->cname = NULL;
880     r->addr = NULL;
881     r->req = NULL;
882     r->req_life = 0;
883     r->ret = 0;
884     r->kv = heim_array_create();
885     ci = MHD_get_connection_info(connection,
886                                  MHD_CONNECTION_INFO_CLIENT_ADDRESS);
887     if (ci) {
888         r->addr = ci->client_addr;
889         addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
890     }
891
892     heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
893     heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
894     token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
895                                         MHD_HTTP_HEADER_AUTHORIZATION);
896     if (token && r->kv) {
897         const char *token_end;
898
899         if ((token_end = strchr(token, ' ')) == NULL ||
900             (token_end - token) > INT_MAX || (token_end - token) < 2)
901             heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
902         else
903             heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
904                              (int)(token_end - token), token);
905
906     }
907
908     if (ret == 0 && r->kv == NULL) {
909         krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
910         ret = ENOMEM;
911     }
912     return ret;
913 }
914
915 static void
916 clean_req_desc(struct bx509_request_desc *r)
917 {
918     if (!r)
919         return;
920     if (r->pkix_store)
921         (void) unlink(strchr(r->pkix_store, ':') + 1);
922     krb5_free_addresses(r->context, &r->tgt_addresses);
923     hx509_request_free(&r->req);
924     heim_release(r->reason);
925     heim_release(r->kv);
926     free(r->pkix_store);
927     free(r->freeme1);
928     free(r->ccname);
929     free(r->cname);
930     free(r->sname);
931 }
932
933 /* Implements GETs of /bx509 */
934 static krb5_error_code
935 bx509(struct bx509_request_desc *r)
936 {
937     krb5_error_code ret;
938     const char *csr;
939
940     /* Get required inputs */
941     csr = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
942                                       "csr");
943     if (csr == NULL)
944         return bad_400(r, EINVAL, "CSR is missing");
945
946     if ((ret = validate_token(r)))
947         return ret; /* validate_token() calls bad_req() */
948
949     if (r->cname == NULL)
950         return bad_403(r, EINVAL,
951                        "Could not extract principal name from token");
952
953     /* Parse CSR, add extensions from parameters, authorize, issue cert */
954     if ((ret = do_CA(r, csr)))
955         return ret;
956
957     /* Read and send the contents of the PKIX store */
958     krb5_log_msg(r->context, logfac, 1, NULL, "Issued certificate to %s",
959                  r->cname);
960     return good_bx509(r);
961 }
962
963 /*
964  * princ_fs_encode_sz() and princ_fs_encode() encode a principal name to be
965  * safe for use as a file name.  They function very much like URL encoders, but
966  * '~' and '.' also get encoded, and '@' does not.
967  *
968  * A corresponding decoder is not needed.
969  */
970 static size_t
971 princ_fs_encode_sz(const char *in)
972 {
973     size_t sz = strlen(in);
974
975     while (*in) {
976         unsigned char c = *(const unsigned char *)(in++);
977
978         if (isalnum(c))
979             continue;
980         switch (c) {
981         case '@':
982         case '-':
983         case '_':
984             continue;
985         default:
986             sz += 2;
987         }
988     }
989     return sz;
990 }
991
992 static char *
993 princ_fs_encode(const char *in)
994 {
995     size_t len = strlen(in);
996     size_t sz = princ_fs_encode_sz(in);
997     size_t i, k;
998     char *s;
999
1000     if ((s = malloc(sz + 1)) == NULL)
1001         return NULL;
1002     s[sz] = '\0';
1003
1004     for (i = k = 0; i < len; i++) {
1005         char c = in[i];
1006
1007         switch (c) {
1008         case '@':
1009         case '-':
1010         case '_':
1011             s[k++] = c;
1012             break;
1013         default:
1014             if (isalnum(c)) {
1015                 s[k++] = c;
1016             } else  {
1017                 s[k++] = '%';
1018                 s[k++] = "0123456789abcdef"[(c&0xff)>>4];
1019                 s[k++] = "0123456789abcdef"[(c&0x0f)];
1020             }
1021         }
1022     }
1023     return s;
1024 }
1025
1026
1027 /*
1028  * Find an existing, live ccache for `princ' in `cache_dir' or acquire Kerberos
1029  * creds for `princ' with PKINIT and put them in a ccache in `cache_dir'.
1030  */
1031 static krb5_error_code
1032 find_ccache(krb5_context context, const char *princ, char **ccname)
1033 {
1034     krb5_error_code ret = ENOMEM;
1035     krb5_ccache cc = NULL;
1036     time_t life;
1037     char *s = NULL;
1038
1039     *ccname = NULL;
1040
1041     /*
1042      * Name the ccache after the principal.  The principal may have special
1043      * characters in it, such as / or \ (path component separarot), or shell
1044      * special characters, so princ_fs_encode() it to make a ccache name.
1045      */
1046     if ((s = princ_fs_encode(princ)) == NULL ||
1047         asprintf(ccname, "FILE:%s/%s.cc", cache_dir, s) == -1 ||
1048         *ccname == NULL)
1049         return ENOMEM;
1050     free(s);
1051
1052     if ((ret = krb5_cc_resolve(context, *ccname, &cc))) {
1053         /* krb5_cc_resolve() suceeds even if the file doesn't exist */
1054         free(*ccname);
1055         *ccname = NULL;
1056         cc = NULL;
1057     }
1058
1059     /* Check if we have a good enough credential */
1060     if (ret == 0 &&
1061         (ret = krb5_cc_get_lifetime(context, cc, &life)) == 0 && life > 60) {
1062         krb5_cc_close(context, cc);
1063         return 0;
1064     }
1065     if (cc)
1066         krb5_cc_close(context, cc);
1067     return ret ? ret : ENOENT;
1068 }
1069
1070 enum k5_creds_kind { K5_CREDS_EPHEMERAL, K5_CREDS_CACHED };
1071
1072 static krb5_error_code
1073 get_ccache(struct bx509_request_desc *r, krb5_ccache *cc, int *won)
1074 {
1075     krb5_error_code ret = 0;
1076     struct stat st1, st2;
1077     char *temp_ccname = NULL;
1078     const char *fn = NULL;
1079     time_t life;
1080     int fd = -1;
1081
1082     /*
1083      * Open and lock a .new ccache file.  Use .new to avoid garbage files on
1084      * crash.
1085      *
1086      * We can race with other threads to do this, so we loop until we
1087      * definitively win or definitely lose the race.  We win when we have a) an
1088      * open FD that is b) flock'ed, and c) we observe with lstat() that the
1089      * file we opened and locked is the same as on disk after locking.
1090      *
1091      * We don't close the FD until we're done.
1092      *
1093      * If we had a proper anon MEMORY ccache, we could instead use that for a
1094      * temporary ccache, and then the initialization of and move to the final
1095      * FILE ccache would take care to mkstemp() and rename() into place.
1096      * fcc_open() basically does a similar thing.
1097      */
1098     *cc = NULL;
1099     *won = -1;
1100     if (asprintf(&temp_ccname, "%s.ccnew", r->ccname) == -1 ||
1101         temp_ccname == NULL)
1102         ret = ENOMEM;
1103     if (ret == 0)
1104         fn = temp_ccname + sizeof("FILE:") - 1;
1105     if (ret == 0) do {
1106         /*
1107          * Open and flock the temp ccache file.
1108          *
1109          * XXX We should really a) use _krb5_xlock(), or move that into
1110          * lib/roken anyways, b) abstract this loop into a utility function in
1111          * lib/roken.
1112          */
1113         if (fd != -1) {
1114             (void) close(fd);
1115             fd = -1;
1116         }
1117         errno = 0;
1118         if (ret == 0 &&
1119             ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1 ||
1120              flock(fd, LOCK_EX) == -1 ||
1121              (lstat(fn, &st1) == -1 && errno != ENOENT) ||
1122              fstat(fd, &st2) == -1))
1123             ret = errno;
1124         if (ret == 0 && errno == 0 &&
1125             st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) {
1126             if (S_ISREG(st1.st_mode))
1127                 break;
1128             if (unlink(fn) == -1)
1129                 ret = errno;
1130         }
1131     } while (ret == 0);
1132
1133     /* Check if we lost any race to acquire Kerberos creds */
1134     if (ret == 0)
1135         ret = krb5_cc_resolve(r->context, temp_ccname, cc);
1136     if (ret == 0) {
1137         ret = krb5_cc_get_lifetime(r->context, *cc, &life);
1138         if (ret == 0 && life > 60)
1139             *won = 0; /* We lost the race, but we win: we get to do less work */
1140         *won = 1;
1141         ret = 0;
1142     }
1143     free(temp_ccname);
1144     if (fd != -1)
1145         (void) close(fd); /* Drops the flock */
1146     return ret;
1147 }
1148
1149 /*
1150  * Acquire credentials for `princ' using PKINIT and the PKIX credentials in
1151  * `pkix_store', then place the result in the ccache named `ccname' (which will
1152  * be in our own private `cache_dir').
1153  *
1154  * XXX This function could be rewritten using gss_acquire_cred_from() and
1155  * gss_store_cred_into() provided we add new generic cred store key/value pairs
1156  * for PKINIT.
1157  */
1158 static krb5_error_code
1159 do_pkinit(struct bx509_request_desc *r, enum k5_creds_kind kind)
1160 {
1161     krb5_get_init_creds_opt *opt = NULL;
1162     krb5_init_creds_context ctx = NULL;
1163     krb5_error_code ret = 0;
1164     krb5_ccache temp_cc = NULL;
1165     krb5_ccache cc = NULL;
1166     krb5_principal p = NULL;
1167     const char *crealm;
1168     const char *cname = r->for_cname ? r->for_cname : r->cname;
1169
1170     if (kind == K5_CREDS_CACHED) {
1171         int won = -1;
1172
1173         ret = get_ccache(r, &temp_cc, &won);
1174         if (ret || !won)
1175             goto out;
1176         /*
1177          * We won the race to do PKINIT.  Setup to acquire Kerberos creds with
1178          * PKINIT.
1179          *
1180          * We should really make sure that gss_acquire_cred_from() can do this
1181          * for us.  We'd add generic cred store key/value pairs for PKIX cred
1182          * store, trust anchors, and so on, and acquire that way, then
1183          * gss_store_cred_into() to save it in a FILE ccache.
1184          */
1185     } else {
1186         ret = krb5_cc_new_unique(r->context, "FILE", NULL, &temp_cc);
1187     }
1188
1189     ret = krb5_parse_name(r->context, cname, &p);
1190     if (ret == 0)
1191         crealm = krb5_principal_get_realm(r->context, p);
1192     if (ret == 0)
1193         ret = krb5_get_init_creds_opt_alloc(r->context, &opt);
1194     if (ret == 0)
1195         krb5_get_init_creds_opt_set_default_flags(r->context, "kinit", crealm,
1196                                                   opt);
1197     if (ret == 0 && kind == K5_CREDS_EPHEMERAL &&
1198         !krb5_config_get_bool_default(r->context, NULL, TRUE,
1199                                       "get-tgt", "no_addresses", NULL)) {
1200         krb5_addresses addr;
1201
1202         ret = _krb5_parse_address_no_lookup(r->context, r->frombuf, &addr);
1203         if (ret == 0)
1204             ret = krb5_append_addresses(r->context, &r->tgt_addresses,
1205                                         &addr);
1206     }
1207     if (ret == 0 && r->tgt_addresses.len == 0)
1208         ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
1209     else
1210         krb5_get_init_creds_opt_set_address_list(opt, &r->tgt_addresses);
1211     if (ret == 0)
1212         ret = krb5_get_init_creds_opt_set_pkinit(r->context, opt, p,
1213                                                  r->pkix_store,
1214                                                  NULL,  /* pkinit_anchor */
1215                                                  NULL,  /* anchor_chain */
1216                                                  NULL,  /* pkinit_crl */
1217                                                  0,     /* flags */
1218                                                  NULL,  /* prompter */
1219                                                  NULL,  /* prompter data */
1220                                                  NULL   /* password */);
1221     if (ret == 0)
1222         ret = krb5_init_creds_init(r->context, p,
1223                                    NULL /* prompter */,
1224                                    NULL /* prompter data */,
1225                                    0 /* start_time */,
1226                                    opt, &ctx);
1227
1228     /*
1229      * Finally, do the AS exchange w/ PKINIT, extract the new Kerberos creds
1230      * into temp_cc, and rename into place.  Note that krb5_cc_move() closes
1231      * the source ccache, so we set temp_cc = NULL if it succeeds.
1232      */
1233     if (ret == 0)
1234         ret = krb5_init_creds_get(r->context, ctx);
1235     if (ret == 0)
1236         ret = krb5_init_creds_store(r->context, ctx, temp_cc);
1237     if (kind == K5_CREDS_CACHED) {
1238         if (ret == 0)
1239             ret = krb5_cc_resolve(r->context, r->ccname, &cc);
1240         if (ret == 0)
1241             ret = krb5_cc_move(r->context, temp_cc, cc);
1242         if (ret == 0)
1243             temp_cc = NULL;
1244     } else if (ret == 0 && kind == K5_CREDS_EPHEMERAL) {
1245         ret = krb5_cc_get_full_name(r->context, temp_cc, &r->ccname);
1246     }
1247
1248 out:
1249     if (ctx)
1250         krb5_init_creds_free(r->context, ctx);
1251     krb5_get_init_creds_opt_free(r->context, opt);
1252     krb5_free_principal(r->context, p);
1253     krb5_cc_close(r->context, temp_cc);
1254     krb5_cc_close(r->context, cc);
1255     return ret;
1256 }
1257
1258 static krb5_error_code
1259 load_priv_key(krb5_context context, const char *fn, hx509_private_key *key)
1260 {
1261     hx509_private_key *keys = NULL;
1262     krb5_error_code ret;
1263     hx509_certs certs = NULL;
1264
1265     *key = NULL;
1266     ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs);
1267     if (ret == ENOENT)
1268         return 0;
1269     if (ret == 0)
1270         ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys);
1271     if (ret == 0 && keys[0] == NULL)
1272         ret = ENOENT; /* XXX Better error please */
1273     if (ret == 0)
1274         *key = _hx509_private_key_ref(keys[0]);
1275     if (ret)
1276         krb5_set_error_message(context, ret, "Could not load private "
1277                                "impersonation key from %s for PKINIT: %s", fn,
1278                                hx509_get_error_string(context->hx509ctx, ret));
1279     _hx509_certs_keys_free(context->hx509ctx, keys);
1280     hx509_certs_free(&certs);
1281     return ret;
1282 }
1283
1284 static krb5_error_code
1285 k5_do_CA(struct bx509_request_desc *r)
1286 {
1287     SubjectPublicKeyInfo spki;
1288     hx509_private_key key = NULL;
1289     krb5_error_code ret = 0;
1290     krb5_principal p = NULL;
1291     hx509_request req = NULL;
1292     hx509_certs certs = NULL;
1293     KeyUsage ku = int2KeyUsage(0);
1294     const char *cname = r->for_cname ? r->for_cname : r->cname;
1295
1296     memset(&spki, 0, sizeof(spki));
1297     ku.digitalSignature = 1;
1298
1299     /* Make a CSR (halfway -- we don't need to sign it here) */
1300     /* XXX Load impersonation key just once?? */
1301     ret = load_priv_key(r->context, impersonation_key_fn, &key);
1302     if (ret == 0)
1303     ret = hx509_request_init(r->context->hx509ctx, &req);
1304     if (ret == 0)
1305         ret = krb5_parse_name(r->context, cname, &p);
1306     if (ret == 0)
1307         hx509_private_key2SPKI(r->context->hx509ctx, key, &spki);
1308     if (ret == 0)
1309         hx509_request_set_SubjectPublicKeyInfo(r->context->hx509ctx, req,
1310                                                &spki);
1311     free_SubjectPublicKeyInfo(&spki);
1312     if (ret == 0)
1313         ret = hx509_request_add_pkinit(r->context->hx509ctx, req, cname);
1314     if (ret == 0)
1315         ret = hx509_request_add_eku(r->context->hx509ctx, req,
1316                                     &asn1_oid_id_pkekuoid);
1317
1318     /* Mark it authorized */
1319     if (ret == 0)
1320         ret = hx509_request_authorize_san(req, 0);
1321     if (ret == 0)
1322         ret = hx509_request_authorize_eku(req, 0);
1323     if (ret == 0)
1324         hx509_request_authorize_ku(req, ku);
1325
1326     /* Issue the certificate */
1327     if (ret == 0)
1328         ret = kdc_issue_certificate(r->context, "get-tgt", logfac, req, p,
1329                                     &r->token_times, r->req_life,
1330                                     1 /* send_chain */, &certs);
1331     krb5_free_principal(r->context, p);
1332     hx509_request_free(&req);
1333     p = NULL;
1334
1335     if (ret == KRB5KDC_ERR_POLICY || ret == EACCES) {
1336         hx509_private_key_free(&key);
1337         return bad_403(r, ret,
1338                        "Certificate request denied for policy reasons");
1339     }
1340     if (ret == ENOMEM) {
1341         hx509_private_key_free(&key);
1342         return bad_503(r, ret, "Certificate issuance failed");
1343     }
1344     if (ret) {
1345         hx509_private_key_free(&key);
1346         return bad_500(r, ret, "Certificate issuance failed");
1347     }
1348
1349     /* Setup PKIX store and extract the certificate chain into it */
1350     ret = mk_pkix_store(&r->pkix_store);
1351     if (ret == 0)
1352         ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, key);
1353     hx509_private_key_free(&key);
1354     hx509_certs_free(&certs);
1355     if (ret)
1356         return bad_500(r, ret,
1357                        "Could not create PEM store for issued certificate");
1358     return 0;
1359 }
1360
1361 /* Get impersonated Kerberos credentials for `cprinc' */
1362 static krb5_error_code
1363 k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
1364 {
1365     krb5_error_code ret;
1366     const char *cname = r->for_cname ? r->for_cname : r->cname;
1367
1368     /* If we have a live ccache for `cprinc', we're done */
1369     if (kind == K5_CREDS_CACHED &&
1370         (ret = find_ccache(r->context, cname, &r->ccname)) == 0)
1371         return ret; /* Success */
1372
1373     /*
1374      * Else we have to acquire a credential for them using their bearer token
1375      * for authentication (and our keytab / initiator credentials perhaps).
1376      */
1377     if ((ret = k5_do_CA(r)))
1378         return ret; /* k5_do_CA() calls bad_req() */
1379
1380     if (ret == 0 && (ret = do_pkinit(r, kind)))
1381         ret = bad_403(r, ret,
1382                       "Could not acquire Kerberos credentials using PKINIT");
1383     return ret;
1384 }
1385
1386 /* Accumulate strings */
1387 static void
1388 acc_str(char **acc, char *adds, size_t addslen)
1389 {
1390     char *tmp;
1391     int l = addslen <= INT_MAX ? (int)addslen : INT_MAX;
1392
1393     if (asprintf(&tmp, "%s%s%.*s",
1394                  *acc ? *acc : "",
1395                  *acc ? "; " : "", l, adds) > -1 &&
1396         tmp) {
1397         free(*acc);
1398         *acc = tmp;
1399     }
1400 }
1401
1402 static char *
1403 fmt_gss_error(OM_uint32 code, gss_OID mech)
1404 {
1405     gss_buffer_desc buf;
1406     OM_uint32 major, minor;
1407     OM_uint32 type = mech == GSS_C_NO_OID ? GSS_C_GSS_CODE: GSS_C_MECH_CODE;
1408     OM_uint32 more = 0;
1409     char *r = NULL;
1410
1411     do {
1412         major = gss_display_status(&minor, code, type, mech, &more, &buf);
1413         if (!GSS_ERROR(major))
1414             acc_str(&r, (char *)buf.value, buf.length);
1415         gss_release_buffer(&minor, &buf);
1416     } while (!GSS_ERROR(major) && more);
1417     return r ? r : "Out of memory while formatting GSS-API error";
1418 }
1419
1420 static char *
1421 fmt_gss_errors(const char *r, OM_uint32 major, OM_uint32 minor, gss_OID mech)
1422 {
1423     char *ma, *mi, *s;
1424
1425     ma = fmt_gss_error(major, GSS_C_NO_OID);
1426     mi = mech == GSS_C_NO_OID ? NULL : fmt_gss_error(minor, mech);
1427     if (asprintf(&s, "%s: %s%s%s", r, ma, mi ? ": " : "", mi ? mi : "") > -1 &&
1428         s) {
1429         free(ma);
1430         free(mi);
1431         return s;
1432     }
1433     free(mi);
1434     return ma;
1435 }
1436
1437 /* GSS-API error */
1438 static krb5_error_code
1439 bad_req_gss(struct bx509_request_desc *r,
1440             OM_uint32 major,
1441             OM_uint32 minor,
1442             gss_OID mech,
1443             int http_status_code,
1444             const char *reason)
1445 {
1446     krb5_error_code ret;
1447     char *msg = fmt_gss_errors(reason, major, minor, mech);
1448
1449     if (major == GSS_S_BAD_NAME || major == GSS_S_BAD_NAMETYPE)
1450         http_status_code = MHD_HTTP_BAD_REQUEST;
1451
1452     ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
1453                msg, strlen(msg), NULL);
1454     free(msg);
1455     return ret;
1456 }
1457
1458 /* Make an HTTP/Negotiate token */
1459 static krb5_error_code
1460 mk_nego_tok(struct bx509_request_desc *r,
1461             char **nego_tok,
1462             size_t *nego_toksz)
1463 {
1464     gss_key_value_element_desc kv[1] = { { "ccache", r->ccname } };
1465     gss_key_value_set_desc store = { 1, kv };
1466     gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
1467     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
1468     gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
1469     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
1470     gss_name_t iname = GSS_C_NO_NAME;
1471     gss_name_t aname = GSS_C_NO_NAME;
1472     OM_uint32 major, minor, junk;
1473     krb5_error_code ret; /* More like a system error code here */
1474     const char *cname = r->for_cname ? r->for_cname : r->cname;
1475     char *token_b64 = NULL;
1476
1477     *nego_tok = NULL;
1478     *nego_toksz = 0;
1479
1480     /* Import initiator name */
1481     name.length = strlen(cname);
1482     name.value = rk_UNCONST(cname);
1483     major = gss_import_name(&minor, &name, GSS_KRB5_NT_PRINCIPAL_NAME, &iname);
1484     if (major != GSS_S_COMPLETE)
1485         return bad_req_gss(r, major, minor, GSS_C_NO_OID,
1486                            MHD_HTTP_SERVICE_UNAVAILABLE,
1487                            "Could not import cprinc parameter value as "
1488                            "Kerberos principal name");
1489
1490     /* Import target acceptor name */
1491     name.length = strlen(r->target);
1492     name.value = rk_UNCONST(r->target);
1493     major = gss_import_name(&minor, &name, GSS_C_NT_HOSTBASED_SERVICE, &aname);
1494     if (major != GSS_S_COMPLETE) {
1495         (void) gss_release_name(&junk, &iname);
1496         return bad_req_gss(r, major, minor, GSS_C_NO_OID,
1497                            MHD_HTTP_SERVICE_UNAVAILABLE,
1498                            "Could not import target parameter value as "
1499                            "Kerberos principal name");
1500     }
1501
1502     /* Acquire a credential from the given ccache */
1503     major = gss_add_cred_from(&minor, cred, iname, GSS_KRB5_MECHANISM,
1504                               GSS_C_INITIATE, GSS_C_INDEFINITE, 0, &store,
1505                               &cred, NULL, NULL, NULL);
1506     (void) gss_release_name(&junk, &iname);
1507     if (major != GSS_S_COMPLETE) {
1508         (void) gss_release_name(&junk, &aname);
1509         return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
1510                            MHD_HTTP_FORBIDDEN, "Could not acquire credentials "
1511                            "for requested cprinc");
1512     }
1513
1514     major = gss_init_sec_context(&minor, cred, &ctx, aname,
1515                                  GSS_KRB5_MECHANISM, 0, GSS_C_INDEFINITE,
1516                                  NULL, GSS_C_NO_BUFFER, NULL, &token, NULL,
1517                                  NULL);
1518     (void) gss_delete_sec_context(&junk, &ctx, GSS_C_NO_BUFFER);
1519     (void) gss_release_name(&junk, &aname);
1520     (void) gss_release_cred(&junk, &cred);
1521     if (major != GSS_S_COMPLETE)
1522         return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
1523                            MHD_HTTP_SERVICE_UNAVAILABLE, "Could not acquire "
1524                            "Negotiate token for requested target");
1525
1526     /* Encode token, output */
1527     ret = rk_base64_encode(token.value, token.length, &token_b64);
1528     (void) gss_release_buffer(&junk, &token);
1529     if (ret > 0)
1530         ret = asprintf(nego_tok, "Negotiate %s", token_b64);
1531     free(token_b64);
1532     if (ret < 0 || *nego_tok == NULL)
1533         return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
1534                        "Could not allocate memory for encoding Negotiate "
1535                        "token");
1536     *nego_toksz = ret;
1537     return 0;
1538 }
1539
1540 static krb5_error_code
1541 bnegotiate_get_target(struct bx509_request_desc *r)
1542 {
1543     const char *target;
1544     const char *redir;
1545     const char *referer; /* misspelled on the wire, misspelled here, FYI */
1546     const char *authority;
1547     const char *local_part;
1548     char *s1 = NULL;
1549     char *s2 = NULL;
1550
1551     target = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1552                                          "target");
1553     redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1554                                         "redirect");
1555     referer = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
1556                                           MHD_HTTP_HEADER_REFERER);
1557     if (target != NULL && redir == NULL) {
1558         r->target = target;
1559         return 0;
1560     }
1561     if (target == NULL && redir == NULL)
1562         return bad_400(r, EINVAL,
1563                        "Query missing 'target' or 'redirect' parameter value");
1564     if (target != NULL && redir != NULL)
1565         return bad_403(r, EACCES,
1566                        "Only one of 'target' or 'redirect' parameter allowed");
1567     if (redir != NULL && referer == NULL)
1568         return bad_403(r, EACCES,
1569                        "Redirect request without Referer header nor allowed");
1570
1571     if (strncmp(referer, "https://", sizeof("https://") - 1) != 0 ||
1572         strncmp(redir, "https://", sizeof("https://") - 1) != 0)
1573         return bad_403(r, EACCES,
1574                        "Redirect requests permitted only for https referrers");
1575
1576     /* Parse out authority from each URI, redirect and referrer */
1577     authority = redir + sizeof("https://") - 1;
1578     if ((local_part = strchr(authority, '/')) == NULL)
1579         local_part = authority + strlen(authority);
1580     if ((s1 = strndup(authority, local_part - authority)) == NULL)
1581         return bad_enomem(r, ENOMEM);
1582
1583     authority = referer + sizeof("https://") - 1;
1584     if ((local_part = strchr(authority, '/')) == NULL)
1585         local_part = authority + strlen(authority);
1586     if ((s2 = strndup(authority, local_part - authority)) == NULL) {
1587         free(s1);
1588         return bad_enomem(r, ENOMEM);
1589     }
1590
1591     /* Both must match */
1592     if (strcasecmp(s1, s2) != 0) {
1593         free(s2);
1594         free(s1);
1595         return bad_403(r, EACCES, "Redirect request does not match referer");
1596     }
1597     free(s2);
1598
1599     if (strchr(s1, '@')) {
1600         free(s1);
1601         return bad_403(r, EACCES,
1602                        "Redirect request authority has login information");
1603     }
1604
1605     /* Extract hostname portion of authority and format GSS name */
1606     if (strchr(s1, ':'))
1607         *strchr(s1, ':') = '\0';
1608     if (asprintf(&r->freeme1, "HTTP@%s", s1) == -1 || r->freeme1 == NULL) {
1609         free(s1);
1610         return bad_enomem(r, ENOMEM);
1611     }
1612
1613     r->target = r->freeme1;
1614     r->redir = redir;
1615     free(s1);
1616     return 0;
1617 }
1618
1619 /*
1620  * Implements /bnegotiate end-point.
1621  *
1622  * Query parameters (mutually exclusive):
1623  *
1624  *  - target=<name>
1625  *  - redirect=<URL-encoded-URL>
1626  *
1627  * If the redirect query parameter is set then the Referer: header must be as
1628  * well, and the authority of the redirect and Referer URIs must be the same.
1629  */
1630 static krb5_error_code
1631 bnegotiate(struct bx509_request_desc *r)
1632 {
1633     krb5_error_code ret;
1634     size_t nego_toksz = 0;
1635     char *nego_tok = NULL;
1636
1637     ret = bnegotiate_get_target(r);
1638     if (ret == 0) {
1639         heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "target", "%s",
1640                          r->target ? r->target : "<unknown>");
1641         heim_audit_addkv((heim_svc_req_desc)r, 0, "redir", "%s",
1642                          r->redir ? "yes" : "no");
1643         ret = validate_token(r);
1644     }
1645     /* bnegotiate_get_target() and validate_token() call bad_req() */
1646     if (ret)
1647         return ret;
1648
1649     /*
1650      * Make sure we have Kerberos credentials for cprinc.  If we have them
1651      * cached from earlier, this will be fast (all local), else it will involve
1652      * taking a file lock and talking to the KDC using kx509 and PKINIT.
1653      *
1654      * Perhaps we could use S4U instead, which would speed up the slow path a
1655      * bit.
1656      */
1657     ret = k5_get_creds(r, K5_CREDS_CACHED);
1658     if (ret)
1659         return ret;
1660
1661     /* Acquire the Negotiate token and output it */
1662     if (ret == 0 && r->ccname != NULL)
1663         ret = mk_nego_tok(r, &nego_tok, &nego_toksz);
1664
1665     if (ret == 0) {
1666         /* Look ma', Negotiate as an OAuth-like token system! */
1667         if (r->redir)
1668             ret = resp(r, MHD_HTTP_TEMPORARY_REDIRECT, MHD_RESPMEM_PERSISTENT,
1669                        NULL, "", 0, nego_tok);
1670         else
1671             ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
1672                        "application/x-negotiate-token", nego_tok, nego_toksz,
1673                        NULL);
1674     }
1675
1676     free(nego_tok);
1677     return ret;
1678 }
1679
1680 static krb5_error_code
1681 authorize_TGT_REQ(struct bx509_request_desc *r)
1682 {
1683     krb5_principal p = NULL;
1684     krb5_error_code ret;
1685     const char *for_cname = r->for_cname ? r->for_cname : r->cname;
1686
1687     if (for_cname == r->cname || strcmp(r->cname, r->for_cname) == 0)
1688         return 0;
1689
1690     ret = krb5_parse_name(r->context, r->cname, &p);
1691     ret = hx509_request_init(r->context->hx509ctx, &r->req);
1692     if (ret)
1693         return bad_500(r, ret, "Out of resources");
1694     heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1695                      "requested_krb5PrincipalName", "%s", for_cname);
1696     ret = hx509_request_add_eku(r->context->hx509ctx, r->req,
1697                                 ASN1_OID_ID_PKEKUOID);
1698     if (ret == 0)
1699         ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
1700                                        for_cname);
1701     if (ret == 0)
1702         ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
1703     krb5_free_principal(r->context, p);
1704     hx509_request_free(&r->req);
1705     if (ret)
1706         return bad_403(r, ret, "Not authorized to requested TGT");
1707     return ret;
1708 }
1709
1710 static int
1711 get_tgt_param_cb(void *d,
1712                  enum MHD_ValueKind kind,
1713                  const char *key,
1714                  const char *val)
1715 {
1716     struct bx509_request_desc *r = d;
1717
1718     if (strcmp(key, "address") == 0 && val) {
1719         if (!krb5_config_get_bool_default(r->context, NULL,
1720                                          FALSE,
1721                                          "get-tgt", "allow_addresses", NULL)) {
1722             krb5_set_error_message(r->context, r->ret = ENOTSUP,
1723                                    "Query parameter %s not allowed", key);
1724         } else {
1725             krb5_addresses addresses;
1726
1727             r->ret = _krb5_parse_address_no_lookup(r->context, val,
1728                                                    &addresses);
1729             if (r->ret == 0)
1730                 r->ret = krb5_append_addresses(r->context, &r->tgt_addresses,
1731                                                &addresses);
1732             krb5_free_addresses(r->context, &addresses);
1733         }
1734     } else if (strcmp(key, "cname") == 0) {
1735         /* Handled upstairs */
1736         ;
1737     } else if (strcmp(key, "lifetime") == 0 && val) {
1738         r->req_life = parse_time(val, "day");
1739     } else {
1740         /* Produce error for unknown params */
1741         heim_audit_addkv((heim_svc_req_desc)r, 0, "requested_unknown", "true");
1742         krb5_set_error_message(r->context, r->ret = ENOTSUP,
1743                                "Query parameter %s not supported", key);
1744     }
1745     return r->ret == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
1746 }
1747
1748 /*
1749  * Implements /get-tgt end-point.
1750  *
1751  * Query parameters (mutually exclusive):
1752  *
1753  *  - cname=<name> (client principal name, if not the same as the authenticated
1754  *                  name, then this will be impersonated if allowed)
1755  */
1756 static krb5_error_code
1757 get_tgt(struct bx509_request_desc *r)
1758 {
1759     krb5_error_code ret;
1760     size_t bodylen;
1761     const char *fn;
1762     void *body;
1763
1764     r->for_cname = MHD_lookup_connection_value(r->connection,
1765                                                MHD_GET_ARGUMENT_KIND, "cname");
1766     if (r->for_cname && r->for_cname[0] == '\0')
1767         r->for_cname = NULL;
1768     ret = validate_token(r);
1769     if (ret == 0)
1770         ret = authorize_TGT_REQ(r);
1771     /* validate_token() and authorize_TGT_REQ() call bad_req() */
1772     if (ret)
1773         return ret;
1774
1775     r->ret = 0;
1776     (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
1777                                      get_tgt_param_cb, r);
1778     ret = r->ret;
1779
1780     /* k5_get_creds() calls bad_req() */
1781     ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
1782     if (ret)
1783         return ret;
1784
1785     fn = strchr(r->ccname, ':');
1786     if (fn == NULL)
1787         return bad_500(r, ret, "Impossible error");
1788     fn++;
1789     if ((errno = rk_undumpdata(fn, &body, &bodylen))) {
1790         (void) unlink(fn);
1791         return bad_503(r, ret, "Could not get TGT");
1792     }
1793
1794     ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
1795                "application/x-krb5-ccache", body, bodylen, NULL);
1796     free(body);
1797     return ret;
1798 }
1799
1800 static krb5_error_code
1801 health(const char *method, struct bx509_request_desc *r)
1802 {
1803     if (strcmp(method, "HEAD") == 0)
1804         return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
1805     return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL,
1806                 "To determine the health of the service, use the /bx509 "
1807                 "end-point.\n",
1808                 sizeof("To determine the health of the service, use the "
1809                        "/bx509 end-point.\n") - 1, NULL);
1810
1811 }
1812
1813 /* Implements the entirety of this REST service */
1814 static int
1815 route(void *cls,
1816       struct MHD_Connection *connection,
1817       const char *url,
1818       const char *method,
1819       const char *version,
1820       const char *upload_data,
1821       size_t *upload_data_size,
1822       void **ctx)
1823 {
1824     static int aptr = 0;
1825     struct bx509_request_desc r;
1826     int ret;
1827
1828     if (*ctx == NULL) {
1829         /*
1830          * This is the first call, right after headers were read.
1831          *
1832          * We must return quickly so that any 100-Continue might be sent with
1833          * celerity.
1834          *
1835          * We'll get called again to really do the processing.  If we handled
1836          * POSTs then we'd also get called with upload_data != NULL between the
1837          * first and last calls.  We need to keep no state between the first
1838          * and last calls, but we do need to distinguish first and last call,
1839          * so we use the ctx argument for this.
1840          */
1841         *ctx = &aptr;
1842         return MHD_YES;
1843     }
1844
1845     if ((ret = set_req_desc(connection, url, &r)))
1846         return bad_503(&r, ret, "Could not initialize request state");
1847     if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
1848         (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0))
1849         ret = health(method, &r);
1850     else if (strcmp(method, "GET") != 0)
1851         ret = bad_405(&r, method);
1852     else if (strcmp(url, "/get-cert") == 0 ||
1853              strcmp(url, "/bx509") == 0) /* old name */
1854         ret = bx509(&r);
1855     else if (strcmp(url, "/get-negotiate-token") == 0 ||
1856              strcmp(url, "/bnegotiate") == 0) /* old name */
1857         ret = bnegotiate(&r);
1858     else if (strcmp(url, "/get-tgt") == 0)
1859         ret = get_tgt(&r);
1860     else
1861         ret = bad_404(&r, url);
1862
1863     clean_req_desc(&r);
1864     return ret == -1 ? MHD_NO : MHD_YES;
1865 }
1866
1867 static struct getargs args[] = {
1868     { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
1869     { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
1870     { NULL, 'H', arg_strings, &audiences,
1871         "expected token audience(s) of bx509 service", "HOSTNAME" },
1872     { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
1873     { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
1874     { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
1875         "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
1876     { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
1877     { "cache-dir", 0, arg_string, &cache_dir,
1878         "cache directory", "DIRECTORY" },
1879     { "cert", 0, arg_string, &cert_file,
1880         "certificate file path (PEM)", "HX509-STORE" },
1881     { "private-key", 0, arg_string, &priv_key_file,
1882         "private key file path (PEM)", "HX509-STORE" },
1883     { "thread-per-client", 't', arg_flag, &thread_per_client_flag,
1884         "thread per-client", "use thread per-client" },
1885     { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
1886 };
1887
1888 static int
1889 usage(int e)
1890 {
1891     arg_printusage(args, sizeof(args) / sizeof(args[0]), "bx509",
1892         "\nServes RESTful GETs of /bx509 and /bnegotiate,\n"
1893         "performing corresponding kx509 and, possibly, PKINIT requests\n"
1894         "to the KDCs of the requested realms (or just the given REALM).\n");
1895     exit(e);
1896 }
1897
1898 static int sigpipe[2] = { -1, -1 };
1899
1900 static void
1901 sighandler(int sig)
1902 {
1903     char c = sig;
1904     while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
1905         ;
1906 }
1907
1908 static void
1909 bx509_openlog(krb5_context context,
1910               const char *svc,
1911               krb5_log_facility **fac)
1912 {
1913     char **s = NULL, **p;
1914
1915     krb5_initlog(context, "bx509d", fac);
1916     s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
1917     if (s == NULL)
1918         s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
1919     if (s) {
1920         for(p = s; *p; p++)
1921             krb5_addlog_dest(context, *fac, *p);
1922         krb5_config_free_strings(s);
1923     } else {
1924         char *ss;
1925         if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
1926             KDC_LOG_FILE) < 0)
1927             err(1, "out of memory");
1928         krb5_addlog_dest(context, *fac, ss);
1929         free(ss);
1930     }
1931     krb5_set_warn_dest(context, *fac);
1932 }
1933
1934 static const char *sysplugin_dirs[] =  {
1935 #ifdef _WIN32
1936     "$ORIGIN",
1937 #else
1938     "$ORIGIN/../lib/plugin/kdc",
1939 #endif
1940 #ifdef __APPLE__
1941     LIBDIR "/plugin/kdc",
1942 #endif
1943     NULL
1944 };
1945
1946 static void
1947 load_plugins(krb5_context context)
1948 {
1949     const char * const *dirs = sysplugin_dirs;
1950 #ifndef _WIN32
1951     char **cfdirs;
1952
1953     cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
1954     if (cfdirs)
1955         dirs = (const char * const *)cfdirs;
1956 #endif
1957
1958     /* XXX kdc? */
1959     _krb5_load_plugins(context, "kdc", (const char **)dirs);
1960
1961 #ifndef _WIN32
1962     krb5_config_free_strings(cfdirs);
1963 #endif
1964 }
1965
1966 int
1967 main(int argc, char **argv)
1968 {
1969     unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
1970     struct sockaddr_in sin;
1971     struct MHD_Daemon *previous = NULL;
1972     struct MHD_Daemon *current = NULL;
1973     struct sigaction sa;
1974     krb5_context context = NULL;
1975     MHD_socket sock = MHD_INVALID_SOCKET;
1976     char *priv_key_pem = NULL;
1977     char *cert_pem = NULL;
1978     char sig;
1979     int optidx = 0;
1980     int ret;
1981
1982     setprogname("bx509d");
1983     if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
1984         usage(1);
1985     if (help_flag)
1986         usage(0);
1987     if (version_flag) {
1988         print_version(NULL);
1989         exit(0);
1990     }
1991     if (argc > optidx) /* Add option to set a URI local part prefix? */
1992         usage(1);
1993     if (port < 0)
1994         errx(1, "Port number must be given");
1995
1996     if (audiences.num_strings == 0) {
1997         char localhost[MAXHOSTNAMELEN];
1998
1999         ret = gethostname(localhost, sizeof(localhost));
2000         if (ret == -1)
2001             errx(1, "Could not determine local hostname; use --audience");
2002
2003         if ((audiences.strings =
2004                  calloc(1, sizeof(audiences.strings[0]))) == NULL ||
2005             (audiences.strings[0] = strdup(localhost)) == NULL)
2006             err(1, "Out of memory");
2007         audiences.num_strings = 1;
2008     }
2009
2010     if (daemonize && daemon_child_fd == -1)
2011         daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
2012     daemonize = 0;
2013
2014     argc -= optidx;
2015     argv += optidx;
2016
2017     if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
2018         err(1, "Could not create thread-specific storage");
2019
2020     if ((errno = get_krb5_context(&context)))
2021         err(1, "Could not init krb5 context");
2022
2023     bx509_openlog(context, "bx509d", &logfac);
2024     load_plugins(context);
2025
2026     if (cache_dir == NULL) {
2027         char *s = NULL;
2028
2029         if (asprintf(&s, "%s/bx509d-XXXXXX",
2030                      getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2031             s == NULL ||
2032             (cache_dir = mkdtemp(s)) == NULL)
2033             err(1, "could not create temporary cache directory");
2034         if (verbose_counter)
2035             fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
2036         atexit(rm_cache_dir);
2037         setenv("TMPDIR", cache_dir, 1);
2038     }
2039
2040     generate_key(context->hx509ctx, "impersonation", "rsa", 2048, &impersonation_key_fn);
2041
2042 again:
2043     if (cert_file && !priv_key_file)
2044         priv_key_file = cert_file;
2045
2046     if (cert_file) {
2047         hx509_cursor cursor = NULL;
2048         hx509_certs certs = NULL;
2049         hx509_cert cert = NULL;
2050         time_t min_cert_life = 0;
2051         size_t len;
2052         void *s;
2053
2054         ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
2055         if (ret == 0)
2056             ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
2057         while (ret == 0 &&
2058                (ret = hx509_certs_next_cert(context->hx509ctx, certs,
2059                                             cursor, &cert)) == 0 && cert) {
2060             time_t notAfter = 0;
2061
2062             if (!hx509_cert_have_private_key_only(cert) &&
2063                 (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
2064                 errx(1, "One or more certificates in %s are expired",
2065                      cert_file);
2066             if (notAfter) {
2067                 notAfter -= time(NULL);
2068                 if (notAfter < 600)
2069                     warnx("One or more certificates in %s expire soon",
2070                           cert_file);
2071                 /* Reload 5 minutes prior to expiration */
2072                 if (notAfter < min_cert_life || min_cert_life < 1)
2073                     min_cert_life = notAfter;
2074             }
2075             hx509_cert_free(cert);
2076         }
2077         if (certs)
2078             (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
2079         if (min_cert_life > 4)
2080             alarm(min_cert_life >> 1);
2081         hx509_certs_free(&certs);
2082         if (ret)
2083             hx509_err(context->hx509ctx, 1, ret,
2084                       "could not read certificate from %s", cert_file);
2085
2086         if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
2087             (cert_pem = strndup(s, len)) == NULL)
2088             err(1, "could not read certificate from %s", cert_file);
2089         if (strlen(cert_pem) != len)
2090             err(1, "NULs in certificate file contents: %s", cert_file);
2091         free(s);
2092     }
2093
2094     if (priv_key_file) {
2095         size_t len;
2096         void *s;
2097
2098         if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
2099             (priv_key_pem = strndup(s, len)) == NULL)
2100             err(1, "could not read private key from %s", priv_key_file);
2101         if (strlen(priv_key_pem) != len)
2102             err(1, "NULs in private key file contents: %s", priv_key_file);
2103         free(s);
2104     }
2105
2106     if (verbose_counter > 1)
2107         flags |= MHD_USE_DEBUG;
2108     if (thread_per_client_flag)
2109         flags |= MHD_USE_THREAD_PER_CONNECTION;
2110
2111
2112     if (pipe(sigpipe) == -1)
2113         err(1, "Could not set up key/cert reloading");
2114     memset(&sa, 0, sizeof(sa));
2115     sa.sa_handler = sighandler;
2116     if (reverse_proxied_flag) {
2117         /*
2118          * We won't use TLS in the reverse proxy case, so no need to reload
2119          * certs.  But we'll still read them if given, and alarm() will get
2120          * called.
2121          */
2122         (void) signal(SIGHUP, SIG_IGN);
2123         (void) signal(SIGUSR1, SIG_IGN);
2124         (void) signal(SIGALRM, SIG_IGN);
2125     } else {
2126         (void) sigaction(SIGHUP, &sa, NULL);    /* Reload key & cert */
2127         (void) sigaction(SIGUSR1, &sa, NULL);   /* Reload key & cert */
2128         (void) sigaction(SIGALRM, &sa, NULL);   /* Reload key & cert */
2129     }
2130     (void) sigaction(SIGINT, &sa, NULL);    /* Graceful shutdown */
2131     (void) sigaction(SIGTERM, &sa, NULL);   /* Graceful shutdown */
2132     (void) signal(SIGPIPE, SIG_IGN);
2133
2134     if (previous)
2135         sock = MHD_quiesce_daemon(previous);
2136
2137     if (reverse_proxied_flag) {
2138         /*
2139          * XXX IPv6 too.  Create the sockets and tell MHD_start_daemon() about
2140          * them.
2141          */
2142         sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2143         sin.sin_family = AF_INET;
2144         sin.sin_port = htons(port);
2145         current = MHD_start_daemon(flags, port,
2146                                    NULL, NULL,
2147                                    route, (char *)NULL,
2148                                    MHD_OPTION_SOCK_ADDR, &sin,
2149                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2150                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2151                                    MHD_OPTION_END);
2152     } else if (sock != MHD_INVALID_SOCKET) {
2153         /*
2154          * Certificate/key rollover: reuse the listen socket returned by
2155          * MHD_quiesce_daemon().
2156          */
2157         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2158                                    NULL, NULL,
2159                                    route, (char *)NULL,
2160                                    MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2161                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2162                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2163                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2164                                    MHD_OPTION_LISTEN_SOCKET, sock,
2165                                    MHD_OPTION_END);
2166         sock = MHD_INVALID_SOCKET;
2167     } else {
2168         current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2169                                    NULL, NULL,
2170                                    route, (char *)NULL,
2171                                    MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2172                                    MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2173                                    MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2174                                    MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2175                                    MHD_OPTION_END);
2176     }
2177     if (current == NULL)
2178         err(1, "Could not start bx509 REST service");
2179
2180     if (previous) {
2181         MHD_stop_daemon(previous);
2182         previous = NULL;
2183     }
2184
2185     if (verbose_counter)
2186         fprintf(stderr, "Ready!\n");
2187     if (daemon_child_fd != -1)
2188         roken_detach_finish(NULL, daemon_child_fd);
2189
2190     /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
2191     while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
2192            errno == EINTR)
2193         ;
2194
2195     free(priv_key_pem);
2196     free(cert_pem);
2197     priv_key_pem = NULL;
2198     cert_pem = NULL;
2199
2200     if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
2201         /* Reload certs and restart service gracefully */
2202         previous = current;
2203         current = NULL;
2204         goto again;
2205     }
2206
2207     MHD_stop_daemon(current);
2208     _krb5_unload_plugins(context, "kdc");
2209     pthread_key_delete(k5ctx);
2210     return 0;
2211 }