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