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