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