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