e2331107b89734bd5da3a742a884c6a92489d5a8
[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
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             req->ap_auth_type = apr_pstrdup(req->pool, mc->auth_type);
315             req->user = apr_pstrdup(req->pool, mc->user_name);
316             ret = OK;
317             goto done;
318         }
319         pctx = &mc->ctx;
320     } else {
321         pctx = &ctx;
322     }
323
324     auth_header = apr_table_get(req->headers_in, "Authorization");
325     if (!auth_header) goto done;
326
327     auth_header_type = ap_getword_white(req->pool, &auth_header);
328     if (!auth_header_type) goto done;
329
330     if (strcasecmp(auth_header_type, "Negotiate") == 0) {
331         auth_type = "Negotiate";
332
333         auth_header_value = ap_getword_white(req->pool, &auth_header);
334         if (!auth_header_value) goto done;
335         input.length = apr_base64_decode_len(auth_header_value) + 1;
336         input.value = apr_pcalloc(req->pool, input.length);
337         if (!input.value) goto done;
338         input.length = apr_base64_decode(input.value, auth_header_value);
339     } else if ((strcasecmp(auth_header_type, "Basic") == 0) &&
340                (cfg->use_basic_auth == true)) {
341         auth_type = "Basic";
342         is_basic = true;
343
344         gss_buffer_desc ba_user;
345         gss_buffer_desc ba_pwd;
346
347         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
348         if (!ba_pwd.value) goto done;
349         ba_user.value = ap_getword_nulls_nc(req->pool,
350                                             (char **)&ba_pwd.value, ':');
351         if (!ba_user.value) goto done;
352         if (((char *)ba_user.value)[0] == '\0' ||
353             ((char *)ba_pwd.value)[0] == '\0') {
354             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
355                           "Invalid empty user or password for Basic Auth");
356             goto done;
357         }
358         ba_user.length = strlen(ba_user.value);
359         ba_pwd.length = strlen(ba_pwd.value);
360         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
361         if (GSS_ERROR(maj)) {
362             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
363                           "In Basic Auth, %s",
364                           mag_error(req, "gss_import_name() failed",
365                                     maj, min));
366             goto done;
367         }
368 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
369         /* Set a per-thread ccache in case we are using kerberos,
370          * it is not elegant but avoids interference between threads */
371         long long unsigned int rndname;
372         apr_status_t rs;
373         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
374                                        sizeof(long long unsigned int));
375         if (rs != APR_SUCCESS) {
376             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
377                           "Failed to generate random ccache name");
378             goto done;
379         }
380         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
381         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
382         if (GSS_ERROR(maj)) {
383             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
384                           "In Basic Auth, %s",
385                           mag_error(req, "gss_krb5_ccache_name() "
386                                     "failed", maj, min));
387             goto done;
388         }
389 #endif
390         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
391                                              GSS_C_INDEFINITE,
392                                              GSS_C_NO_OID_SET,
393                                              GSS_C_INITIATE,
394                                              &user_cred, NULL, NULL);
395         if (GSS_ERROR(maj)) {
396             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
397                           "In Basic Auth, %s",
398                           mag_error(req, "gss_acquire_cred_with_password() "
399                                     "failed", maj, min));
400             goto done;
401         }
402         gss_release_name(&min, &client);
403     } else {
404         goto done;
405     }
406
407     req->ap_auth_type = apr_pstrdup(req->pool, auth_type);
408
409 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
410     if (cfg->use_s4u2proxy) {
411         cred_usage = GSS_C_BOTH;
412     }
413     if (cfg->cred_store) {
414         maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
415                                     GSS_C_NO_OID_SET, cred_usage,
416                                     cfg->cred_store, &acquired_cred,
417                                     NULL, NULL);
418         if (GSS_ERROR(maj)) {
419             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
420                           mag_error(req, "gss_acquire_cred_from() failed",
421                                     maj, min));
422             goto done;
423         }
424     }
425 #endif
426
427     if (is_basic) {
428         if (!acquired_cred) {
429             /* Try to acquire default creds */
430             maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
431                                    GSS_C_NO_OID_SET, cred_usage,
432                                    &acquired_cred, NULL, NULL);
433             if (GSS_ERROR(maj)) {
434                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
435                               "%s", mag_error(req, "gss_acquire_cred_from()"
436                                               " failed", maj, min));
437                 goto done;
438             }
439         }
440         maj = gss_inquire_cred(&min, acquired_cred, &server,
441                                NULL, NULL, NULL);
442         if (GSS_ERROR(maj)) {
443             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
444                           "%s", mag_error(req, "gss_inquired_cred_() "
445                                           "failed", maj, min));
446             goto done;
447         }
448         /* output and input are inverted here, this is intentional */
449         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
450                                    GSS_C_NO_OID, 0, 300,
451                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
452                                    NULL, &input, NULL, NULL);
453         if (GSS_ERROR(maj)) {
454             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
455                           "%s", mag_error(req, "gss_init_sec_context() "
456                                           "failed", maj, min));
457             goto done;
458         }
459     }
460
461     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
462                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
463                                  &client, &mech_type, &output, &flags, &vtime,
464                                  &delegated_cred);
465     if (GSS_ERROR(maj)) {
466         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
467                       mag_error(req, "gss_accept_sec_context() failed",
468                                 maj, min));
469         goto done;
470     }
471     if (is_basic) {
472         while (maj == GSS_S_CONTINUE_NEEDED) {
473             gss_release_buffer(&min, &input);
474             /* output and input are inverted here, this is intentional */
475             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
476                                        GSS_C_NO_OID, 0, 300,
477                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
478                                        NULL, &input, NULL, NULL);
479             if (GSS_ERROR(maj)) {
480                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
481                               "%s", mag_error(req, "gss_init_sec_context() "
482                                               "failed", maj, min));
483                 goto done;
484             }
485             gss_release_buffer(&min, &output);
486             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
487                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
488                                          &client, &mech_type, &output, &flags,
489                                          &vtime, &delegated_cred);
490             if (GSS_ERROR(maj)) {
491                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
492                               "%s", mag_error(req, "gss_accept_sec_context()"
493                                               " failed", maj, min));
494                 goto done;
495             }
496         }
497     } else if (maj == GSS_S_CONTINUE_NEEDED) {
498         if (!mc) {
499             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
500                           "Mechanism needs continuation but neither "
501                           "GssapiConnectionBound nor "
502                           "GssapiUseSessions are available");
503             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
504             gss_release_buffer(&min, &output);
505             output.length = 0;
506         }
507         /* auth not complete send token and wait next packet */
508         goto done;
509     }
510
511     /* Always set the GSS name in an env var */
512     maj = gss_display_name(&min, client, &name, NULL);
513     if (GSS_ERROR(maj)) {
514         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
515                       mag_error(req, "gss_display_name() failed",
516                                 maj, min));
517         goto done;
518     }
519     clientname = apr_pstrndup(req->pool, name.value, name.length);
520     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
521
522 #ifdef HAVE_GSS_STORE_CRED_INTO
523     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
524         char *ccachefile = NULL;
525
526         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
527                               delegated_cred, &ccachefile);
528
529         if (ccachefile) {
530             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
531         }
532     }
533 #endif
534
535     if (cfg->map_to_local) {
536         maj = gss_localname(&min, client, mech_type, &lname);
537         if (maj != GSS_S_COMPLETE) {
538             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
539                           mag_error(req, "gss_localname() failed", maj, min));
540             goto done;
541         }
542         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
543     } else {
544         req->user = clientname;
545     }
546
547     if (mc) {
548         mc->user_name = apr_pstrdup(mc->parent, req->user);
549         mc->gss_name = apr_pstrdup(mc->parent, clientname);
550         mc->established = true;
551         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
552             vtime = MIN_SESS_EXP_TIME;
553         }
554         mc->expiration = time(NULL) + vtime;
555         if (cfg->use_sessions) {
556             mag_attempt_session(req, cfg, mc);
557         }
558         mc->auth_type = auth_type;
559     }
560
561     ret = OK;
562
563 done:
564     if (ret == HTTP_UNAUTHORIZED) {
565         if (output.length != 0) {
566             replen = apr_base64_encode_len(output.length) + 1;
567             reply = apr_pcalloc(req->pool, 10 + replen);
568             if (reply) {
569                 memcpy(reply, "Negotiate ", 10);
570                 apr_base64_encode(&reply[10], output.value, output.length);
571                 apr_table_add(req->err_headers_out,
572                               "WWW-Authenticate", reply);
573             }
574         } else {
575             apr_table_add(req->err_headers_out,
576                           "WWW-Authenticate", "Negotiate");
577             if (cfg->use_basic_auth) {
578                 apr_table_add(req->err_headers_out,
579                               "WWW-Authenticate",
580                               apr_psprintf(req->pool, "Basic realm=\"%s\"",
581                                            ap_auth_name(req)));
582             }
583         }
584     }
585 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
586     if (user_ccache != NULL) {
587         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
588         if (maj != GSS_S_COMPLETE) {
589             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
590                           "Failed to restore per-thread ccache, %s",
591                           mag_error(req, "gss_krb5_ccache_name() "
592                                     "failed", maj, min));
593         }
594     }
595 #endif
596     gss_delete_sec_context(&min, &user_ctx, &output);
597     gss_release_cred(&min, &user_cred);
598     gss_release_cred(&min, &acquired_cred);
599     gss_release_cred(&min, &delegated_cred);
600     gss_release_buffer(&min, &output);
601     gss_release_name(&min, &client);
602     gss_release_name(&min, &server);
603     gss_release_buffer(&min, &name);
604     gss_release_buffer(&min, &lname);
605     return ret;
606 }
607
608
609 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
610 {
611     struct mag_config *cfg;
612
613     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
614     if (!cfg) return NULL;
615     cfg->pool = p;
616
617     return cfg;
618 }
619
620 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
621 {
622     struct mag_config *cfg = (struct mag_config *)mconfig;
623     cfg->ssl_only = on ? true : false;
624     return NULL;
625 }
626
627 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
628 {
629     struct mag_config *cfg = (struct mag_config *)mconfig;
630     cfg->map_to_local = on ? true : false;
631     return NULL;
632 }
633
634 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
635 {
636     struct mag_config *cfg = (struct mag_config *)mconfig;
637     cfg->gss_conn_ctx = on ? true : false;
638     return NULL;
639 }
640
641 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
642 {
643     struct mag_config *cfg = (struct mag_config *)mconfig;
644     cfg->use_sessions = on ? true : false;
645     return NULL;
646 }
647
648 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
649 {
650     struct mag_config *cfg = (struct mag_config *)mconfig;
651     cfg->use_s4u2proxy = on ? true : false;
652
653     if (cfg->deleg_ccache_dir == NULL) {
654         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
655         if (!cfg->deleg_ccache_dir) {
656             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
657                          parms->server, "%s", "OOM setting deleg_ccache_dir.");
658         }
659     }
660     return NULL;
661 }
662
663 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
664 {
665     struct mag_config *cfg = (struct mag_config *)mconfig;
666     struct databuf key;
667     unsigned char *val;
668     apr_status_t rc;
669     const char *k;
670     int l;
671
672     if (strncmp(w, "key:", 4) != 0) {
673         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
674                      "Invalid key format, expected prefix 'key:'");
675         return NULL;
676     }
677     k = w + 4;
678
679     l = apr_base64_decode_len(k);
680     val = apr_palloc(parms->temp_pool, l);
681     if (!val) {
682         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
683                      "Failed to get memory to decode key");
684         return NULL;
685     }
686
687     key.length = (int)apr_base64_decode_binary(val, k);
688     key.value = (unsigned char *)val;
689
690     if (key.length < 32) {
691         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
692                      "Invalid key length, expected >=32 got %d", key.length);
693         return NULL;
694     }
695
696     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &key);
697     if (rc != OK) {
698         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
699                      "Failed to import sealing key!");
700     }
701     return NULL;
702 }
703
704 #define MAX_CRED_OPTIONS 10
705
706 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
707                                   const char *w)
708 {
709     struct mag_config *cfg = (struct mag_config *)mconfig;
710     gss_key_value_element_desc *elements;
711     uint32_t count;
712     size_t size;
713     const char *p;
714     char *value;
715     char *key;
716
717     p = strchr(w, ':');
718     if (!p) {
719         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
720                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
721         return NULL;
722     }
723
724     key = apr_pstrndup(parms->pool, w, (p-w));
725     value = apr_pstrdup(parms->pool, p + 1);
726     if (!key || !value) {
727         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
728                      "%s", "OOM handling GssapiCredStore option");
729         return NULL;
730     }
731
732     if (!cfg->cred_store) {
733         cfg->cred_store = apr_pcalloc(parms->pool,
734                                       sizeof(gss_key_value_set_desc));
735         if (!cfg->cred_store) {
736             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
737                          "%s", "OOM handling GssapiCredStore option");
738             return NULL;
739         }
740         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
741         cfg->cred_store->elements = apr_palloc(parms->pool, size);
742         if (!cfg->cred_store->elements) {
743             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
744                          "%s", "OOM handling GssapiCredStore option");
745         }
746     }
747
748     elements = cfg->cred_store->elements;
749     count = cfg->cred_store->count;
750
751     if (count >= MAX_CRED_OPTIONS) {
752         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
753                      "Too many GssapiCredStore options (MAX: %d)",
754                      MAX_CRED_OPTIONS);
755         return NULL;
756     }
757     cfg->cred_store->count++;
758
759     elements[count].key = key;
760     elements[count].value = value;
761
762     return NULL;
763 }
764
765 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
766                                         const char *value)
767 {
768     struct mag_config *cfg = (struct mag_config *)mconfig;
769
770     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
771     if (!cfg->deleg_ccache_dir) {
772         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
773                      "%s", "OOM handling GssapiDelegCcacheDir option");
774     }
775
776     return NULL;
777 }
778
779 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
780 {
781     struct mag_config *cfg = (struct mag_config *)mconfig;
782
783     cfg->use_basic_auth = on ? true : false;
784     return NULL;
785 }
786
787 static const command_rec mag_commands[] = {
788     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
789                   "Work only if connection is SSL Secured"),
790     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
791                   "Translate principals to local names"),
792     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
793                   "Authentication is bound to the TCP connection"),
794     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
795                   "Authentication uses mod_sessions to hold status"),
796     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
797                      "Key Used to seal session data."),
798 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
799     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
800                   "Initializes credentials for s4u2proxy usage"),
801 #endif
802 #ifdef HAVE_GSS_STORE_CRED_INTO
803     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
804                     "Credential Store"),
805     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
806                      OR_AUTHCFG, "Directory to store delegated credentials"),
807 #endif
808 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
809     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
810                      "Allows use of Basic Auth for authentication"),
811 #endif
812     { NULL }
813 };
814
815 static void
816 mag_register_hooks(apr_pool_t *p)
817 {
818     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
819     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
820     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
821 }
822
823 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
824 {
825     STANDARD20_MODULE_STUFF,
826     mag_create_dir_config,
827     NULL,
828     NULL,
829     NULL,
830     mag_commands,
831     mag_register_hooks
832 };