4 Copyright (C) 2014 Simo Sorce <simo@redhat.com>
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:
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
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.
25 #include "mod_auth_gssapi.h"
27 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
29 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
31 APLOG_USE_MODULE(auth_gssapi);
33 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
35 static char *mag_status(request_rec *req, int type, uint32_t err)
37 uint32_t maj_ret, min_ret;
46 maj_ret = gss_display_status(&min_ret, err, type,
47 GSS_C_NO_OID, &msg_ctx, &text);
48 if (maj_ret != GSS_S_COMPLETE) {
54 msg_ret = apr_psprintf(req->pool, "%s, %*s",
55 msg_ret, len, (char *)text.value);
57 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
59 gss_release_buffer(&min_ret, &text);
60 } while (msg_ctx != 0);
65 static char *mag_error(request_rec *req, const char *msg,
66 uint32_t maj, uint32_t min)
71 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
72 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
73 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
76 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
78 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
79 apr_pool_t *temp, server_rec *s)
81 /* FIXME: create mutex to deal with connections and contexts ? */
82 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
83 mag_post_config_session();
84 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
89 static int mag_pre_connection(conn_rec *c, void *csd)
93 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
94 if (!mc) return DECLINED;
97 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
101 static apr_status_t mag_conn_destroy(void *ptr)
103 struct mag_conn *mc = (struct mag_conn *)ptr;
107 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
108 mc->established = false;
113 static bool mag_conn_is_https(conn_rec *c)
116 if (mag_is_https(c)) return true;
122 static void mag_store_deleg_creds(request_rec *req,
123 char *dir, char *clientname,
124 gss_cred_id_t delegated_cred,
127 gss_key_value_element_desc element;
128 gss_key_value_set_desc store;
132 value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
134 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
135 "OOM storing delegated credentials");
139 element.key = "ccache";
140 element.value = value;
141 store.elements = &element;
144 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
145 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
146 if (GSS_ERROR(maj)) {
147 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
148 mag_error(req, "failed to store delegated creds",
155 static int mag_auth(request_rec *req)
158 const char *auth_type;
159 struct mag_config *cfg;
160 const char *auth_header;
161 char *auth_header_type;
162 char *auth_header_value;
163 int ret = HTTP_UNAUTHORIZED;
164 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
166 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
167 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
168 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
169 gss_name_t client = GSS_C_NO_NAME;
170 gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
171 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
172 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
173 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
180 gss_OID mech_type = GSS_C_NO_OID;
181 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
182 struct mag_conn *mc = NULL;
183 bool is_basic = false;
184 gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
185 gss_name_t server = GSS_C_NO_NAME;
187 type = ap_auth_type(req);
188 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
192 /* ignore auth for subrequests */
193 if (!ap_is_initial_req(req)) {
197 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
200 if (!mag_conn_is_https(req->connection)) {
201 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
202 "Not a TLS connection, refusing to authenticate!");
207 if (cfg->gss_conn_ctx) {
208 mc = (struct mag_conn *)ap_get_module_config(
209 req->connection->conn_config,
210 &auth_gssapi_module);
212 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
213 "Failed to retrieve connection context!");
218 /* if available, session always supersedes connection bound data */
219 if (cfg->use_sessions) {
220 mag_check_session(req, cfg, &mc);
224 /* register the context in the memory pool, so it can be freed
225 * when the connection/request is terminated */
226 apr_pool_userdata_set(mc, "mag_conn_ptr",
227 mag_conn_destroy, mc->parent);
229 if (mc->established) {
230 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
231 "Already established context found!");
232 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
233 req->ap_auth_type = apr_pstrdup(req->pool, mc->auth_type);
234 req->user = apr_pstrdup(req->pool, mc->user_name);
243 auth_header = apr_table_get(req->headers_in, "Authorization");
244 if (!auth_header) goto done;
246 auth_header_type = ap_getword_white(req->pool, &auth_header);
247 if (!auth_header_type) goto done;
249 if (strcasecmp(auth_header_type, "Negotiate") == 0) {
250 auth_type = "Negotiate";
252 auth_header_value = ap_getword_white(req->pool, &auth_header);
253 if (!auth_header_value) goto done;
254 input.length = apr_base64_decode_len(auth_header_value) + 1;
255 input.value = apr_pcalloc(req->pool, input.length);
256 if (!input.value) goto done;
257 input.length = apr_base64_decode(input.value, auth_header_value);
258 } else if ((strcasecmp(auth_header_type, "Basic") == 0) &&
259 (cfg->use_basic_auth == true)) {
263 gss_buffer_desc ba_user;
264 gss_buffer_desc ba_pwd;
266 ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
267 if (!ba_pwd.value) goto done;
268 ba_user.value = ap_getword_nulls_nc(req->pool,
269 (char **)&ba_pwd.value, ':');
270 if (!ba_user.value) goto done;
271 if (((char *)ba_user.value)[0] == '\0' ||
272 ((char *)ba_pwd.value)[0] == '\0') {
273 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
274 "Invalid empty user or password for Basic Auth");
277 ba_user.length = strlen(ba_user.value);
278 ba_pwd.length = strlen(ba_pwd.value);
279 maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
280 if (GSS_ERROR(maj)) {
281 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
283 mag_error(req, "gss_import_name() failed",
287 maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
291 &user_cred, NULL, NULL);
292 if (GSS_ERROR(maj)) {
293 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
295 mag_error(req, "gss_acquire_cred_with_password() "
296 "failed", maj, min));
299 gss_release_name(&min, &client);
304 req->ap_auth_type = apr_pstrdup(req->pool, auth_type);
306 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
307 if (cfg->use_s4u2proxy) {
308 cred_usage = GSS_C_BOTH;
310 if (cfg->cred_store) {
311 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
312 GSS_C_NO_OID_SET, cred_usage,
313 cfg->cred_store, &acquired_cred,
315 if (GSS_ERROR(maj)) {
316 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
317 mag_error(req, "gss_acquire_cred_from() failed",
325 if (!acquired_cred) {
326 /* Try to acquire default creds */
327 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
328 GSS_C_NO_OID_SET, cred_usage,
329 &acquired_cred, NULL, NULL);
330 if (GSS_ERROR(maj)) {
331 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
332 "%s", mag_error(req, "gss_acquire_cred_from()"
333 " failed", maj, min));
337 maj = gss_inquire_cred(&min, acquired_cred, &server,
339 if (GSS_ERROR(maj)) {
340 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
341 "%s", mag_error(req, "gss_inquired_cred_() "
342 "failed", maj, min));
345 /* output and input are inverted here, this is intentional */
346 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
347 GSS_C_NO_OID, 0, 300,
348 GSS_C_NO_CHANNEL_BINDINGS, &output,
349 NULL, &input, NULL, NULL);
350 if (GSS_ERROR(maj)) {
351 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
352 "%s", mag_error(req, "gss_init_sec_context() "
353 "failed", maj, min));
358 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
359 &input, GSS_C_NO_CHANNEL_BINDINGS,
360 &client, &mech_type, &output, &flags, &vtime,
362 if (GSS_ERROR(maj)) {
363 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
364 mag_error(req, "gss_accept_sec_context() failed",
369 while (maj == GSS_S_CONTINUE_NEEDED) {
370 gss_release_buffer(&min, &input);
371 /* output and input are inverted here, this is intentional */
372 maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
373 GSS_C_NO_OID, 0, 300,
374 GSS_C_NO_CHANNEL_BINDINGS, &output,
375 NULL, &input, NULL, NULL);
376 if (GSS_ERROR(maj)) {
377 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
378 "%s", mag_error(req, "gss_init_sec_context() "
379 "failed", maj, min));
382 gss_release_buffer(&min, &output);
383 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
384 &input, GSS_C_NO_CHANNEL_BINDINGS,
385 &client, &mech_type, &output, &flags,
386 &vtime, &delegated_cred);
387 if (GSS_ERROR(maj)) {
388 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
389 "%s", mag_error(req, "gss_accept_sec_context()"
390 " failed", maj, min));
394 } else if (maj == GSS_S_CONTINUE_NEEDED) {
396 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
397 "Mechanism needs continuation but neither "
398 "GssapiConnectionBound nor "
399 "GssapiUseSessions are available");
400 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
401 gss_release_buffer(&min, &output);
404 /* auth not complete send token and wait next packet */
408 /* Always set the GSS name in an env var */
409 maj = gss_display_name(&min, client, &name, NULL);
410 if (GSS_ERROR(maj)) {
411 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
412 mag_error(req, "gss_display_name() failed",
416 clientname = apr_pstrndup(req->pool, name.value, name.length);
417 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
419 #ifdef HAVE_GSS_STORE_CRED_INTO
420 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
421 char *ccachefile = NULL;
423 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
424 delegated_cred, &ccachefile);
427 apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
432 if (cfg->map_to_local) {
433 maj = gss_localname(&min, client, mech_type, &lname);
434 if (maj != GSS_S_COMPLETE) {
435 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
436 mag_error(req, "gss_localname() failed", maj, min));
439 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
441 req->user = clientname;
445 mc->user_name = apr_pstrdup(mc->parent, req->user);
446 mc->gss_name = apr_pstrdup(mc->parent, clientname);
447 mc->established = true;
448 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
449 vtime = MIN_SESS_EXP_TIME;
451 mc->expiration = time(NULL) + vtime;
452 if (cfg->use_sessions) {
453 mag_attempt_session(req, cfg, mc);
455 mc->auth_type = auth_type;
461 if (ret == HTTP_UNAUTHORIZED) {
462 if (output.length != 0) {
463 replen = apr_base64_encode_len(output.length) + 1;
464 reply = apr_pcalloc(req->pool, 10 + replen);
466 memcpy(reply, "Negotiate ", 10);
467 apr_base64_encode(&reply[10], output.value, output.length);
468 apr_table_add(req->err_headers_out,
469 "WWW-Authenticate", reply);
472 apr_table_add(req->err_headers_out,
473 "WWW-Authenticate", "Negotiate");
474 if (cfg->use_basic_auth) {
475 apr_table_add(req->err_headers_out,
477 apr_psprintf(req->pool, "Basic realm=\"%s\"",
482 gss_delete_sec_context(&min, &user_ctx, &output);
483 gss_release_cred(&min, &user_cred);
484 gss_release_cred(&min, &acquired_cred);
485 gss_release_cred(&min, &delegated_cred);
486 gss_release_buffer(&min, &output);
487 gss_release_name(&min, &client);
488 gss_release_name(&min, &server);
489 gss_release_buffer(&min, &name);
490 gss_release_buffer(&min, &lname);
495 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
497 struct mag_config *cfg;
499 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
500 if (!cfg) return NULL;
506 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
508 struct mag_config *cfg = (struct mag_config *)mconfig;
509 cfg->ssl_only = on ? true : false;
513 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
515 struct mag_config *cfg = (struct mag_config *)mconfig;
516 cfg->map_to_local = on ? true : false;
520 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
522 struct mag_config *cfg = (struct mag_config *)mconfig;
523 cfg->gss_conn_ctx = on ? true : false;
527 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
529 struct mag_config *cfg = (struct mag_config *)mconfig;
530 cfg->use_sessions = on ? true : false;
534 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
536 struct mag_config *cfg = (struct mag_config *)mconfig;
537 cfg->use_s4u2proxy = on ? true : false;
539 if (cfg->deleg_ccache_dir == NULL) {
540 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
541 if (!cfg->deleg_ccache_dir) {
542 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
543 parms->server, "%s", "OOM setting deleg_ccache_dir.");
549 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
551 struct mag_config *cfg = (struct mag_config *)mconfig;
558 if (strncmp(w, "key:", 4) != 0) {
559 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
560 "Invalid key format, expected prefix 'key:'");
565 l = apr_base64_decode_len(k);
566 val = apr_palloc(parms->temp_pool, l);
568 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
569 "Failed to get memory to decode key");
573 keys.length = (int)apr_base64_decode_binary(val, k);
574 keys.value = (unsigned char *)val;
576 if (keys.length != 32) {
577 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
578 "Invalid key lenght, expected 32 got %d", keys.length);
582 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
584 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
585 "Failed to import sealing key!");
590 #define MAX_CRED_OPTIONS 10
592 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
595 struct mag_config *cfg = (struct mag_config *)mconfig;
596 gss_key_value_element_desc *elements;
605 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
606 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
610 key = apr_pstrndup(parms->pool, w, (p-w));
611 value = apr_pstrdup(parms->pool, p + 1);
612 if (!key || !value) {
613 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
614 "%s", "OOM handling GssapiCredStore option");
618 if (!cfg->cred_store) {
619 cfg->cred_store = apr_pcalloc(parms->pool,
620 sizeof(gss_key_value_set_desc));
621 if (!cfg->cred_store) {
622 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
623 "%s", "OOM handling GssapiCredStore option");
626 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
627 cfg->cred_store->elements = apr_palloc(parms->pool, size);
628 if (!cfg->cred_store->elements) {
629 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
630 "%s", "OOM handling GssapiCredStore option");
634 elements = cfg->cred_store->elements;
635 count = cfg->cred_store->count;
637 if (count >= MAX_CRED_OPTIONS) {
638 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
639 "Too many GssapiCredStore options (MAX: %d)",
643 cfg->cred_store->count++;
645 elements[count].key = key;
646 elements[count].value = value;
651 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
654 struct mag_config *cfg = (struct mag_config *)mconfig;
656 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
657 if (!cfg->deleg_ccache_dir) {
658 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
659 "%s", "OOM handling GssapiDelegCcacheDir option");
665 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
667 struct mag_config *cfg = (struct mag_config *)mconfig;
669 cfg->use_basic_auth = on ? true : false;
673 static const command_rec mag_commands[] = {
674 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
675 "Work only if connection is SSL Secured"),
676 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
677 "Translate principals to local names"),
678 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
679 "Authentication is bound to the TCP connection"),
680 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
681 "Authentication uses mod_sessions to hold status"),
682 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
683 "Key Used to seal session data."),
684 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
685 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
686 "Initializes credentials for s4u2proxy usage"),
688 #ifdef HAVE_GSS_STORE_CRED_INTO
689 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
691 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
692 OR_AUTHCFG, "Directory to store delegated credentials"),
694 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
695 AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
696 "Allows use of Basic Auth for authentication"),
702 mag_register_hooks(apr_pool_t *p)
704 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
705 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
706 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
709 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
711 STANDARD20_MODULE_STUFF,
712 mag_create_dir_config,