Added correct parameters to ap_strcat()
[mod_auth_kerb.cvs/.git] / src / mod_auth_kerb.c
1 /*
2  * Daniel Kouril <kouril@users.sourceforge.org>
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  */
11
12 /* ====================================================================
13  * The Apache Software License, Version 1.1
14  *
15  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
16  * reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  *
22  * 1. Redistributions of source code must retain the above copyright
23  *    notice, this list of conditions and the following disclaimer.
24  *
25  * 2. Redistributions in binary form must reproduce the above copyright
26  *    notice, this list of conditions and the following disclaimer in
27  *    the documentation and/or other materials provided with the
28  *    distribution.
29  *
30  * 3. The end-user documentation included with the redistribution,
31  *    if any, must include the following acknowledgment:
32  *       "This product includes software developed by the
33  *        Apache Software Foundation (http://www.apache.org/)."
34  *    Alternately, this acknowledgment may appear in the software itself,
35  *    if and wherever such third-party acknowledgments normally appear.
36  *
37  * 4. The names "Apache" and "Apache Software Foundation" must
38  *    not be used to endorse or promote products derived from this
39  *    software without prior written permission. For written
40  *    permission, please contact apache@apache.org.
41  *
42  * 5. Products derived from this software may not be called "Apache",
43  *    nor may "Apache" appear in their name, without prior written
44  *    permission of the Apache Software Foundation.
45  *
46  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
47  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
48  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
49  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
50  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
51  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
52  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
53  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
54  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
55  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
56  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57  * SUCH DAMAGE.
58  * ====================================================================
59  *
60  * This software consists of voluntary contributions made by many
61  * individuals on behalf of the Apache Software Foundation.  For more
62  * information on the Apache Software Foundation, please see
63  * <http://www.apache.org/>.
64  *
65  * Portions of this software are based upon public domain software
66  * originally written at the National Center for Supercomputing Applications,
67  * University of Illinois, Urbana-Champaign.
68  */
69
70 #ident "$Id$"
71
72 #ifndef APXS1
73 #include "ap_compat.h"
74 #include "apr_strings.h"
75 #endif
76 #include "httpd.h"
77 #include "http_config.h"
78 #include "http_core.h"
79 #include "http_log.h"
80 #include "http_protocol.h"
81 #include "http_request.h"
82
83 #ifdef KRB5
84 #include <krb5.h>
85 #ifdef HEIMDAL
86 #  include <gssapi.h>
87 #else
88 #  include <gssapi/gssapi.h>
89 #  include <gssapi/gssapi_generic.h>
90 #  define GSS_C_NT_USER_NAME gss_nt_user_name
91 #  define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
92 #  define krb5_get_err_text(context,code) error_message(code)
93 #endif
94 #endif /* KRB5 */
95
96 #ifdef KRB4
97 /*Prevent warning about closesocket redefinition (Apache's ap_config.h and 
98  * MIT Kerberos' port-sockets.h both define it as close) */
99 #ifdef closesocket
100 #  undef closesocket
101 #endif
102 #include <krb.h>
103 #endif /* KRB4 */
104
105 #ifdef APXS1
106 module auth_kerb_module;
107 #else
108 module AP_MODULE_DECLARE_DATA auth_kerb_module;
109 #endif
110
111 /*************************************************************************** 
112  Macros To Ease Compatibility
113  ***************************************************************************/
114 #ifdef APXS1
115 #define MK_POOL pool
116 #define MK_TABLE_GET ap_table_get
117 #define MK_TABLE_SET ap_table_set
118 #define MK_TABLE_TYPE table
119 #define MK_PSTRDUP ap_pstrdup
120 #define MK_USER r->connection->user
121 #define MK_AUTH_TYPE r->connection->ap_auth_type
122 #define MK_ARRAY_HEADER array_header
123 #else
124 #define MK_POOL apr_pool_t
125 #define MK_TABLE_GET apr_table_get
126 #define MK_TABLE_SET apr_table_set
127 #define MK_TABLE_TYPE apr_table_t
128 #define MK_PSTRDUP apr_pstrdup
129 #define MK_USER r->user
130 #define MK_AUTH_TYPE r->ap_auth_type
131 #define MK_ARRAY_HEADER apr_array_header_t
132 #endif /* APXS1 */
133
134
135 /*************************************************************************** 
136  Auth Configuration Structure
137  ***************************************************************************/
138 typedef struct {
139         char *krb_auth_realms;
140         int krb_save_credentials;
141 #ifdef KRB5
142         char *krb_5_keytab;
143         int krb_method_gssapi;
144         int krb_method_k5pass;
145 #endif
146 #ifdef KRB4
147         char *krb_4_srvtab;
148         int krb_method_k4pass;
149 #endif
150 } kerb_auth_config;
151
152 static const char*
153 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg);
154
155 #ifdef APXS1
156 #define command(name, func, var, type, usage)           \
157   { name, func,                                         \
158     (void*)XtOffsetOf(kerb_auth_config, var),           \
159     OR_AUTHCFG, type, usage }
160 #else
161 #define command(name, func, var, type, usage)           \
162   AP_INIT_ ## type (name, func,                         \
163         (void*)APR_XtOffsetOf(kerb_auth_config, var),   \
164         OR_AUTHCFG, usage)
165 #endif
166
167 static const command_rec kerb_auth_cmds[] = {
168    command("KrbAuthRealms", krb5_save_realms, krb_auth_realms,
169      RAW_ARGS, "Realms to attempt authentication against (can be multiple)."),
170
171    command("KrbAuthRealm", krb5_save_realms, krb_auth_realms,
172      RAW_ARGS, "Alias for KrbAuthRealms."),
173
174    command("KrbSaveCredentials", ap_set_flag_slot, krb_save_credentials,
175      FLAG, "Save and store credentials/tickets retrieved during auth."),
176
177 #ifdef KRB5
178    command("Krb5Keytab", ap_set_file_slot, krb_5_keytab,
179      TAKE1, "Location of Kerberos V5 keytab file."),
180
181    command("KrbMethodNegotiate", ap_set_flag_slot, krb_method_gssapi,
182      FLAG, "Enable Negotiate authentication method."),
183
184    command("KrbMethodK5Pass", ap_set_flag_slot, krb_method_k5pass,
185      FLAG, "Enable Kerberos V5 password authentication."),
186 #endif 
187
188 #ifdef KRB4
189    command("Krb4Srvtab", ap_set_file_slot, krb_4_srvtab,
190      TAKE1, "Location of Kerberos V4 srvtab file."),
191
192    command("KrbMethodK4Pass", ap_set_flag_slot, krb_method_k4pass,
193      FLAG, "Enable Kerberos V4 password authentication."),
194 #endif
195
196    { NULL }
197 };
198
199 #ifdef KRB5
200 typedef struct {
201    gss_ctx_id_t context;
202    gss_cred_id_t server_creds;
203 } gss_connection_t;
204
205 static gss_connection_t *gss_connection = NULL;
206 #endif
207
208
209 /*************************************************************************** 
210  Auth Configuration Initialization
211  ***************************************************************************/
212 static void *kerb_dir_create_config(MK_POOL *p, char *d)
213 {
214         kerb_auth_config *rec;
215
216         rec = (kerb_auth_config *) ap_pcalloc(p, sizeof(kerb_auth_config));
217 #ifdef KRB5
218         ((kerb_auth_config *)rec)->krb_method_k5pass = 1;
219         ((kerb_auth_config *)rec)->krb_method_gssapi = 1;
220 #endif
221 #ifdef KRB4
222         ((kerb_auth_config *)rec)->krb_method_k4pass = 1;
223 #endif
224         return rec;
225 }
226
227 static const char*
228 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg)
229 {
230    sec->krb_auth_realms= ap_pstrdup(cmd->pool, arg);
231    return NULL;
232 }
233
234 void log_rerror(const char *file, int line, int level, int status,
235                 const request_rec *r, const char *fmt, ...)
236 {
237    char errstr[1024];
238    va_list ap;
239
240    va_start(ap, fmt);
241    vsnprintf(errstr, sizeof(errstr), fmt, ap);
242    va_end(ap);
243
244 #ifdef APXS1
245    ap_log_rerror(file, line, level, r, "%s", errstr);
246 #else
247    ap_log_rerror(file, line, level, status, r, "%s", errstr);
248 #endif
249 }
250
251 #ifdef KRB4
252 /*************************************************************************** 
253  Username/Password Validation for Krb4
254  ***************************************************************************/
255 static int
256 verify_krb4_user(request_rec *r, char *name, char *instance, char *realm,
257                  char *password, char *linstance, char *srvtab)
258 {
259    int ret;
260    char *phost;
261    unsigned long addr;
262    struct hostent *hp;
263    const char *hostname;
264    KTEXT_ST ticket;
265    AUTH_DAT authdata;
266    char lrealm[REALM_SZ];
267
268    ret = krb_get_pw_in_tkt(name, instance, realm, "krbtgt", realm, 
269                            DEFAULT_TKT_LIFE, password);
270    if (ret)
271       /* log(krb_err_txt[ret]) */
272       return ret;
273
274    hostname = ap_get_server_name(r);
275
276    hp = gethostbyname(hostname);
277    if (hp == NULL) {
278       dest_tkt();
279       return h_errno;
280    }
281    memcpy(&addr, hp->h_addr, sizeof(addr));
282
283    phost = krb_get_phost((char *)hostname);
284
285    krb_get_lrealm(lrealm, 1);
286
287    ret = krb_mk_req(&ticket, linstance, phost, lrealm, 0);
288    if (ret) {
289       dest_tkt();
290       return ret;
291    }
292
293    ret = krb_rd_req(&ticket, linstance, phost, addr, &authdata, srvtab);
294    if (ret)
295       dest_tkt();
296
297    return ret;
298 }
299
300 static int
301 krb4_cache_cleanup(void *data)
302 {
303    char *tkt_file = (char *) data;
304    
305    krb_set_tkt_string(tkt_file);
306    dest_tkt();
307    return OK;
308 }
309
310 static int 
311 authenticate_user_krb4pwd(request_rec *r,
312                           kerb_auth_config *conf,
313                           const char *auth_line)
314 {
315    int ret;
316    const char *sent_pw;
317    const char *sent_name;
318    char *sent_instance;
319    char tkt_file[32];
320    char *tkt_file_p = NULL;
321    int fd;
322    const char *realms;
323    const char *realm;
324    char *user;
325    char lrealm[REALM_SZ];
326
327    sent_pw = ap_pbase64decode(r->pool, auth_line);
328    sent_name = ap_getword (r->pool, &sent_pw, ':');
329
330    /* do not allow user to override realm setting of server */
331    if (strchr(sent_name, '@')) {
332       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
333                  "specifying realm in user name is prohibited");
334       return HTTP_UNAUTHORIZED;
335    }
336
337    sent_instance = strchr(sent_name, '.');
338    if (sent_instance)
339       *sent_instance++ = '\0'; 
340
341    snprintf(tkt_file, sizeof(tkt_file), "/tmp/apache_tkt_XXXXXX");
342    fd = mkstemp(tkt_file);
343    if (fd < 0) {
344       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
345                  "Cannot create krb4 ccache: mkstemp() failed: %s",
346                  strerror(errno));
347       return HTTP_INTERNAL_SERVER_ERROR;
348    }
349
350    tkt_file_p = ap_pstrdup(r->pool, tkt_file);
351    ap_register_cleanup(r->pool, tkt_file_p,
352                        krb4_cache_cleanup, ap_null_cleanup);
353
354    krb_set_tkt_string(tkt_file);
355
356    realms = conf->krb_auth_realms;
357    do {
358       memset(lrealm, 0, sizeof(lrealm));
359       realm = NULL;
360       if (realms)
361          realm = ap_getword_white(r->pool, &realms);
362
363       if (realm == NULL) {
364          ret = krb_get_lrealm(lrealm, 1);
365          realm = lrealm;
366       }
367       if (realm == NULL || *realm == '\0')
368          break;
369
370       ret = verify_krb4_user(r, (char *)sent_name, 
371                              (sent_instance) ? sent_instance : "",
372                              (char *)realm, (char *)sent_pw, "khttp",
373                              conf->krb_4_srvtab);
374       if (ret == 0)
375          break;
376    } while (realms && *realms);
377
378    if (ret) {
379       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
380                  "Verifying krb4 password failed (%d)", ret);
381       ret = HTTP_UNAUTHORIZED;
382       goto end;
383    }
384
385    user = ap_pstrdup(r->pool, sent_name);
386    if (sent_instance)
387       user = ap_pstrcat(r->pool, user, ".", sent_instance, NULL);
388    user = ap_pstrcat(r->pool, user, "@", realm, NULL);
389
390    MK_USER = user;
391    MK_AUTH_TYPE = "Basic";
392    ap_table_setn(r->subprocess_env, "KRBTKFILE", tkt_file_p);
393
394    if (!conf->krb_save_credentials)
395       krb4_cache_cleanup(tkt_file);
396
397 end:
398    if (ret)
399       krb4_cache_cleanup(tkt_file);
400    close(fd);
401    tf_close();
402
403    return ret;
404 }
405 #endif /* KRB4 */
406
407 #ifdef KRB5
408 /*************************************************************************** 
409  Username/Password Validation for Krb5
410  ***************************************************************************/
411 /* Inspired by krb5_verify_user from Heimdal */
412 static krb5_error_code
413 verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
414                  krb5_ccache ccache, const char *password, const char *service,
415                  krb5_keytab keytab)
416 {
417    krb5_creds creds;
418    krb5_principal server = NULL;
419    krb5_error_code ret;
420    krb5_verify_init_creds_opt opt;
421
422    memset(&creds, 0, sizeof(creds));
423
424    ret = krb5_get_init_creds_password(context, &creds, principal, 
425                                       (char *)password, krb5_prompter_posix,
426                                       NULL, 0, NULL, NULL);
427    if (ret)
428       return ret;
429
430    ret = krb5_sname_to_principal(context, ap_get_server_name(r), service, 
431                                  KRB5_NT_SRV_HST, &server);
432    if (ret)
433       goto end;
434
435    krb5_verify_init_creds_opt_init(&opt);
436    krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, 1);
437
438    ret = krb5_verify_init_creds(context, &creds, server, keytab, NULL, &opt);
439    if (ret)
440       goto end;
441
442    if (ccache) {
443       ret = krb5_cc_initialize(context, ccache, principal);
444       if (ret == 0)
445          ret = krb5_cc_store_cred(context, ccache, &creds);
446    }
447
448 end:
449    krb5_free_cred_contents(context, &creds);
450    if (server)
451       krb5_free_principal(context, server);
452    return ret;
453 }
454
455 static int
456 krb5_cache_cleanup(void *data)
457 {
458    krb5_context context;
459    krb5_ccache  cache;
460    krb5_error_code problem;
461    char *cache_name = (char *) data;
462
463    problem = krb5_init_context(&context);
464    if (problem) {
465       /* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "krb5_init_context() failed"); */
466       return HTTP_INTERNAL_SERVER_ERROR;
467    }
468
469    problem = krb5_cc_resolve(context, cache_name, &cache);
470    if (problem) {
471       /* log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
472                 "krb5_cc_resolve() failed (%s: %s)",
473                 cache_name, krb5_get_err_text(context, problem)); */
474       return HTTP_INTERNAL_SERVER_ERROR;
475    }
476
477    krb5_cc_destroy(context, cache);
478    krb5_free_context(context);
479    return OK;
480 }
481
482 static int
483 create_krb5_ccache(krb5_context kcontext,
484                    request_rec *r,
485                    kerb_auth_config *conf,
486                    krb5_principal princ,
487                    krb5_ccache *ccache)
488 {
489    char *ccname;
490    krb5_error_code problem;
491    int ret;
492    krb5_ccache tmp_ccache = NULL;
493
494 #ifdef HEIMDAL
495    problem = krb5_cc_gen_new(kcontext, &krb5_fcc_ops, &tmp_ccache);
496 #else
497    problem = krb5_fcc_generate_new(kcontext, &tmp_ccache);
498    /* krb5_fcc_generate_new() doesn't set KRB5_TC_OPENCLOSE, which makes 
499       krb5_cc_initialize() fail */
500    krb5_fcc_set_flags(kcontext, tmp_ccache, KRB5_TC_OPENCLOSE);
501 #endif
502    if (problem) {
503       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
504                  "Cannot create file for new krb5 ccache: %s",
505                  krb5_get_err_text(kcontext, problem));
506       ret = HTTP_INTERNAL_SERVER_ERROR;
507       goto end;
508    }
509
510    ccname = ap_pstrdup(r->pool, krb5_cc_get_name(kcontext, tmp_ccache));
511
512    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
513    if (problem) {
514       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
515                  "Cannot initialize krb5 ccache %s: krb5_cc_initialize() failed: %s",
516                  ccname, krb5_get_err_text(kcontext, problem));
517       ret = HTTP_INTERNAL_SERVER_ERROR;
518       goto end;
519    }
520
521    ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
522    ap_register_cleanup(r->pool, ccname,
523                        krb5_cache_cleanup, ap_null_cleanup);
524
525    *ccache = tmp_ccache;
526    tmp_ccache = NULL;
527
528    ret = OK;
529
530 end:
531    if (tmp_ccache)
532       krb5_cc_destroy(kcontext, tmp_ccache);
533
534    return ret;
535 }
536
537 static int
538 store_krb5_creds(krb5_context kcontext,
539                  request_rec *r,
540                  kerb_auth_config *conf,
541                  krb5_ccache delegated_cred)
542 {
543    char errstr[1024];
544    krb5_error_code problem;
545    krb5_principal princ;
546    krb5_ccache ccache;
547    int ret;
548
549    problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
550    if (problem) {
551       snprintf(errstr, sizeof(errstr), "krb5_cc_get_principal() failed: %s",
552                krb5_get_err_text(kcontext, problem));
553       return HTTP_INTERNAL_SERVER_ERROR;
554    }
555
556    ret = create_krb5_ccache(kcontext, r, conf, princ, &ccache);
557    if (ret) {
558       krb5_free_principal(kcontext, princ);
559       return ret;
560    }
561
562 #ifdef HEIMDAL
563    problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache);
564 #else
565    problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
566 #endif
567    krb5_free_principal(kcontext, princ);
568    if (problem) {
569       snprintf(errstr, sizeof(errstr), "Failed to store credentials: %s",
570                krb5_get_err_text(kcontext, problem));
571       krb5_cc_destroy(kcontext, ccache);
572       return HTTP_INTERNAL_SERVER_ERROR;
573    }
574
575    krb5_cc_close(kcontext, ccache);
576    return OK;
577 }
578
579
580 int authenticate_user_krb5pwd(request_rec *r,
581                               kerb_auth_config *conf,
582                               const char *auth_line)
583 {
584    const char      *sent_pw = NULL; 
585    const char      *sent_name = NULL;
586    const char      *realms = NULL;
587    krb5_context    kcontext = NULL;
588    krb5_error_code code;
589    krb5_principal  client = NULL;
590    krb5_ccache     ccache = NULL;
591    krb5_keytab     keytab = NULL;
592    int             ret;
593    char *name = NULL;
594
595    code = krb5_init_context(&kcontext);
596    if (code) {
597       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
598                  "Cannot initialize Kerberos5 context (%d)", code);
599       return HTTP_INTERNAL_SERVER_ERROR;
600    }
601
602    sent_pw = ap_pbase64decode(r->pool, auth_line);
603    sent_name = ap_getword (r->pool, &sent_pw, ':');
604    /* do not allow user to override realm setting of server */
605    if (strchr(sent_name, '@')) {
606       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
607                  "specifying realm in user name is prohibited");
608       ret = HTTP_UNAUTHORIZED;
609       goto end;
610    } 
611
612 #ifdef HEIMDAL
613    code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
614 #else
615    code = krb5_mcc_generate_new(kcontext, &ccache);
616 #endif
617    if (code) {
618       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
619                  "Cannot generate new ccache: %s",
620                  krb5_get_err_text(kcontext, code));
621       ret = HTTP_INTERNAL_SERVER_ERROR;
622       goto end;
623    }
624
625    if (conf->krb_5_keytab)
626       krb5_kt_resolve(kcontext, conf->krb_5_keytab, &keytab);
627       /* setenv("KRB5_KTNAME", conf->krb_5_keytab, 1); */
628
629    realms = conf->krb_auth_realms;
630    do {
631       if (realms && (code = krb5_set_default_realm(kcontext,
632                                            ap_getword_white(r->pool, &realms))))
633          continue;
634
635       if (client) {
636          krb5_free_principal(kcontext, client);
637          client = NULL;
638       }
639       code = krb5_parse_name(kcontext, sent_name, &client);
640       if (code)
641          continue;
642
643       code = verify_krb5_user(r, kcontext, client, ccache, sent_pw, "khttp",
644                               keytab);
645       if (code == 0)
646          break;
647
648       /* ap_getword_white() used above shifts the parameter, so it's not
649          needed to touch the realms variable */
650    } while (realms && *realms);
651
652    memset((char *)sent_pw, 0, strlen(sent_pw));
653
654    if (code) {
655       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
656                  "Verifying krb5 password failed: %s",
657                  krb5_get_err_text(kcontext, code));
658       ret = HTTP_UNAUTHORIZED;
659       goto end;
660    }
661
662    code = krb5_unparse_name(kcontext, client, &name);
663    if (code) {
664       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s",
665                  krb5_get_err_text(kcontext, code));
666       ret = HTTP_UNAUTHORIZED;
667       goto end;
668    }
669    MK_USER = ap_pstrdup (r->pool, name);
670    MK_AUTH_TYPE = "Basic";
671    free(name);
672
673    if (conf->krb_save_credentials)
674       store_krb5_creds(kcontext, r, conf, ccache);
675
676    ret = OK;
677
678 end:
679    if (client)
680       krb5_free_principal(kcontext, client);
681    if (ccache)
682       krb5_cc_destroy(kcontext, ccache);
683    if (keytab)
684       krb5_kt_close(kcontext, keytab);
685    krb5_free_context(kcontext);
686
687    return ret;
688 }
689
690 /*********************************************************************
691  * GSSAPI Authentication
692  ********************************************************************/
693
694 static const char *
695 get_gss_error(MK_POOL *p, OM_uint32 error_status, char *prefix)
696 {
697    OM_uint32 maj_stat, min_stat;
698    OM_uint32 msg_ctx = 0;
699    gss_buffer_desc status_string;
700    char buf[1024];
701    size_t len;
702
703    snprintf(buf, sizeof(buf), "%s", prefix);
704    len = strlen(buf);
705    do {
706       maj_stat = gss_display_status (&min_stat,
707                                      error_status,
708                                      GSS_C_MECH_CODE,
709                                      GSS_C_NO_OID,
710                                      &msg_ctx,
711                                      &status_string);
712       if (sizeof(buf) > len + status_string.length + 1) {
713          sprintf(buf+len, ": %s", (char*) status_string.value);
714          len += status_string.length;
715       }
716       gss_release_buffer(&min_stat, &status_string);
717    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
718
719    return (ap_pstrdup(p, buf));
720 }
721
722 static int
723 cleanup_gss_connection(void *data)
724 {
725    OM_uint32 minor_status;
726    gss_connection_t *gss_conn = (gss_connection_t *)data;
727
728    if (data == NULL)
729       return OK;
730    if (gss_conn->context != GSS_C_NO_CONTEXT)
731       gss_delete_sec_context(&minor_status, &gss_conn->context,
732                              GSS_C_NO_BUFFER);
733    if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL)
734       gss_release_cred(&minor_status, &gss_conn->server_creds);
735
736    gss_connection = NULL;
737
738    return OK;
739 }
740
741 static int
742 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
743                 gss_cred_id_t delegated_cred)
744 {
745    OM_uint32 maj_stat, min_stat;
746    krb5_principal princ = NULL;
747    krb5_ccache ccache = NULL;
748    krb5_error_code problem;
749    krb5_context context;
750    int ret = HTTP_INTERNAL_SERVER_ERROR;
751
752    problem = krb5_init_context(&context);
753    if (problem) {
754       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
755       return HTTP_INTERNAL_SERVER_ERROR;
756    }
757
758    problem = krb5_parse_name(context, princ_name, &princ);
759    if (problem) {
760       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
761          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
762       goto end;
763    }
764
765    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
766    if (problem) {
767       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
768          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
769       goto end;
770    }
771
772    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
773    if (GSS_ERROR(maj_stat)) {
774       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
775          "Cannot store delegated credential (%s)", 
776          get_gss_error(r->pool, min_stat, "gss_krb5_copy_ccache"));
777       goto end;
778    }
779
780    krb5_cc_close(context, ccache);
781    ccache = NULL;
782    ret = 0;
783
784 end:
785    if (princ)
786       krb5_free_principal(context, princ);
787    if (ccache)
788       krb5_cc_destroy(context, ccache);
789    krb5_free_context(context);
790    return ret;
791 }
792
793 static int
794 get_gss_creds(request_rec *r,
795               kerb_auth_config *conf,
796               gss_cred_id_t *server_creds)
797 {
798    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
799    OM_uint32 major_status, minor_status, minor_status2;
800    gss_name_t server_name = GSS_C_NO_NAME;
801    char buf[1024];
802
803    snprintf(buf, sizeof(buf), "%s/%s", "khttp", ap_get_server_name(r));
804
805    input_token.value = buf;
806    input_token.length = strlen(buf) + 1;
807
808    major_status = gss_import_name(&minor_status, &input_token,
809                                   GSS_C_NT_USER_NAME,
810                                   &server_name);
811    if (GSS_ERROR(major_status)) {
812       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
813                  "%s", get_gss_error(r->pool, minor_status,
814                  "gss_import_name() failed"));
815       return HTTP_INTERNAL_SERVER_ERROR;
816    }
817    
818    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
819                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
820                                    server_creds, NULL, NULL);
821    gss_release_name(&minor_status2, &server_name);
822    if (GSS_ERROR(major_status)) {
823       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
824                  "%s", get_gss_error(r->pool, minor_status,
825                                      "gss_acquire_cred() failed"));
826       return HTTP_INTERNAL_SERVER_ERROR;
827    }
828    
829    return 0;
830 }
831
832 static int
833 authenticate_user_gss(request_rec *r,
834                       kerb_auth_config *conf,
835                       const char *auth_line)
836 {
837   OM_uint32 major_status, minor_status, minor_status2;
838   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
839   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
840   const char *auth_param = NULL;
841   int ret;
842   gss_name_t client_name = GSS_C_NO_NAME;
843   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
844   static int initial_return = HTTP_UNAUTHORIZED;
845
846   /* needed to work around replay caches */
847   if (!ap_is_initial_req(r))
848      return initial_return;
849   initial_return = HTTP_UNAUTHORIZED;
850
851   if (gss_connection == NULL) {
852      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
853      if (gss_connection == NULL) {
854         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
855                    "ap_pcalloc() failed (not enough memory)");
856         ret = HTTP_INTERNAL_SERVER_ERROR;
857         goto end;
858      }
859      memset(gss_connection, 0, sizeof(*gss_connection));
860      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
861   }
862
863   if (conf->krb_5_keytab)
864      setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
865
866   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
867      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
868      if (ret)
869         goto end;
870   }
871
872   /* ap_getword() shifts parameter */
873   auth_param = ap_getword_white(r->pool, &auth_line);
874   if (auth_param == NULL) {
875      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
876                 "No Authorization parameter in request from client");
877      ret = HTTP_UNAUTHORIZED;
878      goto end;
879   }
880
881   input_token.length = ap_base64decode_len(auth_param) + 1;
882   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
883   if (input_token.value == NULL) {
884      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
885                 "ap_pcalloc() failed (not enough memory)");
886      ret = HTTP_INTERNAL_SERVER_ERROR;
887      goto end;
888   }
889   input_token.length = ap_base64decode(input_token.value, auth_param);
890
891   major_status = gss_accept_sec_context(&minor_status,
892                                         &gss_connection->context,
893                                         gss_connection->server_creds,
894                                         &input_token,
895                                         GSS_C_NO_CHANNEL_BINDINGS,
896                                         &client_name,
897                                         NULL,
898                                         &output_token,
899                                         NULL,
900                                         NULL,
901                                         &delegated_cred);
902   if (output_token.length) {
903      char *token = NULL;
904      size_t len;
905      
906      len = ap_base64encode_len(output_token.length) + 1;
907      token = ap_pcalloc(r->connection->pool, len + 1);
908      if (token == NULL) {
909         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
910                    "ap_pcalloc() failed (not enough memory)");
911         ret = HTTP_INTERNAL_SERVER_ERROR;
912         gss_release_buffer(&minor_status2, &output_token);
913         goto end;
914      }
915      ap_base64encode(token, output_token.value, output_token.length);
916      token[len] = '\0';
917      ap_table_set(r->err_headers_out, "WWW-Authenticate",
918                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
919      gss_release_buffer(&minor_status2, &output_token);
920   }
921
922   if (GSS_ERROR(major_status)) {
923      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
924                 "%s", get_gss_error(r->pool, minor_status,
925                                     "gss_accept_sec_context() failed"));
926      ret = HTTP_UNAUTHORIZED;
927      goto end;
928   }
929
930   if (major_status & GSS_S_CONTINUE_NEEDED) {
931      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
932       * iterations to establish authentication */
933      ret = HTTP_UNAUTHORIZED;
934      goto end;
935   }
936
937   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
938   gss_release_name(&minor_status, &client_name); 
939   if (GSS_ERROR(major_status)) {
940     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
941                "%s", get_gss_error(r->pool, minor_status, 
942                                    "gss_export_name() failed"));
943     ret = HTTP_INTERNAL_SERVER_ERROR;
944     goto end;
945   }
946
947   MK_AUTH_TYPE = "Negotiate";
948   MK_USER = ap_pstrdup(r->pool, output_token.value);
949
950   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
951      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
952
953   gss_release_buffer(&minor_status, &output_token);
954
955   ret = OK;
956
957 end:
958   if (delegated_cred)
959      gss_release_cred(&minor_status, &delegated_cred);
960
961   if (output_token.length) 
962      gss_release_buffer(&minor_status, &output_token);
963
964   if (client_name != GSS_C_NO_NAME)
965      gss_release_name(&minor_status, &client_name);
966
967   cleanup_gss_connection(gss_connection);
968
969   initial_return = ret;
970   return ret;
971 }
972 #endif /* KRB5 */
973
974
975 static void
976 note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf,
977                        int use_krb4, int use_krb5)
978 {
979    const char *auth_name = NULL;
980    int set_basic = 0;
981
982    /* get the user realm specified in .htaccess */
983    auth_name = ap_auth_name(r);
984
985    /* XXX should the WWW-Authenticate header be cleared first? */
986 #ifdef KRB5
987    if (use_krb5 && conf->krb_method_gssapi)
988       ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
989    if (use_krb5 && conf->krb_method_k5pass) {
990       ap_table_add(r->err_headers_out, "WWW-Authenticate",
991                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
992       set_basic = 1;
993    }
994 #endif
995
996 #ifdef KRB4
997    if (use_krb4 && conf->krb_method_k4pass && !set_basic)
998       ap_table_add(r->err_headers_out, "WWW-Authenticate",
999                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
1000 #endif
1001 }
1002
1003 int kerb_authenticate_user(request_rec *r)
1004 {
1005    kerb_auth_config *conf = 
1006       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
1007                                                 &auth_kerb_module);
1008    const char *auth_type = NULL;
1009    const char *auth_line = NULL;
1010    const char *type = NULL;
1011    int use_krb5 = 0, use_krb4 = 0;
1012    int ret;
1013
1014    /* get the type specified in .htaccess */
1015    type = ap_auth_type(r);
1016
1017    if (type && strcasecmp(type, "Kerberos") == 0)
1018       use_krb5 = use_krb4 = 1;
1019    else if(type && strcasecmp(type, "KerberosV5") == 0)
1020       use_krb4 = 0;
1021    else if(type && strcasecmp(type, "KerberosV4") == 0)
1022       use_krb5 = 0;
1023    else
1024       return DECLINED;
1025
1026    /* get what the user sent us in the HTTP header */
1027    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
1028    if (!auth_line) {
1029       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
1030       return HTTP_UNAUTHORIZED;
1031    }
1032    auth_type = ap_getword_white(r->pool, &auth_line);
1033
1034    ret = HTTP_UNAUTHORIZED;
1035
1036 #ifdef KRB5
1037    if (use_krb5 && conf->krb_method_gssapi &&
1038        strcasecmp(auth_type, "GSS-Negotiate") == 0) {
1039       ret = authenticate_user_gss(r, conf, auth_line);
1040    } else if (use_krb5 && conf->krb_method_k5pass &&
1041               strcasecmp(auth_type, "Basic") == 0) {
1042        ret = authenticate_user_krb5pwd(r, conf, auth_line);
1043    }
1044 #endif
1045
1046 #ifdef KRB4
1047    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
1048        strcasecmp(auth_type, "Basic") == 0)
1049       ret = authenticate_user_krb4pwd(r, conf, auth_line);
1050 #endif
1051
1052    if (ret == HTTP_UNAUTHORIZED)
1053       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
1054
1055    return ret;
1056 }
1057
1058
1059 /*************************************************************************** 
1060  Module Setup/Configuration
1061  ***************************************************************************/
1062 #ifdef APXS1
1063 module MODULE_VAR_EXPORT auth_kerb_module = {
1064         STANDARD_MODULE_STUFF,
1065         NULL,                           /*      module initializer            */
1066         kerb_dir_create_config,         /*      per-directory config creator  */
1067         NULL,                           /*      per-directory config merger   */
1068         NULL,                           /*      per-server    config creator  */
1069         NULL,                           /*      per-server    config merger   */
1070         kerb_auth_cmds,                 /*      command table                 */
1071         NULL,                           /* [ 9] content handlers              */
1072         NULL,                           /* [ 2] URI-to-filename translation   */
1073         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1074         NULL,                           /* [ 6] check user_id is valid *here* */
1075         NULL,                           /* [ 4] check access by host address  */
1076         NULL,                           /* [ 7] MIME type checker/setter      */
1077         NULL,                           /* [ 8] fixups                        */
1078         NULL,                           /* [10] logger                        */
1079         NULL,                           /* [ 3] header parser                 */
1080         NULL,                           /*      process initialization        */
1081         NULL,                           /*      process exit/cleanup          */
1082         NULL                            /* [ 1] post read_request handling    */
1083 };
1084 #else
1085 void kerb_register_hooks(apr_pool_t *p)
1086 {
1087    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1088 }
1089
1090 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1091 {
1092    STANDARD20_MODULE_STUFF,
1093    kerb_dir_create_config,      /* create per-dir    conf structures  */
1094    NULL,                        /* merge  per-dir    conf structures  */
1095    NULL,                        /* create per-server conf structures  */
1096    NULL,                        /* merge  per-server conf structures  */
1097    kerb_auth_cmds,              /* table of configuration directives  */
1098    kerb_register_hooks          /* register hooks                     */
1099 };
1100 #endif