Find the right server name when using basic auth
[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_from()"
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             if (cfg->cred_store) {
437                 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME,
438                                             GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
439                                             GSS_C_ACCEPT, cfg->cred_store,
440                                             &server_cred, NULL, NULL);
441             } else {
442                 /* Try to acquire default creds */
443                 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
444                                        GSS_C_NO_OID_SET, GSS_C_ACCEPT,
445                                        &server_cred, NULL, NULL);
446             }
447             if (GSS_ERROR(maj)) {
448                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
449                               mag_error(req, "gss_acquire_cred[_from]() "
450                                         "failed to get server creds",
451                                         maj, min));
452                 goto done;
453             }
454         } else {
455             server_cred = acquired_cred;
456         }
457         maj = gss_inquire_cred(&min, server_cred, &server,
458                                NULL, NULL, NULL);
459         if (GSS_ERROR(maj)) {
460             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
461                           "%s", mag_error(req, "gss_inquired_cred_() "
462                                           "failed", maj, min));
463             goto done;
464         }
465         if (server_cred != acquired_cred) {
466             gss_release_cred(&min, &server_cred);
467         }
468
469         if (cfg->deleg_ccache_dir) {
470             /* delegate ourselves credentials so we store them as requested */
471             init_flags |= GSS_C_DELEG_FLAG;
472         }
473
474         /* output and input are inverted here, this is intentional */
475         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
476                                    GSS_C_NO_OID, init_flags, 300,
477                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
478                                    NULL, &input, NULL, NULL);
479         if (GSS_ERROR(maj)) {
480             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
481                           "%s", mag_error(req, "gss_init_sec_context() "
482                                           "failed", maj, min));
483             goto done;
484         }
485     }
486
487     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
488                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
489                                  &client, &mech_type, &output, &flags, &vtime,
490                                  &delegated_cred);
491     if (GSS_ERROR(maj)) {
492         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
493                       mag_error(req, "gss_accept_sec_context() failed",
494                                 maj, min));
495         goto done;
496     }
497     if (is_basic) {
498         while (maj == GSS_S_CONTINUE_NEEDED) {
499             gss_release_buffer(&min, &input);
500             /* output and input are inverted here, this is intentional */
501             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
502                                        GSS_C_NO_OID, init_flags, 300,
503                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
504                                        NULL, &input, NULL, NULL);
505             if (GSS_ERROR(maj)) {
506                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
507                               "%s", mag_error(req, "gss_init_sec_context() "
508                                               "failed", maj, min));
509                 goto done;
510             }
511             gss_release_buffer(&min, &output);
512             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
513                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
514                                          &client, &mech_type, &output, &flags,
515                                          &vtime, &delegated_cred);
516             if (GSS_ERROR(maj)) {
517                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
518                               "%s", mag_error(req, "gss_accept_sec_context()"
519                                               " failed", maj, min));
520                 goto done;
521             }
522         }
523     } else if (maj == GSS_S_CONTINUE_NEEDED) {
524         if (!mc) {
525             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
526                           "Mechanism needs continuation but neither "
527                           "GssapiConnectionBound nor "
528                           "GssapiUseSessions are available");
529             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
530             gss_release_buffer(&min, &output);
531             output.length = 0;
532         }
533         /* auth not complete send token and wait next packet */
534         goto done;
535     }
536
537     /* Always set the GSS name in an env var */
538     maj = gss_display_name(&min, client, &name, NULL);
539     if (GSS_ERROR(maj)) {
540         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
541                       mag_error(req, "gss_display_name() failed",
542                                 maj, min));
543         goto done;
544     }
545     clientname = apr_pstrndup(req->pool, name.value, name.length);
546     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
547     expiration = time(NULL) + vtime;
548     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
549                   apr_psprintf(req->pool, "%ld", (long)expiration));
550
551 #ifdef HAVE_GSS_STORE_CRED_INTO
552     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
553         char *ccachefile = NULL;
554
555         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
556                               delegated_cred, &ccachefile);
557
558         if (ccachefile) {
559             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
560         }
561     }
562 #endif
563
564     if (cfg->map_to_local) {
565         maj = gss_localname(&min, client, mech_type, &lname);
566         if (maj != GSS_S_COMPLETE) {
567             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
568                           mag_error(req, "gss_localname() failed", maj, min));
569             goto done;
570         }
571         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
572     } else {
573         req->user = clientname;
574     }
575
576     if (mc) {
577         mc->user_name = apr_pstrdup(mc->parent, req->user);
578         mc->gss_name = apr_pstrdup(mc->parent, clientname);
579         mc->established = true;
580         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
581             vtime = MIN_SESS_EXP_TIME;
582         }
583         mc->expiration = expiration;
584         if (cfg->use_sessions) {
585             mag_attempt_session(req, cfg, mc);
586         }
587         mc->auth_type = auth_type;
588     }
589
590     if (cfg->send_persist)
591         apr_table_set(req->headers_out, "Persistent-Auth",
592             cfg->gss_conn_ctx ? "true" : "false");
593
594     ret = OK;
595
596 done:
597     if ((!is_basic) && (output.length != 0)) {
598         replen = apr_base64_encode_len(output.length) + 1;
599         reply = apr_pcalloc(req->pool, 10 + replen);
600         if (reply) {
601             memcpy(reply, "Negotiate ", 10);
602             apr_base64_encode(&reply[10], output.value, output.length);
603             apr_table_add(req->err_headers_out,
604                           "WWW-Authenticate", reply);
605         }
606     } else if (ret == HTTP_UNAUTHORIZED) {
607         apr_table_add(req->err_headers_out,
608                       "WWW-Authenticate", "Negotiate");
609         if (cfg->use_basic_auth) {
610             apr_table_add(req->err_headers_out,
611                           "WWW-Authenticate",
612                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
613                                        ap_auth_name(req)));
614         }
615     }
616 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
617     if (user_ccache != NULL) {
618         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
619         if (maj != GSS_S_COMPLETE) {
620             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
621                           "Failed to restore per-thread ccache, %s",
622                           mag_error(req, "gss_krb5_ccache_name() "
623                                     "failed", maj, min));
624         }
625     }
626 #endif
627     gss_delete_sec_context(&min, &user_ctx, &output);
628     gss_release_cred(&min, &user_cred);
629     gss_release_cred(&min, &acquired_cred);
630     gss_release_cred(&min, &delegated_cred);
631     gss_release_buffer(&min, &output);
632     gss_release_name(&min, &client);
633     gss_release_name(&min, &server);
634     gss_release_buffer(&min, &name);
635     gss_release_buffer(&min, &lname);
636     return ret;
637 }
638
639
640 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
641 {
642     struct mag_config *cfg;
643
644     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
645     cfg->pool = p;
646
647     return cfg;
648 }
649
650 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
651 {
652     struct mag_config *cfg = (struct mag_config *)mconfig;
653     cfg->ssl_only = on ? true : false;
654     return NULL;
655 }
656
657 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
658 {
659     struct mag_config *cfg = (struct mag_config *)mconfig;
660     cfg->map_to_local = on ? true : false;
661     return NULL;
662 }
663
664 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
665 {
666     struct mag_config *cfg = (struct mag_config *)mconfig;
667     cfg->gss_conn_ctx = on ? true : false;
668     return NULL;
669 }
670
671 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
672 {
673     struct mag_config *cfg = (struct mag_config *)mconfig;
674     cfg->send_persist = on ? true : false;
675     return NULL;
676 }
677
678 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
679 {
680     struct mag_config *cfg = (struct mag_config *)mconfig;
681     cfg->use_sessions = on ? true : false;
682     return NULL;
683 }
684
685 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
686 {
687     struct mag_config *cfg = (struct mag_config *)mconfig;
688     cfg->use_s4u2proxy = on ? true : false;
689
690     if (cfg->deleg_ccache_dir == NULL) {
691         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
692     }
693     return NULL;
694 }
695
696 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
697 {
698     struct mag_config *cfg = (struct mag_config *)mconfig;
699     struct databuf keys;
700     unsigned char *val;
701     apr_status_t rc;
702     const char *k;
703     int l;
704
705     if (strncmp(w, "key:", 4) != 0) {
706         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
707                      "Invalid key format, expected prefix 'key:'");
708         return NULL;
709     }
710     k = w + 4;
711
712     l = apr_base64_decode_len(k);
713     val = apr_palloc(parms->temp_pool, l);
714
715     keys.length = (int)apr_base64_decode_binary(val, k);
716     keys.value = (unsigned char *)val;
717
718     if (keys.length != 32) {
719         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
720                      "Invalid key length, expected 32 got %d", keys.length);
721         return NULL;
722     }
723
724     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
725     if (rc != OK) {
726         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
727                      "Failed to import sealing key!");
728     }
729     return NULL;
730 }
731
732 #define MAX_CRED_OPTIONS 10
733
734 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
735                                   const char *w)
736 {
737     struct mag_config *cfg = (struct mag_config *)mconfig;
738     gss_key_value_element_desc *elements;
739     uint32_t count;
740     size_t size;
741     const char *p;
742     char *value;
743     char *key;
744
745     p = strchr(w, ':');
746     if (!p) {
747         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
748                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
749         return NULL;
750     }
751
752     key = apr_pstrndup(parms->pool, w, (p-w));
753     value = apr_pstrdup(parms->pool, p + 1);
754
755     if (!cfg->cred_store) {
756         cfg->cred_store = apr_pcalloc(parms->pool,
757                                       sizeof(gss_key_value_set_desc));
758         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
759         cfg->cred_store->elements = apr_palloc(parms->pool, size);
760     }
761
762     elements = cfg->cred_store->elements;
763     count = cfg->cred_store->count;
764
765     if (count >= MAX_CRED_OPTIONS) {
766         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
767                      "Too many GssapiCredStore options (MAX: %d)",
768                      MAX_CRED_OPTIONS);
769         return NULL;
770     }
771     cfg->cred_store->count++;
772
773     elements[count].key = key;
774     elements[count].value = value;
775
776     return NULL;
777 }
778
779 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
780                                         const char *value)
781 {
782     struct mag_config *cfg = (struct mag_config *)mconfig;
783
784     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
785
786     return NULL;
787 }
788
789 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
790 {
791     struct mag_config *cfg = (struct mag_config *)mconfig;
792
793     cfg->use_basic_auth = on ? true : false;
794     return NULL;
795 }
796
797 static const command_rec mag_commands[] = {
798     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
799                   "Work only if connection is SSL Secured"),
800     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
801                   "Translate principals to local names"),
802     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
803                   "Authentication is bound to the TCP connection"),
804     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
805                   "Send Persitent-Auth header according to connection bound"),
806     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
807                   "Authentication uses mod_sessions to hold status"),
808     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
809                      "Key Used to seal session data."),
810 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
811     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
812                   "Initializes credentials for s4u2proxy usage"),
813 #endif
814 #ifdef HAVE_GSS_STORE_CRED_INTO
815     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
816                     "Credential Store"),
817     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
818                      OR_AUTHCFG, "Directory to store delegated credentials"),
819 #endif
820 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
821     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
822                      "Allows use of Basic Auth for authentication"),
823 #endif
824     { NULL }
825 };
826
827 static void
828 mag_register_hooks(apr_pool_t *p)
829 {
830     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
831     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
832     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
833 }
834
835 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
836 {
837     STANDARD20_MODULE_STUFF,
838     mag_create_dir_config,
839     NULL,
840     NULL,
841     NULL,
842     mag_commands,
843     mag_register_hooks
844 };