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