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