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