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