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