9c9b1b2c6c6485b9915139955cb3f9f63b3ec882
[mod_auth_gssapi.git] / src / mod_auth_gssapi.c
1 /*
2    MOD AUTH GSSAPI
3
4    Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5
6    Permission is hereby granted, free of charge, to any person obtaining a
7    copy of this software and associated documentation files (the "Software"),
8    to deal in the Software without restriction, including without limitation
9    the rights to use, copy, modify, merge, publish, distribute, sublicense,
10    and/or sell copies of the Software, and to permit persons to whom the
11    Software is furnished to do so, subject to the following conditions:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22    DEALINGS IN THE SOFTWARE.
23 */
24
25 #include "mod_auth_gssapi.h"
26
27 const gss_OID_desc gss_mech_ntlmssp = {
28     GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
29 };
30
31 const gss_OID_set_desc gss_mech_set_ntlmssp = {
32     1, discard_const(&gss_mech_ntlmssp)
33 };
34
35 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
36
37 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
38
39 APLOG_USE_MODULE(auth_gssapi);
40
41 static char *mag_status(request_rec *req, int type, uint32_t err)
42 {
43     uint32_t maj_ret, min_ret;
44     gss_buffer_desc text;
45     uint32_t msg_ctx;
46     char *msg_ret;
47     int len;
48
49     msg_ret = NULL;
50     msg_ctx = 0;
51     do {
52         maj_ret = gss_display_status(&min_ret, err, type,
53                                      GSS_C_NO_OID, &msg_ctx, &text);
54         if (maj_ret != GSS_S_COMPLETE) {
55             return msg_ret;
56         }
57
58         len = text.length;
59         if (msg_ret) {
60             msg_ret = apr_psprintf(req->pool, "%s, %*s",
61                                    msg_ret, len, (char *)text.value);
62         } else {
63             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
64         }
65         gss_release_buffer(&min_ret, &text);
66     } while (msg_ctx != 0);
67
68     return msg_ret;
69 }
70
71 static char *mag_error(request_rec *req, const char *msg,
72                        uint32_t maj, uint32_t min)
73 {
74     char *msg_maj;
75     char *msg_min;
76
77     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
78     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
79     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
80 }
81
82 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
83
84 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
85                            apr_pool_t *temp, server_rec *s)
86 {
87     /* FIXME: create mutex to deal with connections and contexts ? */
88     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
89     mag_post_config_session();
90     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
91
92     return OK;
93 }
94
95 static int mag_pre_connection(conn_rec *c, void *csd)
96 {
97     struct mag_conn *mc;
98
99     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
100
101     mc->parent = c->pool;
102     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
103     return OK;
104 }
105
106 static apr_status_t mag_conn_destroy(void *ptr)
107 {
108     struct mag_conn *mc = (struct mag_conn *)ptr;
109     uint32_t min;
110
111     if (mc->ctx) {
112         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
113         mc->established = false;
114     }
115     return APR_SUCCESS;
116 }
117
118 static bool mag_conn_is_https(conn_rec *c)
119 {
120     if (mag_is_https) {
121         if (mag_is_https(c)) return true;
122     }
123
124     return false;
125 }
126
127 static bool mag_acquire_creds(request_rec *req,
128                               struct mag_config *cfg,
129                               gss_OID_set desired_mechs,
130                               gss_cred_usage_t cred_usage,
131                               gss_cred_id_t *creds,
132                               gss_OID_set *actual_mechs)
133 {
134     uint32_t maj, min;
135 #ifdef HAVE_CRED_STORE
136     gss_const_key_value_set_t store = cfg->cred_store;
137
138     maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
139                                 desired_mechs, cred_usage, store, creds,
140                                 actual_mechs, NULL);
141 #else
142     maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
143                            desired_mechs, cred_usage, creds,
144                            actual_mechs, NULL);
145 #endif
146
147     if (GSS_ERROR(maj)) {
148         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
149                       mag_error(req, "gss_acquire_cred[_from]() "
150                                 "failed to get server creds",
151                                 maj, min));
152         return false;
153     }
154
155     return true;
156 }
157
158 #ifdef HAVE_CRED_STORE
159 static char *escape(apr_pool_t *pool, const char *name,
160                     char find, const char *replace)
161 {
162     char *escaped = NULL;
163     char *namecopy;
164     char *n;
165     char *p;
166
167     namecopy = apr_pstrdup(pool, name);
168
169     p = strchr(namecopy, find);
170     if (!p) return namecopy;
171
172     /* first segment */
173     n = namecopy;
174     while (p) {
175         /* terminate previous segment */
176         *p = '\0';
177         if (escaped) {
178             escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
179         } else {
180             escaped = apr_pstrcat(pool, n, replace, NULL);
181         }
182         /* move to next segment */
183         n = p + 1;
184         p = strchr(n, find);
185     }
186     /* append last segment if any */
187     if (*n) {
188         escaped = apr_pstrcat(pool, escaped, n, NULL);
189     }
190
191     return escaped;
192 }
193
194 static void mag_store_deleg_creds(request_rec *req,
195                                   char *dir, char *clientname,
196                                   gss_cred_id_t delegated_cred,
197                                   char **ccachefile)
198 {
199     gss_key_value_element_desc element;
200     gss_key_value_set_desc store;
201     char *value;
202     uint32_t maj, min;
203     char *escaped;
204
205     /* We need to escape away '/', we can't have path separators in
206      * a ccache file name */
207     /* first double escape the esacping char (~) if any */
208     escaped = escape(req->pool, clientname, '~', "~~");
209     if (!escaped) return;
210     /* then escape away the separator (/) if any */
211     escaped = escape(req->pool, escaped, '/', "~");
212     if (!escaped) return;
213
214     value = apr_psprintf(req->pool, "FILE:%s/%s", dir, escaped);
215
216     element.key = "ccache";
217     element.value = value;
218     store.elements = &element;
219     store.count = 1;
220
221     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
222                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
223     if (GSS_ERROR(maj)) {
224         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
225                       mag_error(req, "failed to store delegated creds",
226                                 maj, min));
227     }
228
229     *ccachefile = value;
230 }
231 #endif
232
233 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
234                               gss_buffer_t value)
235 {
236     char *auth_header_value;
237
238     auth_header_value = ap_getword_white(pool, auth_header);
239     if (!auth_header_value) return false;
240     value->length = apr_base64_decode_len(auth_header_value) + 1;
241     value->value = apr_pcalloc(pool, value->length);
242     if (!value->value) return false;
243     value->length = apr_base64_decode(value->value, auth_header_value);
244
245     return true;
246 }
247
248 static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
249 {
250     if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
251
252     for (int i = 0; i < cfg->allowed_mechs->count; i++) {
253         if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
254             return true;
255         }
256     }
257     return false;
258 }
259
260 #define AUTH_TYPE_NEGOTIATE 0
261 #define AUTH_TYPE_BASIC 1
262 #define AUTH_TYPE_RAW_NTLM 2
263 const char *auth_types[] = {
264     "Negotiate",
265     "Basic",
266     "NTLM",
267     NULL
268 };
269
270 static int mag_auth(request_rec *req)
271 {
272     const char *type;
273     int auth_type = -1;
274     struct mag_config *cfg;
275     const char *auth_header;
276     char *auth_header_type;
277     int ret = HTTP_UNAUTHORIZED;
278     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
279     gss_ctx_id_t *pctx;
280     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
281     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
282     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
283     gss_buffer_desc ba_user;
284     gss_buffer_desc ba_pwd;
285     gss_name_t client = GSS_C_NO_NAME;
286     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
287     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
288     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
289     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
290     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
291     uint32_t flags;
292     uint32_t vtime;
293     uint32_t maj, min;
294     char *reply;
295     size_t replen;
296     char *clientname;
297     gss_OID mech_type = GSS_C_NO_OID;
298     gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
299     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
300     struct mag_conn *mc = NULL;
301     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
302     gss_name_t server = GSS_C_NO_NAME;
303 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
304     const char *user_ccache = NULL;
305     const char *orig_ccache = NULL;
306 #endif
307     uint32_t init_flags = 0;
308     time_t expiration;
309     int i;
310
311     type = ap_auth_type(req);
312     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
313         return DECLINED;
314     }
315
316     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
317
318     if (!cfg->allowed_mechs) {
319         /* Try to fetch the default set if not explicitly configured */
320         (void)mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
321                                 &server_cred, &cfg->allowed_mechs);
322         (void)gss_release_cred(&min, &server_cred);
323     }
324
325     /* implicit auth for subrequests if main auth already happened */
326     if (!ap_is_initial_req(req)) {
327         type = ap_auth_type(req->main);
328         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
329             /* warn if the subrequest location and the main request
330              * location have different configs */
331             if (cfg != ap_get_module_config(req->main->per_dir_config,
332                                             &auth_gssapi_module)) {
333                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
334                               req, "Subrequest authentication bypass on "
335                                    "location with different configuration!");
336             }
337             if (req->main->user) {
338                 req->user = apr_pstrdup(req->pool, req->main->user);
339                 return OK;
340             } else {
341                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
342                               "The main request is tasked to establish the "
343                               "security context, can't proceed!");
344                 return HTTP_UNAUTHORIZED;
345             }
346         } else {
347             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
348                           "Subrequest GSSAPI auth with no auth on the main "
349                           "request. This operation may fail if other "
350                           "subrequests already established a context or the "
351                           "mechanism requires multiple roundtrips.");
352         }
353     }
354
355     if (cfg->ssl_only) {
356         if (!mag_conn_is_https(req->connection)) {
357             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
358                           "Not a TLS connection, refusing to authenticate!");
359             goto done;
360         }
361     }
362
363     if (cfg->gss_conn_ctx) {
364         mc = (struct mag_conn *)ap_get_module_config(
365                                                 req->connection->conn_config,
366                                                 &auth_gssapi_module);
367         if (!mc) {
368             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
369                           "Failed to retrieve connection context!");
370             goto done;
371         }
372     }
373
374     /* if available, session always supersedes connection bound data */
375     if (cfg->use_sessions) {
376         mag_check_session(req, cfg, &mc);
377     }
378
379     if (mc) {
380         /* register the context in the memory pool, so it can be freed
381          * when the connection/request is terminated */
382         apr_pool_userdata_set(mc, "mag_conn_ptr",
383                               mag_conn_destroy, mc->parent);
384
385         if (mc->established) {
386             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
387                           "Already established context found!");
388             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
389             apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
390                           apr_psprintf(req->pool,
391                                        "%ld", (long)mc->expiration));
392             req->ap_auth_type = apr_pstrdup(req->pool,
393                                             auth_types[mc->auth_type]);
394             req->user = apr_pstrdup(req->pool, mc->user_name);
395             ret = OK;
396             goto done;
397         }
398         pctx = &mc->ctx;
399     } else {
400         pctx = &ctx;
401     }
402
403     auth_header = apr_table_get(req->headers_in, "Authorization");
404     if (!auth_header) goto done;
405
406     auth_header_type = ap_getword_white(req->pool, &auth_header);
407     if (!auth_header_type) goto done;
408
409     for (i = 0; auth_types[i] != NULL; i++) {
410         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
411             auth_type = i;
412             break;
413         }
414     }
415
416     switch (auth_type) {
417     case AUTH_TYPE_NEGOTIATE:
418         if (!parse_auth_header(req->pool, &auth_header, &input)) {
419             goto done;
420         }
421         break;
422     case AUTH_TYPE_BASIC:
423         if (!cfg->use_basic_auth) {
424             goto done;
425         }
426
427         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
428         if (!ba_pwd.value) goto done;
429         ba_user.value = ap_getword_nulls_nc(req->pool,
430                                             (char **)&ba_pwd.value, ':');
431         if (!ba_user.value) goto done;
432         if (((char *)ba_user.value)[0] == '\0' ||
433             ((char *)ba_pwd.value)[0] == '\0') {
434             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
435                           "Invalid empty user or password for Basic Auth");
436             goto done;
437         }
438         ba_user.length = strlen(ba_user.value);
439         ba_pwd.length = strlen(ba_pwd.value);
440         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
441         if (GSS_ERROR(maj)) {
442             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
443                           "In Basic Auth, %s",
444                           mag_error(req, "gss_import_name() failed",
445                                     maj, min));
446             goto done;
447         }
448 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
449         /* Set a per-thread ccache in case we are using kerberos,
450          * it is not elegant but avoids interference between threads */
451         long long unsigned int rndname;
452         apr_status_t rs;
453         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
454                                        sizeof(long long unsigned int));
455         if (rs != APR_SUCCESS) {
456             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
457                           "Failed to generate random ccache name");
458             goto done;
459         }
460         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
461         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
462         if (GSS_ERROR(maj)) {
463             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
464                           "In Basic Auth, %s",
465                           mag_error(req, "gss_krb5_ccache_name() "
466                                     "failed", maj, min));
467             goto done;
468         }
469 #endif
470         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
471                                              GSS_C_INDEFINITE,
472                                              cfg->allowed_mechs,
473                                              GSS_C_INITIATE,
474                                              &user_cred, NULL, NULL);
475         if (GSS_ERROR(maj)) {
476             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
477                           "In Basic Auth, %s",
478                           mag_error(req, "gss_acquire_cred_with_password() "
479                                     "failed", maj, min));
480             goto done;
481         }
482         gss_release_name(&min, &client);
483         break;
484
485     case AUTH_TYPE_RAW_NTLM:
486         if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
487             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
488                           "NTLM Authentication is not allowed!");
489             goto done;
490         }
491
492         if (!parse_auth_header(req->pool, &auth_header, &input)) {
493             goto done;
494         }
495
496         desired_mechs = discard_const(&gss_mech_set_ntlmssp);
497         break;
498
499     default:
500         goto done;
501     }
502
503     req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
504
505 #ifdef HAVE_CRED_STORE
506     if (cfg->use_s4u2proxy) {
507         cred_usage = GSS_C_BOTH;
508     }
509 #endif
510     if (!mag_acquire_creds(req, cfg, desired_mechs,
511                            cred_usage, &acquired_cred, NULL)) {
512         goto done;
513     }
514
515     if (auth_type == AUTH_TYPE_BASIC) {
516         if (cred_usage == GSS_C_BOTH) {
517             /* If GSS_C_BOTH is used then inquire_cred will return the client
518              * name instead of the SPN of the server credentials. Therefore we
519              * need to acquire a different set of credential setting
520              * GSS_C_ACCEPT explicitly */
521             if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
522                                    GSS_C_ACCEPT, &server_cred, NULL)) {
523                 goto done;
524             }
525         } else {
526             server_cred = acquired_cred;
527         }
528         maj = gss_inquire_cred(&min, server_cred, &server,
529                                NULL, NULL, NULL);
530         if (GSS_ERROR(maj)) {
531             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
532                           "%s", mag_error(req, "gss_inquired_cred_() "
533                                           "failed", maj, min));
534             goto done;
535         }
536         if (server_cred != acquired_cred) {
537             gss_release_cred(&min, &server_cred);
538         }
539
540 #ifdef HAVE_CRED_STORE
541         if (cfg->deleg_ccache_dir) {
542             /* delegate ourselves credentials so we store them as requested */
543             init_flags |= GSS_C_DELEG_FLAG;
544         }
545 #endif
546
547         /* output and input are inverted here, this is intentional */
548         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
549                                    GSS_C_NO_OID, init_flags, 300,
550                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
551                                    NULL, &input, NULL, NULL);
552         if (GSS_ERROR(maj)) {
553             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
554                           "%s", mag_error(req, "gss_init_sec_context() "
555                                           "failed", maj, min));
556             goto done;
557         }
558     }
559
560     if (auth_type == AUTH_TYPE_NEGOTIATE &&
561         cfg->allowed_mechs != GSS_C_NO_OID_SET) {
562         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
563         if (GSS_ERROR(maj)) {
564             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
565                           mag_error(req, "gss_set_neg_mechs() failed",
566                                     maj, min));
567             goto done;
568         }
569     }
570
571     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
572                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
573                                  &client, &mech_type, &output, &flags, &vtime,
574                                  &delegated_cred);
575     if (GSS_ERROR(maj)) {
576         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
577                       mag_error(req, "gss_accept_sec_context() failed",
578                                 maj, min));
579         goto done;
580     }
581     if (auth_type == AUTH_TYPE_BASIC) {
582         while (maj == GSS_S_CONTINUE_NEEDED) {
583             gss_release_buffer(&min, &input);
584             /* output and input are inverted here, this is intentional */
585             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
586                                        GSS_C_NO_OID, init_flags, 300,
587                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
588                                        NULL, &input, NULL, NULL);
589             if (GSS_ERROR(maj)) {
590                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
591                               "%s", mag_error(req, "gss_init_sec_context() "
592                                               "failed", maj, min));
593                 goto done;
594             }
595             gss_release_buffer(&min, &output);
596             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
597                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
598                                          &client, &mech_type, &output, &flags,
599                                          &vtime, &delegated_cred);
600             if (GSS_ERROR(maj)) {
601                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
602                               "%s", mag_error(req, "gss_accept_sec_context()"
603                                               " failed", maj, min));
604                 goto done;
605             }
606         }
607     } else if (maj == GSS_S_CONTINUE_NEEDED) {
608         if (!mc) {
609             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
610                           "Mechanism needs continuation but neither "
611                           "GssapiConnectionBound nor "
612                           "GssapiUseSessions are available");
613             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
614             gss_release_buffer(&min, &output);
615             output.length = 0;
616         }
617         /* auth not complete send token and wait next packet */
618         goto done;
619     }
620
621     /* Always set the GSS name in an env var */
622     maj = gss_display_name(&min, client, &name, NULL);
623     if (GSS_ERROR(maj)) {
624         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
625                       mag_error(req, "gss_display_name() failed",
626                                 maj, min));
627         goto done;
628     }
629     clientname = apr_pstrndup(req->pool, name.value, name.length);
630     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
631     expiration = time(NULL) + vtime;
632     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
633                   apr_psprintf(req->pool, "%ld", (long)expiration));
634
635 #ifdef HAVE_CRED_STORE
636     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
637         char *ccachefile = NULL;
638
639         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
640                               delegated_cred, &ccachefile);
641
642         if (ccachefile) {
643             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
644         }
645     }
646 #endif
647
648     if (cfg->map_to_local) {
649         maj = gss_localname(&min, client, mech_type, &lname);
650         if (maj != GSS_S_COMPLETE) {
651             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
652                           mag_error(req, "gss_localname() failed", maj, min));
653             goto done;
654         }
655         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
656     } else {
657         req->user = clientname;
658     }
659
660     if (mc) {
661         mc->user_name = apr_pstrdup(mc->parent, req->user);
662         mc->gss_name = apr_pstrdup(mc->parent, clientname);
663         mc->established = true;
664         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
665             vtime = MIN_SESS_EXP_TIME;
666         }
667         mc->expiration = expiration;
668         if (cfg->use_sessions) {
669             mag_attempt_session(req, cfg, mc);
670         }
671         mc->auth_type = auth_type;
672     }
673
674     if (cfg->send_persist)
675         apr_table_set(req->headers_out, "Persistent-Auth",
676             cfg->gss_conn_ctx ? "true" : "false");
677
678     ret = OK;
679
680 done:
681     if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
682         int prefixlen = strlen(auth_types[auth_type]) + 1;
683         replen = apr_base64_encode_len(output.length) + 1;
684         reply = apr_pcalloc(req->pool, prefixlen + replen);
685         if (reply) {
686             memcpy(reply, auth_types[auth_type], prefixlen - 1);
687             reply[prefixlen - 1] = ' ';
688             apr_base64_encode(&reply[prefixlen], output.value, output.length);
689             apr_table_add(req->err_headers_out,
690                           "WWW-Authenticate", reply);
691         }
692     } else if (ret == HTTP_UNAUTHORIZED) {
693         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
694         if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
695             apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
696         }
697         if (cfg->use_basic_auth) {
698             apr_table_add(req->err_headers_out,
699                           "WWW-Authenticate",
700                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
701                                        ap_auth_name(req)));
702         }
703     }
704 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
705     if (user_ccache != NULL) {
706         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
707         if (maj != GSS_S_COMPLETE) {
708             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
709                           "Failed to restore per-thread ccache, %s",
710                           mag_error(req, "gss_krb5_ccache_name() "
711                                     "failed", maj, min));
712         }
713     }
714 #endif
715     gss_delete_sec_context(&min, &user_ctx, &output);
716     gss_release_cred(&min, &user_cred);
717     gss_release_cred(&min, &acquired_cred);
718     gss_release_cred(&min, &delegated_cred);
719     gss_release_buffer(&min, &output);
720     gss_release_name(&min, &client);
721     gss_release_name(&min, &server);
722     gss_release_buffer(&min, &name);
723     gss_release_buffer(&min, &lname);
724     return ret;
725 }
726
727
728 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
729 {
730     struct mag_config *cfg;
731
732     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
733     cfg->pool = p;
734
735     return cfg;
736 }
737
738 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
739 {
740     struct mag_config *cfg = (struct mag_config *)mconfig;
741     cfg->ssl_only = on ? true : false;
742     return NULL;
743 }
744
745 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
746 {
747     struct mag_config *cfg = (struct mag_config *)mconfig;
748     cfg->map_to_local = on ? true : false;
749     return NULL;
750 }
751
752 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
753 {
754     struct mag_config *cfg = (struct mag_config *)mconfig;
755     cfg->gss_conn_ctx = on ? true : false;
756     return NULL;
757 }
758
759 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
760 {
761     struct mag_config *cfg = (struct mag_config *)mconfig;
762     cfg->send_persist = on ? true : false;
763     return NULL;
764 }
765
766 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
767 {
768     struct mag_config *cfg = (struct mag_config *)mconfig;
769     cfg->use_sessions = on ? true : false;
770     return NULL;
771 }
772
773 #ifdef HAVE_CRED_STORE
774 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
775 {
776     struct mag_config *cfg = (struct mag_config *)mconfig;
777     cfg->use_s4u2proxy = on ? true : false;
778
779     if (cfg->deleg_ccache_dir == NULL) {
780         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
781     }
782     return NULL;
783 }
784 #endif
785
786 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
787 {
788     struct mag_config *cfg = (struct mag_config *)mconfig;
789     struct databuf keys;
790     unsigned char *val;
791     apr_status_t rc;
792     const char *k;
793     int l;
794
795     if (strncmp(w, "key:", 4) != 0) {
796         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
797                      "Invalid key format, expected prefix 'key:'");
798         return NULL;
799     }
800     k = w + 4;
801
802     l = apr_base64_decode_len(k);
803     val = apr_palloc(parms->temp_pool, l);
804
805     keys.length = (int)apr_base64_decode_binary(val, k);
806     keys.value = (unsigned char *)val;
807
808     if (keys.length != 32) {
809         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
810                      "Invalid key length, expected 32 got %d", keys.length);
811         return NULL;
812     }
813
814     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
815     if (rc != OK) {
816         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
817                      "Failed to import sealing key!");
818     }
819     return NULL;
820 }
821
822 #ifdef HAVE_CRED_STORE
823
824 #define MAX_CRED_OPTIONS 10
825
826 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
827                                   const char *w)
828 {
829     struct mag_config *cfg = (struct mag_config *)mconfig;
830     gss_key_value_element_desc *elements;
831     uint32_t count;
832     size_t size;
833     const char *p;
834     char *value;
835     char *key;
836
837     p = strchr(w, ':');
838     if (!p) {
839         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
840                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
841         return NULL;
842     }
843
844     key = apr_pstrndup(parms->pool, w, (p-w));
845     value = apr_pstrdup(parms->pool, p + 1);
846
847     if (!cfg->cred_store) {
848         cfg->cred_store = apr_pcalloc(parms->pool,
849                                       sizeof(gss_key_value_set_desc));
850         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
851         cfg->cred_store->elements = apr_palloc(parms->pool, size);
852     }
853
854     elements = cfg->cred_store->elements;
855     count = cfg->cred_store->count;
856
857     if (count >= MAX_CRED_OPTIONS) {
858         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
859                      "Too many GssapiCredStore options (MAX: %d)",
860                      MAX_CRED_OPTIONS);
861         return NULL;
862     }
863     cfg->cred_store->count++;
864
865     elements[count].key = key;
866     elements[count].value = value;
867
868     return NULL;
869 }
870
871 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
872                                         const char *value)
873 {
874     struct mag_config *cfg = (struct mag_config *)mconfig;
875
876     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
877
878     return NULL;
879 }
880 #endif
881
882 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
883 {
884     struct mag_config *cfg = (struct mag_config *)mconfig;
885
886     cfg->use_basic_auth = on ? true : false;
887     return NULL;
888 }
889
890 #define MAX_ALLOWED_MECHS 10
891
892 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
893                                   const char *w)
894 {
895     struct mag_config *cfg = (struct mag_config *)mconfig;
896     gss_const_OID oid;
897     size_t size;
898
899     if (!cfg->allowed_mechs) {
900         cfg->allowed_mechs = apr_pcalloc(parms->pool,
901                                          sizeof(gss_OID_set_desc));
902         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
903         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
904     }
905
906     if (strcmp(w, "krb5") == 0) {
907         oid = gss_mech_krb5;
908     } else if (strcmp(w, "iakerb") == 0) {
909         oid = gss_mech_iakerb;
910     } else if (strcmp(w, "ntlmssp") == 0) {
911         oid = &gss_mech_ntlmssp;
912     } else {
913         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
914                      "Unrecognized GSSAPI Mechanism: %s", w);
915         return NULL;
916     }
917
918     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
919         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
920                      "Too many GssapiAllowedMech options (MAX: %d)",
921                      MAX_ALLOWED_MECHS);
922         return NULL;
923     }
924     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
925     cfg->allowed_mechs->count++;
926
927     return NULL;
928 }
929
930 static const command_rec mag_commands[] = {
931     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
932                   "Work only if connection is SSL Secured"),
933     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
934                   "Translate principals to local names"),
935     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
936                   "Authentication is bound to the TCP connection"),
937     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
938                   "Send Persitent-Auth header according to connection bound"),
939     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
940                   "Authentication uses mod_sessions to hold status"),
941     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
942                      "Key Used to seal session data."),
943 #ifdef HAVE_CRED_STORE
944     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
945                   "Initializes credentials for s4u2proxy usage"),
946     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
947                     "Credential Store"),
948     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
949                      OR_AUTHCFG, "Directory to store delegated credentials"),
950 #endif
951 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
952     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
953                      "Allows use of Basic Auth for authentication"),
954 #endif
955     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
956                     "Allowed Mechanisms"),
957     { NULL }
958 };
959
960 static void
961 mag_register_hooks(apr_pool_t *p)
962 {
963     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
964     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
965     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
966 }
967
968 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
969 {
970     STANDARD20_MODULE_STUFF,
971     mag_create_dir_config,
972     NULL,
973     NULL,
974     NULL,
975     mag_commands,
976     mag_register_hooks
977 };