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