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