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