Set KRB5CCNAME on follow up requests with session
[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 const gss_OID_desc gss_mech_ntlmssp = {
28     GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
29 };
30
31 const gss_OID_set_desc gss_mech_set_ntlmssp = {
32     1, discard_const(&gss_mech_ntlmssp)
33 };
34
35 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
36
37 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
38
39 APLOG_USE_MODULE(auth_gssapi);
40
41 static char *mag_status(request_rec *req, int type, uint32_t err)
42 {
43     uint32_t maj_ret, min_ret;
44     gss_buffer_desc text;
45     uint32_t msg_ctx;
46     char *msg_ret;
47     int len;
48
49     msg_ret = NULL;
50     msg_ctx = 0;
51     do {
52         maj_ret = gss_display_status(&min_ret, err, type,
53                                      GSS_C_NO_OID, &msg_ctx, &text);
54         if (maj_ret != GSS_S_COMPLETE) {
55             return msg_ret;
56         }
57
58         len = text.length;
59         if (msg_ret) {
60             msg_ret = apr_psprintf(req->pool, "%s, %*s",
61                                    msg_ret, len, (char *)text.value);
62         } else {
63             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
64         }
65         gss_release_buffer(&min_ret, &text);
66     } while (msg_ctx != 0);
67
68     return msg_ret;
69 }
70
71 static char *mag_error(request_rec *req, const char *msg,
72                        uint32_t maj, uint32_t min)
73 {
74     char *msg_maj;
75     char *msg_min;
76
77     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
78     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
79     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
80 }
81
82 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
83
84 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
85                            apr_pool_t *temp, server_rec *s)
86 {
87     /* FIXME: create mutex to deal with connections and contexts ? */
88     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
89     mag_post_config_session();
90     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
91
92     return OK;
93 }
94
95 static int mag_pre_connection(conn_rec *c, void *csd)
96 {
97     struct mag_conn *mc;
98
99     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
100
101     mc->parent = c->pool;
102     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
103     return OK;
104 }
105
106 static apr_status_t mag_conn_destroy(void *ptr)
107 {
108     struct mag_conn *mc = (struct mag_conn *)ptr;
109     uint32_t min;
110
111     if (mc->ctx) {
112         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
113         mc->established = false;
114     }
115     return APR_SUCCESS;
116 }
117
118 static bool mag_conn_is_https(conn_rec *c)
119 {
120     if (mag_is_https) {
121         if (mag_is_https(c)) return true;
122     }
123
124     return false;
125 }
126
127 static bool mag_acquire_creds(request_rec *req,
128                               struct mag_config *cfg,
129                               gss_OID_set desired_mechs,
130                               gss_cred_usage_t cred_usage,
131                               gss_cred_id_t *creds,
132                               gss_OID_set *actual_mechs)
133 {
134     uint32_t maj, min;
135 #ifdef HAVE_CRED_STORE
136     gss_const_key_value_set_t store = cfg->cred_store;
137
138     maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
139                                 desired_mechs, cred_usage, store, creds,
140                                 actual_mechs, NULL);
141 #else
142     maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
143                            desired_mechs, cred_usage, creds,
144                            actual_mechs, NULL);
145 #endif
146
147     if (GSS_ERROR(maj)) {
148         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
149                       mag_error(req, "gss_acquire_cred[_from]() "
150                                 "failed to get server creds",
151                                 maj, min));
152         return false;
153     }
154
155     return true;
156 }
157
158 #ifdef HAVE_CRED_STORE
159 static char *escape(apr_pool_t *pool, const char *name,
160                     char find, const char *replace)
161 {
162     char *escaped = NULL;
163     char *namecopy;
164     char *n;
165     char *p;
166
167     namecopy = apr_pstrdup(pool, name);
168
169     p = strchr(namecopy, find);
170     if (!p) return namecopy;
171
172     /* first segment */
173     n = namecopy;
174     while (p) {
175         /* terminate previous segment */
176         *p = '\0';
177         if (escaped) {
178             escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
179         } else {
180             escaped = apr_pstrcat(pool, n, replace, NULL);
181         }
182         /* move to next segment */
183         n = p + 1;
184         p = strchr(n, find);
185     }
186     /* append last segment if any */
187     if (*n) {
188         escaped = apr_pstrcat(pool, escaped, n, NULL);
189     }
190
191     return escaped;
192 }
193
194 static char *mag_gss_name_to_ccache_name(request_rec *req,
195                                          char *dir, const char *gss_name)
196 {
197     char *escaped;
198
199     /* We need to escape away '/', we can't have path separators in
200      * a ccache file name */
201     /* first double escape the esacping char (~) if any */
202     escaped = escape(req->pool, gss_name, '~', "~~");
203     /* then escape away the separator (/) if any */
204     escaped = escape(req->pool, escaped, '/', "~");
205
206     return apr_psprintf(req->pool, "%s/%s", dir, escaped);
207 }
208
209 static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
210 {
211     apr_status_t status;
212     apr_finfo_t finfo;
213     char *value;
214
215     status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
216     if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
217         /* set the file cache anyway, but warn */
218         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
219                       "KRB5CCNAME file (%s) lookup failed!", ccname);
220     }
221
222     value = apr_psprintf(req->pool, "FILE:%s", ccname);
223     apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
224 }
225
226 static void mag_store_deleg_creds(request_rec *req,
227                                   char *dir, char *clientname,
228                                   gss_cred_id_t delegated_cred,
229                                   char **ccachefile)
230 {
231     gss_key_value_element_desc element;
232     gss_key_value_set_desc store;
233     char *ccname;
234     uint32_t maj, min;
235     element.key = "ccache";
236     store.elements = &element;
237     store.count = 1;
238
239     ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
240     element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
241
242     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
243                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
244     if (GSS_ERROR(maj)) {
245         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
246                       mag_error(req, "failed to store delegated creds",
247                                 maj, min));
248     }
249
250     *ccachefile = ccname;
251 }
252 #endif
253
254 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
255                               gss_buffer_t value)
256 {
257     char *auth_header_value;
258
259     auth_header_value = ap_getword_white(pool, auth_header);
260     if (!auth_header_value) return false;
261     value->length = apr_base64_decode_len(auth_header_value) + 1;
262     value->value = apr_pcalloc(pool, value->length);
263     if (!value->value) return false;
264     value->length = apr_base64_decode(value->value, auth_header_value);
265
266     return true;
267 }
268
269 static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
270 {
271     if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
272
273     for (int i = 0; i < cfg->allowed_mechs->count; i++) {
274         if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
275             return true;
276         }
277     }
278     return false;
279 }
280
281 #define AUTH_TYPE_NEGOTIATE 0
282 #define AUTH_TYPE_BASIC 1
283 #define AUTH_TYPE_RAW_NTLM 2
284 const char *auth_types[] = {
285     "Negotiate",
286     "Basic",
287     "NTLM",
288     NULL
289 };
290
291 static int mag_auth(request_rec *req)
292 {
293     const char *type;
294     int auth_type = -1;
295     struct mag_config *cfg;
296     const char *auth_header;
297     char *auth_header_type;
298     int ret = HTTP_UNAUTHORIZED;
299     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
300     gss_ctx_id_t *pctx;
301     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
302     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
303     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
304     gss_buffer_desc ba_user;
305     gss_buffer_desc ba_pwd;
306     gss_name_t client = GSS_C_NO_NAME;
307     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
308     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
309     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
310     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
311     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
312     uint32_t flags;
313     uint32_t vtime;
314     uint32_t maj, min;
315     char *reply;
316     size_t replen;
317     char *clientname;
318     gss_OID mech_type = GSS_C_NO_OID;
319     gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
320     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
321     struct mag_conn *mc = NULL;
322     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
323     gss_name_t server = GSS_C_NO_NAME;
324 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
325     const char *user_ccache = NULL;
326     const char *orig_ccache = NULL;
327 #endif
328     uint32_t init_flags = 0;
329     time_t expiration;
330     int i;
331
332     type = ap_auth_type(req);
333     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
334         return DECLINED;
335     }
336
337     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
338
339     if (!cfg->allowed_mechs) {
340         /* Try to fetch the default set if not explicitly configured */
341         (void)mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
342                                 &server_cred, &cfg->allowed_mechs);
343         (void)gss_release_cred(&min, &server_cred);
344     }
345
346     /* implicit auth for subrequests if main auth already happened */
347     if (!ap_is_initial_req(req) && req->main != NULL) {
348         type = ap_auth_type(req->main);
349         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
350             /* warn if the subrequest location and the main request
351              * location have different configs */
352             if (cfg != ap_get_module_config(req->main->per_dir_config,
353                                             &auth_gssapi_module)) {
354                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
355                               req, "Subrequest authentication bypass on "
356                                    "location with different configuration!");
357             }
358             if (req->main->user) {
359                 req->user = apr_pstrdup(req->pool, req->main->user);
360                 return OK;
361             } else {
362                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
363                               "The main request is tasked to establish the "
364                               "security context, can't proceed!");
365                 return HTTP_UNAUTHORIZED;
366             }
367         } else {
368             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
369                           "Subrequest GSSAPI auth with no auth on the main "
370                           "request. This operation may fail if other "
371                           "subrequests already established a context or the "
372                           "mechanism requires multiple roundtrips.");
373         }
374     }
375
376     if (cfg->ssl_only) {
377         if (!mag_conn_is_https(req->connection)) {
378             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
379                           "Not a TLS connection, refusing to authenticate!");
380             goto done;
381         }
382     }
383
384     if (cfg->gss_conn_ctx) {
385         mc = (struct mag_conn *)ap_get_module_config(
386                                                 req->connection->conn_config,
387                                                 &auth_gssapi_module);
388         if (!mc) {
389             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
390                           "Failed to retrieve connection context!");
391             goto done;
392         }
393     }
394
395     /* if available, session always supersedes connection bound data */
396     if (cfg->use_sessions) {
397         mag_check_session(req, cfg, &mc);
398     }
399
400     if (mc) {
401         /* register the context in the memory pool, so it can be freed
402          * when the connection/request is terminated */
403         apr_pool_userdata_set(mc, "mag_conn_ptr",
404                               mag_conn_destroy, mc->parent);
405
406         if (mc->established) {
407             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
408                           "Already established context found!");
409             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
410             apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
411                           apr_psprintf(req->pool,
412                                        "%ld", (long)mc->expiration));
413             req->ap_auth_type = apr_pstrdup(req->pool,
414                                             auth_types[mc->auth_type]);
415             req->user = apr_pstrdup(req->pool, mc->user_name);
416             if (cfg->deleg_ccache_dir && mc->delegated) {
417                 char *ccname;
418                 ccname = mag_gss_name_to_ccache_name(req,
419                                                      cfg->deleg_ccache_dir,
420                                                      mc->gss_name);
421                 if (ccname) {
422                     mag_set_KRB5CCANME(req, ccname);
423                 }
424             }
425             ret = OK;
426             goto done;
427         }
428         pctx = &mc->ctx;
429     } else {
430         pctx = &ctx;
431     }
432
433     auth_header = apr_table_get(req->headers_in, "Authorization");
434     if (!auth_header) goto done;
435
436     auth_header_type = ap_getword_white(req->pool, &auth_header);
437     if (!auth_header_type) goto done;
438
439     for (i = 0; auth_types[i] != NULL; i++) {
440         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
441             auth_type = i;
442             break;
443         }
444     }
445
446     switch (auth_type) {
447     case AUTH_TYPE_NEGOTIATE:
448         if (!parse_auth_header(req->pool, &auth_header, &input)) {
449             goto done;
450         }
451         break;
452     case AUTH_TYPE_BASIC:
453         if (!cfg->use_basic_auth) {
454             goto done;
455         }
456
457         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
458         if (!ba_pwd.value) goto done;
459         ba_user.value = ap_getword_nulls_nc(req->pool,
460                                             (char **)&ba_pwd.value, ':');
461         if (!ba_user.value) goto done;
462         if (((char *)ba_user.value)[0] == '\0' ||
463             ((char *)ba_pwd.value)[0] == '\0') {
464             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
465                           "Invalid empty user or password for Basic Auth");
466             goto done;
467         }
468         ba_user.length = strlen(ba_user.value);
469         ba_pwd.length = strlen(ba_pwd.value);
470         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
471         if (GSS_ERROR(maj)) {
472             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
473                           "In Basic Auth, %s",
474                           mag_error(req, "gss_import_name() failed",
475                                     maj, min));
476             goto done;
477         }
478 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
479         /* Set a per-thread ccache in case we are using kerberos,
480          * it is not elegant but avoids interference between threads */
481         long long unsigned int rndname;
482         apr_status_t rs;
483         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
484                                        sizeof(long long unsigned int));
485         if (rs != APR_SUCCESS) {
486             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
487                           "Failed to generate random ccache name");
488             goto done;
489         }
490         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
491         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
492         if (GSS_ERROR(maj)) {
493             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
494                           "In Basic Auth, %s",
495                           mag_error(req, "gss_krb5_ccache_name() "
496                                     "failed", maj, min));
497             goto done;
498         }
499 #endif
500         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
501                                              GSS_C_INDEFINITE,
502                                              cfg->allowed_mechs,
503                                              GSS_C_INITIATE,
504                                              &user_cred, NULL, NULL);
505         if (GSS_ERROR(maj)) {
506             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
507                           "In Basic Auth, %s",
508                           mag_error(req, "gss_acquire_cred_with_password() "
509                                     "failed", maj, min));
510             goto done;
511         }
512         gss_release_name(&min, &client);
513         break;
514
515     case AUTH_TYPE_RAW_NTLM:
516         if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
517             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
518                           "NTLM Authentication is not allowed!");
519             goto done;
520         }
521
522         if (!parse_auth_header(req->pool, &auth_header, &input)) {
523             goto done;
524         }
525
526         desired_mechs = discard_const(&gss_mech_set_ntlmssp);
527         break;
528
529     default:
530         goto done;
531     }
532
533     req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
534
535 #ifdef HAVE_CRED_STORE
536     if (cfg->use_s4u2proxy) {
537         cred_usage = GSS_C_BOTH;
538     }
539 #endif
540     if (!mag_acquire_creds(req, cfg, desired_mechs,
541                            cred_usage, &acquired_cred, NULL)) {
542         goto done;
543     }
544
545     if (auth_type == AUTH_TYPE_BASIC) {
546         if (cred_usage == GSS_C_BOTH) {
547             /* If GSS_C_BOTH is used then inquire_cred will return the client
548              * name instead of the SPN of the server credentials. Therefore we
549              * need to acquire a different set of credential setting
550              * GSS_C_ACCEPT explicitly */
551             if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
552                                    GSS_C_ACCEPT, &server_cred, NULL)) {
553                 goto done;
554             }
555         } else {
556             server_cred = acquired_cred;
557         }
558         maj = gss_inquire_cred(&min, server_cred, &server,
559                                NULL, NULL, NULL);
560         if (GSS_ERROR(maj)) {
561             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
562                           "%s", mag_error(req, "gss_inquired_cred_() "
563                                           "failed", maj, min));
564             goto done;
565         }
566         if (server_cred != acquired_cred) {
567             gss_release_cred(&min, &server_cred);
568         }
569
570 #ifdef HAVE_CRED_STORE
571         if (cfg->deleg_ccache_dir) {
572             /* delegate ourselves credentials so we store them as requested */
573             init_flags |= GSS_C_DELEG_FLAG;
574         }
575 #endif
576
577         /* output and input are inverted here, this is intentional */
578         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
579                                    GSS_C_NO_OID, init_flags, 300,
580                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
581                                    NULL, &input, NULL, NULL);
582         if (GSS_ERROR(maj)) {
583             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
584                           "%s", mag_error(req, "gss_init_sec_context() "
585                                           "failed", maj, min));
586             goto done;
587         }
588     }
589
590     if (auth_type == AUTH_TYPE_NEGOTIATE &&
591         cfg->allowed_mechs != GSS_C_NO_OID_SET) {
592         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
593         if (GSS_ERROR(maj)) {
594             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
595                           mag_error(req, "gss_set_neg_mechs() failed",
596                                     maj, min));
597             goto done;
598         }
599     }
600
601     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
602                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
603                                  &client, &mech_type, &output, &flags, &vtime,
604                                  &delegated_cred);
605     if (GSS_ERROR(maj)) {
606         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
607                       mag_error(req, "gss_accept_sec_context() failed",
608                                 maj, min));
609         goto done;
610     }
611     if (auth_type == AUTH_TYPE_BASIC) {
612         if (mc) {
613             apr_pool_cleanup_run(mc->parent, mc, mag_conn_destroy);
614             mc = NULL;
615         }
616         while (maj == GSS_S_CONTINUE_NEEDED) {
617             gss_release_buffer(&min, &input);
618             /* output and input are inverted here, this is intentional */
619             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
620                                        GSS_C_NO_OID, init_flags, 300,
621                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
622                                        NULL, &input, NULL, NULL);
623             if (GSS_ERROR(maj)) {
624                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
625                               "%s", mag_error(req, "gss_init_sec_context() "
626                                               "failed", maj, min));
627                 goto done;
628             }
629             gss_release_buffer(&min, &output);
630             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
631                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
632                                          &client, &mech_type, &output, &flags,
633                                          &vtime, &delegated_cred);
634             if (GSS_ERROR(maj)) {
635                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
636                               "%s", mag_error(req, "gss_accept_sec_context()"
637                                               " failed", maj, min));
638                 goto done;
639             }
640         }
641     } else if (maj == GSS_S_CONTINUE_NEEDED) {
642         if (!mc) {
643             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
644                           "Mechanism needs continuation but neither "
645                           "GssapiConnectionBound nor "
646                           "GssapiUseSessions are available");
647             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
648             gss_release_buffer(&min, &output);
649             output.length = 0;
650         }
651         /* auth not complete send token and wait next packet */
652         goto done;
653     }
654
655     /* Always set the GSS name in an env var */
656     maj = gss_display_name(&min, client, &name, NULL);
657     if (GSS_ERROR(maj)) {
658         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
659                       mag_error(req, "gss_display_name() failed",
660                                 maj, min));
661         goto done;
662     }
663     clientname = apr_pstrndup(req->pool, name.value, name.length);
664     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
665     expiration = time(NULL) + vtime;
666     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
667                   apr_psprintf(req->pool, "%ld", (long)expiration));
668
669 #ifdef HAVE_CRED_STORE
670     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
671         char *ccachefile = NULL;
672
673         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
674                               delegated_cred, &ccachefile);
675
676         if (ccachefile) {
677             mag_set_KRB5CCANME(req, ccachefile);
678         }
679
680         if (mc) {
681             mc->delegated = true;
682         }
683     }
684 #endif
685
686     if (cfg->map_to_local) {
687         maj = gss_localname(&min, client, mech_type, &lname);
688         if (maj != GSS_S_COMPLETE) {
689             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
690                           mag_error(req, "gss_localname() failed", maj, min));
691             goto done;
692         }
693         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
694     } else {
695         req->user = clientname;
696     }
697
698     if (mc) {
699         mc->user_name = apr_pstrdup(mc->parent, req->user);
700         mc->gss_name = apr_pstrdup(mc->parent, clientname);
701         mc->established = true;
702         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
703             vtime = MIN_SESS_EXP_TIME;
704         }
705         mc->expiration = expiration;
706         if (cfg->use_sessions) {
707             mag_attempt_session(req, cfg, mc);
708         }
709         mc->auth_type = auth_type;
710     }
711
712     if (cfg->send_persist)
713         apr_table_set(req->headers_out, "Persistent-Auth",
714             cfg->gss_conn_ctx ? "true" : "false");
715
716     ret = OK;
717
718 done:
719     if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
720         int prefixlen = strlen(auth_types[auth_type]) + 1;
721         replen = apr_base64_encode_len(output.length) + 1;
722         reply = apr_pcalloc(req->pool, prefixlen + replen);
723         if (reply) {
724             memcpy(reply, auth_types[auth_type], prefixlen - 1);
725             reply[prefixlen - 1] = ' ';
726             apr_base64_encode(&reply[prefixlen], output.value, output.length);
727             apr_table_add(req->err_headers_out,
728                           "WWW-Authenticate", reply);
729         }
730     } else if (ret == HTTP_UNAUTHORIZED) {
731         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
732         if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
733             apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
734         }
735         if (cfg->use_basic_auth) {
736             apr_table_add(req->err_headers_out,
737                           "WWW-Authenticate",
738                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
739                                        ap_auth_name(req)));
740         }
741     }
742 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
743     if (user_ccache != NULL) {
744         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
745         if (maj != GSS_S_COMPLETE) {
746             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
747                           "Failed to restore per-thread ccache, %s",
748                           mag_error(req, "gss_krb5_ccache_name() "
749                                     "failed", maj, min));
750         }
751     }
752 #endif
753     gss_delete_sec_context(&min, &user_ctx, &output);
754     gss_release_cred(&min, &user_cred);
755     gss_release_cred(&min, &acquired_cred);
756     gss_release_cred(&min, &delegated_cred);
757     gss_release_buffer(&min, &output);
758     gss_release_name(&min, &client);
759     gss_release_name(&min, &server);
760     gss_release_buffer(&min, &name);
761     gss_release_buffer(&min, &lname);
762     return ret;
763 }
764
765
766 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
767 {
768     struct mag_config *cfg;
769
770     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
771     cfg->pool = p;
772
773     return cfg;
774 }
775
776 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
777 {
778     struct mag_config *cfg = (struct mag_config *)mconfig;
779     cfg->ssl_only = on ? true : false;
780     return NULL;
781 }
782
783 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
784 {
785     struct mag_config *cfg = (struct mag_config *)mconfig;
786     cfg->map_to_local = on ? true : false;
787     return NULL;
788 }
789
790 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
791 {
792     struct mag_config *cfg = (struct mag_config *)mconfig;
793     cfg->gss_conn_ctx = on ? true : false;
794     return NULL;
795 }
796
797 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
798 {
799     struct mag_config *cfg = (struct mag_config *)mconfig;
800     cfg->send_persist = on ? true : false;
801     return NULL;
802 }
803
804 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
805 {
806     struct mag_config *cfg = (struct mag_config *)mconfig;
807     cfg->use_sessions = on ? true : false;
808     return NULL;
809 }
810
811 #ifdef HAVE_CRED_STORE
812 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
813 {
814     struct mag_config *cfg = (struct mag_config *)mconfig;
815     cfg->use_s4u2proxy = on ? true : false;
816
817     if (cfg->deleg_ccache_dir == NULL) {
818         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
819     }
820     return NULL;
821 }
822 #endif
823
824 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
825 {
826     struct mag_config *cfg = (struct mag_config *)mconfig;
827     struct databuf keys;
828     unsigned char *val;
829     apr_status_t rc;
830     const char *k;
831     int l;
832
833     if (strncmp(w, "key:", 4) != 0) {
834         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
835                      "Invalid key format, expected prefix 'key:'");
836         return NULL;
837     }
838     k = w + 4;
839
840     l = apr_base64_decode_len(k);
841     val = apr_palloc(parms->temp_pool, l);
842
843     keys.length = (int)apr_base64_decode_binary(val, k);
844     keys.value = (unsigned char *)val;
845
846     if (keys.length != 32) {
847         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
848                      "Invalid key length, expected 32 got %d", keys.length);
849         return NULL;
850     }
851
852     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
853     if (rc != OK) {
854         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
855                      "Failed to import sealing key!");
856     }
857     return NULL;
858 }
859
860 #ifdef HAVE_CRED_STORE
861
862 #define MAX_CRED_OPTIONS 10
863
864 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
865                                   const char *w)
866 {
867     struct mag_config *cfg = (struct mag_config *)mconfig;
868     gss_key_value_element_desc *elements;
869     uint32_t count;
870     size_t size;
871     const char *p;
872     char *value;
873     char *key;
874
875     p = strchr(w, ':');
876     if (!p) {
877         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
878                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
879         return NULL;
880     }
881
882     key = apr_pstrndup(parms->pool, w, (p-w));
883     value = apr_pstrdup(parms->pool, p + 1);
884
885     if (!cfg->cred_store) {
886         cfg->cred_store = apr_pcalloc(parms->pool,
887                                       sizeof(gss_key_value_set_desc));
888         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
889         cfg->cred_store->elements = apr_palloc(parms->pool, size);
890     }
891
892     elements = cfg->cred_store->elements;
893     count = cfg->cred_store->count;
894
895     if (count >= MAX_CRED_OPTIONS) {
896         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
897                      "Too many GssapiCredStore options (MAX: %d)",
898                      MAX_CRED_OPTIONS);
899         return NULL;
900     }
901     cfg->cred_store->count++;
902
903     elements[count].key = key;
904     elements[count].value = value;
905
906     return NULL;
907 }
908
909 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
910                                         const char *value)
911 {
912     struct mag_config *cfg = (struct mag_config *)mconfig;
913
914     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
915
916     return NULL;
917 }
918 #endif
919
920 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
921 {
922     struct mag_config *cfg = (struct mag_config *)mconfig;
923
924     cfg->use_basic_auth = on ? true : false;
925     return NULL;
926 }
927
928 #define MAX_ALLOWED_MECHS 10
929
930 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
931                                   const char *w)
932 {
933     struct mag_config *cfg = (struct mag_config *)mconfig;
934     gss_const_OID oid;
935     size_t size;
936
937     if (!cfg->allowed_mechs) {
938         cfg->allowed_mechs = apr_pcalloc(parms->pool,
939                                          sizeof(gss_OID_set_desc));
940         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
941         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
942     }
943
944     if (strcmp(w, "krb5") == 0) {
945         oid = gss_mech_krb5;
946     } else if (strcmp(w, "iakerb") == 0) {
947         oid = gss_mech_iakerb;
948     } else if (strcmp(w, "ntlmssp") == 0) {
949         oid = &gss_mech_ntlmssp;
950     } else {
951         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
952                      "Unrecognized GSSAPI Mechanism: %s", w);
953         return NULL;
954     }
955
956     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
957         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
958                      "Too many GssapiAllowedMech options (MAX: %d)",
959                      MAX_ALLOWED_MECHS);
960         return NULL;
961     }
962     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
963     cfg->allowed_mechs->count++;
964
965     return NULL;
966 }
967
968 static const command_rec mag_commands[] = {
969     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
970                   "Work only if connection is SSL Secured"),
971     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
972                   "Translate principals to local names"),
973     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
974                   "Authentication is bound to the TCP connection"),
975     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
976                   "Send Persitent-Auth header according to connection bound"),
977     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
978                   "Authentication uses mod_sessions to hold status"),
979     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
980                      "Key Used to seal session data."),
981 #ifdef HAVE_CRED_STORE
982     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
983                   "Initializes credentials for s4u2proxy usage"),
984     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
985                     "Credential Store"),
986     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
987                      OR_AUTHCFG, "Directory to store delegated credentials"),
988 #endif
989 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
990     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
991                      "Allows use of Basic Auth for authentication"),
992 #endif
993     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
994                     "Allowed Mechanisms"),
995     { NULL }
996 };
997
998 static void
999 mag_register_hooks(apr_pool_t *p)
1000 {
1001     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1002     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1003     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1004 }
1005
1006 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1007 {
1008     STANDARD20_MODULE_STUFF,
1009     mag_create_dir_config,
1010     NULL,
1011     NULL,
1012     NULL,
1013     mag_commands,
1014     mag_register_hooks
1015 };