Better handling of desired_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_OID_set indicated_mechs = GSS_C_NO_OID_SET;
518     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
519     struct mag_conn *mc = NULL;
520     time_t expiration;
521     int i;
522
523     type = ap_auth_type(req);
524     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
525         return DECLINED;
526     }
527
528     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
529
530     if (cfg->allowed_mechs) {
531         desired_mechs = cfg->allowed_mechs;
532     } else {
533         /* Try to fetch the default set if not explicitly configured */
534         maj = gss_indicate_mechs(&min, &indicated_mechs);
535         if (maj != GSS_S_COMPLETE) {
536             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, req, "%s",
537                           mag_error(req, "gss_indicate_mechs() failed",
538                                     maj, min));
539         }
540         desired_mechs = indicated_mechs;
541     }
542
543     /* implicit auth for subrequests if main auth already happened */
544     if (!ap_is_initial_req(req) && req->main != NULL) {
545         type = ap_auth_type(req->main);
546         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
547             /* warn if the subrequest location and the main request
548              * location have different configs */
549             if (cfg != ap_get_module_config(req->main->per_dir_config,
550                                             &auth_gssapi_module)) {
551                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
552                               req, "Subrequest authentication bypass on "
553                                    "location with different configuration!");
554             }
555             if (req->main->user) {
556                 req->user = apr_pstrdup(req->pool, req->main->user);
557                 return OK;
558             } else {
559                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
560                               "The main request is tasked to establish the "
561                               "security context, can't proceed!");
562                 return HTTP_UNAUTHORIZED;
563             }
564         } else {
565             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
566                           "Subrequest GSSAPI auth with no auth on the main "
567                           "request. This operation may fail if other "
568                           "subrequests already established a context or the "
569                           "mechanism requires multiple roundtrips.");
570         }
571     }
572
573     if (cfg->ssl_only) {
574         if (!mag_conn_is_https(req->connection)) {
575             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
576                           "Not a TLS connection, refusing to authenticate!");
577             goto done;
578         }
579     }
580
581     if (cfg->gss_conn_ctx) {
582         mc = (struct mag_conn *)ap_get_module_config(
583                                                 req->connection->conn_config,
584                                                 &auth_gssapi_module);
585         if (!mc) {
586             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
587                           "Failed to retrieve connection context!");
588             goto done;
589         }
590     }
591
592     /* if available, session always supersedes connection bound data */
593     if (cfg->use_sessions) {
594         mag_check_session(req, cfg, &mc);
595     }
596
597     auth_header = apr_table_get(req->headers_in, "Authorization");
598
599     if (mc) {
600         if (mc->established &&
601             (auth_header == NULL) &&
602             (mc->auth_type != AUTH_TYPE_BASIC)) {
603             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
604                           "Already established context found!");
605             mag_set_req_data(req, cfg, mc);
606             ret = OK;
607             goto done;
608         }
609         pctx = &mc->ctx;
610     } else {
611         pctx = &ctx;
612     }
613
614     /* We can proceed only if we do have an auth header */
615     if (!auth_header) goto done;
616
617     auth_header_type = ap_getword_white(req->pool, &auth_header);
618     if (!auth_header_type) goto done;
619
620     for (i = 0; auth_types[i] != NULL; i++) {
621         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
622             auth_type = i;
623             break;
624         }
625     }
626
627     switch (auth_type) {
628     case AUTH_TYPE_NEGOTIATE:
629         if (!parse_auth_header(req->pool, &auth_header, &input)) {
630             goto done;
631         }
632         break;
633     case AUTH_TYPE_BASIC:
634         if (!cfg->use_basic_auth) {
635             goto done;
636         }
637
638         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
639         if (!ba_pwd.value) goto done;
640         ba_user.value = ap_getword_nulls_nc(req->pool,
641                                             (char **)&ba_pwd.value, ':');
642         if (!ba_user.value) goto done;
643         if (((char *)ba_user.value)[0] == '\0' ||
644             ((char *)ba_pwd.value)[0] == '\0') {
645             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
646                           "Invalid empty user or password for Basic Auth");
647             goto done;
648         }
649         ba_user.length = strlen(ba_user.value);
650         ba_pwd.length = strlen(ba_pwd.value);
651
652         if (mc && mc->established &&
653             mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
654             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
655                           "Already established BASIC AUTH context found!");
656             mag_set_req_data(req, cfg, mc);
657             ret = OK;
658             goto done;
659         }
660
661         break;
662
663     case AUTH_TYPE_RAW_NTLM:
664         if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
665             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
666                           "NTLM Authentication is not allowed!");
667             goto done;
668         }
669
670         if (!parse_auth_header(req->pool, &auth_header, &input)) {
671             goto done;
672         }
673
674         desired_mechs = discard_const(&gss_mech_set_ntlmssp);
675         break;
676
677     default:
678         goto done;
679     }
680
681     if (mc && mc->established) {
682         /* if we are re-authenticating make sure the conn context
683          * is cleaned up so we do not accidentally reuse an existing
684          * established context */
685         mag_conn_clear(mc);
686     }
687
688     req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
689
690 #ifdef HAVE_CRED_STORE
691     if (cfg->use_s4u2proxy) {
692         cred_usage = GSS_C_BOTH;
693     }
694 #endif
695     if (!mag_acquire_creds(req, cfg, desired_mechs,
696                            cred_usage, &acquired_cred, NULL)) {
697         goto done;
698     }
699
700     if (auth_type == AUTH_TYPE_BASIC) {
701         if (mag_auth_basic(req, cfg, ba_user, ba_pwd,
702                            acquired_cred, cred_usage,
703                            &client, &mech_type,
704                            &delegated_cred, &vtime)) {
705             goto complete;
706         }
707         goto done;
708     }
709
710     if (auth_type == AUTH_TYPE_NEGOTIATE &&
711         cfg->allowed_mechs != GSS_C_NO_OID_SET) {
712         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
713         if (GSS_ERROR(maj)) {
714             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
715                           mag_error(req, "gss_set_neg_mechs() failed",
716                                     maj, min));
717             goto done;
718         }
719     }
720
721     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
722                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
723                                  &client, &mech_type, &output, NULL, &vtime,
724                                  &delegated_cred);
725     if (GSS_ERROR(maj)) {
726         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
727                       mag_error(req, "gss_accept_sec_context() failed",
728                                 maj, min));
729         goto done;
730     } else if (maj == GSS_S_CONTINUE_NEEDED) {
731         if (!mc) {
732             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
733                           "Mechanism needs continuation but neither "
734                           "GssapiConnectionBound nor "
735                           "GssapiUseSessions are available");
736             gss_release_buffer(&min, &output);
737             output.length = 0;
738         }
739         /* auth not complete send token and wait next packet */
740         goto done;
741     }
742
743 complete:
744     /* Always set the GSS name in an env var */
745     maj = gss_display_name(&min, client, &name, NULL);
746     if (GSS_ERROR(maj)) {
747         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
748                       mag_error(req, "gss_display_name() failed",
749                                 maj, min));
750         goto done;
751     }
752     clientname = apr_pstrndup(req->pool, name.value, name.length);
753     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
754     expiration = time(NULL) + vtime;
755     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
756                   apr_psprintf(req->pool, "%ld", (long)expiration));
757
758 #ifdef HAVE_CRED_STORE
759     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
760         char *ccachefile = NULL;
761
762         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
763                               delegated_cred, &ccachefile);
764
765         if (ccachefile) {
766             mag_set_KRB5CCANME(req, ccachefile);
767         }
768
769         if (mc) {
770             mc->delegated = true;
771         }
772     }
773 #endif
774
775     if (cfg->map_to_local) {
776         maj = gss_localname(&min, client, mech_type, &lname);
777         if (maj != GSS_S_COMPLETE) {
778             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
779                           mag_error(req, "gss_localname() failed", maj, min));
780             goto done;
781         }
782         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
783     } else {
784         req->user = clientname;
785     }
786
787     if (mc) {
788         mc->user_name = apr_pstrdup(mc->pool, req->user);
789         mc->gss_name = apr_pstrdup(mc->pool, clientname);
790         mc->established = true;
791         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
792             vtime = MIN_SESS_EXP_TIME;
793         }
794         mc->expiration = expiration;
795         mc->auth_type = auth_type;
796         if (auth_type == AUTH_TYPE_BASIC) {
797             mag_basic_cache(cfg, mc, ba_user, ba_pwd);
798         }
799         if (cfg->use_sessions) {
800             mag_attempt_session(req, cfg, mc);
801         }
802     }
803
804     if (cfg->send_persist)
805         apr_table_set(req->headers_out, "Persistent-Auth",
806             cfg->gss_conn_ctx ? "true" : "false");
807
808     ret = OK;
809
810 done:
811     if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
812         int prefixlen = strlen(auth_types[auth_type]) + 1;
813         replen = apr_base64_encode_len(output.length) + 1;
814         reply = apr_pcalloc(req->pool, prefixlen + replen);
815         if (reply) {
816             memcpy(reply, auth_types[auth_type], prefixlen - 1);
817             reply[prefixlen - 1] = ' ';
818             apr_base64_encode(&reply[prefixlen], output.value, output.length);
819             apr_table_add(req->err_headers_out,
820                           "WWW-Authenticate", reply);
821         }
822     } else if (ret == HTTP_UNAUTHORIZED) {
823         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
824         if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
825             apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
826         }
827         if (cfg->use_basic_auth) {
828             apr_table_add(req->err_headers_out,
829                           "WWW-Authenticate",
830                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
831                                        ap_auth_name(req)));
832         }
833     }
834     gss_release_oid_set(&min, &indicated_mechs);
835     if (ctx != GSS_C_NO_CONTEXT)
836         gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
837     gss_release_cred(&min, &acquired_cred);
838     gss_release_cred(&min, &delegated_cred);
839     gss_release_buffer(&min, &output);
840     gss_release_name(&min, &client);
841     gss_release_buffer(&min, &name);
842     gss_release_buffer(&min, &lname);
843     return ret;
844 }
845
846
847 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
848 {
849     struct mag_config *cfg;
850
851     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
852     cfg->pool = p;
853
854     return cfg;
855 }
856
857 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
858 {
859     struct mag_config *cfg = (struct mag_config *)mconfig;
860     cfg->ssl_only = on ? true : false;
861     return NULL;
862 }
863
864 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
865 {
866     struct mag_config *cfg = (struct mag_config *)mconfig;
867     cfg->map_to_local = on ? true : false;
868     return NULL;
869 }
870
871 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
872 {
873     struct mag_config *cfg = (struct mag_config *)mconfig;
874     cfg->gss_conn_ctx = on ? true : false;
875     return NULL;
876 }
877
878 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
879 {
880     struct mag_config *cfg = (struct mag_config *)mconfig;
881     cfg->send_persist = on ? true : false;
882     return NULL;
883 }
884
885 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
886 {
887     struct mag_config *cfg = (struct mag_config *)mconfig;
888     cfg->use_sessions = on ? true : false;
889     return NULL;
890 }
891
892 #ifdef HAVE_CRED_STORE
893 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
894 {
895     struct mag_config *cfg = (struct mag_config *)mconfig;
896     cfg->use_s4u2proxy = on ? true : false;
897
898     if (cfg->deleg_ccache_dir == NULL) {
899         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
900     }
901     return NULL;
902 }
903 #endif
904
905 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
906 {
907     struct mag_config *cfg = (struct mag_config *)mconfig;
908     struct databuf keys;
909     unsigned char *val;
910     apr_status_t rc;
911     const char *k;
912     int l;
913
914     if (strncmp(w, "key:", 4) != 0) {
915         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
916                      "Invalid key format, expected prefix 'key:'");
917         return NULL;
918     }
919     k = w + 4;
920
921     l = apr_base64_decode_len(k);
922     val = apr_palloc(parms->temp_pool, l);
923
924     keys.length = (int)apr_base64_decode_binary(val, k);
925     keys.value = (unsigned char *)val;
926
927     if (keys.length != 32) {
928         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
929                      "Invalid key length, expected 32 got %d", keys.length);
930         return NULL;
931     }
932
933     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
934     if (rc != OK) {
935         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
936                      "Failed to import sealing key!");
937     }
938     return NULL;
939 }
940
941 #ifdef HAVE_CRED_STORE
942
943 #define MAX_CRED_OPTIONS 10
944
945 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
946                                   const char *w)
947 {
948     struct mag_config *cfg = (struct mag_config *)mconfig;
949     gss_key_value_element_desc *elements;
950     uint32_t count;
951     size_t size;
952     const char *p;
953     char *value;
954     char *key;
955
956     p = strchr(w, ':');
957     if (!p) {
958         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
959                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
960         return NULL;
961     }
962
963     key = apr_pstrndup(parms->pool, w, (p-w));
964     value = apr_pstrdup(parms->pool, p + 1);
965
966     if (!cfg->cred_store) {
967         cfg->cred_store = apr_pcalloc(parms->pool,
968                                       sizeof(gss_key_value_set_desc));
969         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
970         cfg->cred_store->elements = apr_palloc(parms->pool, size);
971     }
972
973     elements = cfg->cred_store->elements;
974     count = cfg->cred_store->count;
975
976     if (count >= MAX_CRED_OPTIONS) {
977         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
978                      "Too many GssapiCredStore options (MAX: %d)",
979                      MAX_CRED_OPTIONS);
980         return NULL;
981     }
982     cfg->cred_store->count++;
983
984     elements[count].key = key;
985     elements[count].value = value;
986
987     return NULL;
988 }
989
990 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
991                                         const char *value)
992 {
993     struct mag_config *cfg = (struct mag_config *)mconfig;
994
995     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
996
997     return NULL;
998 }
999 #endif
1000
1001 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
1002 {
1003     struct mag_config *cfg = (struct mag_config *)mconfig;
1004
1005     cfg->use_basic_auth = on ? true : false;
1006     return NULL;
1007 }
1008
1009 #define MAX_ALLOWED_MECHS 10
1010
1011 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
1012                                   const char *w)
1013 {
1014     struct mag_config *cfg = (struct mag_config *)mconfig;
1015     gss_const_OID oid;
1016     size_t size;
1017
1018     if (!cfg->allowed_mechs) {
1019         cfg->allowed_mechs = apr_pcalloc(parms->pool,
1020                                          sizeof(gss_OID_set_desc));
1021         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
1022         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
1023
1024         cfg->allowed_mechs->elements[0] = gss_mech_spnego;
1025         cfg->allowed_mechs->count++;
1026     }
1027
1028     if (strcmp(w, "krb5") == 0) {
1029         oid = gss_mech_krb5;
1030     } else if (strcmp(w, "iakerb") == 0) {
1031         oid = gss_mech_iakerb;
1032     } else if (strcmp(w, "ntlmssp") == 0) {
1033         oid = &gss_mech_ntlmssp;
1034     } else {
1035         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1036                      "Unrecognized GSSAPI Mechanism: %s", w);
1037         return NULL;
1038     }
1039
1040     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
1041         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
1042                      "Too many GssapiAllowedMech options (MAX: %d)",
1043                      MAX_ALLOWED_MECHS);
1044         return NULL;
1045     }
1046     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
1047     cfg->allowed_mechs->count++;
1048
1049     return NULL;
1050 }
1051
1052 static const command_rec mag_commands[] = {
1053     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
1054                   "Work only if connection is SSL Secured"),
1055     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
1056                   "Translate principals to local names"),
1057     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
1058                   "Authentication is bound to the TCP connection"),
1059     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
1060                   "Send Persitent-Auth header according to connection bound"),
1061     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
1062                   "Authentication uses mod_sessions to hold status"),
1063     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
1064                      "Key Used to seal session data."),
1065 #ifdef HAVE_CRED_STORE
1066     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
1067                   "Initializes credentials for s4u2proxy usage"),
1068     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
1069                     "Credential Store"),
1070     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
1071                      OR_AUTHCFG, "Directory to store delegated credentials"),
1072 #endif
1073 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
1074     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1075                      "Allows use of Basic Auth for authentication"),
1076 #endif
1077     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1078                     "Allowed Mechanisms"),
1079     { NULL }
1080 };
1081
1082 static void
1083 mag_register_hooks(apr_pool_t *p)
1084 {
1085     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1086     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1087     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1088 }
1089
1090 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1091 {
1092     STANDARD20_MODULE_STUFF,
1093     mag_create_dir_config,
1094     NULL,
1095     NULL,
1096     NULL,
1097     mag_commands,
1098     mag_register_hooks
1099 };