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