[core] attempt to quiet coverity false positives
[lighttpd.git] / src / mod_authn_gssapi.c
blobfa0a33921c1dc7d54d9b75782840f057a4f17e4e
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 "base.h"
31 #include "log.h"
32 #include "md5.h"
33 #include "base64.h"
34 #include "response.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 } plugin_config;
46 typedef struct {
47 PLUGIN_DATA;
48 plugin_config **config_storage;
49 plugin_config conf;
50 } plugin_data;
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);
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));
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);
68 return p;
71 FREE_FUNC(mod_authn_gssapi_free) {
72 plugin_data *p = p_d;
74 UNUSED(srv);
76 if (!p) return HANDLER_GO_ON;
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];
83 if (NULL == s) continue;
85 buffer_free(s->auth_gssapi_keytab);
86 buffer_free(s->auth_gssapi_principal);
88 free(s);
90 free(p->config_storage);
93 free(p);
95 return HANDLER_GO_ON;
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 }
107 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
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;
113 s = calloc(1, sizeof(plugin_config));
115 s->auth_gssapi_keytab = buffer_init();
116 s->auth_gssapi_principal = buffer_init();
118 cv[0].destination = s->auth_gssapi_keytab;
119 cv[1].destination = s->auth_gssapi_principal;
121 p->config_storage[i] = s;
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;
128 return HANDLER_GO_ON;
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)
135 size_t i, j;
136 plugin_config *s = p->config_storage[0];
138 PATCH(auth_gssapi_keytab);
139 PATCH(auth_gssapi_principal);
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];
146 /* condition didn't match */
147 if (!config_check_cond(srv, con, dc)) continue;
149 /* merge config */
150 for (j = 0; j < dc->value->used; j++) {
151 data_unset *du = dc->value->data[j];
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);
161 return 0;
163 #undef PATCH
165 static handler_t mod_authn_gssapi_send_400_bad_request (server *srv, connection *con)
167 UNUSED(srv);
168 con->http_status = 400;
169 con->mode = DIRECT;
170 return HANDLER_FINISHED;
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)
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;
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("):"));
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;
190 buffer_append_string(msg, status_string.value);
191 gss_release_buffer(&min_stat, &status_string);
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(msg, " (");
197 buffer_append_string(msg, status_string.value);
198 buffer_append_string(msg, ")");
199 gss_release_buffer(&min_stat, &status_string);
201 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
203 log_error_write(srv, file, line, "b", msg);
204 buffer_free(msg);
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)
209 UNUSED(context);
210 /*(extra might be NULL)*/
211 log_error_write(srv, file, line, "sssss", func, "(", extra, "):",
212 error_message(code));
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)
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;
232 close(fd);
234 do {
235 krb5_error_code problem;
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;
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;
249 con->plugin_ctx[p->id] = kccname;
251 array_set_key_value(con->environment, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen);
252 array_set_key_value(con->request.headers, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen);
254 return 0;
256 } while (0);
258 if (*ccache) {
259 krb5_cc_destroy(kcontext, *ccache);
260 *ccache = NULL;
262 unlink(ccname);
263 buffer_free(kccname);
265 return -1;
269 * HTTP auth Negotiate
272 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (server *srv, connection *con)
274 con->http_status = 401;
275 con->mode = DIRECT;
276 response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate"));
277 return HANDLER_FINISHED;
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)
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;
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;
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;
300 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, context, princ, &ccache))
301 goto end;
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;
309 krb5_cc_close(context, ccache);
310 krb5_free_principal(context, princ);
311 krb5_free_context(context);
312 return 1;
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);
321 return 0;
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)
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;
336 buffer *sprinc;
337 int ret = 0;
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);
346 mod_authn_gssapi_patch_connection(srv, con, p);
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 */
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(sprinc, "/");
369 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
372 if (strchr(sprinc->ptr, '@') == NULL) {
373 buffer_append_string(sprinc, "@");
374 buffer_append_string_buffer(sprinc, require->realm);
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
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;
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;
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;
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;
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;
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;
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;
429 /* check the allow-rules */
430 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) {
431 goto end;
434 ret = mod_authn_gssapi_store_gss_creds(srv, con, p, token_out.value, client_cred);
435 if (ret)
436 http_auth_setenv(con->environment, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI"));
438 end:
439 buffer_free(t_in);
440 buffer_free(sprinc);
442 if (context != GSS_C_NO_CONTEXT)
443 gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER);
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);
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);
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);
462 return ret ? HANDLER_GO_ON : mod_authn_gssapi_send_401_unauthorized_negotiate(srv, con);
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)
467 data_string * const ds =
468 (data_string *)array_get_element(con->request.headers, "Authorization");
470 UNUSED(backend);
471 if (NULL == ds || buffer_is_empty(ds->value)) {
472 return mod_authn_gssapi_send_401_unauthorized_negotiate(srv, con);
475 if (0 != strncasecmp(ds->value->ptr, "Negotiate ", sizeof("Negotiate ")-1)) {
476 return mod_authn_gssapi_send_400_bad_request(srv, con);
479 return mod_authn_gssapi_check_spnego(srv, con, (plugin_data *)p_d, require, ds->value->ptr+sizeof("Negotiate ")-1);
483 * HTTP auth Basic realm="kerberos"
486 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)
488 krb5_error_code ret;
489 krb5_data req;
490 krb5_ccache local_ccache = NULL;
491 krb5_creds *new_creds = NULL;
492 krb5_auth_context auth_context = NULL;
493 krb5_keytab keytab = NULL;
494 char *server_name;
496 memset(&req, 0, sizeof(req));
498 if (ap_req_keytab == NULL) {
499 ret = krb5_kt_default(context, &keytab);
500 if (ret)
501 return ret;
502 } else
503 keytab = ap_req_keytab;
505 ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache);
506 if (ret) {
507 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_resolve() failed when verifying KDC");
508 /* return ret; */
509 goto end;
512 ret = krb5_cc_initialize(context, local_ccache, creds->client);
513 if (ret) {
514 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_initialize() failed when verifying KDC");
515 goto end;
518 ret = krb5_cc_store_cred(context, local_ccache, creds);
519 if (ret) {
520 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_store_cred() failed when verifying KDC");
521 goto end;
524 ret = krb5_unparse_name(context, ap_req_server, &server_name);
525 if (ret) {
526 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_unparse_name() failed when verifying KDC");
527 goto end;
529 krb5_free_unparsed_name(context, server_name);
531 if (!krb5_principal_compare(context, ap_req_server, creds->server)) {
532 krb5_creds match_cred;
534 memset(&match_cred, 0, sizeof(match_cred));
536 match_cred.client = creds->client;
537 match_cred.server = ap_req_server;
539 ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds);
540 if (ret) {
541 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_get_credentials() failed when verifying KDC");
542 goto end;
544 creds = new_creds;
547 ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req);
548 if (ret) {
549 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_mk_req_extended() failed when verifying KDC");
550 goto end;
553 krb5_auth_con_free(context, auth_context);
554 auth_context = NULL;
555 ret = krb5_auth_con_init(context, &auth_context);
556 if (ret) {
557 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_auth_con_init() failed when verifying KDC");
558 goto end;
561 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
562 krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
563 ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL);
564 if (ret) {
565 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_rd_req() failed when verifying KDC");
566 goto end;
569 end:
570 krb5_free_data_contents(context, &req);
571 if (auth_context)
572 krb5_auth_con_free(context, auth_context);
573 if (new_creds)
574 krb5_free_creds(context, new_creds);
575 if (ap_req_keytab == NULL && keytab)
576 krb5_kt_close(context, keytab);
577 if (local_ccache)
578 krb5_cc_destroy(context, local_ccache);
580 return ret;
583 static int mod_authn_gssapi_store_krb5_creds(server *srv, connection *con, plugin_data *p,
584 krb5_context kcontext, krb5_ccache delegated_cred)
586 krb5_error_code problem;
587 krb5_principal princ = NULL;
588 krb5_ccache ccache = NULL;
590 problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
591 if (problem) {
592 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem);
593 goto end;
596 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, kcontext, princ, &ccache)) {
597 goto end;
600 problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
601 if (problem) {
602 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem);
603 goto end;
606 krb5_free_principal(kcontext, princ);
607 krb5_cc_close(kcontext, ccache);
608 return 0;
610 end:
611 if (princ)
612 krb5_free_principal(kcontext, princ);
613 if (ccache)
614 krb5_cc_destroy(kcontext, ccache);
615 return -1;
618 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (server *srv, connection *con)
620 con->http_status = 401;
621 con->mode = DIRECT;
622 response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\""));
623 return HANDLER_FINISHED;
626 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)
628 krb5_context kcontext = NULL;
629 krb5_keytab keytab = NULL;
630 krb5_principal s_princ = NULL;
631 krb5_principal c_princ = NULL;
632 krb5_creds c_creds;
633 krb5_ccache c_ccache = NULL;
634 krb5_ccache ret_ccache = NULL;
635 krb5_error_code code;
636 int ret;
637 buffer *sprinc;
638 buffer *user_at_realm = NULL;
639 plugin_data * const p = (plugin_data *)p_d;
641 if (*pw == '\0') {
642 log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted");
643 return mod_authn_gssapi_send_401_unauthorized_basic(srv, con);
646 mod_authn_gssapi_patch_connection(srv, con, p);
648 code = krb5_init_context(&kcontext);
649 if (code) {
650 log_error_write(srv, __FILE__, __LINE__, "sd", "krb5_init_context():", code);
651 return mod_authn_gssapi_send_401_unauthorized_basic(srv, con); /*(well, should be 500)*/
654 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
655 if (code) {
656 log_error_write(srv, __FILE__, __LINE__, "sdb", "krb5_kt_resolve():", code, p->conf.auth_gssapi_keytab);
657 return mod_authn_gssapi_send_401_unauthorized_basic(srv, con); /*(well, should be 500)*/
660 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
661 if (strchr(sprinc->ptr, '/') == NULL) {
662 /*(copy HTTP Host, omitting port if port is present)*/
663 /* ??? Should con->server_name be used if http_host not present?
664 * ??? What if con->server_name is not set?
665 * ??? Will this work below if IPv6 provided in Host? probably not */
666 if (!buffer_is_empty(con->request.http_host)) {
667 buffer_append_string(sprinc, "/");
668 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
672 /*(init c_creds before anything which might krb5_free_cred_contents())*/
673 memset(&c_creds, 0, sizeof(c_creds));
675 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ);
676 if (ret) {
677 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret);
678 ret = -1;
679 goto end;
682 if (strchr(username->ptr, '@') == NULL) {
683 user_at_realm = buffer_init_buffer(username);
684 BUFFER_APPEND_STRING_CONST(user_at_realm, "@");
685 buffer_append_string_buffer(user_at_realm, require->realm);
688 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ);
689 if (ret) {
690 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret);
691 if (user_at_realm) buffer_free(user_at_realm);
692 ret = -1;
693 goto end;
695 if (user_at_realm) buffer_free(user_at_realm);
696 /* XXX: if the qualified username with @realm should be in REMOTE_USER,
697 * then http_auth_backend_t basic interface needs to change to pass
698 * modifiable buffer *username, but note that const char *pw follows
699 * in the truncated buffer *username, so pw would need to be copied
700 * before modifying buffer *username */
703 * char *name = NULL;
704 * ret = krb5_unparse_name(kcontext, c_princ, &name);
705 * if (ret == 0) {
706 * log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw);
708 * krb5_free_unparsed_name(kcontext, name);
711 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL);
712 if (ret) {
713 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret);
714 goto end;
717 ret = mod_authn_gssapi_verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab);
718 if (ret) {
719 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret);
720 goto end;
723 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache);
724 if (ret) {
725 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret);
726 goto end;
729 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ);
730 if (ret) {
731 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret);
732 goto end;
735 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds);
736 if (ret) {
737 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret);
738 goto end;
741 c_ccache = ret_ccache;
742 ret_ccache = NULL;
744 end:
746 krb5_free_cred_contents(kcontext, &c_creds);
747 if (ret_ccache)
748 krb5_cc_destroy(kcontext, ret_ccache);
750 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(srv, con, p, kcontext, c_ccache))) {
751 log_error_write(srv, __FILE__, __LINE__, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username);
754 buffer_free(sprinc);
755 if (c_princ)
756 krb5_free_principal(kcontext, c_princ);
757 if (s_princ)
758 krb5_free_principal(kcontext, s_princ);
759 if (c_ccache)
760 krb5_cc_destroy(kcontext, c_ccache);
761 if (keytab)
762 krb5_kt_close(kcontext, keytab);
764 krb5_free_context(kcontext);
766 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){
767 return HANDLER_GO_ON;
769 else {
770 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
771 log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
772 return mod_authn_gssapi_send_401_unauthorized_basic(srv, con);
777 CONNECTION_FUNC(mod_authn_gssapi_handle_reset) {
778 plugin_data *p = (plugin_data *)p_d;
779 buffer *kccname = (buffer *)con->plugin_ctx[p->id];
780 if (NULL != kccname) {
781 con->plugin_ctx[p->id] = NULL;
782 unlink(kccname->ptr+sizeof("FILE:")-1);
783 buffer_free(kccname);
786 UNUSED(srv);
787 return HANDLER_GO_ON;
790 int mod_authn_gssapi_plugin_init(plugin *p);
791 int mod_authn_gssapi_plugin_init(plugin *p) {
792 p->version = LIGHTTPD_VERSION_ID;
793 p->name = buffer_init_string("authn_gssapi");
794 p->init = mod_authn_gssapi_init;
795 p->set_defaults= mod_authn_gssapi_set_defaults;
796 p->cleanup = mod_authn_gssapi_free;
797 p->connection_reset = mod_authn_gssapi_handle_reset;
799 p->data = NULL;
801 return 0;