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