xref: /lighttpd1.4/src/mod_authn_gssapi.c (revision 49e9f0ac)
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 "md5.h"
34 #include "base64.h"
35 
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 typedef struct {
42     buffer *auth_gssapi_keytab;
43     buffer *auth_gssapi_principal;
44 } plugin_config;
45 
46 typedef struct {
47     PLUGIN_DATA;
48     plugin_config **config_storage;
49     plugin_config conf;
50 } plugin_data;
51 
52 static handler_t mod_authn_gssapi_check(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
53 static handler_t mod_authn_gssapi_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw);
54 
55 INIT_FUNC(mod_authn_gssapi_init) {
56     static http_auth_scheme_t http_auth_scheme_gssapi =
57       { "gssapi", mod_authn_gssapi_check, NULL };
58     static http_auth_backend_t http_auth_backend_gssapi =
59       { "gssapi", mod_authn_gssapi_basic, NULL, NULL };
60     plugin_data *p = calloc(1, sizeof(*p));
61 
62     /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */
63     http_auth_scheme_gssapi.p_d = p;
64     http_auth_scheme_set(&http_auth_scheme_gssapi);
65     http_auth_backend_gssapi.p_d = p;
66     http_auth_backend_set(&http_auth_backend_gssapi);
67 
68     return p;
69 }
70 
71 FREE_FUNC(mod_authn_gssapi_free) {
72     plugin_data *p = p_d;
73 
74     UNUSED(srv);
75 
76     if (!p) return HANDLER_GO_ON;
77 
78     if (p->config_storage) {
79         size_t i;
80         for (i = 0; i < srv->config_context->used; i++) {
81             plugin_config *s = p->config_storage[i];
82 
83             if (NULL == s) continue;
84 
85             buffer_free(s->auth_gssapi_keytab);
86             buffer_free(s->auth_gssapi_principal);
87 
88             free(s);
89         }
90         free(p->config_storage);
91     }
92 
93     free(p);
94 
95     return HANDLER_GO_ON;
96 }
97 
98 SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults) {
99     plugin_data *p = p_d;
100     size_t i;
101     config_values_t cv[] = {
102         { "auth.backend.gssapi.keytab",     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
103         { "auth.backend.gssapi.principal",  NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
104         { NULL,                             NULL, T_CONFIG_UNSET,  T_CONFIG_SCOPE_UNSET }
105     };
106 
107     p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
108 
109     for (i = 0; i < srv->config_context->used; i++) {
110         data_config const* config = (data_config const*)srv->config_context->data[i];
111         plugin_config *s;
112 
113         s = calloc(1, sizeof(plugin_config));
114 
115         s->auth_gssapi_keytab = buffer_init();
116         s->auth_gssapi_principal = buffer_init();
117 
118         cv[0].destination = s->auth_gssapi_keytab;
119         cv[1].destination = s->auth_gssapi_principal;
120 
121         p->config_storage[i] = s;
122 
123         if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
124             return HANDLER_ERROR;
125         }
126     }
127 
128     return HANDLER_GO_ON;
129 }
130 
131 #define PATCH(x) \
132     p->conf.x = s->x;
133 static int mod_authn_gssapi_patch_connection(server *srv, connection *con, plugin_data *p)
134 {
135     size_t i, j;
136     plugin_config *s = p->config_storage[0];
137 
138     PATCH(auth_gssapi_keytab);
139     PATCH(auth_gssapi_principal);
140 
141     /* skip the first, the global context */
142     for (i = 1; i < srv->config_context->used; i++) {
143         data_config *dc = (data_config *)srv->config_context->data[i];
144         s = p->config_storage[i];
145 
146         /* condition didn't match */
147         if (!config_check_cond(srv, con, dc)) continue;
148 
149         /* merge config */
150         for (j = 0; j < dc->value->used; j++) {
151             data_unset *du = dc->value->data[j];
152 
153             if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.keytab"))) {
154                 PATCH(auth_gssapi_keytab);
155             } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.principal"))) {
156                 PATCH(auth_gssapi_principal);
157             }
158         }
159     }
160 
161     return 0;
162 }
163 #undef PATCH
164 
165 static handler_t mod_authn_gssapi_send_400_bad_request (server *srv, connection *con)
166 {
167     UNUSED(srv);
168     con->http_status = 400;
169     con->mode = DIRECT;
170     return HANDLER_FINISHED;
171 }
172 
173 static void mod_authn_gssapi_log_gss_error(server *srv, const char *file, unsigned int line, const char *func, const char *extra, OM_uint32 err_maj, OM_uint32 err_min)
174 {
175     buffer * const msg = buffer_init_string(func);
176     OM_uint32 maj_stat, min_stat;
177     OM_uint32 msg_ctx = 0;
178     gss_buffer_desc status_string;
179 
180     buffer_append_string_len(msg, CONST_STR_LEN("("));
181     if (extra) buffer_append_string(msg, extra);
182     buffer_append_string_len(msg, CONST_STR_LEN("):"));
183 
184     do {
185         maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE,
186                                       GSS_C_NO_OID, &msg_ctx, &status_string);
187         if (GSS_ERROR(maj_stat))
188             break;
189 
190         buffer_append_string(msg, status_string.value);
191         gss_release_buffer(&min_stat, &status_string);
192 
193         maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE,
194                                       GSS_C_NULL_OID, &msg_ctx, &status_string);
195         if (!GSS_ERROR(maj_stat)) {
196             buffer_append_string_len(msg, CONST_STR_LEN(" ("));
197             buffer_append_string(msg, status_string.value);
198             buffer_append_string_len(msg, CONST_STR_LEN(")"));
199             gss_release_buffer(&min_stat, &status_string);
200         }
201     } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
202 
203     log_error_write(srv, file, line, "b", msg);
204     buffer_free(msg);
205 }
206 
207 static void mod_authn_gssapi_log_krb5_error(server *srv, const char *file, unsigned int line, const char *func, const char *extra, krb5_context context, int code)
208 {
209     UNUSED(context);
210     /*(extra might be NULL)*/
211     log_error_write(srv, file, line, "sssss", func, "(", extra, "):",
212                     error_message(code));
213 }
214 
215 static int mod_authn_gssapi_create_krb5_ccache(server *srv, connection *con, plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache)
216 {
217     buffer * const kccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX");
218     char * const ccname    = kccname->ptr + sizeof("FILE:")-1;
219     const size_t ccnamelen = buffer_string_length(kccname)-(sizeof("FILE:")-1);
220     /*(future: might consider using server.upload-dirs instead of /tmp)*/
221   #ifdef __COVERITY__
222     /* POSIX-2008 requires mkstemp create file with 0600 perms */
223     umask(0600);
224   #endif
225     /* coverity[secure_temp : FALSE] */
226     int fd = mkstemp(ccname);
227     if (fd < 0) {
228         log_error_write(srv, __FILE__, __LINE__, "sss", "mkstemp():", ccname, strerror(errno));
229         buffer_free(kccname);
230         return -1;
231     }
232     close(fd);
233 
234     do {
235         krb5_error_code problem;
236 
237         problem = krb5_cc_resolve(kcontext, kccname->ptr, ccache);
238         if (problem) {
239             mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem);
240             break;
241         }
242 
243         problem = krb5_cc_initialize(kcontext, *ccache, princ);
244         if (problem) {
245             mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", kccname->ptr, kcontext, problem);
246             break;
247         }
248 
249         con->plugin_ctx[p->id] = kccname;
250 
251         http_header_env_set(con, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen);
252         http_header_request_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen);
253 
254         return 0;
255 
256     } while (0);
257 
258     if (*ccache) {
259         krb5_cc_destroy(kcontext, *ccache);
260         *ccache = NULL;
261     }
262     unlink(ccname);
263     buffer_free(kccname);
264 
265     return -1;
266 }
267 
268 /*
269  * HTTP auth Negotiate
270  */
271 
272 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (connection *con)
273 {
274     con->http_status = 401;
275     con->mode = DIRECT;
276     http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate"));
277     return HANDLER_FINISHED;
278 }
279 
280 static int mod_authn_gssapi_store_gss_creds(server *srv, connection *con, plugin_data *p, char *princ_name, gss_cred_id_t delegated_cred)
281 {
282     OM_uint32 maj_stat, min_stat;
283     krb5_principal princ = NULL;
284     krb5_ccache ccache   = NULL;
285     krb5_error_code problem;
286     krb5_context context;
287 
288     problem = krb5_init_context(&context);
289     if (problem) {
290         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem);
291         return 0;
292     }
293 
294     problem = krb5_parse_name(context, princ_name, &princ);
295     if (problem) {
296         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem);
297         goto end;
298     }
299 
300     if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, context, princ, &ccache))
301         goto end;
302 
303     maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
304     if (GSS_ERROR(maj_stat)) {
305         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat);
306         goto end;
307     }
308 
309     krb5_cc_close(context, ccache);
310     krb5_free_principal(context, princ);
311     krb5_free_context(context);
312     return 1;
313 
314     end:
315         if (princ)
316             krb5_free_principal(context, princ);
317         if (ccache)
318             krb5_cc_destroy(context, ccache);
319         krb5_free_context(context);
320 
321         return 0;
322 }
323 
324 static handler_t mod_authn_gssapi_check_spnego(server *srv, connection *con, plugin_data *p, const http_auth_require_t *require, const char *realm_str)
325 {
326     OM_uint32 st_major, st_minor, acc_flags;
327     gss_buffer_desc token_s   = GSS_C_EMPTY_BUFFER;
328     gss_buffer_desc token_in  = GSS_C_EMPTY_BUFFER;
329     gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER;
330     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
331     gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL;
332     gss_ctx_id_t context      = GSS_C_NO_CONTEXT;
333     gss_name_t server_name    = GSS_C_NO_NAME;
334     gss_name_t client_name    = GSS_C_NO_NAME;
335 
336     buffer *sprinc;
337     int ret = 0;
338 
339     buffer *t_in = buffer_init();
340     if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) {
341         log_error_write(srv, __FILE__, __LINE__, "ss", "decoding GSSAPI authentication header failed", realm_str);
342         buffer_free(t_in);
343         return mod_authn_gssapi_send_400_bad_request(srv, con);
344     }
345 
346     mod_authn_gssapi_patch_connection(srv, con, p);
347 
348     {
349         /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
350          *     be used, instead of putenv() of KRB5_KTNAME=...?  See mod_authn_gssapi_basic() */
351         /* ??? Should KRB5_KTNAME go into con->environment instead ??? */
352         /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */
353         buffer ktname;
354         memset(&ktname, 0, sizeof(ktname));
355         buffer_copy_string(&ktname, "KRB5_KTNAME=");
356         buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab);
357         putenv(ktname.ptr);
358         /* ktname.ptr becomes part of the environment, do not free */
359     }
360 
361     sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
362     if (strchr(sprinc->ptr, '/') == NULL) {
363         /*(copy HTTP Host, omitting port if port is present)*/
364         /* ??? Should con->server_name be used if http_host not present?
365          * ??? What if con->server_name is not set?
366          * ??? Will this work below if IPv6 provided in Host?  probably not */
367         if (!buffer_is_empty(con->request.http_host)) {
368             buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
369             buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
370         }
371     }
372     if (strchr(sprinc->ptr, '@') == NULL) {
373         buffer_append_string_len(sprinc, CONST_STR_LEN("@"));
374         buffer_append_string_buffer(sprinc, require->realm);
375     }
376     /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/
377     /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/
378     #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name
379 
380     token_s.value = sprinc->ptr;
381     token_s.length = buffer_string_length(sprinc);
382     st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name);
383     if (GSS_ERROR(st_major)) {
384         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor);
385         goto end;
386     }
387 
388     memset(&token_s, 0, sizeof(token_s));
389     st_major = gss_display_name(&st_minor, server_name, &token_s, NULL);
390     if (GSS_ERROR(st_major)) {
391         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
392         goto end;
393     }
394 
395     /* acquire server's own credentials */
396     st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL);
397     if (GSS_ERROR(st_major)) {
398         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor);
399         goto end;
400     }
401 
402     /* accept the user's context */
403     token_in.length = buffer_string_length(t_in);
404     token_in.value = t_in->ptr;
405     st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS,
406                                       &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred);
407     if (GSS_ERROR(st_major)) {
408         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor);
409         goto end;
410     }
411 
412     /* fetch the username */
413     st_major = gss_display_name(&st_minor, client_name, &token_out, NULL);
414     if (GSS_ERROR(st_major)) {
415         mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
416         goto end;
417     }
418 
419     if (!(acc_flags & GSS_C_CONF_FLAG)) {
420         log_error_write(srv, __FILE__, __LINE__, "ss", "No confidentiality for user:", token_out.value);
421         goto end;
422     }
423 
424     if (!(acc_flags & GSS_C_DELEG_FLAG)) {
425         log_error_write(srv, __FILE__, __LINE__, "ss", "Unable to delegate credentials for user:", token_out.value);
426         goto end;
427     }
428 
429     /* check the allow-rules */
430     if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) {
431         goto end;
432     }
433 
434     ret = mod_authn_gssapi_store_gss_creds(srv, con, p, token_out.value, client_cred);
435     if (ret)
436         http_auth_setenv(con, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI"));
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 ret ? HANDLER_GO_ON : mod_authn_gssapi_send_401_unauthorized_negotiate(con);
463 }
464 
465 static handler_t mod_authn_gssapi_check (server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend)
466 {
467     buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
468 
469     UNUSED(backend);
470     if (NULL == vb) {
471         return mod_authn_gssapi_send_401_unauthorized_negotiate(con);
472     }
473 
474     if (0 != strncasecmp(vb->ptr, "Negotiate ", sizeof("Negotiate ")-1)) {
475         return mod_authn_gssapi_send_400_bad_request(srv, con);
476     }
477 
478     return mod_authn_gssapi_check_spnego(srv, con, (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(server *srv, krb5_context context, krb5_creds *creds, krb5_principal ap_req_server, krb5_keytab ap_req_keytab)
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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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_write(srv, __FILE__, __LINE__, "s", "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(server *srv, connection *con, plugin_data *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(srv, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem);
592         goto end;
593     }
594 
595     if (mod_authn_gssapi_create_krb5_ccache(srv, con, 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(srv, __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 (connection *con)
618 {
619     con->http_status = 401;
620     con->mode = DIRECT;
621     http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\""));
622     return HANDLER_FINISHED;
623 }
624 
625 static handler_t mod_authn_gssapi_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw)
626 {
627     krb5_context kcontext  = NULL;
628     krb5_keytab keytab     = NULL;
629     krb5_principal s_princ = NULL;
630     krb5_principal c_princ = NULL;
631     krb5_creds c_creds;
632     krb5_ccache c_ccache   = NULL;
633     krb5_ccache ret_ccache = NULL;
634     krb5_error_code code;
635     int ret;
636     buffer *sprinc;
637     buffer *user_at_realm  = NULL;
638     plugin_data * const p = (plugin_data *)p_d;
639 
640     if (*pw == '\0') {
641         log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted");
642         return mod_authn_gssapi_send_401_unauthorized_basic(con);
643     }
644 
645     mod_authn_gssapi_patch_connection(srv, con, p);
646 
647     code = krb5_init_context(&kcontext);
648     if (code) {
649         log_error_write(srv, __FILE__, __LINE__, "sd", "krb5_init_context():", code);
650         return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
651     }
652 
653     code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
654     if (code) {
655         log_error_write(srv, __FILE__, __LINE__, "sdb", "krb5_kt_resolve():", code, p->conf.auth_gssapi_keytab);
656         return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
657     }
658 
659     sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
660     if (strchr(sprinc->ptr, '/') == NULL) {
661         /*(copy HTTP Host, omitting port if port is present)*/
662         /* ??? Should con->server_name be used if http_host not present?
663          * ??? What if con->server_name is not set?
664          * ??? Will this work below if IPv6 provided in Host?  probably not */
665         if (!buffer_is_empty(con->request.http_host)) {
666             buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
667             buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
668         }
669     }
670 
671     /*(init c_creds before anything which might krb5_free_cred_contents())*/
672     memset(&c_creds, 0, sizeof(c_creds));
673 
674     ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ);
675     if (ret) {
676         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret);
677         ret = -1;
678         goto end;
679     }
680 
681     if (strchr(username->ptr, '@') == NULL) {
682         user_at_realm = buffer_init_buffer(username);
683         BUFFER_APPEND_STRING_CONST(user_at_realm, "@");
684         buffer_append_string_buffer(user_at_realm, require->realm);
685     }
686 
687     ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ);
688     if (ret) {
689         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret);
690         if (user_at_realm) buffer_free(user_at_realm);
691         ret = -1;
692         goto end;
693     }
694     if (user_at_realm) buffer_free(user_at_realm);
695     /* XXX: if the qualified username with @realm should be in REMOTE_USER,
696      * then http_auth_backend_t basic interface needs to change to pass
697      * modifiable buffer *username, but note that const char *pw follows
698      * in the truncated buffer *username, so pw would need to be copied
699      * before modifying buffer *username */
700 
701     /*
702      * char *name = NULL;
703      * ret = krb5_unparse_name(kcontext, c_princ, &name);
704      * if (ret == 0) {
705      *    log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw);
706      * }
707      * krb5_free_unparsed_name(kcontext, name);
708      */
709 
710     ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL);
711     if (ret) {
712         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret);
713         goto end;
714     }
715 
716     ret = mod_authn_gssapi_verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab);
717     if (ret) {
718         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret);
719         goto end;
720     }
721 
722     ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache);
723     if (ret) {
724         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret);
725         goto end;
726     }
727 
728     ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ);
729     if (ret) {
730         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret);
731         goto end;
732     }
733 
734     ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds);
735     if (ret) {
736         mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret);
737         goto end;
738     }
739 
740     c_ccache = ret_ccache;
741     ret_ccache = NULL;
742 
743     end:
744 
745         krb5_free_cred_contents(kcontext, &c_creds);
746         if (ret_ccache)
747             krb5_cc_destroy(kcontext, ret_ccache);
748 
749         if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(srv, con, p, kcontext, c_ccache))) {
750             log_error_write(srv, __FILE__, __LINE__, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username);
751         }
752 
753         buffer_free(sprinc);
754         if (c_princ)
755             krb5_free_principal(kcontext, c_princ);
756         if (s_princ)
757             krb5_free_principal(kcontext, s_princ);
758         if (c_ccache)
759             krb5_cc_destroy(kcontext, c_ccache);
760         if (keytab)
761             krb5_kt_close(kcontext, keytab);
762 
763         krb5_free_context(kcontext);
764 
765         if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){
766             return HANDLER_GO_ON;
767         }
768         else {
769             /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
770             log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
771             return mod_authn_gssapi_send_401_unauthorized_basic(con);
772         }
773 }
774 
775 
776 CONNECTION_FUNC(mod_authn_gssapi_handle_reset) {
777     plugin_data *p = (plugin_data *)p_d;
778     buffer *kccname = (buffer *)con->plugin_ctx[p->id];
779     if (NULL != kccname) {
780         con->plugin_ctx[p->id] = NULL;
781         unlink(kccname->ptr+sizeof("FILE:")-1);
782         buffer_free(kccname);
783     }
784 
785     UNUSED(srv);
786     return HANDLER_GO_ON;
787 }
788 
789 int mod_authn_gssapi_plugin_init(plugin *p);
790 int mod_authn_gssapi_plugin_init(plugin *p) {
791     p->version     = LIGHTTPD_VERSION_ID;
792     p->name        = buffer_init_string("authn_gssapi");
793     p->init        = mod_authn_gssapi_init;
794     p->set_defaults= mod_authn_gssapi_set_defaults;
795     p->cleanup     = mod_authn_gssapi_free;
796     p->connection_reset = mod_authn_gssapi_handle_reset;
797 
798     p->data        = NULL;
799 
800     return 0;
801 }
802