43933a09e91c2e569c44fdc9b1dacb430906fd9b
[mod_auth_kerb.git] / src / mod_auth_kerb.c
1 /*
2  * Daniel Kouril <kouril@users.sourceforge.net>
3  *
4  * Source and Documentation can be found at:
5  * http://modauthkerb.sourceforge.net/
6  *
7  * Based on work by
8  *   James E. Robinson, III <james@ncstate.net>
9  *   Daniel Henninger <daniel@ncsu.edu>
10  *   Ludek Sulak <xsulak@fi.muni.cz>
11  */
12
13 /*
14  * Copyright (c) 2004 Masarykova universita
15  * (Masaryk University, Brno, Czech Republic)
16  * All rights reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions are met:
20  *
21  * 1. Redistributions of source code must retain the above copyright notice,
22  *    this list of conditions and the following disclaimer.
23  *
24  * 2. Redistributions in binary form must reproduce the above copyright
25  *    notice, this list of conditions and the following disclaimer in the
26  *    documentation and/or other materials provided with the distribution.
27  *
28  * 3. Neither the name of the University nor the names of its contributors may
29  *    be used to endorse or promote products derived from this software
30  *    without specific prior written permission.
31  *
32  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
36  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42  * POSSIBILITY OF SUCH DAMAGE.
43  */
44
45 #ident "$Id$"
46
47 #include "config.h"
48
49 #define MODAUTHKERB_VERSION "5.0-rc4"
50
51 #ifndef APXS1
52 #include "ap_compat.h"
53 #include "apr_strings.h"
54 #endif
55 #include "httpd.h"
56 #include "http_config.h"
57 #include "http_core.h"
58 #include "http_log.h"
59 #include "http_protocol.h"
60 #include "http_request.h"
61
62 #ifdef KRB5
63 #include <krb5.h>
64 #ifdef HEIMDAL
65 #  include <gssapi.h>
66 #else
67 #  include <gssapi/gssapi.h>
68 #  include <gssapi/gssapi_generic.h>
69 #  define GSS_C_NT_USER_NAME gss_nt_user_name
70 #  define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
71 #  define krb5_get_err_text(context,code) error_message(code)
72 #endif
73 #include "spnegokrb5.h"
74 #endif /* KRB5 */
75
76 #ifdef KRB4
77 /*Prevent warning about closesocket redefinition (Apache's ap_config.h and 
78  * MIT Kerberos' port-sockets.h both define it as close) */
79 #ifdef closesocket
80 #  undef closesocket
81 #endif
82 #include <krb.h>
83 #include <netdb.h> /* gethostbyname() */
84 #endif /* KRB4 */
85
86 #ifdef APXS1
87 module auth_kerb_module;
88 #else
89 module AP_MODULE_DECLARE_DATA auth_kerb_module;
90 #endif
91
92 /*************************************************************************** 
93  Macros To Ease Compatibility
94  ***************************************************************************/
95 #ifdef APXS1
96 #define MK_POOL pool
97 #define MK_TABLE_GET ap_table_get
98 #define MK_USER r->connection->user
99 #define MK_AUTH_TYPE r->connection->ap_auth_type
100 #define PROXYREQ_PROXY STD_PROXY
101 #else
102 #define MK_POOL apr_pool_t
103 #define MK_TABLE_GET apr_table_get
104 #define MK_USER r->user
105 #define MK_AUTH_TYPE r->ap_auth_type
106 #endif /* APXS1 */
107
108
109 /*************************************************************************** 
110  Auth Configuration Structure
111  ***************************************************************************/
112 typedef struct {
113         char *krb_auth_realms;
114         int krb_save_credentials;
115         int krb_verify_kdc;
116         char *krb_service_name;
117         int krb_authoritative;
118 #ifdef KRB5
119         char *krb_5_keytab;
120         int krb_method_gssapi;
121         int krb_method_k5pass;
122 #endif
123 #ifdef KRB4
124         char *krb_4_srvtab;
125         int krb_method_k4pass;
126 #endif
127 } kerb_auth_config;
128
129 static const char*
130 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg);
131
132 #ifdef APXS1
133 #define command(name, func, var, type, usage)           \
134   { name, func,                                         \
135     (void*)XtOffsetOf(kerb_auth_config, var),           \
136     OR_AUTHCFG, type, usage }
137 #else
138 #define command(name, func, var, type, usage)           \
139   AP_INIT_ ## type (name, func,                         \
140         (void*)APR_XtOffsetOf(kerb_auth_config, var),   \
141         OR_AUTHCFG, usage)
142 #endif
143
144 static const command_rec kerb_auth_cmds[] = {
145    command("KrbAuthRealms", krb5_save_realms, krb_auth_realms,
146      RAW_ARGS, "Realms to attempt authentication against (can be multiple)."),
147
148    command("KrbAuthRealm", krb5_save_realms, krb_auth_realms,
149      RAW_ARGS, "Alias for KrbAuthRealms."),
150
151    command("KrbSaveCredentials", ap_set_flag_slot, krb_save_credentials,
152      FLAG, "Save and store credentials/tickets retrieved during auth."),
153
154    command("KrbVerifyKDC", ap_set_flag_slot, krb_verify_kdc,
155      FLAG, "Verify tickets against keytab to prevent KDC spoofing attacks."),
156
157    command("KrbServiceName", ap_set_string_slot, krb_service_name,
158      TAKE1, "Service name to be used by Apache for authentication."),
159
160    command("KrbAuthoritative", ap_set_flag_slot, krb_authoritative,
161      FLAG, "Set to 'off' to allow access control to be passed along to lower modules if the UserID is not known to this module."),
162
163 #ifdef KRB5
164    command("Krb5Keytab", ap_set_file_slot, krb_5_keytab,
165      TAKE1, "Location of Kerberos V5 keytab file."),
166
167    command("KrbMethodNegotiate", ap_set_flag_slot, krb_method_gssapi,
168      FLAG, "Enable Negotiate authentication method."),
169
170    command("KrbMethodK5Passwd", ap_set_flag_slot, krb_method_k5pass,
171      FLAG, "Enable Kerberos V5 password authentication."),
172 #endif 
173
174 #ifdef KRB4
175    command("Krb4Srvtab", ap_set_file_slot, krb_4_srvtab,
176      TAKE1, "Location of Kerberos V4 srvtab file."),
177
178    command("KrbMethodK4Passwd", ap_set_flag_slot, krb_method_k4pass,
179      FLAG, "Enable Kerberos V4 password authentication."),
180 #endif
181
182    { NULL }
183 };
184
185 #ifdef KRB5
186 typedef struct {
187    gss_ctx_id_t context;
188    gss_cred_id_t server_creds;
189 } gss_connection_t;
190
191 static gss_connection_t *gss_connection = NULL;
192
193 static const char *EMPTY_STRING = "\0";
194 #endif
195
196
197 /*************************************************************************** 
198  Auth Configuration Initialization
199  ***************************************************************************/
200 static void *kerb_dir_create_config(MK_POOL *p, char *d)
201 {
202         kerb_auth_config *rec;
203
204         rec = (kerb_auth_config *) ap_pcalloc(p, sizeof(kerb_auth_config));
205         ((kerb_auth_config *)rec)->krb_verify_kdc = 1;
206         ((kerb_auth_config *)rec)->krb_service_name = "HTTP";
207         ((kerb_auth_config *)rec)->krb_authoritative = 1;
208 #ifdef KRB5
209         ((kerb_auth_config *)rec)->krb_method_k5pass = 1;
210         ((kerb_auth_config *)rec)->krb_method_gssapi = 1;
211 #endif
212 #ifdef KRB4
213         ((kerb_auth_config *)rec)->krb_method_k4pass = 1;
214 #endif
215         return rec;
216 }
217
218 static const char*
219 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg)
220 {
221    sec->krb_auth_realms= ap_pstrdup(cmd->pool, arg);
222    return NULL;
223 }
224
225 void log_rerror(const char *file, int line, int level, int status,
226                 const request_rec *r, const char *fmt, ...)
227 {
228    char errstr[1024];
229    va_list ap;
230
231    va_start(ap, fmt);
232    vsnprintf(errstr, sizeof(errstr), fmt, ap);
233    va_end(ap);
234
235    
236 #ifdef APXS1
237    ap_log_rerror(file, line, level | APLOG_NOERRNO, r, "%s", errstr);
238 #else
239    ap_log_rerror(file, line, level | APLOG_NOERRNO, status, r, "%s", errstr);
240 #endif
241 }
242
243 #ifdef KRB4
244 /*************************************************************************** 
245  Username/Password Validation for Krb4
246  ***************************************************************************/
247 static int
248 verify_krb4_user(request_rec *r, char *name, char *instance, char *realm,
249                  char *password, char *linstance, char *srvtab, int krb_verify_kdc)
250 {
251    int ret;
252    char *phost;
253    unsigned long addr;
254    struct hostent *hp;
255    const char *hostname;
256    KTEXT_ST ticket;
257    AUTH_DAT authdata;
258    char lrealm[REALM_SZ];
259
260    ret = krb_get_pw_in_tkt(name, instance, realm, "krbtgt", realm, 
261                            DEFAULT_TKT_LIFE, password);
262    if (ret) {
263       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
264                  "Cannot get krb4 ticket: krb_get_pw_in_tkt() failed: %s",
265                  krb_get_err_text(ret));
266       return ret;
267    }
268
269    if (!krb_verify_kdc)
270       return ret;
271
272    hostname = ap_get_server_name(r);
273
274    hp = gethostbyname(hostname);
275    if (hp == NULL) {
276       dest_tkt();
277       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
278                  "Cannot verify krb4 ticket: gethostbyname() failed: %s",
279                  hstrerror(h_errno));
280       return h_errno;
281    }
282    memcpy(&addr, hp->h_addr, sizeof(addr));
283
284    phost = krb_get_phost((char *)hostname);
285
286    krb_get_lrealm(lrealm, 1);
287
288    ret = krb_mk_req(&ticket, linstance, phost, lrealm, 0);
289    if (ret) {
290       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
291                  "Cannot verify krb4 ticket: krb_mk_req() failed: %s",
292                  krb_get_err_text(ret));
293       dest_tkt();
294       return ret;
295    }
296
297    ret = krb_rd_req(&ticket, linstance, phost, addr, &authdata, srvtab);
298    if (ret) {
299       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
300                  "Cannot verify krb4 ticket: krb_rd_req() failed: %s",
301                  krb_get_err_text(ret));
302       dest_tkt();
303    }
304
305    return ret;
306 }
307
308 static int
309 krb4_cache_cleanup(void *data)
310 {
311    char *tkt_file = (char *) data;
312    
313    krb_set_tkt_string(tkt_file);
314    dest_tkt();
315    return OK;
316 }
317
318 static int 
319 authenticate_user_krb4pwd(request_rec *r,
320                           kerb_auth_config *conf,
321                           const char *auth_line)
322 {
323    int ret;
324    const char *sent_pw;
325    const char *sent_name;
326    char *sent_instance;
327    char tkt_file[32];
328    char *tkt_file_p = NULL;
329    int fd;
330    const char *realms;
331    const char *realm;
332    char *user;
333    char lrealm[REALM_SZ];
334    int all_principals_unkown;
335
336    sent_pw = ap_pbase64decode(r->pool, auth_line);
337    sent_name = ap_getword (r->pool, &sent_pw, ':');
338
339    /* do not allow user to override realm setting of server */
340    if (strchr(sent_name, '@')) {
341       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
342                  "specifying realm in user name is prohibited");
343       return HTTP_UNAUTHORIZED;
344    }
345
346    sent_instance = strchr(sent_name, '.');
347    if (sent_instance)
348       *sent_instance++ = '\0'; 
349
350    snprintf(tkt_file, sizeof(tkt_file), "/tmp/apache_tkt_XXXXXX");
351    fd = mkstemp(tkt_file);
352    if (fd < 0) {
353       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
354                  "Cannot create krb4 ccache: mkstemp() failed: %s",
355                  strerror(errno));
356       return HTTP_INTERNAL_SERVER_ERROR;
357    }
358
359    tkt_file_p = ap_pstrdup(r->pool, tkt_file);
360    ap_register_cleanup(r->pool, tkt_file_p,
361                        krb4_cache_cleanup, ap_null_cleanup);
362
363    krb_set_tkt_string(tkt_file);
364
365    all_principals_unkown = 1;
366    realms = conf->krb_auth_realms;
367    do {
368       memset(lrealm, 0, sizeof(lrealm));
369       realm = NULL;
370       if (realms)
371          realm = ap_getword_white(r->pool, &realms);
372
373       if (realm == NULL) {
374          ret = krb_get_lrealm(lrealm, 1);
375          if (ret)
376             break;
377          realm = lrealm;
378       }
379
380       ret = verify_krb4_user(r, (char *)sent_name, 
381                              (sent_instance) ? sent_instance : "",
382                              (char *)realm, (char *)sent_pw,
383                              conf->krb_service_name,
384                              conf->krb_4_srvtab, conf->krb_verify_kdc);
385       if (!conf->krb_authoritative && ret) {
386          /* if we're not authoritative, we allow authentication to pass on
387           * to another modules if (and only if) the user is not known to us */
388          if (all_principals_unkown && ret != KDC_PR_UNKNOWN)
389             all_principals_unkown = 0;
390       }
391
392       if (ret == 0)
393          break;
394    } while (realms && *realms);
395
396    if (ret) {
397       /* XXX log only in the verify_krb4_user() call */
398       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Verifying krb4 password failed");
399       ret = (!conf->krb_authoritative && all_principals_unkown == 1 && ret == KDC_PR_UNKNOWN) ?
400                  DECLINED : HTTP_UNAUTHORIZED;
401       goto end;
402    }
403
404    user = ap_pstrdup(r->pool, sent_name);
405    if (sent_instance)
406       user = ap_pstrcat(r->pool, user, ".", sent_instance, NULL);
407    user = ap_pstrcat(r->pool, user, "@", realm, NULL);
408
409    MK_USER = user;
410    MK_AUTH_TYPE = "Basic";
411    ap_table_setn(r->subprocess_env, "KRBTKFILE", tkt_file_p);
412
413    if (!conf->krb_save_credentials)
414       krb4_cache_cleanup(tkt_file);
415
416 end:
417    if (ret)
418       krb4_cache_cleanup(tkt_file);
419    close(fd);
420    tf_close();
421
422    return ret;
423 }
424 #endif /* KRB4 */
425
426 #ifdef KRB5
427 /*************************************************************************** 
428  Username/Password Validation for Krb5
429  ***************************************************************************/
430 /* Inspired by krb5_verify_user from Heimdal */
431 static krb5_error_code
432 verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
433                  krb5_ccache ccache, const char *password, const char *service,
434                  krb5_keytab keytab, int krb_verify_kdc)
435 {
436    krb5_creds creds;
437    krb5_principal server = NULL;
438    krb5_error_code ret;
439    krb5_verify_init_creds_opt opt;
440
441    /* XXX error messages shouldn't be logged here (and in the while() loop in
442     * authenticate_user_krb5pwd() as weell), in order to avoid confusing log
443     * entries when using multiple realms */
444
445    memset(&creds, 0, sizeof(creds));
446
447    ret = krb5_get_init_creds_password(context, &creds, principal, 
448                                       (char *)password, krb5_prompter_posix,
449                                       NULL, 0, NULL, NULL);
450    if (ret) {
451       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
452                  "krb5_get_init_creds_password() failed: %s",
453                  krb5_get_err_text(context, ret));
454       return ret;
455    }
456
457    ret = krb5_sname_to_principal(context, ap_get_server_name(r), service, 
458                                  KRB5_NT_UNKNOWN, &server);
459    if (ret) {
460       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
461                  "krb5_sname_to_principal() failed: %s",
462                  krb5_get_err_text(context, ret));
463       goto end;
464    }
465
466    krb5_verify_init_creds_opt_init(&opt);
467    krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, krb_verify_kdc);
468
469    ret = krb5_verify_init_creds(context, &creds, server, keytab, NULL, &opt);
470    if (ret) {
471       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
472                  "krb5_verify_init_creds() failed: %s",
473                  krb5_get_err_text(context, ret));
474       goto end;
475    }
476                  
477    if (ccache) {
478       ret = krb5_cc_initialize(context, ccache, principal);
479       if (ret) {
480          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
481                     "krb5_cc_initialize() failed: %s",
482                     krb5_get_err_text(context, ret));
483          goto end;
484       }
485
486       ret = krb5_cc_store_cred(context, ccache, &creds);
487       if (ret) {
488          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
489                     "krb5_cc_store_cred() failed: %s",
490                     krb5_get_err_text(context, ret));
491          goto end;
492       }
493    }
494
495 end:
496    krb5_free_cred_contents(context, &creds);
497    if (server)
498       krb5_free_principal(context, server);
499    return ret;
500 }
501
502 static int
503 krb5_cache_cleanup(void *data)
504 {
505    krb5_context context;
506    krb5_ccache  cache;
507    krb5_error_code problem;
508    char *cache_name = (char *) data;
509
510    problem = krb5_init_context(&context);
511    if (problem) {
512       /* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "krb5_init_context() failed"); */
513       return HTTP_INTERNAL_SERVER_ERROR;
514    }
515
516    problem = krb5_cc_resolve(context, cache_name, &cache);
517    if (problem) {
518       /* log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
519                 "krb5_cc_resolve() failed (%s: %s)",
520                 cache_name, krb5_get_err_text(context, problem)); */
521       return HTTP_INTERNAL_SERVER_ERROR;
522    }
523
524    krb5_cc_destroy(context, cache);
525    krb5_free_context(context);
526    return OK;
527 }
528
529 static int
530 create_krb5_ccache(krb5_context kcontext,
531                    request_rec *r,
532                    kerb_auth_config *conf,
533                    krb5_principal princ,
534                    krb5_ccache *ccache)
535 {
536    char *ccname;
537    int fd;
538    krb5_error_code problem;
539    int ret;
540    krb5_ccache tmp_ccache = NULL;
541
542    ccname = ap_psprintf(r->pool, "FILE:%s/krb5cc_apache_XXXXXX", P_tmpdir);
543    fd = mkstemp(ccname + strlen("FILE:"));
544    if (fd < 0) {
545       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
546                  "mkstemp() failed: %s", strerror(errno));
547       ret = HTTP_INTERNAL_SERVER_ERROR;
548       goto end;
549    }
550    close(fd);
551
552    problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache);
553    if (problem) {
554       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
555                  "krb5_cc_resolve() failed: %s",
556                  krb5_get_err_text(kcontext, problem));
557       ret = HTTP_INTERNAL_SERVER_ERROR;
558       unlink(ccname);
559       goto end;
560    }
561
562    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
563    if (problem) {
564       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
565                  "Cannot initialize krb5 ccache %s: krb5_cc_initialize() failed: %s",
566                  ccname, krb5_get_err_text(kcontext, problem));
567       ret = HTTP_INTERNAL_SERVER_ERROR;
568       goto end;
569    }
570
571    ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
572    ap_register_cleanup(r->pool, ccname,
573                        krb5_cache_cleanup, ap_null_cleanup);
574
575    *ccache = tmp_ccache;
576    tmp_ccache = NULL;
577
578    ret = OK;
579
580 end:
581    if (tmp_ccache)
582       krb5_cc_destroy(kcontext, tmp_ccache);
583
584    return ret;
585 }
586
587 static int
588 store_krb5_creds(krb5_context kcontext,
589                  request_rec *r,
590                  kerb_auth_config *conf,
591                  krb5_ccache delegated_cred)
592 {
593    char errstr[1024];
594    krb5_error_code problem;
595    krb5_principal princ;
596    krb5_ccache ccache;
597    int ret;
598
599    problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
600    if (problem) {
601       snprintf(errstr, sizeof(errstr), "krb5_cc_get_principal() failed: %s",
602                krb5_get_err_text(kcontext, problem));
603       return HTTP_INTERNAL_SERVER_ERROR;
604    }
605
606    ret = create_krb5_ccache(kcontext, r, conf, princ, &ccache);
607    if (ret) {
608       krb5_free_principal(kcontext, princ);
609       return ret;
610    }
611
612 #ifdef HEIMDAL
613    problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache);
614 #else
615    problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
616 #endif
617    krb5_free_principal(kcontext, princ);
618    if (problem) {
619       snprintf(errstr, sizeof(errstr), "Failed to store credentials: %s",
620                krb5_get_err_text(kcontext, problem));
621       krb5_cc_destroy(kcontext, ccache);
622       return HTTP_INTERNAL_SERVER_ERROR;
623    }
624
625    krb5_cc_close(kcontext, ccache);
626    return OK;
627 }
628
629
630 int authenticate_user_krb5pwd(request_rec *r,
631                               kerb_auth_config *conf,
632                               const char *auth_line)
633 {
634    const char      *sent_pw = NULL; 
635    const char      *sent_name = NULL;
636    const char      *realms = NULL;
637    krb5_context    kcontext = NULL;
638    krb5_error_code code;
639    krb5_principal  client = NULL;
640    krb5_ccache     ccache = NULL;
641    krb5_keytab     keytab = NULL;
642    int             ret;
643    char            *name = NULL;
644    int             all_principals_unkown;
645    char            *ccname = NULL;
646    int             fd;
647
648    code = krb5_init_context(&kcontext);
649    if (code) {
650       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
651                  "Cannot initialize Kerberos5 context (%d)", code);
652       return HTTP_INTERNAL_SERVER_ERROR;
653    }
654
655    sent_pw = ap_pbase64decode(r->pool, auth_line);
656    sent_name = ap_getword (r->pool, &sent_pw, ':');
657    /* do not allow user to override realm setting of server */
658    if (strchr(sent_name, '@')) {
659       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
660                  "specifying realm in user name is prohibited");
661       ret = HTTP_UNAUTHORIZED;
662       goto end;
663    }
664
665    if (sent_pw == NULL || *sent_pw == '\0') {
666       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
667                  "empty passwords are not accepted");
668       ret = HTTP_UNAUTHORIZED;
669       goto end;
670    }
671
672 #ifdef HEIMDAL
673    code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
674 #else
675    code = krb5_cc_resolve(kcontext, "MEMORY:", &ccache);
676 #endif
677    if (code) {
678       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
679                  "generating new memory ccache failed: %s",
680                  krb5_get_err_text(kcontext, code));
681       ret = HTTP_INTERNAL_SERVER_ERROR;
682       unlink(ccname);
683       goto end;
684    }
685
686    if (conf->krb_5_keytab)
687       krb5_kt_resolve(kcontext, conf->krb_5_keytab, &keytab);
688
689    all_principals_unkown = 1;
690    realms = conf->krb_auth_realms;
691    do {
692       if (realms && (code = krb5_set_default_realm(kcontext,
693                                            ap_getword_white(r->pool, &realms)))){
694          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
695                     "krb5_set_default_realm() failed: %s",
696                     krb5_get_err_text(kcontext, code));
697          continue;
698       }
699
700       if (client) {
701          krb5_free_principal(kcontext, client);
702          client = NULL;
703       }
704       code = krb5_parse_name(kcontext, sent_name, &client);
705       if (code) {
706          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
707                     "krb5_parse_name() failed: %s",
708                     krb5_get_err_text(kcontext, code));
709          continue;
710       }
711
712       code = verify_krb5_user(r, kcontext, client, ccache, sent_pw, 
713                               conf->krb_service_name, 
714                               keytab, conf->krb_verify_kdc);
715       if (!conf->krb_authoritative && code) {
716          /* if we're not authoritative, we allow authentication to pass on
717           * to another modules if (and only if) the user is not known to us */
718          if (all_principals_unkown && code != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN)
719             all_principals_unkown = 0;
720       }
721
722       if (code == 0)
723          break;
724
725       /* ap_getword_white() used above shifts the parameter, so it's not
726          needed to touch the realms variable */
727    } while (realms && *realms);
728
729    memset((char *)sent_pw, 0, strlen(sent_pw));
730
731    if (code) {
732       if (!conf->krb_authoritative && all_principals_unkown == 1 && code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN)
733          ret = DECLINED;
734       else
735          ret = HTTP_UNAUTHORIZED;
736
737       goto end;
738    }
739
740    code = krb5_unparse_name(kcontext, client, &name);
741    if (code) {
742       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s",
743                  krb5_get_err_text(kcontext, code));
744       ret = HTTP_UNAUTHORIZED;
745       goto end;
746    }
747    MK_USER = ap_pstrdup (r->pool, name);
748    MK_AUTH_TYPE = "Basic";
749    free(name);
750
751    if (conf->krb_save_credentials)
752       store_krb5_creds(kcontext, r, conf, ccache);
753
754    ret = OK;
755
756 end:
757    if (client)
758       krb5_free_principal(kcontext, client);
759    if (ccache)
760       krb5_cc_destroy(kcontext, ccache);
761    if (keytab)
762       krb5_kt_close(kcontext, keytab);
763    krb5_free_context(kcontext);
764
765    return ret;
766 }
767
768 /*********************************************************************
769  * GSSAPI Authentication
770  ********************************************************************/
771
772 static const char *
773 get_gss_error(MK_POOL *p, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
774 {
775    OM_uint32 maj_stat, min_stat; 
776    OM_uint32 msg_ctx = 0;
777    gss_buffer_desc status_string;
778    char *err_msg;
779    size_t len;
780
781    err_msg = ap_pstrdup(p, prefix);
782    do {
783       maj_stat = gss_display_status (&min_stat,
784                                      err_maj,
785                                      GSS_C_GSS_CODE,
786                                      GSS_C_NO_OID,
787                                      &msg_ctx,
788                                      &status_string);
789       if (GSS_ERROR(maj_stat))
790          break;
791       err_msg = ap_pstrcat(p, err_msg, ": ", (char*) status_string.value, NULL);
792       gss_release_buffer(&min_stat, &status_string);
793       
794       maj_stat = gss_display_status (&min_stat,
795                                      err_min,
796                                      GSS_C_MECH_CODE,
797                                      GSS_C_NULL_OID,
798                                      &msg_ctx,
799                                      &status_string);
800       if (!GSS_ERROR(maj_stat)) {
801          err_msg = ap_pstrcat(p, err_msg,
802                               " (", (char*) status_string.value, ")", NULL);
803          gss_release_buffer(&min_stat, &status_string);
804       }
805    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
806
807    return err_msg;
808 }
809
810 static int
811 cleanup_gss_connection(void *data)
812 {
813    OM_uint32 minor_status;
814    gss_connection_t *gss_conn = (gss_connection_t *)data;
815
816    if (data == NULL)
817       return OK;
818    if (gss_conn->context != GSS_C_NO_CONTEXT)
819       gss_delete_sec_context(&minor_status, &gss_conn->context,
820                              GSS_C_NO_BUFFER);
821    if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL)
822       gss_release_cred(&minor_status, &gss_conn->server_creds);
823
824    gss_connection = NULL;
825
826    return OK;
827 }
828
829 static int
830 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
831                 gss_cred_id_t delegated_cred)
832 {
833    OM_uint32 maj_stat, min_stat;
834    krb5_principal princ = NULL;
835    krb5_ccache ccache = NULL;
836    krb5_error_code problem;
837    krb5_context context;
838    int ret = HTTP_INTERNAL_SERVER_ERROR;
839
840    problem = krb5_init_context(&context);
841    if (problem) {
842       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
843       return HTTP_INTERNAL_SERVER_ERROR;
844    }
845
846    problem = krb5_parse_name(context, princ_name, &princ);
847    if (problem) {
848       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
849          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
850       goto end;
851    }
852
853    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
854    if (problem) {
855       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
856          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
857       goto end;
858    }
859
860    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
861    if (GSS_ERROR(maj_stat)) {
862       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
863          "Cannot store delegated credential (%s)", 
864          get_gss_error(r->pool, maj_stat, min_stat, "gss_krb5_copy_ccache"));
865       goto end;
866    }
867
868    krb5_cc_close(context, ccache);
869    ccache = NULL;
870    ret = 0;
871
872 end:
873    if (princ)
874       krb5_free_principal(context, princ);
875    if (ccache)
876       krb5_cc_destroy(context, ccache);
877    krb5_free_context(context);
878    return ret;
879 }
880
881 static int
882 get_gss_creds(request_rec *r,
883               kerb_auth_config *conf,
884               gss_cred_id_t *server_creds)
885 {
886    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
887    OM_uint32 major_status, minor_status, minor_status2;
888    gss_name_t server_name = GSS_C_NO_NAME;
889    char buf[1024];
890
891 #if 0
892    /* Don't specify service name. This makes MIT 1.3 not to use replay caches,
893     * which causes large problems with the Microsoft krb5 implementation. MS
894     * obviously uses a format of the krb5 authenticator that is considered by
895     * the MIT as replay (Two valid MS authenticators may contain the same time
896     * and utime fields and only differ in the sequential numbers).
897     */
898    snprintf(buf, sizeof(buf), "%s@%s", conf->krb_service_name,
899          ap_get_server_name(r));
900
901    input_token.value = buf;
902    input_token.length = strlen(buf) + 1;
903
904    major_status = gss_import_name(&minor_status, &input_token,
905                                   GSS_C_NT_HOSTBASED_SERVICE,
906                                   &server_name);
907    if (GSS_ERROR(major_status)) {
908       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
909                  "%s", get_gss_error(r->pool, major_status, minor_status,
910                  "gss_import_name() failed"));
911       return HTTP_INTERNAL_SERVER_ERROR;
912    }
913 #endif
914    
915    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
916                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
917                                    server_creds, NULL, NULL);
918    gss_release_name(&minor_status2, &server_name);
919    if (GSS_ERROR(major_status)) {
920       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
921                  "%s", get_gss_error(r->pool, major_status, minor_status,
922                                      "gss_acquire_cred() failed"));
923       return HTTP_INTERNAL_SERVER_ERROR;
924    }
925    
926    return 0;
927 }
928
929 static int
930 cmp_gss_type(gss_buffer_t token, gss_OID oid)
931 {
932    unsigned char *p;
933    size_t len;
934
935    if (token->length == 0)
936       return GSS_S_DEFECTIVE_TOKEN;
937
938    p = token->value;
939    if (*p++ != 0x60)
940       return GSS_S_DEFECTIVE_TOKEN;
941    len = *p++;
942    if (len & 0x80) {
943       if ((len & 0x7f) > 4)
944          return GSS_S_DEFECTIVE_TOKEN;
945       p += len & 0x7f;
946    }
947    if (*p++ != 0x06)
948       return GSS_S_DEFECTIVE_TOKEN;
949
950    if (((OM_uint32) *p++) != oid->length)
951       return GSS_S_DEFECTIVE_TOKEN;
952
953    return memcmp(p, oid->elements, oid->length);
954 }
955
956 static int
957 authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
958                       const char *auth_line, char **negotiate_ret_value)
959 {
960   OM_uint32 major_status, minor_status, minor_status2;
961   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
962   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
963   const char *auth_param = NULL;
964   int ret;
965   gss_name_t client_name = GSS_C_NO_NAME;
966   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
967   OM_uint32 (*accept_sec_token)();
968   gss_OID_desc spnego_oid;
969
970   *negotiate_ret_value = (char *)EMPTY_STRING;
971
972   spnego_oid.length = 6;
973   spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02";
974
975   if (gss_connection == NULL) {
976      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
977      if (gss_connection == NULL) {
978         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
979                    "ap_pcalloc() failed (not enough memory)");
980         ret = HTTP_INTERNAL_SERVER_ERROR;
981         goto end;
982      }
983      memset(gss_connection, 0, sizeof(*gss_connection));
984      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
985   }
986
987   if (conf->krb_5_keytab) {
988      char *ktname;
989      /* we don't use the ap_* calls here, since the string passed to putenv()
990       * will become part of the enviroment and shouldn't be free()ed by apache
991       */
992      ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb_5_keytab) + 1);
993      if (ktname == NULL) {
994         log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory");
995         ret = HTTP_INTERNAL_SERVER_ERROR;
996         goto end;
997      }
998      sprintf(ktname, "KRB5_KTNAME=%s", conf->krb_5_keytab);
999      putenv(ktname);
1000   }
1001
1002   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
1003      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
1004      if (ret)
1005         goto end;
1006   }
1007
1008   /* ap_getword() shifts parameter */
1009   auth_param = ap_getword_white(r->pool, &auth_line);
1010   if (auth_param == NULL) {
1011      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1012                 "No Authorization parameter in request from client");
1013      ret = HTTP_UNAUTHORIZED;
1014      goto end;
1015   }
1016
1017   input_token.length = ap_base64decode_len(auth_param) + 1;
1018   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
1019   if (input_token.value == NULL) {
1020      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1021                 "ap_pcalloc() failed (not enough memory)");
1022      ret = HTTP_INTERNAL_SERVER_ERROR;
1023      goto end;
1024   }
1025   input_token.length = ap_base64decode(input_token.value, auth_param);
1026
1027   accept_sec_token = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
1028                         gss_accept_sec_context_spnego : gss_accept_sec_context;
1029
1030   major_status = accept_sec_token(&minor_status,
1031                                   &gss_connection->context,
1032                                   gss_connection->server_creds,
1033                                   &input_token,
1034                                   GSS_C_NO_CHANNEL_BINDINGS,
1035                                   &client_name,
1036                                   NULL,
1037                                   &output_token,
1038                                   NULL,
1039                                   NULL,
1040                                   &delegated_cred);
1041   if (output_token.length) {
1042      char *token = NULL;
1043      size_t len;
1044      
1045      len = ap_base64encode_len(output_token.length) + 1;
1046      token = ap_pcalloc(r->connection->pool, len + 1);
1047      if (token == NULL) {
1048         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1049                    "ap_pcalloc() failed (not enough memory)");
1050         ret = HTTP_INTERNAL_SERVER_ERROR;
1051         gss_release_buffer(&minor_status2, &output_token);
1052         goto end;
1053      }
1054      ap_base64encode(token, output_token.value, output_token.length);
1055      token[len] = '\0';
1056      *negotiate_ret_value = token;
1057      gss_release_buffer(&minor_status2, &output_token);
1058   }
1059
1060   if (GSS_ERROR(major_status)) {
1061      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1062                 "%s", get_gss_error(r->pool, major_status, minor_status,
1063                                     "gss_accept_sec_context() failed"));
1064      /* Don't offer the Negotiate method again if call to GSS layer failed */
1065      *negotiate_ret_value = NULL;
1066      ret = HTTP_UNAUTHORIZED;
1067      goto end;
1068   }
1069
1070   if (major_status & GSS_S_CONTINUE_NEEDED) {
1071      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
1072       * iterations to establish authentication */
1073      ret = HTTP_UNAUTHORIZED;
1074      goto end;
1075   }
1076
1077   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
1078   gss_release_name(&minor_status, &client_name); 
1079   if (GSS_ERROR(major_status)) {
1080     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1081                "%s", get_gss_error(r->pool, major_status, minor_status,
1082                                    "gss_export_name() failed"));
1083     ret = HTTP_INTERNAL_SERVER_ERROR;
1084     goto end;
1085   }
1086
1087   MK_AUTH_TYPE = "Negotiate";
1088   MK_USER = ap_pstrdup(r->pool, output_token.value);
1089
1090   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
1091      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
1092
1093   gss_release_buffer(&minor_status, &output_token);
1094
1095   ret = OK;
1096
1097 end:
1098   if (delegated_cred)
1099      gss_release_cred(&minor_status, &delegated_cred);
1100
1101   if (output_token.length) 
1102      gss_release_buffer(&minor_status, &output_token);
1103
1104   if (client_name != GSS_C_NO_NAME)
1105      gss_release_name(&minor_status, &client_name);
1106
1107   if (! major_status & GSS_S_CONTINUE_NEEDED)
1108      cleanup_gss_connection(gss_connection);
1109
1110   return ret;
1111 }
1112 #endif /* KRB5 */
1113
1114 static int
1115 already_succeeded(request_rec *r)
1116 {
1117    if (ap_is_initial_req(r) || MK_AUTH_TYPE == NULL)
1118       return 0;
1119    if (strcmp(MK_AUTH_TYPE, "Negotiate") ||
1120        (strcmp(MK_AUTH_TYPE, "Basic") && strchr(MK_USER, '@')))
1121       return 1;
1122    return 0;
1123 }
1124
1125 static void
1126 note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf,
1127                        int use_krb4, int use_krb5, char *negotiate_ret_value)
1128 {
1129    const char *auth_name = NULL;
1130    int set_basic = 0;
1131    char *negoauth_param;
1132    const char *header_name = 
1133       (r->proxyreq == PROXYREQ_PROXY) ? "Proxy-Authenticate" : "WWW-Authenticate";
1134
1135    /* get the user realm specified in .htaccess */
1136    auth_name = ap_auth_name(r);
1137
1138    /* XXX should the WWW-Authenticate header be cleared first? */
1139 #ifdef KRB5
1140    if (use_krb5 && conf->krb_method_gssapi && negotiate_ret_value != NULL) {
1141       negoauth_param = (*negotiate_ret_value == '\0') ? "Negotiate" :
1142                   ap_pstrcat(r->pool, "Negotiate ", negotiate_ret_value, NULL);
1143       ap_table_add(r->err_headers_out, header_name, negoauth_param);
1144    }
1145    if (use_krb5 && conf->krb_method_k5pass) {
1146       ap_table_add(r->err_headers_out, header_name,
1147                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
1148       set_basic = 1;
1149    }
1150 #endif
1151
1152 #ifdef KRB4
1153    if (use_krb4 && conf->krb_method_k4pass && !set_basic)
1154       ap_table_add(r->err_headers_out, header_name,
1155                   ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
1156 #endif
1157 }
1158
1159 int kerb_authenticate_user(request_rec *r)
1160 {
1161    kerb_auth_config *conf = 
1162       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
1163                                                 &auth_kerb_module);
1164    const char *auth_type = NULL;
1165    const char *auth_line = NULL;
1166    const char *type = NULL;
1167    int use_krb5 = 0, use_krb4 = 0;
1168    int ret;
1169    static int last_return = HTTP_UNAUTHORIZED;
1170    char *negotiate_ret_value;
1171
1172    /* get the type specified in .htaccess */
1173    type = ap_auth_type(r);
1174
1175    if (type && strcasecmp(type, "Kerberos") == 0)
1176       use_krb5 = use_krb4 = 1;
1177    else if(type && strcasecmp(type, "KerberosV5") == 0)
1178       use_krb4 = 0;
1179    else if(type && strcasecmp(type, "KerberosV4") == 0)
1180       use_krb5 = 0;
1181    else
1182       return DECLINED;
1183
1184    /* get what the user sent us in the HTTP header */
1185    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
1186    if (!auth_line) {
1187        auth_line = MK_TABLE_GET(r->headers_in, "Proxy-Authorization");
1188        if (!auth_line) {
1189                note_kerb_auth_failure(r, conf, use_krb4, use_krb5, "\0");
1190                return HTTP_UNAUTHORIZED;
1191        }
1192    }
1193    auth_type = ap_getword_white(r->pool, &auth_line);
1194
1195    if (already_succeeded(r))
1196       return last_return;
1197
1198    ret = HTTP_UNAUTHORIZED;
1199
1200 #ifdef KRB5
1201    if (use_krb5 && conf->krb_method_gssapi &&
1202        strcasecmp(auth_type, "Negotiate") == 0) {
1203       ret = authenticate_user_gss(r, conf, auth_line, &negotiate_ret_value);
1204    } else if (use_krb5 && conf->krb_method_k5pass &&
1205               strcasecmp(auth_type, "Basic") == 0) {
1206        ret = authenticate_user_krb5pwd(r, conf, auth_line);
1207    }
1208 #endif
1209
1210 #ifdef KRB4
1211    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
1212        strcasecmp(auth_type, "Basic") == 0)
1213       ret = authenticate_user_krb4pwd(r, conf, auth_line);
1214 #endif
1215
1216    if (ret == HTTP_UNAUTHORIZED)
1217       note_kerb_auth_failure(r, conf, use_krb4, use_krb5, negotiate_ret_value);
1218
1219    last_return = ret;
1220    return ret;
1221 }
1222
1223
1224 /*************************************************************************** 
1225  Module Setup/Configuration
1226  ***************************************************************************/
1227 #ifdef APXS1
1228 module MODULE_VAR_EXPORT auth_kerb_module = {
1229         STANDARD_MODULE_STUFF,
1230         NULL,                           /*      module initializer            */
1231         kerb_dir_create_config,         /*      per-directory config creator  */
1232         NULL,                           /*      per-directory config merger   */
1233         NULL,                           /*      per-server    config creator  */
1234         NULL,                           /*      per-server    config merger   */
1235         kerb_auth_cmds,                 /*      command table                 */
1236         NULL,                           /* [ 9] content handlers              */
1237         NULL,                           /* [ 2] URI-to-filename translation   */
1238         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1239         NULL,                           /* [ 6] check user_id is valid *here* */
1240         NULL,                           /* [ 4] check access by host address  */
1241         NULL,                           /* [ 7] MIME type checker/setter      */
1242         NULL,                           /* [ 8] fixups                        */
1243         NULL,                           /* [10] logger                        */
1244         NULL,                           /* [ 3] header parser                 */
1245         NULL,                           /*      process initialization        */
1246         NULL,                           /*      process exit/cleanup          */
1247         NULL                            /* [ 1] post read_request handling    */
1248 };
1249 #else
1250 static int
1251 kerb_init_handler(apr_pool_t *p, apr_pool_t *plog,
1252                   apr_pool_t *ptemp, server_rec *s)
1253 {
1254    ap_add_version_component(p, "mod_auth_kerb/" MODAUTHKERB_VERSION);
1255    return OK;
1256 }
1257
1258 void kerb_register_hooks(apr_pool_t *p)
1259 {
1260    ap_hook_post_config(kerb_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
1261    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1262 }
1263
1264 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1265 {
1266    STANDARD20_MODULE_STUFF,
1267    kerb_dir_create_config,      /* create per-dir    conf structures  */
1268    NULL,                        /* merge  per-dir    conf structures  */
1269    NULL,                        /* create per-server conf structures  */
1270    NULL,                        /* merge  per-server conf structures  */
1271    kerb_auth_cmds,              /* table of configuration directives  */
1272    kerb_register_hooks          /* register hooks                     */
1273 };
1274 #endif