increased versions to 5.1
[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, char *name, char *instance, char *realm,
357                  char *password, char *linstance, 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, linstance, phost, addr, &authdata, 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    /* do not allow user to override realm setting of server */
448    if (ap_strchr_c(sent_name, '@')) {
449       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
450                  "specifying realm in user name is prohibited");
451       return HTTP_UNAUTHORIZED;
452    }
453
454    sent_instance = strchr(sent_name, '.');
455    if (sent_instance)
456       *sent_instance++ = '\0'; 
457
458    snprintf(tkt_file, sizeof(tkt_file), "/tmp/apache_tkt_XXXXXX");
459    fd = mkstemp(tkt_file);
460    if (fd < 0) {
461       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
462                  "Cannot create krb4 ccache: mkstemp() failed: %s",
463                  strerror(errno));
464       return HTTP_INTERNAL_SERVER_ERROR;
465    }
466
467    tkt_file_p = ap_pstrdup(r->pool, tkt_file);
468    ap_register_cleanup(r->pool, tkt_file_p,
469                        krb4_cache_cleanup, ap_null_cleanup);
470
471    krb_set_tkt_string(tkt_file);
472
473    all_principals_unkown = 1;
474    realms = conf->krb_auth_realms;
475    do {
476       memset(lrealm, 0, sizeof(lrealm));
477       realm = NULL;
478       if (realms)
479          realm = ap_getword_white(r->pool, &realms);
480
481       if (realm == NULL) {
482          ret = krb_get_lrealm(lrealm, 1);
483          if (ret)
484             break;
485          realm = lrealm;
486       }
487
488       /* XXX conf->krb_service_name */
489       ret = verify_krb4_user(r, (char *)sent_name, 
490                              (sent_instance) ? sent_instance : "",
491                              (char *)realm, (char *)sent_pw,
492                              conf->krb_service_name,
493                              conf->krb_4_srvtab, conf->krb_verify_kdc);
494       if (!conf->krb_authoritative && ret) {
495          /* if we're not authoritative, we allow authentication to pass on
496           * to another modules if (and only if) the user is not known to us */
497          if (all_principals_unkown && ret != KDC_PR_UNKNOWN)
498             all_principals_unkown = 0;
499       }
500
501       if (ret == 0)
502          break;
503    } while (realms && *realms);
504
505    if (ret) {
506       /* XXX log only in the verify_krb4_user() call */
507       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Verifying krb4 password failed");
508       ret = (!conf->krb_authoritative && all_principals_unkown == 1 && ret == KDC_PR_UNKNOWN) ?
509                  DECLINED : HTTP_UNAUTHORIZED;
510       goto end;
511    }
512
513    user = ap_pstrdup(r->pool, sent_name);
514    if (sent_instance)
515       user = ap_pstrcat(r->pool, user, ".", sent_instance, NULL);
516    user = ap_pstrcat(r->pool, user, "@", realm, NULL);
517
518    MK_USER = user;
519    MK_AUTH_TYPE = "Basic";
520    ap_table_setn(r->subprocess_env, "KRBTKFILE", tkt_file_p);
521
522    if (!conf->krb_save_credentials)
523       krb4_cache_cleanup(tkt_file);
524
525 end:
526    if (ret)
527       krb4_cache_cleanup(tkt_file);
528    close(fd);
529    tf_close();
530
531    return ret;
532 }
533 #endif /* KRB4 */
534
535 #ifdef KRB5
536 /*************************************************************************** 
537  Username/Password Validation for Krb5
538  ***************************************************************************/
539
540 /* MIT kerberos uses replay cache checks even during credential verification
541  * (i.e. in krb5_verify_init_creds()), which is obviosuly useless. In order to
542  * avoid problems with multiple apache processes accessing the same rcache file
543  * we had to use this call instead, which is only a bit modified version of
544  * krb5_verify_init_creds() */
545 static krb5_error_code
546 verify_krb5_init_creds(request_rec *r, krb5_context context, krb5_creds *creds,
547                        krb5_principal ap_req_server, krb5_keytab ap_req_keytab)
548 {
549    krb5_error_code ret;
550    krb5_data req;
551    krb5_ccache local_ccache = NULL;
552    krb5_creds *new_creds = NULL;
553    krb5_auth_context auth_context = NULL;
554    krb5_keytab keytab = NULL;
555    char *server_name;
556
557    memset(&req, 0, sizeof(req));
558
559    if (ap_req_keytab == NULL) {
560       ret = krb5_kt_default (context, &keytab);
561       if (ret)
562          return ret;
563    } else
564       keytab = ap_req_keytab;
565
566    ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache);
567    if (ret) {
568       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
569                  "krb5_cc_resolve() failed when verifying KDC");
570       return ret;
571    }
572
573    ret = krb5_cc_initialize(context, local_ccache, creds->client);
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_cc_store_cred (context, local_ccache, creds);
581    if (ret) {
582       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
583                  "krb5_cc_initialize() failed when verifying KDC");
584       goto end;
585    }
586    
587    ret = krb5_unparse_name(context, ap_req_server, &server_name);
588    if (ret) {
589       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
590                  "krb5_unparse_name() failed when verifying KDC");
591       goto end;
592    }
593    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
594               "Trying to verify authenticity of KDC using principal %s", server_name);
595    free(server_name);
596
597    if (!krb5_principal_compare (context, ap_req_server, creds->server)) {
598       krb5_creds match_cred;
599
600       memset (&match_cred, 0, sizeof(match_cred));
601
602       match_cred.client = creds->client;
603       match_cred.server = ap_req_server;
604
605       ret = krb5_get_credentials (context, 0, local_ccache, 
606                                   &match_cred, &new_creds);
607       if (ret) {
608          log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
609                     "krb5_get_credentials() failed when verifying KDC");
610          goto end;
611       }
612       creds = new_creds;
613    }
614
615    ret = krb5_mk_req_extended (context, &auth_context, 0, NULL, creds, &req);
616    if (ret) {
617       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
618                  "krb5_mk_req_extended() failed when verifying KDC");
619       goto end;
620    }
621
622    krb5_auth_con_free (context, auth_context);
623    auth_context = NULL;
624    ret = krb5_auth_con_init(context, &auth_context);
625    if (ret) {
626       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
627                  "krb5_auth_con_init() failed when verifying KDC");
628       goto end;
629    }
630    /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
631    krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
632
633    ret = krb5_rd_req (context, &auth_context, &req, ap_req_server,
634                       keytab, 0, NULL);
635    if (ret) {
636       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
637                  "krb5_rd_req() failed when verifying KDC");
638       goto end;
639    }
640
641 end:
642 #ifdef HEIMDAL
643    /* XXX Do I ever want to support Heimdal 0.4 ??? */
644    krb5_data_free(&req);
645 #else
646    krb5_free_data_contents(context, &req);
647 #endif
648    if (auth_context)
649       krb5_auth_con_free (context, auth_context);
650    if (new_creds)
651       krb5_free_creds (context, new_creds);
652    if (ap_req_keytab == NULL && keytab)
653       krb5_kt_close (context, keytab);
654    if (local_ccache)
655       krb5_cc_destroy (context, local_ccache);
656
657    return ret;
658 }
659
660 /* Inspired by krb5_verify_user from Heimdal */
661 static krb5_error_code
662 verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
663                  const char *password, krb5_principal server,
664                  krb5_keytab keytab, int krb_verify_kdc, krb5_ccache *ccache)
665 {
666    krb5_creds creds;
667    krb5_error_code ret;
668    krb5_ccache ret_ccache = NULL;
669    char *name = NULL;
670
671    /* XXX error messages shouldn't be logged here (and in the while() loop in
672     * authenticate_user_krb5pwd() as weell), in order to avoid confusing log
673     * entries when using multiple realms */
674
675    memset(&creds, 0, sizeof(creds));
676
677    ret = krb5_unparse_name(context, principal, &name);
678    if (ret == 0) {
679       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
680                  "Trying to get TGT for user %s", name);
681       free(name);
682    }
683
684    ret = krb5_get_init_creds_password(context, &creds, principal, 
685                                       (char *)password, NULL,
686                                       NULL, 0, NULL, NULL);
687    if (ret) {
688       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
689                  "krb5_get_init_creds_password() failed: %s",
690                  krb5_get_err_text(context, ret));
691       goto end;
692    }
693
694    /* XXX
695    {
696       char *realm;
697
698       krb5_get_default_realm(context, &realm);
699       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
700                  "trying to verify password using key for %s/%s@%s",
701                  service, ap_get_server_name(r), realm);
702    }
703    */
704
705    if (krb_verify_kdc &&
706        (ret = verify_krb5_init_creds(r, context, &creds, server, keytab))) {
707        log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
708                   "failed to verify krb5 credentials: %s",
709                   krb5_get_err_text(context, ret));
710        goto end;
711    }
712
713    ret = krb5_cc_resolve(context, "MEMORY:", &ret_ccache);
714    if (ret) {
715       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
716                  "generating new memory ccache failed: %s",
717                  krb5_get_err_text(context, ret));
718       goto end;
719    }
720
721    ret = krb5_cc_initialize(context, ret_ccache, principal);
722    if (ret) {
723       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
724                  "krb5_cc_initialize() failed: %s",
725                  krb5_get_err_text(context, ret));
726       goto end;
727    }
728
729    ret = krb5_cc_store_cred(context, ret_ccache, &creds);
730    if (ret) {
731       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
732                  "krb5_cc_store_cred() failed: %s",
733                  krb5_get_err_text(context, ret));
734       goto end;
735    }
736    *ccache = ret_ccache;
737    ret_ccache = NULL;
738
739 end:
740    krb5_free_cred_contents(context, &creds);
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 = apr_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    apr_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
817    apr_pool_cleanup_register(r->pool, ccname, krb5_cache_cleanup,
818                              apr_pool_cleanup_null);
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_principal  server = NULL;
888    krb5_ccache     ccache = NULL;
889    krb5_keytab     keytab = NULL;
890    int             ret;
891    char            *name = NULL;
892    int             all_principals_unkown;
893    char            *p = NULL;
894
895    code = krb5_init_context(&kcontext);
896    if (code) {
897       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
898                  "Cannot initialize Kerberos5 context (%d)", code);
899       return HTTP_INTERNAL_SERVER_ERROR;
900    }
901
902    sent_pw = ap_pbase64decode(r->pool, auth_line);
903    sent_name = ap_getword (r->pool, &sent_pw, ':');
904
905    if (sent_pw == NULL || *sent_pw == '\0') {
906       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
907                  "empty passwords are not accepted");
908       ret = HTTP_UNAUTHORIZED;
909       goto end;
910    }
911
912    if (conf->krb_5_keytab)
913       krb5_kt_resolve(kcontext, conf->krb_5_keytab, &keytab);
914
915    if (conf->krb_service_name && strchr(conf->krb_service_name, '/') != NULL)
916       ret = krb5_parse_name (kcontext, conf->krb_service_name, &server);
917    else
918       ret = krb5_sname_to_principal(kcontext, ap_get_server_name(r),
919                                     (conf->krb_service_name) ? conf->krb_service_name : SERVICE_NAME,
920                                     KRB5_NT_SRV_HST, &server);
921
922    if (ret) {
923       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
924                  "Error parsing server name (%s): %s",
925                  (conf->krb_service_name) ? conf->krb_service_name : SERVICE_NAME,
926                  krb5_get_err_text(kcontext, ret));
927       ret = HTTP_UNAUTHORIZED;
928       goto end;
929    }
930
931    code = krb5_unparse_name(kcontext, server, &name);
932    if (code) {
933       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
934                  "krb5_unparse_name() failed: %s",
935                  krb5_get_err_text(kcontext, code));
936       ret = HTTP_UNAUTHORIZED;
937       goto end;
938    }
939    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Using %s as server principal for password verification", name);
940    free(name);
941    name = NULL;
942
943    p = strchr(sent_name, '@');
944    if (p) {
945       *p++ = '\0';
946       if (conf->krb_auth_realms && !ap_find_token(r->pool, conf->krb_auth_realms, p)) {
947          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
948                     "Specified realm `%s' not allowed by configuration", p);
949          ret = HTTP_UNAUTHORIZED;
950          goto end;
951       }
952    }
953
954    realms = (p) ? p : conf->krb_auth_realms;
955    all_principals_unkown = 1;
956    do {
957       name = (char *) sent_name;
958       if (realms && (realm = ap_getword_white(r->pool, &realms)))
959          name = apr_psprintf(r->pool, "%s@%s", sent_name, realm);
960
961       if (client) {
962          krb5_free_principal(kcontext, client);
963          client = NULL;
964       }
965
966       code = krb5_parse_name(kcontext, name, &client);
967       if (code) {
968          log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
969                     "krb5_parse_name() failed: %s",
970                     krb5_get_err_text(kcontext, code));
971          continue;
972       }
973
974       code = verify_krb5_user(r, kcontext, client, sent_pw,
975                               server, keytab, conf->krb_verify_kdc, &ccache);
976       if (!conf->krb_authoritative && code) {
977          /* if we're not authoritative, we allow authentication to pass on
978           * to another modules if (and only if) the user is not known to us */
979          if (all_principals_unkown && code != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN)
980             all_principals_unkown = 0;
981       }
982
983       if (code == 0)
984          break;
985
986       /* ap_getword_white() used above shifts the parameter, so it's not
987          needed to touch the realms variable */
988    } while (realms && *realms);
989
990    memset((char *)sent_pw, 0, strlen(sent_pw));
991
992    if (code) {
993       if (!conf->krb_authoritative && all_principals_unkown == 1 && code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN)
994          ret = DECLINED;
995       else
996          ret = HTTP_UNAUTHORIZED;
997
998       goto end;
999    }
1000
1001    code = krb5_unparse_name(kcontext, client, &name);
1002    if (code) {
1003       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s",
1004                  krb5_get_err_text(kcontext, code));
1005       ret = HTTP_UNAUTHORIZED;
1006       goto end;
1007    }
1008    MK_USER = apr_pstrdup (r->pool, name);
1009    MK_AUTH_TYPE = "Basic";
1010    free(name);
1011
1012    if (conf->krb_save_credentials)
1013       store_krb5_creds(kcontext, r, conf, ccache);
1014
1015    ret = OK;
1016
1017 end:
1018    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1019               "kerb_authenticate_user_krb5pwd ret=%d user=%s authtype=%s",
1020               ret, (MK_USER)?MK_USER:"(NULL)", (MK_AUTH_TYPE)?MK_AUTH_TYPE:"(NULL)");
1021    if (client)
1022       krb5_free_principal(kcontext, client);
1023    if (server)
1024       krb5_free_principal(kcontext, server);
1025    if (ccache)
1026       krb5_cc_destroy(kcontext, ccache);
1027    if (keytab)
1028       krb5_kt_close(kcontext, keytab);
1029    krb5_free_context(kcontext);
1030
1031    return ret;
1032 }
1033
1034 /*********************************************************************
1035  * GSSAPI Authentication
1036  ********************************************************************/
1037
1038 static const char *
1039 get_gss_error(MK_POOL *p, OM_uint32 err_maj, OM_uint32 err_min, char *prefix)
1040 {
1041    OM_uint32 maj_stat, min_stat; 
1042    OM_uint32 msg_ctx = 0;
1043    gss_buffer_desc status_string;
1044    char *err_msg;
1045
1046    err_msg = apr_pstrdup(p, prefix);
1047    do {
1048       maj_stat = gss_display_status (&min_stat,
1049                                      err_maj,
1050                                      GSS_C_GSS_CODE,
1051                                      GSS_C_NO_OID,
1052                                      &msg_ctx,
1053                                      &status_string);
1054       if (GSS_ERROR(maj_stat))
1055          break;
1056       err_msg = apr_pstrcat(p, err_msg, ": ", (char*) status_string.value, NULL);
1057       gss_release_buffer(&min_stat, &status_string);
1058       
1059       maj_stat = gss_display_status (&min_stat,
1060                                      err_min,
1061                                      GSS_C_MECH_CODE,
1062                                      GSS_C_NULL_OID,
1063                                      &msg_ctx,
1064                                      &status_string);
1065       if (!GSS_ERROR(maj_stat)) {
1066          err_msg = apr_pstrcat(p, err_msg,
1067                               " (", (char*) status_string.value, ")", NULL);
1068          gss_release_buffer(&min_stat, &status_string);
1069       }
1070    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
1071
1072    return err_msg;
1073 }
1074
1075 static int
1076 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
1077                 gss_cred_id_t delegated_cred)
1078 {
1079    OM_uint32 maj_stat, min_stat;
1080    krb5_principal princ = NULL;
1081    krb5_ccache ccache = NULL;
1082    krb5_error_code problem;
1083    krb5_context context;
1084    int ret = HTTP_INTERNAL_SERVER_ERROR;
1085
1086    problem = krb5_init_context(&context);
1087    if (problem) {
1088       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
1089       return HTTP_INTERNAL_SERVER_ERROR;
1090    }
1091
1092    problem = krb5_parse_name(context, princ_name, &princ);
1093    if (problem) {
1094       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
1095          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
1096       goto end;
1097    }
1098
1099    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
1100    if (problem) {
1101       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1102          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
1103       goto end;
1104    }
1105
1106    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
1107    if (GSS_ERROR(maj_stat)) {
1108       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1109          "Cannot store delegated credential (%s)", 
1110          get_gss_error(r->pool, maj_stat, min_stat, "gss_krb5_copy_ccache"));
1111       goto end;
1112    }
1113
1114    krb5_cc_close(context, ccache);
1115    ccache = NULL;
1116    ret = 0;
1117
1118 end:
1119    if (princ)
1120       krb5_free_principal(context, princ);
1121    if (ccache)
1122       krb5_cc_destroy(context, ccache);
1123    krb5_free_context(context);
1124    return ret;
1125 }
1126
1127 static int
1128 get_gss_creds(request_rec *r,
1129               kerb_auth_config *conf,
1130               gss_cred_id_t *server_creds)
1131 {
1132    gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
1133    OM_uint32 major_status, minor_status, minor_status2;
1134    gss_name_t server_name = GSS_C_NO_NAME;
1135    char buf[1024];
1136    int have_server_princ;
1137
1138
1139    have_server_princ = conf->krb_service_name && strchr(conf->krb_service_name, '/') != NULL;
1140    if (have_server_princ)
1141       strncpy(buf, conf->krb_service_name, sizeof(buf));
1142    else
1143       snprintf(buf, sizeof(buf), "%s@%s",
1144                (conf->krb_service_name) ? conf->krb_service_name : SERVICE_NAME,
1145                ap_get_server_name(r));
1146
1147    token.value = buf;
1148    token.length = strlen(buf) + 1;
1149
1150    major_status = gss_import_name(&minor_status, &token,
1151                                   (have_server_princ) ? GSS_KRB5_NT_PRINCIPAL_NAME : GSS_C_NT_HOSTBASED_SERVICE,
1152                                   &server_name);
1153    memset(&token, 0, sizeof(token));
1154    if (GSS_ERROR(major_status)) {
1155       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1156                  "%s", get_gss_error(r->pool, major_status, minor_status,
1157                  "gss_import_name() failed"));
1158       return HTTP_INTERNAL_SERVER_ERROR;
1159    }
1160
1161    major_status = gss_display_name(&minor_status, server_name, &token, NULL);
1162    if (GSS_ERROR(major_status)) {
1163       /* Perhaps we could just ignore this error but it's safer to give up now,
1164          I think */
1165       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1166                  "%s", get_gss_error(r->pool, major_status, minor_status,
1167                                      "gss_display_name() failed"));
1168       return HTTP_INTERNAL_SERVER_ERROR;
1169    }
1170
1171    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s",
1172               token.value);
1173    gss_release_buffer(&minor_status, &token);
1174    
1175    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
1176                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
1177                                    server_creds, NULL, NULL);
1178    gss_release_name(&minor_status2, &server_name);
1179    if (GSS_ERROR(major_status)) {
1180       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1181                  "%s", get_gss_error(r->pool, major_status, minor_status,
1182                                      "gss_acquire_cred() failed"));
1183       return HTTP_INTERNAL_SERVER_ERROR;
1184    }
1185
1186 #ifndef HEIMDAL
1187    /*
1188     * With MIT Kerberos 5 1.3.x the gss_cred_id_t is the same as
1189     * krb5_gss_cred_id_t and krb5_gss_cred_id_rec contains a pointer to
1190     * the replay cache.
1191     * This allows us to override the replay cache function vector with
1192     * our own one.
1193     * Note that this is a dirty hack to get things working and there may
1194     * well be unknown side-effects.
1195     */
1196    {
1197       krb5_gss_cred_id_t gss_creds = (krb5_gss_cred_id_t) *server_creds;
1198
1199       /* First we try to verify we are linked with 1.3.x to prevent from
1200          crashing when linked with 1.4.x */
1201       if (gss_creds && (gss_creds->usage == GSS_C_ACCEPT)) {
1202          if (gss_creds->rcache && gss_creds->rcache->ops &&
1203              gss_creds->rcache->ops->type &&  
1204              memcmp(gss_creds->rcache->ops->type, "dfl", 3) == 0)
1205           /* Override the rcache operations */
1206          gss_creds->rcache->ops = &mod_auth_kerb_rc_ops;
1207       }
1208    }
1209 #endif
1210    
1211    return 0;
1212 }
1213
1214 static int
1215 cmp_gss_type(gss_buffer_t token, gss_OID oid)
1216 {
1217    unsigned char *p;
1218    size_t len;
1219
1220    if (token->length == 0)
1221       return GSS_S_DEFECTIVE_TOKEN;
1222
1223    p = token->value;
1224    if (*p++ != 0x60)
1225       return GSS_S_DEFECTIVE_TOKEN;
1226    len = *p++;
1227    if (len & 0x80) {
1228       if ((len & 0x7f) > 4)
1229          return GSS_S_DEFECTIVE_TOKEN;
1230       p += len & 0x7f;
1231    }
1232    if (*p++ != 0x06)
1233       return GSS_S_DEFECTIVE_TOKEN;
1234
1235    if (((OM_uint32) *p++) != oid->length)
1236       return GSS_S_DEFECTIVE_TOKEN;
1237
1238    return memcmp(p, oid->elements, oid->length);
1239 }
1240
1241 static int
1242 authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
1243                       const char *auth_line, char **negotiate_ret_value)
1244 {
1245   OM_uint32 major_status, minor_status, minor_status2;
1246   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
1247   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
1248   const char *auth_param = NULL;
1249   int ret;
1250   gss_name_t client_name = GSS_C_NO_NAME;
1251   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
1252   OM_uint32 (KRB5_LIB_FUNCTION *accept_sec_token)
1253                          (OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
1254                          const gss_buffer_t, const gss_channel_bindings_t,
1255                          gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *,
1256                          OM_uint32 *, gss_cred_id_t *);
1257   gss_OID_desc spnego_oid;
1258   gss_ctx_id_t context = GSS_C_NO_CONTEXT;
1259   gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
1260
1261   *negotiate_ret_value = "\0";
1262
1263   spnego_oid.length = 6;
1264   spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02";
1265
1266   if (conf->krb_5_keytab) {
1267      char *ktname;
1268      /* we don't use the ap_* calls here, since the string passed to putenv()
1269       * will become part of the enviroment and shouldn't be free()ed by apache
1270       */
1271      ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb_5_keytab) + 1);
1272      if (ktname == NULL) {
1273         log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory");
1274         ret = HTTP_INTERNAL_SERVER_ERROR;
1275         goto end;
1276      }
1277      sprintf(ktname, "KRB5_KTNAME=%s", conf->krb_5_keytab);
1278      putenv(ktname);
1279 #ifdef HEIMDAL
1280      /* Seems to be also supported by latest MIT */
1281      gsskrb5_register_acceptor_identity(conf->krb_5_keytab);
1282 #endif
1283   }
1284
1285   ret = get_gss_creds(r, conf, &server_creds);
1286   if (ret)
1287      goto end;
1288
1289   /* ap_getword() shifts parameter */
1290   auth_param = ap_getword_white(r->pool, &auth_line);
1291   if (auth_param == NULL) {
1292      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1293                 "No Authorization parameter in request from client");
1294      ret = HTTP_UNAUTHORIZED;
1295      goto end;
1296   }
1297
1298   input_token.length = apr_base64_decode_len(auth_param) + 1;
1299   input_token.value = apr_pcalloc(r->connection->pool, input_token.length);
1300   if (input_token.value == NULL) {
1301      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1302                 "ap_pcalloc() failed (not enough memory)");
1303      ret = HTTP_INTERNAL_SERVER_ERROR;
1304      goto end;
1305   }
1306   input_token.length = apr_base64_decode(input_token.value, auth_param);
1307
1308 #ifdef GSSAPI_SUPPORTS_SPNEGO
1309   accept_sec_token = gss_accept_sec_context;
1310 #else
1311   accept_sec_token = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
1312                         gss_accept_sec_context_spnego : gss_accept_sec_context;
1313 #endif
1314
1315   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Verifying client data using %s",
1316              (accept_sec_token == gss_accept_sec_context)
1317                ? "KRB5 GSS-API"
1318                : "SPNEGO GSS-API");
1319
1320   major_status = accept_sec_token(&minor_status,
1321                                   &context,
1322                                   server_creds,
1323                                   &input_token,
1324                                   GSS_C_NO_CHANNEL_BINDINGS,
1325                                   &client_name,
1326                                   NULL,
1327                                   &output_token,
1328                                   NULL,
1329                                   NULL,
1330                                   &delegated_cred);
1331   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1332              "Verification returned code %d", major_status);
1333   if (output_token.length) {
1334      char *token = NULL;
1335      size_t len;
1336      
1337      len = apr_base64_encode_len(output_token.length) + 1;
1338      token = apr_pcalloc(r->connection->pool, len + 1);
1339      if (token == NULL) {
1340         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1341                    "ap_pcalloc() failed (not enough memory)");
1342         ret = HTTP_INTERNAL_SERVER_ERROR;
1343         gss_release_buffer(&minor_status2, &output_token);
1344         goto end;
1345      }
1346      apr_base64_encode(token, output_token.value, output_token.length);
1347      token[len] = '\0';
1348      *negotiate_ret_value = token;
1349      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1350                 "GSS-API token of length %d bytes will be sent back",
1351                 output_token.length);
1352      gss_release_buffer(&minor_status2, &output_token);
1353      set_kerb_auth_headers(r, conf, 0, 0, *negotiate_ret_value);
1354   }
1355
1356   if (GSS_ERROR(major_status)) {
1357      if (input_token.length > 7 && memcmp(input_token.value, "NTLMSSP", 7) == 0)
1358         log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1359                   "Warning: received token seems to be NTLM, which isn't supported by the Kerberos module. Check your IE configuration.");
1360
1361      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1362                 "%s", get_gss_error(r->pool, major_status, minor_status,
1363                                     "gss_accept_sec_context() failed"));
1364      /* Don't offer the Negotiate method again if call to GSS layer failed */
1365      *negotiate_ret_value = NULL;
1366      ret = HTTP_UNAUTHORIZED;
1367      goto end;
1368   }
1369
1370 #if 0
1371   /* This is a _Kerberos_ module so multiple authentication rounds aren't
1372    * supported. If we wanted a generic GSS authentication we would have to do
1373    * some magic with exporting context etc. */
1374   if (major_status & GSS_S_CONTINUE_NEEDED) {
1375      ret = HTTP_UNAUTHORIZED;
1376      goto end;
1377   }
1378 #endif
1379
1380   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
1381   gss_release_name(&minor_status, &client_name); 
1382   if (GSS_ERROR(major_status)) {
1383     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1384                "%s", get_gss_error(r->pool, major_status, minor_status,
1385                                    "gss_display_name() failed"));
1386     ret = HTTP_INTERNAL_SERVER_ERROR;
1387     goto end;
1388   }
1389
1390   MK_AUTH_TYPE = MECH_NEGOTIATE;
1391   MK_USER = apr_pstrdup(r->pool, output_token.value);
1392
1393   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
1394      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
1395
1396   gss_release_buffer(&minor_status, &output_token);
1397
1398   ret = OK;
1399
1400 end:
1401   if (delegated_cred)
1402      gss_release_cred(&minor_status, &delegated_cred);
1403
1404   if (output_token.length) 
1405      gss_release_buffer(&minor_status, &output_token);
1406
1407   if (client_name != GSS_C_NO_NAME)
1408      gss_release_name(&minor_status, &client_name);
1409
1410   if (server_creds != GSS_C_NO_CREDENTIAL)
1411      gss_release_cred(&minor_status, &server_creds);
1412
1413   if (context != GSS_C_NO_CONTEXT)
1414      gss_delete_sec_context(&minor_status, &context, GSS_C_NO_BUFFER);
1415
1416   return ret;
1417 }
1418 #endif /* KRB5 */
1419
1420 static int
1421 already_succeeded(request_rec *r)
1422 {
1423    if (ap_is_initial_req(r) || MK_AUTH_TYPE == NULL)
1424       return 0;
1425    if (strcmp(MK_AUTH_TYPE, MECH_NEGOTIATE) ||
1426        (strcmp(MK_AUTH_TYPE, "Basic") && strchr(MK_USER, '@')))
1427       return 1;
1428    return 0;
1429 }
1430
1431 static void
1432 set_kerb_auth_headers(request_rec *r, const kerb_auth_config *conf,
1433                       int use_krb4, int use_krb5pwd, char *negotiate_ret_value)
1434 {
1435    const char *auth_name = NULL;
1436    int set_basic = 0;
1437    char *negoauth_param;
1438    const char *header_name = 
1439       (r->proxyreq == PROXYREQ_PROXY) ? "Proxy-Authenticate" : "WWW-Authenticate";
1440
1441    /* get the user realm specified in .htaccess */
1442    auth_name = ap_auth_name(r);
1443
1444    /* XXX should the WWW-Authenticate header be cleared first?
1445     * apache in the proxy mode should retain client's authN headers? */
1446 #ifdef KRB5
1447    if (negotiate_ret_value != NULL && conf->krb_method_gssapi) {
1448       negoauth_param = (*negotiate_ret_value == '\0') ? MECH_NEGOTIATE :
1449                   apr_pstrcat(r->pool, MECH_NEGOTIATE " ", negotiate_ret_value, NULL);
1450       apr_table_add(r->err_headers_out, header_name, negoauth_param);
1451    }
1452    if ((use_krb5pwd && conf->krb_method_k5pass) || conf->krb_delegate_basic) {
1453       apr_table_add(r->err_headers_out, header_name,
1454                    apr_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
1455       set_basic = 1;
1456    }
1457 #endif
1458
1459 #ifdef KRB4
1460    if (!set_basic && 
1461        ((use_krb4 && conf->krb_method_k4pass) || conf->krb_delegate_basic))
1462       ap_table_add(r->err_headers_out, header_name,
1463                   ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
1464 #endif
1465 }
1466
1467 static int
1468 kerb_authenticate_user(request_rec *r)
1469 {
1470    kerb_auth_config *conf = 
1471       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
1472                                                 &auth_kerb_module);
1473    const char *auth_type = NULL;
1474    const char *auth_line = NULL;
1475    const char *type = NULL;
1476    int use_krb5 = 0, use_krb4 = 0;
1477    int ret;
1478    static int last_return = HTTP_UNAUTHORIZED;
1479    char *negotiate_ret_value = NULL;
1480
1481    /* get the type specified in .htaccess */
1482    type = ap_auth_type(r);
1483
1484    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
1485               "kerb_authenticate_user entered with user %s and auth_type %s",
1486               (MK_USER)?MK_USER:"(NULL)",type?type:"(NULL)");
1487
1488    if (type && strcasecmp(type, "Kerberos") == 0)
1489       use_krb5 = use_krb4 = 1;
1490    else if(type && strcasecmp(type, "KerberosV5") == 0)
1491       use_krb5 = 1;
1492    else if(type && strcasecmp(type, "KerberosV4") == 0)
1493       use_krb4 = 1;
1494    else
1495       return DECLINED;
1496
1497 #if 0
1498    if (conf->krb_ssl_preauthentication) {
1499       const char *ssl_client_verify = ssl_var_lookup(r->pool, r->server,
1500                 r->connection, r, "SSL_CLIENT_VERIFY");
1501
1502       if (ssl_client_verify && strcmp(ssl_client_verify, "SUCCESS") == 0)
1503          return OK;
1504    }
1505 #endif
1506
1507    /* get what the user sent us in the HTTP header */
1508    auth_line = MK_TABLE_GET(r->headers_in, (r->proxyreq == PROXYREQ_PROXY)
1509                                             ? "Proxy-Authorization"
1510                                             : "Authorization");
1511    if (!auth_line) {
1512       set_kerb_auth_headers(r, conf, use_krb4, use_krb5, 
1513                             (use_krb5) ? "\0" : NULL);
1514       return HTTP_UNAUTHORIZED;
1515    }
1516    auth_type = ap_getword_white(r->pool, &auth_line);
1517
1518    /* If we are delegating Basic to other modules, DECLINE the request */
1519    if (conf->krb_delegate_basic &&
1520 #ifdef KRB5
1521        !conf->krb_method_k5pass &&
1522 #endif
1523 #ifdef KRB4
1524        !conf->krb_method_k4pass &&
1525 #endif
1526        (strcasecmp(auth_type, "Basic") == 0))
1527        return DECLINED;
1528
1529    if (already_succeeded(r))
1530       return last_return;
1531
1532    ret = HTTP_UNAUTHORIZED;
1533
1534 #ifdef KRB5
1535    if (use_krb5 && conf->krb_method_gssapi &&
1536        strcasecmp(auth_type, MECH_NEGOTIATE) == 0) {
1537       ret = authenticate_user_gss(r, conf, auth_line, &negotiate_ret_value);
1538    } else if (use_krb5 && conf->krb_method_k5pass &&
1539               strcasecmp(auth_type, "Basic") == 0) {
1540        ret = authenticate_user_krb5pwd(r, conf, auth_line);
1541    }
1542 #endif
1543
1544 #ifdef KRB4
1545    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
1546        strcasecmp(auth_type, "Basic") == 0)
1547       ret = authenticate_user_krb4pwd(r, conf, auth_line);
1548 #endif
1549
1550    if (ret == HTTP_UNAUTHORIZED)
1551       set_kerb_auth_headers(r, conf, use_krb4, use_krb5, negotiate_ret_value);
1552
1553    /* XXX log_debug: if ret==OK, log(user XY authenticated) */
1554
1555    last_return = ret;
1556    return ret;
1557 }
1558
1559 int
1560 have_rcache_type(const char *type)
1561 {
1562    krb5_error_code ret;
1563    krb5_context context;
1564    krb5_rcache id;
1565    int found;
1566
1567    memset(&id, 0, sizeof(id));
1568
1569    ret = krb5_init_context(&context);
1570    if (ret)
1571       return 0;
1572
1573    ret = krb5_rc_resolve_type(context, &id, type);
1574    found = (ret == 0);
1575
1576    krb5_free_context(context);
1577
1578    return found;
1579 }
1580
1581 /*************************************************************************** 
1582  Module Setup/Configuration
1583  ***************************************************************************/
1584 #ifndef STANDARD20_MODULE_STUFF
1585 static void
1586 kerb_module_init(server_rec *dummy, pool *p)
1587 {
1588 #ifndef HEIMDAL
1589    /* Suppress the MIT replay cache.  Requires MIT Kerberos 1.4.0 or later.
1590       1.3.x are covered by the hack overiding the replay calls */
1591    if (getenv("KRB5RCACHETYPE") == NULL && have_rcache_type("none"))
1592       putenv(strdup("KRB5RCACHETYPE=none"));
1593 #endif
1594 }
1595
1596 module MODULE_VAR_EXPORT auth_kerb_module = {
1597         STANDARD_MODULE_STUFF,
1598         kerb_module_init,               /*      module initializer            */
1599         kerb_dir_create_config,         /*      per-directory config creator  */
1600         NULL,                           /*      per-directory config merger   */
1601         NULL,                           /*      per-server    config creator  */
1602         NULL,                           /*      per-server    config merger   */
1603         kerb_auth_cmds,                 /*      command table                 */
1604         NULL,                           /* [ 9] content handlers              */
1605         NULL,                           /* [ 2] URI-to-filename translation   */
1606         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1607         NULL,                           /* [ 6] check user_id is valid *here* */
1608         NULL,                           /* [ 4] check access by host address  */
1609         NULL,                           /* [ 7] MIME type checker/setter      */
1610         NULL,                           /* [ 8] fixups                        */
1611         NULL,                           /* [10] logger                        */
1612         NULL,                           /* [ 3] header parser                 */
1613         NULL,                           /*      process initialization        */
1614         NULL,                           /*      process exit/cleanup          */
1615         NULL                            /* [ 1] post read_request handling    */
1616 #ifdef EAPI
1617        ,NULL,                           /* EAPI: add_module                   */
1618         NULL,                           /* EAPI: remove_module                */
1619         NULL,                           /* EAPI: rewrite_command              */
1620         NULL                            /* EAPI: new_connection               */
1621 #endif
1622 };
1623 #else
1624 static int
1625 kerb_init_handler(apr_pool_t *p, apr_pool_t *plog,
1626                   apr_pool_t *ptemp, server_rec *s)
1627 {
1628    ap_add_version_component(p, "mod_auth_kerb/" MODAUTHKERB_VERSION);
1629 #ifndef HEIMDAL
1630    /* Suppress the MIT replay cache.  Requires MIT Kerberos 1.4.0 or later.
1631       1.3.x are covered by the hack overiding the replay calls */
1632    if (getenv("KRB5RCACHETYPE") == NULL && have_rcache_type("none"))
1633       putenv(strdup("KRB5RCACHETYPE=none"));
1634 #endif
1635    
1636    return OK;
1637 }
1638
1639 static void
1640 kerb_register_hooks(apr_pool_t *p)
1641 {
1642    ap_hook_post_config(kerb_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
1643    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1644 }
1645
1646 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1647 {
1648    STANDARD20_MODULE_STUFF,
1649    kerb_dir_create_config,      /* create per-dir    conf structures  */
1650    NULL,                        /* merge  per-dir    conf structures  */
1651    NULL,                        /* create per-server conf structures  */
1652    NULL,                        /* merge  per-server conf structures  */
1653    kerb_auth_cmds,              /* table of configuration directives  */
1654    kerb_register_hooks          /* register hooks                     */
1655 };
1656 #endif