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