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