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