Use apr_pool_cleanup_register() to cleanup mc
[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 void mag_set_req_data(request_rec *req,
292                              struct mag_config *cfg,
293                              struct mag_conn *mc)
294 {
295     apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
296     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
297                   apr_psprintf(req->pool,
298                                "%ld", (long)mc->expiration));
299     req->ap_auth_type = apr_pstrdup(req->pool,
300                                     auth_types[mc->auth_type]);
301     req->user = apr_pstrdup(req->pool, mc->user_name);
302     if (cfg->deleg_ccache_dir && mc->delegated) {
303         char *ccname;
304         ccname = mag_gss_name_to_ccache_name(req,
305                                              cfg->deleg_ccache_dir,
306                                              mc->gss_name);
307         if (ccname) {
308             mag_set_KRB5CCANME(req, ccname);
309         }
310     }
311 }
312
313 static int mag_auth(request_rec *req)
314 {
315     const char *type;
316     int auth_type = -1;
317     struct mag_config *cfg;
318     const char *auth_header;
319     char *auth_header_type;
320     int ret = HTTP_UNAUTHORIZED;
321     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
322     gss_ctx_id_t *pctx;
323     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
324     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
325     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
326     gss_buffer_desc ba_user;
327     gss_buffer_desc ba_pwd;
328     gss_name_t client = GSS_C_NO_NAME;
329     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
330     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
331     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
332     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
333     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
334     uint32_t flags;
335     uint32_t vtime;
336     uint32_t maj, min;
337     char *reply;
338     size_t replen;
339     char *clientname;
340     gss_OID mech_type = GSS_C_NO_OID;
341     gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
342     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
343     struct mag_conn *mc = NULL;
344     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
345     gss_name_t server = GSS_C_NO_NAME;
346 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
347     const char *user_ccache = NULL;
348     const char *orig_ccache = NULL;
349 #endif
350     uint32_t init_flags = 0;
351     time_t expiration;
352     int i;
353
354     type = ap_auth_type(req);
355     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
356         return DECLINED;
357     }
358
359     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
360
361     if (!cfg->allowed_mechs) {
362         /* Try to fetch the default set if not explicitly configured */
363         (void)mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
364                                 &server_cred, &cfg->allowed_mechs);
365         (void)gss_release_cred(&min, &server_cred);
366     }
367
368     /* implicit auth for subrequests if main auth already happened */
369     if (!ap_is_initial_req(req) && req->main != NULL) {
370         type = ap_auth_type(req->main);
371         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
372             /* warn if the subrequest location and the main request
373              * location have different configs */
374             if (cfg != ap_get_module_config(req->main->per_dir_config,
375                                             &auth_gssapi_module)) {
376                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
377                               req, "Subrequest authentication bypass on "
378                                    "location with different configuration!");
379             }
380             if (req->main->user) {
381                 req->user = apr_pstrdup(req->pool, req->main->user);
382                 return OK;
383             } else {
384                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
385                               "The main request is tasked to establish the "
386                               "security context, can't proceed!");
387                 return HTTP_UNAUTHORIZED;
388             }
389         } else {
390             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
391                           "Subrequest GSSAPI auth with no auth on the main "
392                           "request. This operation may fail if other "
393                           "subrequests already established a context or the "
394                           "mechanism requires multiple roundtrips.");
395         }
396     }
397
398     if (cfg->ssl_only) {
399         if (!mag_conn_is_https(req->connection)) {
400             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
401                           "Not a TLS connection, refusing to authenticate!");
402             goto done;
403         }
404     }
405
406     if (cfg->gss_conn_ctx) {
407         mc = (struct mag_conn *)ap_get_module_config(
408                                                 req->connection->conn_config,
409                                                 &auth_gssapi_module);
410         if (!mc) {
411             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
412                           "Failed to retrieve connection context!");
413             goto done;
414         }
415     }
416
417     /* if available, session always supersedes connection bound data */
418     if (cfg->use_sessions) {
419         mag_check_session(req, cfg, &mc);
420     }
421
422     auth_header = apr_table_get(req->headers_in, "Authorization");
423
424     if (mc) {
425         /* register the context in the memory pool, so it can be freed
426          * when the connection/request is terminated */
427         apr_pool_cleanup_register(mc->parent, (void *) mc,
428                                   mag_conn_destroy, apr_pool_cleanup_null);
429
430         if (mc->established && !auth_header) {
431             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
432                           "Already established context found!");
433             mag_set_req_data(req, cfg, mc);
434         }
435         pctx = &mc->ctx;
436     } else {
437         pctx = &ctx;
438     }
439
440     /* We can proceed only if we do have an auth header */
441     if (!auth_header) goto done;
442
443     auth_header_type = ap_getword_white(req->pool, &auth_header);
444     if (!auth_header_type) goto done;
445
446     for (i = 0; auth_types[i] != NULL; i++) {
447         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
448             auth_type = i;
449             break;
450         }
451     }
452
453     switch (auth_type) {
454     case AUTH_TYPE_NEGOTIATE:
455         if (!parse_auth_header(req->pool, &auth_header, &input)) {
456             goto done;
457         }
458         break;
459     case AUTH_TYPE_BASIC:
460         if (!cfg->use_basic_auth) {
461             goto done;
462         }
463
464         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
465         if (!ba_pwd.value) goto done;
466         ba_user.value = ap_getword_nulls_nc(req->pool,
467                                             (char **)&ba_pwd.value, ':');
468         if (!ba_user.value) goto done;
469         if (((char *)ba_user.value)[0] == '\0' ||
470             ((char *)ba_pwd.value)[0] == '\0') {
471             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
472                           "Invalid empty user or password for Basic Auth");
473             goto done;
474         }
475         ba_user.length = strlen(ba_user.value);
476         ba_pwd.length = strlen(ba_pwd.value);
477
478         if (mc && mc->established &&
479             mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
480             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
481                           "Already established BASIC AUTH context found!");
482             mag_set_req_data(req, cfg, mc);
483             ret = OK;
484             goto done;
485         }
486
487         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
488         if (GSS_ERROR(maj)) {
489             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
490                           "In Basic Auth, %s",
491                           mag_error(req, "gss_import_name() failed",
492                                     maj, min));
493             goto done;
494         }
495 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
496         /* Set a per-thread ccache in case we are using kerberos,
497          * it is not elegant but avoids interference between threads */
498         long long unsigned int rndname;
499         apr_status_t rs;
500         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
501                                        sizeof(long long unsigned int));
502         if (rs != APR_SUCCESS) {
503             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
504                           "Failed to generate random ccache name");
505             goto done;
506         }
507         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
508         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
509         if (GSS_ERROR(maj)) {
510             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
511                           "In Basic Auth, %s",
512                           mag_error(req, "gss_krb5_ccache_name() "
513                                     "failed", maj, min));
514             goto done;
515         }
516 #endif
517         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
518                                              GSS_C_INDEFINITE,
519                                              cfg->allowed_mechs,
520                                              GSS_C_INITIATE,
521                                              &user_cred, NULL, NULL);
522         if (GSS_ERROR(maj)) {
523             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
524                           "In Basic Auth, %s",
525                           mag_error(req, "gss_acquire_cred_with_password() "
526                                     "failed", maj, min));
527             goto done;
528         }
529         gss_release_name(&min, &client);
530         break;
531
532     case AUTH_TYPE_RAW_NTLM:
533         if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
534             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
535                           "NTLM Authentication is not allowed!");
536             goto done;
537         }
538
539         if (!parse_auth_header(req->pool, &auth_header, &input)) {
540             goto done;
541         }
542
543         desired_mechs = discard_const(&gss_mech_set_ntlmssp);
544         break;
545
546     default:
547         goto done;
548     }
549
550     req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
551
552 #ifdef HAVE_CRED_STORE
553     if (cfg->use_s4u2proxy) {
554         cred_usage = GSS_C_BOTH;
555     }
556 #endif
557     if (!mag_acquire_creds(req, cfg, desired_mechs,
558                            cred_usage, &acquired_cred, NULL)) {
559         goto done;
560     }
561
562     if (auth_type == AUTH_TYPE_BASIC) {
563         if (cred_usage == GSS_C_BOTH) {
564             /* If GSS_C_BOTH is used then inquire_cred will return the client
565              * name instead of the SPN of the server credentials. Therefore we
566              * need to acquire a different set of credential setting
567              * GSS_C_ACCEPT explicitly */
568             if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
569                                    GSS_C_ACCEPT, &server_cred, NULL)) {
570                 goto done;
571             }
572         } else {
573             server_cred = acquired_cred;
574         }
575         maj = gss_inquire_cred(&min, server_cred, &server,
576                                NULL, NULL, NULL);
577         if (GSS_ERROR(maj)) {
578             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
579                           "%s", mag_error(req, "gss_inquired_cred_() "
580                                           "failed", maj, min));
581             goto done;
582         }
583         if (server_cred != acquired_cred) {
584             gss_release_cred(&min, &server_cred);
585         }
586
587 #ifdef HAVE_CRED_STORE
588         if (cfg->deleg_ccache_dir) {
589             /* delegate ourselves credentials so we store them as requested */
590             init_flags |= GSS_C_DELEG_FLAG;
591         }
592 #endif
593
594         /* output and input are inverted here, this is intentional */
595         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
596                                    GSS_C_NO_OID, init_flags, 300,
597                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
598                                    NULL, &input, NULL, NULL);
599         if (GSS_ERROR(maj)) {
600             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
601                           "%s", mag_error(req, "gss_init_sec_context() "
602                                           "failed", maj, min));
603             goto done;
604         }
605     }
606
607     if (auth_type == AUTH_TYPE_NEGOTIATE &&
608         cfg->allowed_mechs != GSS_C_NO_OID_SET) {
609         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
610         if (GSS_ERROR(maj)) {
611             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
612                           mag_error(req, "gss_set_neg_mechs() failed",
613                                     maj, min));
614             goto done;
615         }
616     }
617
618     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
619                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
620                                  &client, &mech_type, &output, &flags, &vtime,
621                                  &delegated_cred);
622     if (GSS_ERROR(maj)) {
623         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
624                       mag_error(req, "gss_accept_sec_context() failed",
625                                 maj, min));
626         goto done;
627     }
628     if (auth_type == AUTH_TYPE_BASIC) {
629         while (maj == GSS_S_CONTINUE_NEEDED) {
630             gss_release_buffer(&min, &input);
631             /* output and input are inverted here, this is intentional */
632             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
633                                        GSS_C_NO_OID, init_flags, 300,
634                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
635                                        NULL, &input, NULL, NULL);
636             if (GSS_ERROR(maj)) {
637                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
638                               "%s", mag_error(req, "gss_init_sec_context() "
639                                               "failed", maj, min));
640                 goto done;
641             }
642             gss_release_buffer(&min, &output);
643             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
644                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
645                                          &client, &mech_type, &output, &flags,
646                                          &vtime, &delegated_cred);
647             if (GSS_ERROR(maj)) {
648                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
649                               "%s", mag_error(req, "gss_accept_sec_context()"
650                                               " failed", maj, min));
651                 goto done;
652             }
653         }
654     } else if (maj == GSS_S_CONTINUE_NEEDED) {
655         if (!mc) {
656             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
657                           "Mechanism needs continuation but neither "
658                           "GssapiConnectionBound nor "
659                           "GssapiUseSessions are available");
660             gss_release_buffer(&min, &output);
661             output.length = 0;
662         }
663         /* auth not complete send token and wait next packet */
664         goto done;
665     }
666
667     /* Always set the GSS name in an env var */
668     maj = gss_display_name(&min, client, &name, NULL);
669     if (GSS_ERROR(maj)) {
670         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
671                       mag_error(req, "gss_display_name() failed",
672                                 maj, min));
673         goto done;
674     }
675     clientname = apr_pstrndup(req->pool, name.value, name.length);
676     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
677     expiration = time(NULL) + vtime;
678     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
679                   apr_psprintf(req->pool, "%ld", (long)expiration));
680
681 #ifdef HAVE_CRED_STORE
682     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
683         char *ccachefile = NULL;
684
685         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
686                               delegated_cred, &ccachefile);
687
688         if (ccachefile) {
689             mag_set_KRB5CCANME(req, ccachefile);
690         }
691
692         if (mc) {
693             mc->delegated = true;
694         }
695     }
696 #endif
697
698     if (cfg->map_to_local) {
699         maj = gss_localname(&min, client, mech_type, &lname);
700         if (maj != GSS_S_COMPLETE) {
701             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
702                           mag_error(req, "gss_localname() failed", maj, min));
703             goto done;
704         }
705         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
706     } else {
707         req->user = clientname;
708     }
709
710     if (mc) {
711         mc->user_name = apr_pstrdup(mc->parent, req->user);
712         mc->gss_name = apr_pstrdup(mc->parent, clientname);
713         mc->established = true;
714         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
715             vtime = MIN_SESS_EXP_TIME;
716         }
717         mc->expiration = expiration;
718         mc->auth_type = auth_type;
719         if (auth_type == AUTH_TYPE_BASIC) {
720             mag_basic_cache(cfg, mc, ba_user, ba_pwd);
721         }
722         if (cfg->use_sessions) {
723             mag_attempt_session(req, cfg, mc);
724         }
725     }
726
727     if (cfg->send_persist)
728         apr_table_set(req->headers_out, "Persistent-Auth",
729             cfg->gss_conn_ctx ? "true" : "false");
730
731     ret = OK;
732
733 done:
734     if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
735         int prefixlen = strlen(auth_types[auth_type]) + 1;
736         replen = apr_base64_encode_len(output.length) + 1;
737         reply = apr_pcalloc(req->pool, prefixlen + replen);
738         if (reply) {
739             memcpy(reply, auth_types[auth_type], prefixlen - 1);
740             reply[prefixlen - 1] = ' ';
741             apr_base64_encode(&reply[prefixlen], output.value, output.length);
742             apr_table_add(req->err_headers_out,
743                           "WWW-Authenticate", reply);
744         }
745     } else if (ret == HTTP_UNAUTHORIZED) {
746         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
747         if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
748             apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
749         }
750         if (cfg->use_basic_auth) {
751             apr_table_add(req->err_headers_out,
752                           "WWW-Authenticate",
753                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
754                                        ap_auth_name(req)));
755         }
756     }
757 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
758     if (user_ccache != NULL) {
759         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
760         if (maj != GSS_S_COMPLETE) {
761             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
762                           "Failed to restore per-thread ccache, %s",
763                           mag_error(req, "gss_krb5_ccache_name() "
764                                     "failed", maj, min));
765         }
766     }
767 #endif
768     if (ctx != GSS_C_NO_CONTEXT)
769         gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
770     gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
771     gss_release_cred(&min, &user_cred);
772     gss_release_cred(&min, &acquired_cred);
773     gss_release_cred(&min, &delegated_cred);
774     gss_release_buffer(&min, &output);
775     gss_release_name(&min, &client);
776     gss_release_name(&min, &server);
777     gss_release_buffer(&min, &name);
778     gss_release_buffer(&min, &lname);
779     return ret;
780 }
781
782
783 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
784 {
785     struct mag_config *cfg;
786
787     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
788     cfg->pool = p;
789
790     return cfg;
791 }
792
793 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
794 {
795     struct mag_config *cfg = (struct mag_config *)mconfig;
796     cfg->ssl_only = on ? true : false;
797     return NULL;
798 }
799
800 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
801 {
802     struct mag_config *cfg = (struct mag_config *)mconfig;
803     cfg->map_to_local = on ? true : false;
804     return NULL;
805 }
806
807 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
808 {
809     struct mag_config *cfg = (struct mag_config *)mconfig;
810     cfg->gss_conn_ctx = on ? true : false;
811     return NULL;
812 }
813
814 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
815 {
816     struct mag_config *cfg = (struct mag_config *)mconfig;
817     cfg->send_persist = on ? true : false;
818     return NULL;
819 }
820
821 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
822 {
823     struct mag_config *cfg = (struct mag_config *)mconfig;
824     cfg->use_sessions = on ? true : false;
825     return NULL;
826 }
827
828 #ifdef HAVE_CRED_STORE
829 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
830 {
831     struct mag_config *cfg = (struct mag_config *)mconfig;
832     cfg->use_s4u2proxy = on ? true : false;
833
834     if (cfg->deleg_ccache_dir == NULL) {
835         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
836     }
837     return NULL;
838 }
839 #endif
840
841 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
842 {
843     struct mag_config *cfg = (struct mag_config *)mconfig;
844     struct databuf keys;
845     unsigned char *val;
846     apr_status_t rc;
847     const char *k;
848     int l;
849
850     if (strncmp(w, "key:", 4) != 0) {
851         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
852                      "Invalid key format, expected prefix 'key:'");
853         return NULL;
854     }
855     k = w + 4;
856
857     l = apr_base64_decode_len(k);
858     val = apr_palloc(parms->temp_pool, l);
859
860     keys.length = (int)apr_base64_decode_binary(val, k);
861     keys.value = (unsigned char *)val;
862
863     if (keys.length != 32) {
864         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
865                      "Invalid key length, expected 32 got %d", keys.length);
866         return NULL;
867     }
868
869     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
870     if (rc != OK) {
871         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
872                      "Failed to import sealing key!");
873     }
874     return NULL;
875 }
876
877 #ifdef HAVE_CRED_STORE
878
879 #define MAX_CRED_OPTIONS 10
880
881 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
882                                   const char *w)
883 {
884     struct mag_config *cfg = (struct mag_config *)mconfig;
885     gss_key_value_element_desc *elements;
886     uint32_t count;
887     size_t size;
888     const char *p;
889     char *value;
890     char *key;
891
892     p = strchr(w, ':');
893     if (!p) {
894         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
895                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
896         return NULL;
897     }
898
899     key = apr_pstrndup(parms->pool, w, (p-w));
900     value = apr_pstrdup(parms->pool, p + 1);
901
902     if (!cfg->cred_store) {
903         cfg->cred_store = apr_pcalloc(parms->pool,
904                                       sizeof(gss_key_value_set_desc));
905         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
906         cfg->cred_store->elements = apr_palloc(parms->pool, size);
907     }
908
909     elements = cfg->cred_store->elements;
910     count = cfg->cred_store->count;
911
912     if (count >= MAX_CRED_OPTIONS) {
913         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
914                      "Too many GssapiCredStore options (MAX: %d)",
915                      MAX_CRED_OPTIONS);
916         return NULL;
917     }
918     cfg->cred_store->count++;
919
920     elements[count].key = key;
921     elements[count].value = value;
922
923     return NULL;
924 }
925
926 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
927                                         const char *value)
928 {
929     struct mag_config *cfg = (struct mag_config *)mconfig;
930
931     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
932
933     return NULL;
934 }
935 #endif
936
937 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
938 {
939     struct mag_config *cfg = (struct mag_config *)mconfig;
940
941     cfg->use_basic_auth = on ? true : false;
942     return NULL;
943 }
944
945 #define MAX_ALLOWED_MECHS 10
946
947 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
948                                   const char *w)
949 {
950     struct mag_config *cfg = (struct mag_config *)mconfig;
951     gss_const_OID oid;
952     size_t size;
953
954     if (!cfg->allowed_mechs) {
955         cfg->allowed_mechs = apr_pcalloc(parms->pool,
956                                          sizeof(gss_OID_set_desc));
957         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
958         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
959     }
960
961     if (strcmp(w, "krb5") == 0) {
962         oid = gss_mech_krb5;
963     } else if (strcmp(w, "iakerb") == 0) {
964         oid = gss_mech_iakerb;
965     } else if (strcmp(w, "ntlmssp") == 0) {
966         oid = &gss_mech_ntlmssp;
967     } else {
968         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
969                      "Unrecognized GSSAPI Mechanism: %s", w);
970         return NULL;
971     }
972
973     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
974         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
975                      "Too many GssapiAllowedMech options (MAX: %d)",
976                      MAX_ALLOWED_MECHS);
977         return NULL;
978     }
979     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
980     cfg->allowed_mechs->count++;
981
982     return NULL;
983 }
984
985 static const command_rec mag_commands[] = {
986     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
987                   "Work only if connection is SSL Secured"),
988     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
989                   "Translate principals to local names"),
990     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
991                   "Authentication is bound to the TCP connection"),
992     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
993                   "Send Persitent-Auth header according to connection bound"),
994     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
995                   "Authentication uses mod_sessions to hold status"),
996     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
997                      "Key Used to seal session data."),
998 #ifdef HAVE_CRED_STORE
999     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1000                   "Initializes credentials for s4u2proxy usage"),
1001     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1002                     "Credential Store"),
1003     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1004                      OR_AUTHCFG, "Directory to store delegated credentials"),
1005 #endif
1006 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1007     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1008                      "Allows use of Basic Auth for authentication"),
1009 #endif
1010     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1011                     "Allowed Mechanisms"),
1012     { NULL }
1013 };
1014
1015 static void
1016 mag_register_hooks(apr_pool_t *p)
1017 {
1018     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1019     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1020     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1021 }
1022
1023 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1024 {
1025     STANDARD20_MODULE_STUFF,
1026     mag_create_dir_config,
1027     NULL,
1028     NULL,
1029     NULL,
1030     mag_commands,
1031     mag_register_hooks
1032 };