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