xref: /lighttpd1.4/src/mod_authn_gssapi.c (revision 66698469)
1 #include "first.h"
2 
3 /* mod_authn_gssapi
4  *
5  * - provides http_auth_backend_t "gssapi" for HTTP auth Basic realm="Kerberos"
6  * - provides http_auth_scheme_t "Negotiate"
7  * - (does not provide http_auth_backend_t for HTTP auth Digest)
8  *
9  * Note: Credentials cache (KRB5CCNAME) is exported into CGI and SSI environment
10  *       as well as passed to FastCGI and SCGI (useful if on same machine
11  *       and running under same user account with access to KRB5CCNAME file).
12  *       Credentials are clean up at the end of each request.
13  *
14  * LIMITATIONS:
15  * - no rate limiting of auth requests, so remote attacker can send many auth
16  *   requests very quickly if attempting brute force password cracking attack
17  *
18  * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
19  * - Kerberos auth is synchronous and blocks waiting for response
20  *   TODO: attempt async?
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include <krb5.h>
28 #include <gssapi.h>
29 #include <gssapi/gssapi_krb5.h>
30 
31 #include "mod_auth_api.h"
32 #include "base.h"
33 #include "base64.h"
34 #include "fdevent.h"
35 #include "http_header.h"
36 #include "log.h"
37 #include "plugin.h"
38 
39 typedef struct {
40     const buffer *auth_gssapi_keytab;
41     const buffer *auth_gssapi_principal;
42     unsigned int auth_gssapi_store_creds;
43 } plugin_config;
44 
45 typedef struct {
46     PLUGIN_DATA;
47     plugin_config defaults;
48     plugin_config conf;
49 } plugin_data;
50 
51 static handler_t mod_authn_gssapi_check(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
52 static handler_t mod_authn_gssapi_basic(request_st *r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw);
53 
54 INIT_FUNC(mod_authn_gssapi_init) {
55     static http_auth_scheme_t http_auth_scheme_gssapi =
56       { "gssapi", mod_authn_gssapi_check, NULL };
57     static http_auth_backend_t http_auth_backend_gssapi =
58       { "gssapi", mod_authn_gssapi_basic, NULL, NULL };
59     plugin_data *p = calloc(1, sizeof(*p));
60 
61     /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */
62     http_auth_scheme_gssapi.p_d = p;
63     http_auth_scheme_set(&http_auth_scheme_gssapi);
64     http_auth_backend_gssapi.p_d = p;
65     http_auth_backend_set(&http_auth_backend_gssapi);
66 
67     return p;
68 }
69 
70 static void mod_authn_gssapi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
71     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
72       case 0: /* auth.backend.gssapi.keytab */
73         pconf->auth_gssapi_keytab = cpv->v.b;
74         break;
75       case 1: /* auth.backend.gssapi.principal */
76         pconf->auth_gssapi_principal = cpv->v.b;
77         break;
78       case 2: /* auth.backend.gssapi.store-creds */
79         pconf->auth_gssapi_store_creds = cpv->v.u;
80         break;
81       default:/* should not happen */
82         return;
83     }
84 }
85 
86 static void mod_authn_gssapi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
87     do {
88         mod_authn_gssapi_merge_config_cpv(pconf, cpv);
89     } while ((++cpv)->k_id != -1);
90 }
91 
92 static void mod_authn_gssapi_patch_config(request_st * const r, plugin_data * const p) {
93     p->conf = p->defaults; /* copy small struct instead of memcpy() */
94     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
95     for (int i = 1, used = p->nconfig; i < used; ++i) {
96         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
97             mod_authn_gssapi_merge_config(&p->conf,
98                                         p->cvlist + p->cvlist[i].v.u2[0]);
99     }
100 }
101 
102 SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults) {
103     static const config_plugin_keys_t cpk[] = {
104       { CONST_STR_LEN("auth.backend.gssapi.keytab"),
105         T_CONFIG_STRING,
106         T_CONFIG_SCOPE_CONNECTION }
107      ,{ CONST_STR_LEN("auth.backend.gssapi.principal"),
108         T_CONFIG_STRING,
109         T_CONFIG_SCOPE_CONNECTION }
110      ,{ CONST_STR_LEN("auth.backend.gssapi.store-creds"),
111         T_CONFIG_BOOL,
112         T_CONFIG_SCOPE_CONNECTION }
113      ,{ NULL, 0,
114         T_CONFIG_UNSET,
115         T_CONFIG_SCOPE_UNSET }
116     };
117 
118     plugin_data * const p = p_d;
119     if (!config_plugin_values_init(srv, p, cpk, "mod_authn_gssapi"))
120         return HANDLER_ERROR;
121 
122     /* process and validate config directives
123      * (init i to 0 if global context; to 1 to skip empty global context) */
124     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
125         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
126         for (; -1 != cpv->k_id; ++cpv) {
127             switch (cpv->k_id) {
128               case 0: /* auth.backend.gssapi.keytab */
129                 if (buffer_is_blank(cpv->v.b))
130                     cpv->v.b = NULL;
131                 break;
132               case 1: /* auth.backend.gssapi.principal */
133               case 2: /* auth.backend.gssapi.store-creds */
134               default:/* should not happen */
135                 break;
136             }
137         }
138     }
139 
140     /* default enabled for backwards compatibility; disable in future */
141     p->defaults.auth_gssapi_store_creds = 1;
142 
143     /* initialize p->defaults from global config context */
144     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
145         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
146         if (-1 != cpv->k_id)
147             mod_authn_gssapi_merge_config(&p->defaults, cpv);
148     }
149 
150     return HANDLER_GO_ON;
151 }
152 
153 __attribute_cold__
154 static handler_t mod_authn_gssapi_send_400_bad_request (request_st * const r)
155 {
156     r->http_status = 400;
157     r->handler_module = NULL;
158     return HANDLER_FINISHED;
159 }
160 
161 __attribute_cold__
162 static void mod_authn_gssapi_log_gss_error(log_error_st *errh, const char *file, unsigned int line, const char *func, const char *extra, OM_uint32 err_maj, OM_uint32 err_min)
163 {
164     buffer * const msg = buffer_init();
165     OM_uint32 maj_stat, min_stat;
166     OM_uint32 msg_ctx = 0;
167     gss_buffer_desc status_string;
168 
169     buffer_copy_string(msg, func);
170     buffer_append_char(msg, '(');
171     if (extra) buffer_append_string(msg, extra);
172     buffer_append_string_len(msg, CONST_STR_LEN("):"));
173 
174     do {
175         maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE,
176                                       GSS_C_NO_OID, &msg_ctx, &status_string);
177         if (GSS_ERROR(maj_stat))
178             break;
179 
180         buffer_append_string(msg, status_string.value);
181         gss_release_buffer(&min_stat, &status_string);
182 
183         maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE,
184                                       GSS_C_NULL_OID, &msg_ctx, &status_string);
185         if (!GSS_ERROR(maj_stat)) {
186             buffer_append_string_len(msg, CONST_STR_LEN(" ("));
187             buffer_append_string(msg, status_string.value);
188             buffer_append_char(msg, ')');
189             gss_release_buffer(&min_stat, &status_string);
190         }
191     } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
192 
193     log_error(errh, file, line, "%s", msg->ptr);
194     buffer_free(msg);
195 }
196 
197 __attribute_cold__
198 static void mod_authn_gssapi_log_krb5_error(log_error_st *errh, const char *file, unsigned int line, const char *func, const char *extra, krb5_context context, int code)
199 {
200     UNUSED(context);
201     /*(extra might be NULL)*/
202     log_error(errh, file, line,
203       "%s (%s): %s", func, extra ? extra : "", error_message(code));
204 }
205 
206 static int mod_authn_gssapi_create_krb5_ccache(request_st * const r, plugin_data * const p, krb5_context kcontext, krb5_principal princ, krb5_ccache * const ccache)
207 {
208     char kccname[]         = "FILE:/tmp/krb5cc_gssapi_XXXXXX";
209     char * const ccname    = kccname + sizeof("FILE:")-1;
210     /*(future: might consider using server.upload-dirs instead of /tmp)*/
211     int fd = fdevent_mkostemp(ccname, 0);
212     if (fd < 0) {
213         log_perror(r->conf.errh, __FILE__, __LINE__, "mkstemp(): %s", ccname);
214         return -1;
215     }
216     close(fd);
217 
218     do {
219         krb5_error_code problem;
220 
221         problem = krb5_cc_resolve(kcontext, kccname, ccache);
222         if (problem) {
223             mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem);
224             break;
225         }
226 
227         problem = krb5_cc_initialize(kcontext, *ccache, princ);
228         if (problem) {
229             mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", kccname, kcontext, problem);
230             break;
231         }
232 
233         buffer * const vb = http_header_env_set_ptr(r, CONST_STR_LEN("KRB5CCNAME"));
234         r->plugin_ctx[p->id] = vb;
235         const size_t ccnamelen = sizeof(kccname)-sizeof("FILE:");
236         buffer_copy_string_len(vb, ccname, ccnamelen);
237         http_header_request_set(r, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen);
238 
239         return 0;
240 
241     } while (0);
242 
243     if (*ccache) {
244         krb5_cc_destroy(kcontext, *ccache);
245         *ccache = NULL;
246     }
247     unlink(ccname);
248 
249     return -1;
250 }
251 
252 /*
253  * HTTP auth Negotiate
254  */
255 
256 __attribute_cold__
257 static handler_t mod_authn_gssapi_send_500_server_error (request_st * const r)
258 {
259     r->http_status = 500;
260     r->handler_module = NULL;
261     return HANDLER_FINISHED;
262 }
263 
264 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (request_st * const r)
265 {
266     r->http_status = 401;
267     r->handler_module = NULL;
268     http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE,
269                              CONST_STR_LEN("WWW-Authenticate"),
270                              CONST_STR_LEN("Negotiate"));
271     return HANDLER_FINISHED;
272 }
273 
274 static int mod_authn_gssapi_store_gss_creds(request_st * const r, plugin_data * const p, char * const princ_name, gss_cred_id_t delegated_cred)
275 {
276     OM_uint32 maj_stat, min_stat;
277     krb5_principal princ = NULL;
278     krb5_ccache ccache   = NULL;
279     krb5_error_code problem;
280     krb5_context context;
281 
282     problem = krb5_init_context(&context);
283     if (problem) {
284         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem);
285         return 0;
286     }
287 
288     problem = krb5_parse_name(context, princ_name, &princ);
289     if (problem) {
290         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem);
291         goto end;
292     }
293 
294     if (mod_authn_gssapi_create_krb5_ccache(r, p, context, princ, &ccache))
295         goto end;
296 
297     maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
298     if (GSS_ERROR(maj_stat)) {
299         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat);
300         goto end;
301     }
302 
303     krb5_cc_close(context, ccache);
304     krb5_free_principal(context, princ);
305     krb5_free_context(context);
306     return 1;
307 
308     end:
309         if (princ)
310             krb5_free_principal(context, princ);
311         if (ccache)
312             krb5_cc_destroy(context, ccache);
313         krb5_free_context(context);
314 
315         return 0;
316 }
317 
318 static handler_t mod_authn_gssapi_check_spnego(request_st * const r, plugin_data * const p, const http_auth_require_t * const require, const char * const realm_str)
319 {
320     OM_uint32 st_major, st_minor, acc_flags;
321     gss_buffer_desc token_s   = GSS_C_EMPTY_BUFFER;
322     gss_buffer_desc token_in  = GSS_C_EMPTY_BUFFER;
323     gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER;
324     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
325     gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL;
326     gss_ctx_id_t context      = GSS_C_NO_CONTEXT;
327     gss_name_t server_name    = GSS_C_NO_NAME;
328     gss_name_t client_name    = GSS_C_NO_NAME;
329 
330     buffer *sprinc;
331     handler_t rc = HANDLER_ERROR;
332 
333     buffer *t_in = buffer_init();
334     if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) {
335         log_error(r->conf.errh, __FILE__, __LINE__, "decoding GSSAPI authentication header failed %s", realm_str);
336         buffer_free(t_in);
337         return mod_authn_gssapi_send_400_bad_request(r);
338     }
339 
340     mod_authn_gssapi_patch_config(r, p);
341 
342     if (p->conf.auth_gssapi_keytab) {
343         /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
344          *     be used, instead of putenv() of KRB5_KTNAME=...?  See mod_authn_gssapi_basic() */
345         /* ??? Should KRB5_KTNAME go into r->env instead ??? */
346         /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */
347         buffer ktname;
348         memset(&ktname, 0, sizeof(ktname));
349         buffer_copy_string_len(&ktname, CONST_STR_LEN("KRB5_KTNAME="));
350         buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab);
351         putenv(ktname.ptr);
352         /* ktname.ptr becomes part of the environment, do not free */
353     }
354 
355     sprinc = buffer_init();
356     if (p->conf.auth_gssapi_principal)
357         buffer_copy_buffer(sprinc, p->conf.auth_gssapi_principal);
358     if (strchr(sprinc->ptr, '/') == NULL) {
359         /*(copy HTTP Host, omitting port if port is present)*/
360         /* ??? Should r->server_name be used if http_host not present?
361          * ??? What if r->server_name is not set?
362          * ??? Will this work below if IPv6 provided in Host?  probably not */
363         if (r->http_host && !buffer_is_blank(r->http_host))
364             buffer_append_str2(sprinc, CONST_STR_LEN("/"),
365                                        r->http_host->ptr,
366                                        strcspn(r->http_host->ptr, ":"));
367     }
368     if (strchr(sprinc->ptr, '@') == NULL)
369         buffer_append_str2(sprinc, CONST_STR_LEN("@"),
370                                    BUF_PTR_LEN(require->realm));
371     /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/
372     /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/
373     #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name
374 
375     token_s.value = sprinc->ptr;
376     token_s.length = buffer_clen(sprinc);
377     st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name);
378     if (GSS_ERROR(st_major)) {
379         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor);
380         goto end;
381     }
382 
383     memset(&token_s, 0, sizeof(token_s));
384     st_major = gss_display_name(&st_minor, server_name, &token_s, NULL);
385     if (GSS_ERROR(st_major)) {
386         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
387         goto end;
388     }
389 
390     /* acquire server's own credentials */
391     st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL);
392     if (GSS_ERROR(st_major)) {
393         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor);
394         goto end;
395     }
396 
397     /* accept the user's context */
398     token_in.length = buffer_clen(t_in);
399     token_in.value = t_in->ptr;
400     st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS,
401                                       &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred);
402     if (GSS_ERROR(st_major)) {
403         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor);
404         goto end;
405     }
406 
407     /* fetch the username */
408     st_major = gss_display_name(&st_minor, client_name, &token_out, NULL);
409     if (GSS_ERROR(st_major)) {
410         mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
411         goto end;
412     }
413 
414     if (!(acc_flags & GSS_C_CONF_FLAG)) {
415         log_error(r->conf.errh, __FILE__, __LINE__, "No confidentiality for user: %s", (char *)token_out.value);
416         goto end;
417     }
418 
419     /* check the allow-rules */
420     if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) {
421         goto end;
422     }
423 
424     if (p->conf.auth_gssapi_store_creds) {
425         if (!(acc_flags & GSS_C_DELEG_FLAG)) {
426             log_error(r->conf.errh, __FILE__, __LINE__, "Unable to delegate credentials for user: %s", (char *)token_out.value);
427             goto end;
428         }
429         else if (!mod_authn_gssapi_store_gss_creds(r, p, token_out.value, client_cred)) {
430             rc = mod_authn_gssapi_send_500_server_error(r);
431             goto end;
432         }
433     }
434 
435     http_auth_setenv(r, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI"));
436     rc = HANDLER_GO_ON; /* success */
437 
438     end:
439         buffer_free(t_in);
440         buffer_free(sprinc);
441 
442         if (context != GSS_C_NO_CONTEXT)
443             gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER);
444 
445         if (client_cred != GSS_C_NO_CREDENTIAL)
446             gss_release_cred(&st_minor, &client_cred);
447         if (server_cred != GSS_C_NO_CREDENTIAL)
448             gss_release_cred(&st_minor, &server_cred);
449 
450         if (client_name != GSS_C_NO_NAME)
451             gss_release_name(&st_minor, &client_name);
452         if (server_name != GSS_C_NO_NAME)
453             gss_release_name(&st_minor, &server_name);
454 
455         if (token_s.length)
456             gss_release_buffer(&st_minor, &token_s);
457         /* if (token_in.length)
458          *    gss_release_buffer(&st_minor, &token_in); */
459         if (token_out.length)
460             gss_release_buffer(&st_minor, &token_out);
461 
462         return rc != HANDLER_ERROR ? rc : mod_authn_gssapi_send_401_unauthorized_negotiate(r);
463 }
464 
465 static handler_t mod_authn_gssapi_check (request_st * const r, void *p_d, const struct http_auth_require_t * const require, const struct http_auth_backend_t * const backend)
466 {
467     const buffer *vb = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
468 
469     UNUSED(backend);
470     if (NULL == vb) {
471         return mod_authn_gssapi_send_401_unauthorized_negotiate(r);
472     }
473 
474     if (!buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Negotiate "))) {
475         return mod_authn_gssapi_send_400_bad_request(r);
476     }
477 
478     return mod_authn_gssapi_check_spnego(r, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1);
479 }
480 
481 /*
482  * HTTP auth Basic realm="kerberos"
483  */
484 
485 static krb5_error_code mod_authn_gssapi_verify_krb5_init_creds(krb5_context context, krb5_creds *creds, krb5_principal ap_req_server, krb5_keytab ap_req_keytab, log_error_st *errh)
486 {
487     krb5_error_code ret;
488     krb5_data req;
489     krb5_ccache local_ccache       = NULL;
490     krb5_creds *new_creds          = NULL;
491     krb5_auth_context auth_context = NULL;
492     krb5_keytab keytab             = NULL;
493     char *server_name;
494 
495     memset(&req, 0, sizeof(req));
496 
497     if (ap_req_keytab == NULL) {
498         ret = krb5_kt_default(context, &keytab);
499         if (ret)
500             return ret;
501     } else
502         keytab = ap_req_keytab;
503 
504     ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache);
505     if (ret) {
506         log_error(errh, __FILE__, __LINE__, "krb5_cc_resolve() failed when verifying KDC");
507         /* return ret; */
508         goto end;
509     }
510 
511     ret = krb5_cc_initialize(context, local_ccache, creds->client);
512     if (ret) {
513         log_error(errh, __FILE__, __LINE__, "krb5_cc_initialize() failed when verifying KDC");
514         goto end;
515     }
516 
517     ret = krb5_cc_store_cred(context, local_ccache, creds);
518     if (ret) {
519         log_error(errh, __FILE__, __LINE__, "krb5_cc_store_cred() failed when verifying KDC");
520         goto end;
521     }
522 
523     ret = krb5_unparse_name(context, ap_req_server, &server_name);
524     if (ret) {
525         log_error(errh, __FILE__, __LINE__, "krb5_unparse_name() failed when verifying KDC");
526         goto end;
527     }
528     krb5_free_unparsed_name(context, server_name);
529 
530     if (!krb5_principal_compare(context, ap_req_server, creds->server)) {
531         krb5_creds match_cred;
532 
533         memset(&match_cred, 0, sizeof(match_cred));
534 
535         match_cred.client = creds->client;
536         match_cred.server = ap_req_server;
537 
538         ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds);
539         if (ret) {
540             log_error(errh, __FILE__, __LINE__, "krb5_get_credentials() failed when verifying KDC");
541             goto end;
542         }
543         creds = new_creds;
544     }
545 
546     ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req);
547     if (ret) {
548         log_error(errh, __FILE__, __LINE__, "krb5_mk_req_extended() failed when verifying KDC");
549         goto end;
550     }
551 
552     krb5_auth_con_free(context, auth_context);
553     auth_context = NULL;
554     ret = krb5_auth_con_init(context, &auth_context);
555     if (ret) {
556         log_error(errh, __FILE__, __LINE__, "krb5_auth_con_init() failed when verifying KDC");
557         goto end;
558     }
559 
560     /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
561     krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
562     ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL);
563     if (ret) {
564         log_error(errh, __FILE__, __LINE__, "krb5_rd_req() failed when verifying KDC");
565         goto end;
566     }
567 
568     end:
569         krb5_free_data_contents(context, &req);
570         if (auth_context)
571             krb5_auth_con_free(context, auth_context);
572         if (new_creds)
573             krb5_free_creds(context, new_creds);
574         if (ap_req_keytab == NULL && keytab)
575             krb5_kt_close(context, keytab);
576         if (local_ccache)
577             krb5_cc_destroy(context, local_ccache);
578 
579     return ret;
580 }
581 
582 static int mod_authn_gssapi_store_krb5_creds(request_st * const r, plugin_data * const p,
583                                              krb5_context kcontext, krb5_ccache delegated_cred)
584 {
585     krb5_error_code problem;
586     krb5_principal princ = NULL;
587     krb5_ccache ccache   = NULL;
588 
589     problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
590     if (problem) {
591         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem);
592         goto end;
593     }
594 
595     if (mod_authn_gssapi_create_krb5_ccache(r, p, kcontext, princ, &ccache)) {
596         goto end;
597     }
598 
599     problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
600     if (problem) {
601         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem);
602         goto end;
603     }
604 
605     krb5_free_principal(kcontext, princ);
606     krb5_cc_close(kcontext, ccache);
607     return 0;
608 
609     end:
610         if (princ)
611             krb5_free_principal(kcontext, princ);
612         if (ccache)
613             krb5_cc_destroy(kcontext, ccache);
614         return -1;
615 }
616 
617 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (request_st * const r)
618 {
619     r->http_status = 401;
620     r->handler_module = NULL;
621     http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE,
622                              CONST_STR_LEN("WWW-Authenticate"),
623                              CONST_STR_LEN("Basic realm=\"Kerberos\""));
624     return HANDLER_FINISHED;
625 }
626 
627 static handler_t mod_authn_gssapi_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw)
628 {
629     krb5_context kcontext  = NULL;
630     krb5_keytab keytab     = NULL;
631     krb5_principal s_princ = NULL;
632     krb5_principal c_princ = NULL;
633     krb5_creds c_creds;
634     krb5_ccache c_ccache   = NULL;
635     krb5_ccache ret_ccache = NULL;
636     krb5_error_code code;
637     int ret;
638     buffer *sprinc;
639     buffer *user_at_realm  = NULL;
640     plugin_data * const p = (plugin_data *)p_d;
641 
642     if (*pw == '\0') {
643         log_error(r->conf.errh, __FILE__, __LINE__, "Empty passwords are not accepted");
644         return mod_authn_gssapi_send_401_unauthorized_basic(r);
645     }
646 
647     mod_authn_gssapi_patch_config(r, p);
648 
649     code = krb5_init_context(&kcontext);
650     if (code) {
651         log_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context(): %d", code);
652         return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/
653     }
654 
655     if (!p->conf.auth_gssapi_keytab) {
656         log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend.gssapi.keytab not configured");
657         return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/
658     }
659 
660     code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
661     if (code) {
662         log_error(r->conf.errh, __FILE__, __LINE__, "krb5_kt_resolve(): %d %s", code, p->conf.auth_gssapi_keytab->ptr);
663         return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/
664     }
665 
666     sprinc = buffer_init();
667     if (p->conf.auth_gssapi_principal)
668         buffer_copy_buffer(sprinc, p->conf.auth_gssapi_principal);
669     if (strchr(sprinc->ptr, '/') == NULL) {
670         /*(copy HTTP Host, omitting port if port is present)*/
671         /* ??? Should r->server_name be used if http_host not present?
672          * ??? What if r->server_name is not set?
673          * ??? Will this work below if IPv6 provided in Host?  probably not */
674         if (r->http_host && !buffer_is_blank(r->http_host))
675             buffer_append_str2(sprinc, CONST_STR_LEN("/"),
676                                        r->http_host->ptr,
677                                        strcspn(r->http_host->ptr, ":"));
678     }
679 
680     /*(init c_creds before anything which might krb5_free_cred_contents())*/
681     memset(&c_creds, 0, sizeof(c_creds));
682 
683     ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ);
684     if (ret) {
685         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret);
686         ret = -1;
687         goto end;
688     }
689 
690     if (strchr(username->ptr, '@') == NULL) {
691         buffer_copy_buffer((user_at_realm = buffer_init()), username);
692         buffer_append_str2(user_at_realm, CONST_STR_LEN("@"),
693                                           BUF_PTR_LEN(require->realm));
694     }
695 
696     ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ);
697     if (ret) {
698         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret);
699         if (user_at_realm) buffer_free(user_at_realm);
700         ret = -1;
701         goto end;
702     }
703     if (user_at_realm) buffer_free(user_at_realm);
704     /* XXX: if the qualified username with @realm should be in REMOTE_USER,
705      * then http_auth_backend_t basic interface needs to change to pass
706      * modifiable buffer *username, but note that const char *pw follows
707      * in the truncated buffer *username, so pw would need to be copied
708      * before modifying buffer *username */
709 
710     /*
711      * char *name = NULL;
712      * ret = krb5_unparse_name(kcontext, c_princ, &name);
713      * if (ret == 0) {
714      *    log_error(r->conf.errh, __FILE__, __LINE__, "Trying to get TGT for user: %s password: %s", username->ptr, pw);
715      * }
716      * krb5_free_unparsed_name(kcontext, name);
717      */
718 
719     ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL);
720     if (ret) {
721         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret);
722         goto end;
723     }
724 
725     ret = mod_authn_gssapi_verify_krb5_init_creds(kcontext, &c_creds, s_princ, keytab, r->conf.errh);
726     if (ret) {
727         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret);
728         goto end;
729     }
730 
731     if (!p->conf.auth_gssapi_store_creds) goto end;
732 
733     ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache);
734     if (ret) {
735         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret);
736         goto end;
737     }
738 
739     ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ);
740     if (ret) {
741         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret);
742         goto end;
743     }
744 
745     ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds);
746     if (ret) {
747         mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret);
748         goto end;
749     }
750 
751     c_ccache = ret_ccache;
752     ret_ccache = NULL;
753 
754     end:
755 
756         krb5_free_cred_contents(kcontext, &c_creds);
757         if (ret_ccache)
758             krb5_cc_destroy(kcontext, ret_ccache);
759 
760         if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(r, p, kcontext, c_ccache))) {
761             log_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_store_krb5_creds failed for %s", username->ptr);
762         }
763 
764         buffer_free(sprinc);
765         if (c_princ)
766             krb5_free_principal(kcontext, c_princ);
767         if (s_princ)
768             krb5_free_principal(kcontext, s_princ);
769         if (c_ccache)
770             krb5_cc_destroy(kcontext, c_ccache);
771         if (keytab)
772             krb5_kt_close(kcontext, keytab);
773 
774         krb5_free_context(kcontext);
775 
776         if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){
777             return HANDLER_GO_ON;
778         }
779         else {
780             /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
781             log_error(r->conf.errh, __FILE__, __LINE__,
782               "password doesn't match for %s username: %s IP: %s",
783               r->uri.path.ptr, username->ptr, r->con->dst_addr_buf.ptr);
784             return mod_authn_gssapi_send_401_unauthorized_basic(r);
785         }
786 }
787 
788 
789 REQUEST_FUNC(mod_authn_gssapi_handle_reset) {
790     plugin_data *p = (plugin_data *)p_d;
791     buffer * const ccname = (buffer *)r->plugin_ctx[p->id];
792     if (NULL != ccname) {
793         r->plugin_ctx[p->id] = NULL;
794         unlink(ccname->ptr);
795     }
796 
797     return HANDLER_GO_ON;
798 }
799 
800 int mod_authn_gssapi_plugin_init(plugin *p);
801 int mod_authn_gssapi_plugin_init(plugin *p) {
802     p->version     = LIGHTTPD_VERSION_ID;
803     p->name        = "authn_gssapi";
804     p->init        = mod_authn_gssapi_init;
805     p->set_defaults= mod_authn_gssapi_set_defaults;
806     p->handle_request_reset = mod_authn_gssapi_handle_reset;
807 
808     return 0;
809 }
810