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