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