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