- next is 1.4.56
[lighttpd.git] / src / mod_authn_gssapi.c
blobb19c62879858434587945a97f1718aa44e327b5a
1 #include "first.h"
3 /* mod_authn_gssapi
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)
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.
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
18 * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
19 * - Kerberos auth is synchronous and blocks waiting for response
20 * TODO: attempt async?
23 #include "plugin.h"
25 #include <krb5.h>
26 #include <gssapi.h>
27 #include <gssapi/gssapi_krb5.h>
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"
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
41 typedef struct {
42 buffer *auth_gssapi_keytab;
43 buffer *auth_gssapi_principal;
44 unsigned short int auth_gssapi_store_creds;
45 } plugin_config;
47 typedef struct {
48 PLUGIN_DATA;
49 plugin_config **config_storage;
50 plugin_config conf;
51 } plugin_data;
53 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);
54 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);
56 INIT_FUNC(mod_authn_gssapi_init) {
57 static http_auth_scheme_t http_auth_scheme_gssapi =
58 { "gssapi", mod_authn_gssapi_check, NULL };
59 static http_auth_backend_t http_auth_backend_gssapi =
60 { "gssapi", mod_authn_gssapi_basic, NULL, NULL };
61 plugin_data *p = calloc(1, sizeof(*p));
63 /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */
64 http_auth_scheme_gssapi.p_d = p;
65 http_auth_scheme_set(&http_auth_scheme_gssapi);
66 http_auth_backend_gssapi.p_d = p;
67 http_auth_backend_set(&http_auth_backend_gssapi);
69 return p;
72 FREE_FUNC(mod_authn_gssapi_free) {
73 plugin_data *p = p_d;
75 UNUSED(srv);
77 if (!p) return HANDLER_GO_ON;
79 if (p->config_storage) {
80 size_t i;
81 for (i = 0; i < srv->config_context->used; i++) {
82 plugin_config *s = p->config_storage[i];
84 if (NULL == s) continue;
86 buffer_free(s->auth_gssapi_keytab);
87 buffer_free(s->auth_gssapi_principal);
89 free(s);
91 free(p->config_storage);
94 free(p);
96 return HANDLER_GO_ON;
99 SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults) {
100 plugin_data *p = p_d;
101 size_t i;
102 config_values_t cv[] = {
103 { "auth.backend.gssapi.keytab", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
104 { "auth.backend.gssapi.principal", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
105 { "auth.backend.gssapi.store-creds",NULL, T_CONFIG_BOOLEAN,T_CONFIG_SCOPE_CONNECTION },
106 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
109 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
111 for (i = 0; i < srv->config_context->used; i++) {
112 data_config const* config = (data_config const*)srv->config_context->data[i];
113 plugin_config *s;
115 s = calloc(1, sizeof(plugin_config));
117 s->auth_gssapi_keytab = buffer_init();
118 s->auth_gssapi_principal = buffer_init();
120 cv[0].destination = s->auth_gssapi_keytab;
121 cv[1].destination = s->auth_gssapi_principal;
122 cv[2].destination = &s->auth_gssapi_store_creds;
123 /* default enabled for backwards compatibility; disable in future */
124 s->auth_gssapi_store_creds = 1;
126 p->config_storage[i] = s;
128 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
129 return HANDLER_ERROR;
133 return HANDLER_GO_ON;
136 #define PATCH(x) \
137 p->conf.x = s->x;
138 static int mod_authn_gssapi_patch_connection(server *srv, connection *con, plugin_data *p)
140 size_t i, j;
141 plugin_config *s = p->config_storage[0];
143 PATCH(auth_gssapi_keytab);
144 PATCH(auth_gssapi_principal);
145 PATCH(auth_gssapi_store_creds);
147 /* skip the first, the global context */
148 for (i = 1; i < srv->config_context->used; i++) {
149 data_config *dc = (data_config *)srv->config_context->data[i];
150 s = p->config_storage[i];
152 /* condition didn't match */
153 if (!config_check_cond(srv, con, dc)) continue;
155 /* merge config */
156 for (j = 0; j < dc->value->used; j++) {
157 data_unset *du = dc->value->data[j];
159 if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.keytab"))) {
160 PATCH(auth_gssapi_keytab);
161 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.principal"))) {
162 PATCH(auth_gssapi_principal);
163 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.store-creds"))) {
164 PATCH(auth_gssapi_store_creds);
169 return 0;
171 #undef PATCH
173 static handler_t mod_authn_gssapi_send_400_bad_request (server *srv, connection *con)
175 UNUSED(srv);
176 con->http_status = 400;
177 con->mode = DIRECT;
178 return HANDLER_FINISHED;
181 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)
183 buffer * const msg = buffer_init_string(func);
184 OM_uint32 maj_stat, min_stat;
185 OM_uint32 msg_ctx = 0;
186 gss_buffer_desc status_string;
188 buffer_append_string_len(msg, CONST_STR_LEN("("));
189 if (extra) buffer_append_string(msg, extra);
190 buffer_append_string_len(msg, CONST_STR_LEN("):"));
192 do {
193 maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE,
194 GSS_C_NO_OID, &msg_ctx, &status_string);
195 if (GSS_ERROR(maj_stat))
196 break;
198 buffer_append_string(msg, status_string.value);
199 gss_release_buffer(&min_stat, &status_string);
201 maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE,
202 GSS_C_NULL_OID, &msg_ctx, &status_string);
203 if (!GSS_ERROR(maj_stat)) {
204 buffer_append_string_len(msg, CONST_STR_LEN(" ("));
205 buffer_append_string(msg, status_string.value);
206 buffer_append_string_len(msg, CONST_STR_LEN(")"));
207 gss_release_buffer(&min_stat, &status_string);
209 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
211 log_error_write(srv, file, line, "b", msg);
212 buffer_free(msg);
215 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)
217 UNUSED(context);
218 /*(extra might be NULL)*/
219 log_error_write(srv, file, line, "sssss", func, "(", extra, "):",
220 error_message(code));
223 static int mod_authn_gssapi_create_krb5_ccache(server *srv, connection *con, plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache)
225 buffer * const kccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX");
226 char * const ccname = kccname->ptr + sizeof("FILE:")-1;
227 const size_t ccnamelen = buffer_string_length(kccname)-(sizeof("FILE:")-1);
228 /*(future: might consider using server.upload-dirs instead of /tmp)*/
229 #ifdef __COVERITY__
230 /* POSIX-2008 requires mkstemp create file with 0600 perms */
231 umask(0600);
232 #endif
233 /* coverity[secure_temp : FALSE] */
234 int fd = mkstemp(ccname);
235 if (fd < 0) {
236 log_error_write(srv, __FILE__, __LINE__, "sss", "mkstemp():", ccname, strerror(errno));
237 buffer_free(kccname);
238 return -1;
240 close(fd);
242 do {
243 krb5_error_code problem;
245 problem = krb5_cc_resolve(kcontext, kccname->ptr, ccache);
246 if (problem) {
247 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem);
248 break;
251 problem = krb5_cc_initialize(kcontext, *ccache, princ);
252 if (problem) {
253 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", kccname->ptr, kcontext, problem);
254 break;
257 con->plugin_ctx[p->id] = kccname;
259 http_header_env_set(con, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen);
260 http_header_request_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen);
262 return 0;
264 } while (0);
266 if (*ccache) {
267 krb5_cc_destroy(kcontext, *ccache);
268 *ccache = NULL;
270 unlink(ccname);
271 buffer_free(kccname);
273 return -1;
277 * HTTP auth Negotiate
280 static handler_t mod_authn_gssapi_send_500_server_error (connection *con)
282 con->http_status = 500;
283 con->mode = DIRECT;
284 return HANDLER_FINISHED;
287 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (connection *con)
289 con->http_status = 401;
290 con->mode = DIRECT;
291 http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate"));
292 return HANDLER_FINISHED;
295 static int mod_authn_gssapi_store_gss_creds(server *srv, connection *con, plugin_data *p, char *princ_name, gss_cred_id_t delegated_cred)
297 OM_uint32 maj_stat, min_stat;
298 krb5_principal princ = NULL;
299 krb5_ccache ccache = NULL;
300 krb5_error_code problem;
301 krb5_context context;
303 problem = krb5_init_context(&context);
304 if (problem) {
305 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem);
306 return 0;
309 problem = krb5_parse_name(context, princ_name, &princ);
310 if (problem) {
311 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem);
312 goto end;
315 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, context, princ, &ccache))
316 goto end;
318 maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
319 if (GSS_ERROR(maj_stat)) {
320 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat);
321 goto end;
324 krb5_cc_close(context, ccache);
325 krb5_free_principal(context, princ);
326 krb5_free_context(context);
327 return 1;
329 end:
330 if (princ)
331 krb5_free_principal(context, princ);
332 if (ccache)
333 krb5_cc_destroy(context, ccache);
334 krb5_free_context(context);
336 return 0;
339 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)
341 OM_uint32 st_major, st_minor, acc_flags;
342 gss_buffer_desc token_s = GSS_C_EMPTY_BUFFER;
343 gss_buffer_desc token_in = GSS_C_EMPTY_BUFFER;
344 gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER;
345 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
346 gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL;
347 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
348 gss_name_t server_name = GSS_C_NO_NAME;
349 gss_name_t client_name = GSS_C_NO_NAME;
351 buffer *sprinc;
352 handler_t rc = HANDLER_UNSET;
354 buffer *t_in = buffer_init();
355 if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) {
356 log_error_write(srv, __FILE__, __LINE__, "ss", "decoding GSSAPI authentication header failed", realm_str);
357 buffer_free(t_in);
358 return mod_authn_gssapi_send_400_bad_request(srv, con);
361 mod_authn_gssapi_patch_connection(srv, con, p);
364 /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
365 * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */
366 /* ??? Should KRB5_KTNAME go into con->environment instead ??? */
367 /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */
368 buffer ktname;
369 memset(&ktname, 0, sizeof(ktname));
370 buffer_copy_string(&ktname, "KRB5_KTNAME=");
371 buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab);
372 putenv(ktname.ptr);
373 /* ktname.ptr becomes part of the environment, do not free */
376 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
377 if (strchr(sprinc->ptr, '/') == NULL) {
378 /*(copy HTTP Host, omitting port if port is present)*/
379 /* ??? Should con->server_name be used if http_host not present?
380 * ??? What if con->server_name is not set?
381 * ??? Will this work below if IPv6 provided in Host? probably not */
382 if (!buffer_is_empty(con->request.http_host)) {
383 buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
384 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
387 if (strchr(sprinc->ptr, '@') == NULL) {
388 buffer_append_string_len(sprinc, CONST_STR_LEN("@"));
389 buffer_append_string_buffer(sprinc, require->realm);
391 /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/
392 /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/
393 #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name
395 token_s.value = sprinc->ptr;
396 token_s.length = buffer_string_length(sprinc);
397 st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name);
398 if (GSS_ERROR(st_major)) {
399 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor);
400 goto end;
403 memset(&token_s, 0, sizeof(token_s));
404 st_major = gss_display_name(&st_minor, server_name, &token_s, NULL);
405 if (GSS_ERROR(st_major)) {
406 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
407 goto end;
410 /* acquire server's own credentials */
411 st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL);
412 if (GSS_ERROR(st_major)) {
413 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor);
414 goto end;
417 /* accept the user's context */
418 token_in.length = buffer_string_length(t_in);
419 token_in.value = t_in->ptr;
420 st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS,
421 &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred);
422 if (GSS_ERROR(st_major)) {
423 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor);
424 goto end;
427 /* fetch the username */
428 st_major = gss_display_name(&st_minor, client_name, &token_out, NULL);
429 if (GSS_ERROR(st_major)) {
430 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
431 goto end;
434 if (!(acc_flags & GSS_C_CONF_FLAG)) {
435 log_error_write(srv, __FILE__, __LINE__, "ss", "No confidentiality for user:", token_out.value);
436 goto end;
439 /* check the allow-rules */
440 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) {
441 goto end;
444 if (p->conf.auth_gssapi_store_creds) {
445 if (!(acc_flags & GSS_C_DELEG_FLAG)) {
446 log_error_write(srv, __FILE__, __LINE__, "ss", "Unable to delegate credentials for user:", token_out.value);
447 goto end;
449 else if (!mod_authn_gssapi_store_gss_creds(srv, con, p, token_out.value, client_cred)) {
450 rc = mod_authn_gssapi_send_500_server_error(con);
451 goto end;
455 http_auth_setenv(con, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI"));
456 rc = HANDLER_GO_ON; /* success */
458 end:
459 buffer_free(t_in);
460 buffer_free(sprinc);
462 if (context != GSS_C_NO_CONTEXT)
463 gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER);
465 if (client_cred != GSS_C_NO_CREDENTIAL)
466 gss_release_cred(&st_minor, &client_cred);
467 if (server_cred != GSS_C_NO_CREDENTIAL)
468 gss_release_cred(&st_minor, &server_cred);
470 if (client_name != GSS_C_NO_NAME)
471 gss_release_name(&st_minor, &client_name);
472 if (server_name != GSS_C_NO_NAME)
473 gss_release_name(&st_minor, &server_name);
475 if (token_s.length)
476 gss_release_buffer(&st_minor, &token_s);
477 /* if (token_in.length)
478 * gss_release_buffer(&st_minor, &token_in); */
479 if (token_out.length)
480 gss_release_buffer(&st_minor, &token_out);
482 return rc != HANDLER_UNSET ? rc : mod_authn_gssapi_send_401_unauthorized_negotiate(con);
485 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)
487 buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
489 UNUSED(backend);
490 if (NULL == vb) {
491 return mod_authn_gssapi_send_401_unauthorized_negotiate(con);
494 if (!buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Negotiate "))) {
495 return mod_authn_gssapi_send_400_bad_request(srv, con);
498 return mod_authn_gssapi_check_spnego(srv, con, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1);
502 * HTTP auth Basic realm="kerberos"
505 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)
507 krb5_error_code ret;
508 krb5_data req;
509 krb5_ccache local_ccache = NULL;
510 krb5_creds *new_creds = NULL;
511 krb5_auth_context auth_context = NULL;
512 krb5_keytab keytab = NULL;
513 char *server_name;
515 memset(&req, 0, sizeof(req));
517 if (ap_req_keytab == NULL) {
518 ret = krb5_kt_default(context, &keytab);
519 if (ret)
520 return ret;
521 } else
522 keytab = ap_req_keytab;
524 ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache);
525 if (ret) {
526 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_resolve() failed when verifying KDC");
527 /* return ret; */
528 goto end;
531 ret = krb5_cc_initialize(context, local_ccache, creds->client);
532 if (ret) {
533 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_initialize() failed when verifying KDC");
534 goto end;
537 ret = krb5_cc_store_cred(context, local_ccache, creds);
538 if (ret) {
539 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_store_cred() failed when verifying KDC");
540 goto end;
543 ret = krb5_unparse_name(context, ap_req_server, &server_name);
544 if (ret) {
545 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_unparse_name() failed when verifying KDC");
546 goto end;
548 krb5_free_unparsed_name(context, server_name);
550 if (!krb5_principal_compare(context, ap_req_server, creds->server)) {
551 krb5_creds match_cred;
553 memset(&match_cred, 0, sizeof(match_cred));
555 match_cred.client = creds->client;
556 match_cred.server = ap_req_server;
558 ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds);
559 if (ret) {
560 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_get_credentials() failed when verifying KDC");
561 goto end;
563 creds = new_creds;
566 ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req);
567 if (ret) {
568 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_mk_req_extended() failed when verifying KDC");
569 goto end;
572 krb5_auth_con_free(context, auth_context);
573 auth_context = NULL;
574 ret = krb5_auth_con_init(context, &auth_context);
575 if (ret) {
576 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_auth_con_init() failed when verifying KDC");
577 goto end;
580 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
581 krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
582 ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL);
583 if (ret) {
584 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_rd_req() failed when verifying KDC");
585 goto end;
588 end:
589 krb5_free_data_contents(context, &req);
590 if (auth_context)
591 krb5_auth_con_free(context, auth_context);
592 if (new_creds)
593 krb5_free_creds(context, new_creds);
594 if (ap_req_keytab == NULL && keytab)
595 krb5_kt_close(context, keytab);
596 if (local_ccache)
597 krb5_cc_destroy(context, local_ccache);
599 return ret;
602 static int mod_authn_gssapi_store_krb5_creds(server *srv, connection *con, plugin_data *p,
603 krb5_context kcontext, krb5_ccache delegated_cred)
605 krb5_error_code problem;
606 krb5_principal princ = NULL;
607 krb5_ccache ccache = NULL;
609 problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
610 if (problem) {
611 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem);
612 goto end;
615 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, kcontext, princ, &ccache)) {
616 goto end;
619 problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
620 if (problem) {
621 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem);
622 goto end;
625 krb5_free_principal(kcontext, princ);
626 krb5_cc_close(kcontext, ccache);
627 return 0;
629 end:
630 if (princ)
631 krb5_free_principal(kcontext, princ);
632 if (ccache)
633 krb5_cc_destroy(kcontext, ccache);
634 return -1;
637 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (connection *con)
639 con->http_status = 401;
640 con->mode = DIRECT;
641 http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\""));
642 return HANDLER_FINISHED;
645 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)
647 krb5_context kcontext = NULL;
648 krb5_keytab keytab = NULL;
649 krb5_principal s_princ = NULL;
650 krb5_principal c_princ = NULL;
651 krb5_creds c_creds;
652 krb5_ccache c_ccache = NULL;
653 krb5_ccache ret_ccache = NULL;
654 krb5_error_code code;
655 int ret;
656 buffer *sprinc;
657 buffer *user_at_realm = NULL;
658 plugin_data * const p = (plugin_data *)p_d;
660 if (*pw == '\0') {
661 log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted");
662 return mod_authn_gssapi_send_401_unauthorized_basic(con);
665 mod_authn_gssapi_patch_connection(srv, con, p);
667 code = krb5_init_context(&kcontext);
668 if (code) {
669 log_error_write(srv, __FILE__, __LINE__, "sd", "krb5_init_context():", code);
670 return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
673 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
674 if (code) {
675 log_error_write(srv, __FILE__, __LINE__, "sdb", "krb5_kt_resolve():", code, p->conf.auth_gssapi_keytab);
676 return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
679 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
680 if (strchr(sprinc->ptr, '/') == NULL) {
681 /*(copy HTTP Host, omitting port if port is present)*/
682 /* ??? Should con->server_name be used if http_host not present?
683 * ??? What if con->server_name is not set?
684 * ??? Will this work below if IPv6 provided in Host? probably not */
685 if (!buffer_is_empty(con->request.http_host)) {
686 buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
687 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
691 /*(init c_creds before anything which might krb5_free_cred_contents())*/
692 memset(&c_creds, 0, sizeof(c_creds));
694 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ);
695 if (ret) {
696 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret);
697 ret = -1;
698 goto end;
701 if (strchr(username->ptr, '@') == NULL) {
702 user_at_realm = buffer_init_buffer(username);
703 BUFFER_APPEND_STRING_CONST(user_at_realm, "@");
704 buffer_append_string_buffer(user_at_realm, require->realm);
707 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ);
708 if (ret) {
709 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret);
710 if (user_at_realm) buffer_free(user_at_realm);
711 ret = -1;
712 goto end;
714 if (user_at_realm) buffer_free(user_at_realm);
715 /* XXX: if the qualified username with @realm should be in REMOTE_USER,
716 * then http_auth_backend_t basic interface needs to change to pass
717 * modifiable buffer *username, but note that const char *pw follows
718 * in the truncated buffer *username, so pw would need to be copied
719 * before modifying buffer *username */
722 * char *name = NULL;
723 * ret = krb5_unparse_name(kcontext, c_princ, &name);
724 * if (ret == 0) {
725 * log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw);
727 * krb5_free_unparsed_name(kcontext, name);
730 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL);
731 if (ret) {
732 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret);
733 goto end;
736 ret = mod_authn_gssapi_verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab);
737 if (ret) {
738 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret);
739 goto end;
742 if (!p->conf.auth_gssapi_store_creds) goto end;
744 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache);
745 if (ret) {
746 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret);
747 goto end;
750 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ);
751 if (ret) {
752 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret);
753 goto end;
756 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds);
757 if (ret) {
758 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret);
759 goto end;
762 c_ccache = ret_ccache;
763 ret_ccache = NULL;
765 end:
767 krb5_free_cred_contents(kcontext, &c_creds);
768 if (ret_ccache)
769 krb5_cc_destroy(kcontext, ret_ccache);
771 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(srv, con, p, kcontext, c_ccache))) {
772 log_error_write(srv, __FILE__, __LINE__, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username);
775 buffer_free(sprinc);
776 if (c_princ)
777 krb5_free_principal(kcontext, c_princ);
778 if (s_princ)
779 krb5_free_principal(kcontext, s_princ);
780 if (c_ccache)
781 krb5_cc_destroy(kcontext, c_ccache);
782 if (keytab)
783 krb5_kt_close(kcontext, keytab);
785 krb5_free_context(kcontext);
787 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){
788 return HANDLER_GO_ON;
790 else {
791 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
792 log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
793 return mod_authn_gssapi_send_401_unauthorized_basic(con);
798 CONNECTION_FUNC(mod_authn_gssapi_handle_reset) {
799 plugin_data *p = (plugin_data *)p_d;
800 buffer *kccname = (buffer *)con->plugin_ctx[p->id];
801 if (NULL != kccname) {
802 con->plugin_ctx[p->id] = NULL;
803 unlink(kccname->ptr+sizeof("FILE:")-1);
804 buffer_free(kccname);
807 UNUSED(srv);
808 return HANDLER_GO_ON;
811 int mod_authn_gssapi_plugin_init(plugin *p);
812 int mod_authn_gssapi_plugin_init(plugin *p) {
813 p->version = LIGHTTPD_VERSION_ID;
814 p->name = buffer_init_string("authn_gssapi");
815 p->init = mod_authn_gssapi_init;
816 p->set_defaults= mod_authn_gssapi_set_defaults;
817 p->cleanup = mod_authn_gssapi_free;
818 p->connection_reset = mod_authn_gssapi_handle_reset;
820 p->data = NULL;
822 return 0;