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