- removed directive 'KrbTmpdir'
[mod_auth_kerb.cvs/.git] / src / mod_auth_kerb.c
1 #ident "$Id$"
2
3 #ifndef APXS1
4 #include "ap_compat.h"
5 #include "apr_strings.h"
6 #endif
7 #include "httpd.h"
8 #include "http_config.h"
9 #include "http_core.h"
10 #include "http_log.h"
11 #include "http_protocol.h"
12 #include "http_request.h"
13
14 #ifdef KRB5
15 #include <krb5.h>
16 #include <gssapi.h>
17 #endif /* KRB5 */
18
19 #ifdef KRB4
20 #include <krb.h>
21 #endif /* KRB4 */
22
23 #ifdef APXS1
24 module auth_kerb_module;
25 #else
26 module AP_MODULE_DECLARE_DATA auth_kerb_module;
27 #endif
28
29 /*************************************************************************** 
30  Macros To Ease Compatibility
31  ***************************************************************************/
32 #ifdef APXS1
33 #define MK_POOL pool
34 #define MK_TABLE_GET ap_table_get
35 #define MK_TABLE_SET ap_table_set
36 #define MK_TABLE_TYPE table
37 #define MK_PSTRDUP ap_pstrdup
38 #define MK_USER r->connection->user
39 #define MK_AUTH_TYPE r->connection->ap_auth_type
40 #define MK_ARRAY_HEADER array_header
41 #else
42 #define MK_POOL apr_pool_t
43 #define MK_TABLE_GET apr_table_get
44 #define MK_TABLE_SET apr_table_set
45 #define MK_TABLE_TYPE apr_table_t
46 #define MK_PSTRDUP apr_pstrdup
47 #define MK_USER r->user
48 #define MK_AUTH_TYPE r->ap_auth_type
49 #define MK_ARRAY_HEADER apr_array_header_t
50 #endif /* APXS1 */
51
52
53 /*************************************************************************** 
54  Auth Configuration Structure
55  ***************************************************************************/
56 typedef struct {
57         char *krb_auth_realms;
58         int krb_fail_status;
59         char *krb_force_instance;
60         int krb_save_credentials;
61         char *service_name;
62         char *krb_lifetime;
63 #ifdef KRB5
64         char *krb_5_keytab;
65         int krb_forwardable;
66         int krb_method_gssapi;
67         int krb_method_k5pass;
68 #endif
69 #ifdef KRB4
70         char *krb_4_srvtab;
71         int krb_method_k4pass;
72 #endif
73 } kerb_auth_config;
74
75 static const char*
76 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg);
77
78 #ifdef APXS1
79 #define command(name, func, var, type, usage)           \
80   { name, func,                                         \
81     (void*)XtOffsetOf(kerb_auth_config, var),           \
82     OR_AUTHCFG, type, usage }
83 #else
84 #define command(name, func, var, type, usage)           \
85   AP_INIT_ ## type (name, func,                         \
86         (void*)APR_XtOffsetOf(kerb_auth_config, var),   \
87         OR_AUTHCFG, usage)
88 #endif
89
90 static const command_rec kerb_auth_cmds[] = {
91    command("KrbAuthRealm", krb5_save_realms, krb_auth_realms,
92      RAW_ARGS, "Realms to attempt authentication against (can be multiple)."),
93
94    command("KrbAuthRealms", krb5_save_realms, krb_auth_realms,
95      RAW_ARGS, "Alias for KrbAuthRealm."),
96
97 #if 0
98    command("KrbFailStatus", kerb_set_fail_slot, krb_fail_status,
99      TAKE1, "If auth fails, return status set here."),
100 #endif
101
102    command("KrbForceInstance", ap_set_string_slot, krb_force_instance,
103      TAKE1, "Force authentication against an instance specified here."),
104
105    command("KrbSaveCredentials", ap_set_flag_slot, krb_save_credentials,
106      FLAG, "Save and store credentials/tickets retrieved during auth."),
107
108    command("KrbSaveTickets", ap_set_flag_slot, krb_save_credentials,
109      FLAG, "Alias for KrbSaveCredentials."),
110
111    command("KrbServiceName", ap_set_string_slot, service_name,
112      TAKE1, "Kerberos service name to be used by apache."),
113
114 #if 0
115    command("KrbLifetime", ap_set_string_slot, krb_lifetime,
116      TAKE1, "Kerberos ticket lifetime."),
117 #endif
118
119 #ifdef KRB5
120    command("Krb5Keytab", ap_set_file_slot, krb_5_keytab,
121      TAKE1, "Location of Kerberos V5 keytab file."),
122
123    command("KrbForwardable", ap_set_flag_slot, krb_forwardable,
124      FLAG, "Credentials retrieved will be flagged as forwardable."),
125
126    command("KrbMethodGSSAPI", ap_set_flag_slot, krb_method_gssapi,
127      FLAG, "Enable GSSAPI authentication."),
128
129    command("KrbMethodK5Pass", ap_set_flag_slot, krb_method_k5pass,
130      FLAG, "Enable Kerberos V5 password authentication."),
131 #endif 
132
133 #ifdef KRB4
134    command("Krb4Srvtab", ap_set_file_slot, krb_4_srvtab,
135      TAKE1, "Location of Kerberos V4 srvtab file."),
136
137    command("KrbMethodK4Pass", ap_set_flag_slot, krb_method_k4pass,
138      FLAG, "Enable Kerberos V4 password authentication."),
139 #endif
140
141    { NULL }
142 };
143
144 #ifdef KRB5
145 typedef struct {
146    gss_ctx_id_t context;
147    gss_cred_id_t server_creds;
148 } gss_connection_t;
149
150 static gss_connection_t *gss_connection = NULL;
151 #endif
152
153
154 /*************************************************************************** 
155  Auth Configuration Initialization
156  ***************************************************************************/
157 static void *kerb_dir_create_config(MK_POOL *p, char *d)
158 {
159         kerb_auth_config *rec;
160
161         rec = (kerb_auth_config *) ap_pcalloc(p, sizeof(kerb_auth_config));
162         ((kerb_auth_config *)rec)->krb_fail_status = HTTP_UNAUTHORIZED;
163 #ifdef KRB5
164         ((kerb_auth_config *)rec)->krb_method_k5pass = 1;
165         ((kerb_auth_config *)rec)->krb_method_gssapi = 1;
166 #endif
167 #ifdef KRB4
168         ((kerb_auth_config *)rec)->krb_method_k4pass = 1;
169 #endif
170         return rec;
171 }
172
173 static const char*
174 krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg)
175 {
176    sec->krb_auth_realms= ap_pstrdup(cmd->pool, arg);
177    return NULL;
178 }
179
180 void log_rerror(const char *file, int line, int level, int status,
181                 const request_rec *r, const char *fmt, ...)
182 {
183    char errstr[1024];
184    va_list ap;
185
186    va_start(ap, fmt);
187    vsnprintf(errstr, sizeof(errstr), fmt, ap);
188    va_end(ap);
189
190 #ifdef APXS1
191    ap_log_rerror(file, line, level, r, "%s", errstr);
192 #else
193    ap_log_rerror(file, line, level, status, r, "%s", errstr);
194 #endif
195 }
196
197 #if 0
198 static const char *kerb_set_fail_slot(cmd_parms *cmd, void *struct_ptr,
199                                         const char *arg)
200 {
201         int offset = (int) (long) cmd->info;
202         if (!strncasecmp(arg, "unauthorized", 12))
203                 *(int *) ((char *)struct_ptr + offset) = HTTP_UNAUTHORIZED;
204         else if (!strncasecmp(arg, "forbidden", 9))
205                 *(int *) ((char *)struct_ptr + offset) = HTTP_FORBIDDEN;
206         else if (!strncasecmp(arg, "declined", 8))
207                 *(int *) ((char *)struct_ptr + offset) = DECLINED;
208         else
209                 return "KrbAuthFailStatus must be Forbidden, Unauthorized, or Declined.";
210         return NULL;
211 }
212 #endif
213
214 #ifdef KRB4
215 /*************************************************************************** 
216  Username/Password Validation for Krb4
217  ***************************************************************************/
218 int kerb4_password_validate(request_rec *r, const char *user, const char *pass)
219 {
220         kerb_auth_config *conf =
221                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
222                                         &auth_kerb_module);
223         int ret;
224         int lifetime = DEFAULT_TKT_LIFE;
225         char *c, *tfname;
226         char *username = NULL;
227         char *instance = NULL;
228         char *realm = NULL;
229
230         username = (char *)ap_pstrdup(r->pool, user);
231         if (!username) {
232                 return 0;
233         }
234
235         instance = strchr(username, '.');
236         if (instance) {
237                 *instance++ = '\0';
238         }
239         else {
240                 instance = "";
241         }
242
243         realm = strchr(username, '@');
244         if (realm) {
245                 *realm++ = '\0';
246         }
247         else {
248                 realm = "";
249         }
250
251         if (conf->krb_lifetime) {
252                 lifetime = atoi(conf->krb_lifetime);
253         }
254
255         if (conf->krb_force_instance) {
256                 instance = conf->krb_force_instance;
257         }
258
259         if (conf->krb_save_credentials) {
260                 tfname = (char *)malloc(sizeof(char) * MAX_STRING_LEN);
261                 sprintf(tfname, "/tmp/k5cc_ap_%s", MK_USER);
262
263                 if (!strcmp(instance, "")) {
264                         tfname = strcat(tfname, ".");
265                         tfname = strcat(tfname, instance);
266                 }
267
268                 if (!strcmp(realm, "")) {
269                         tfname = strcat(tfname, ".");
270                         tfname = strcat(tfname, realm);
271                 }
272
273                 for (c = tfname + strlen("/tmp") + 1; *c; c++) {
274                         if (*c == '/')
275                                 *c = '.';
276                 }
277
278                 krb_set_tkt_string(tfname);
279         }
280
281         if (!strcmp(realm, "")) {
282                 realm = (char *)malloc(sizeof(char) * (REALM_SZ + 1));
283                 ret = krb_get_lrealm(realm, 1);
284                 if (ret != KSUCCESS)
285                         return 0;
286         }
287
288         ret = krb_get_pw_in_tkt((char *)user, instance, realm, "krbtgt", realm,
289                                         lifetime, (char *)pass);
290         switch (ret) {
291                 case INTK_OK:
292                 case INTK_W_NOTALL:
293                         return 1;
294                         break;
295
296                 default:
297                         return 0;
298                         break;
299         }
300 }
301 #endif /* KRB4 */
302
303 #ifdef KRB5
304 /*************************************************************************** 
305  Username/Password Validation for Krb5
306  ***************************************************************************/
307 #ifndef HEIMDAL
308 krb5_error_code
309 krb5_verify_user(krb5_context context, krb5_principal principal,
310                  krb5_ccache ccache, const char *password, krb5_boolean secure,
311                  const char *service)
312 {
313    int ret;
314    krb5_context kcontext;
315    krb5_principal server, client;
316    krb5_timestamp now;
317    krb5_creds my_creds;
318    krb5_flags options = 0;
319    krb5_principal me = NULL;
320    krb5_data tgtname = {
321       0,
322       KRB5_TGS_NAME_SIZE,
323       KRB5_TGS_NAME
324    };
325
326    memset((char *)&my_creds, 0, sizeof(my_creds));
327    my_creds.client = principal;
328
329    if (krb5_build_principal_ext(kcontext, &server,
330                                 krb5_princ_realm(kcontext, me)->length,
331                                 krb5_princ_realm(kcontext, me)->data,
332                                 tgtname.length, tgtname.data,
333                                 krb5_princ_realm(kcontext, me)->length,
334                                 krb5_princ_realm(kcontext, me)->data,
335                                 0)) {
336         return ret;
337    }
338
339    my_creds.server = server;
340    if (krb5_timeofday(kcontext, &now))
341         return -1;
342
343    my_creds.times.starttime = 0;
344    /* XXX
345    my_creds.times.endtime = now + lifetime;
346    my_creds.times.renew_till = now + renewal;
347    */
348
349    ret = krb5_get_in_tkt_with_password(kcontext, options, 0, NULL, 0,
350                                        password, ccache, &my_creds, 0);
351    if (ret) {
352         return ret;
353    }
354
355    return 0;
356 }
357 #endif
358
359
360 static int
361 krb5_cache_cleanup(void *data)
362 {
363    krb5_context context;
364    krb5_ccache  cache;
365    krb5_error_code problem;
366    char *cache_name = (char *) data;
367
368    problem = krb5_init_context(&context);
369    if (problem) {
370       /* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "krb5_init_context() failed"); */
371       return HTTP_INTERNAL_SERVER_ERROR;
372    }
373
374    problem = krb5_cc_resolve(context, cache_name, &cache);
375    if (problem) {
376       /* log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
377                 "krb5_cc_resolve() failed (%s: %s)",
378                 cache_name, krb5_get_err_text(context, problem)); */
379       return HTTP_INTERNAL_SERVER_ERROR;
380    }
381
382    krb5_cc_destroy(context, cache);
383    krb5_free_context(context);
384    return OK;
385 }
386
387 static int
388 create_krb5_ccache(krb5_context kcontext,
389                    request_rec *r,
390                    kerb_auth_config *conf,
391                    krb5_principal princ,
392                    krb5_ccache *ccache)
393 {
394    char *ccname;
395    krb5_error_code problem;
396    int ret;
397    krb5_ccache tmp_ccache = NULL;
398
399 #ifdef HEIMDAL
400    problem = krb5_cc_gen_new(kcontext, &krb5_fcc_ops, &tmp_ccache);
401 #else
402    problem = krb5_fcc_generate_new(kcontext, &tmp_ccache);
403 #endif
404    if (problem) {
405       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
406                  "Cannot create file for new krb5 ccache: %s",
407                  krb5_get_err_text(kcontext, problem));
408       ret = HTTP_INTERNAL_SERVER_ERROR;
409       goto end;
410    }
411
412    ccname = ap_pstrdup(r->pool, krb5_cc_get_name(kcontext, tmp_ccache));
413
414    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
415    if (problem) {
416       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
417                  "Cannot create krb5 ccache %s: krb5_cc_initialize() failed: %s",
418                  ccname, krb5_get_err_text(kcontext, problem));
419       ret = HTTP_INTERNAL_SERVER_ERROR;
420       goto end;
421    }
422
423    ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
424    ap_register_cleanup(r->pool, ccname,
425                        krb5_cache_cleanup, ap_null_cleanup);
426
427    *ccache = tmp_ccache;
428    tmp_ccache = NULL;
429
430    ret = OK;
431
432 end:
433    if (tmp_ccache)
434       krb5_cc_destroy(kcontext, tmp_ccache);
435
436    return ret;
437 }
438
439 static int
440 store_krb5_creds(krb5_context kcontext,
441                  request_rec *r,
442                  kerb_auth_config *conf,
443                  krb5_ccache delegated_cred)
444 {
445    char errstr[1024];
446    krb5_error_code problem;
447    krb5_principal princ;
448    krb5_ccache ccache;
449    int ret;
450
451    problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
452    if (problem) {
453       snprintf(errstr, sizeof(errstr), "krb5_cc_get_principal() failed: %s",
454                krb5_get_err_text(kcontext, problem));
455       return HTTP_INTERNAL_SERVER_ERROR;
456    }
457
458    ret = create_krb5_ccache(kcontext, r, conf, princ, &ccache);
459    if (ret) {
460       krb5_free_principal(kcontext, princ);
461       return ret;
462    }
463
464    problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache);
465    krb5_free_principal(kcontext, princ);
466    if (problem) {
467       snprintf(errstr, sizeof(errstr), "krb5_cc_copy_cache() failed: %s",
468                krb5_get_err_text(kcontext, problem));
469       krb5_cc_destroy(kcontext, ccache);
470       return HTTP_INTERNAL_SERVER_ERROR;
471    }
472
473    krb5_cc_close(kcontext, ccache);
474    return OK;
475 }
476
477
478 int authenticate_user_krb5pwd(request_rec *r,
479                               kerb_auth_config *conf,
480                               const char *auth_line)
481 {
482    const char      *sent_pw = NULL; 
483    const char      *sent_name = NULL;
484    const char      *realms = NULL;
485    const char      *service_name = NULL;
486    krb5_context    kcontext = NULL;
487    krb5_error_code code;
488    krb5_principal  client = NULL;
489    krb5_ccache     ccache = NULL;
490    int             ret;
491    char *name = NULL;
492
493    code = krb5_init_context(&kcontext);
494    if (code) {
495       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
496                  "Cannot initialize Kerberos5 context (%d)", code);
497       return HTTP_INTERNAL_SERVER_ERROR;
498    }
499
500    sent_pw = ap_pbase64decode(r->pool, auth_line);
501    sent_name = ap_getword (r->pool, &sent_pw, ':');
502    /* do not allow user to override realm setting of server */
503    if (strchr(sent_name, '@')) {
504       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
505                  "specifying realm in user name is prohibited");
506       ret = HTTP_UNAUTHORIZED;
507       goto end;
508    } 
509
510 #ifdef HEIMDAL
511    code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
512 #else
513    code = krb5_mcc_generate_new(kcontext, &ccache);
514 #endif
515    if (code) {
516       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
517                  "Cannot generate new ccache: %s",
518                  krb5_get_err_text(kcontext, code));
519       ret = HTTP_INTERNAL_SERVER_ERROR;
520       goto end;
521    }
522
523    if (conf->krb_5_keytab)
524       /* setenv("KRB5_KTNAME", conf->krb_5_keytab, 1); */
525       kcontext->default_keytab = conf->krb_5_keytab;
526
527    if (conf->service_name) {
528       char *p;
529       service_name = ap_pstrdup(r->pool, conf->service_name);
530       if ((p=strchr(service_name, '/')))
531          *p = '\0';
532    } else
533       service_name = "khttp";
534
535    realms = conf->krb_auth_realms;
536    do {
537       if (realms && (code = krb5_set_default_realm(kcontext,
538                                            ap_getword_white(r->pool, &realms))))
539          continue;
540
541       if (client) {
542          krb5_free_principal(kcontext, client);
543          client = NULL;
544       }
545       code = krb5_parse_name(kcontext, sent_name, &client);
546       if (code)
547          continue;
548
549       code = krb5_verify_user(kcontext, client, ccache, sent_pw, 1, 
550                               service_name);
551       if (code == 0)
552          break;
553
554       /* ap_getword_white() used above shifts the parameter, so it's not
555          needed to touch the realms variable */
556    } while (realms && *realms);
557
558    memset((char *)sent_pw, 0, strlen(sent_pw));
559
560    if (code) {
561       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
562                  "Verifying krb5 password failed: %s",
563                  krb5_get_err_text(kcontext, code));
564       ret = HTTP_UNAUTHORIZED;
565       goto end;
566    }
567
568    code = krb5_unparse_name(kcontext, client, &name);
569    if (code) {
570       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s",
571                  krb5_get_err_text(kcontext, code));
572       ret = HTTP_UNAUTHORIZED;
573       goto end;
574    }
575    MK_USER = ap_pstrdup (r->pool, name);
576    MK_AUTH_TYPE = "Basic";
577    free(name);
578
579    if (conf->krb_save_credentials)
580       store_krb5_creds(kcontext, r, conf, ccache);
581
582    ret = OK;
583
584 end:
585    if (client)
586       krb5_free_principal(kcontext, client);
587    if (ccache)
588       krb5_cc_destroy(kcontext, ccache);
589    krb5_free_context(kcontext);
590
591    return ret;
592 }
593
594 /*********************************************************************
595  * GSSAPI Authentication
596  ********************************************************************/
597
598 static const char *
599 get_gss_error(MK_POOL *p, OM_uint32 error_status, char *prefix)
600 {
601    OM_uint32 maj_stat, min_stat;
602    OM_uint32 msg_ctx = 0;
603    gss_buffer_desc status_string;
604    char buf[1024];
605    size_t len;
606
607    snprintf(buf, sizeof(buf), "%s", prefix);
608    len = strlen(buf);
609    do {
610       maj_stat = gss_display_status (&min_stat,
611                                      error_status,
612                                      GSS_C_MECH_CODE,
613                                      GSS_C_NO_OID,
614                                      &msg_ctx,
615                                      &status_string);
616       if (sizeof(buf) > len + status_string.length + 1) {
617          sprintf(buf+len, ": %s", (char*) status_string.value);
618          len += status_string.length;
619       }
620       gss_release_buffer(&min_stat, &status_string);
621    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
622
623    return (ap_pstrdup(p, buf));
624 }
625
626 static int
627 cleanup_gss_connection(void *data)
628 {
629    OM_uint32 minor_status;
630    gss_connection_t *gss_conn = (gss_connection_t *)data;
631
632    if (data == NULL)
633       return OK;
634    if (gss_conn->context != GSS_C_NO_CONTEXT)
635       gss_delete_sec_context(&minor_status, &gss_conn->context,
636                              GSS_C_NO_BUFFER);
637    if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL)
638       gss_release_cred(&minor_status, &gss_conn->server_creds);
639
640    return OK;
641 }
642
643 static int
644 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
645                 gss_cred_id_t delegated_cred)
646 {
647    OM_uint32 maj_stat, min_stat;
648    krb5_principal princ = NULL;
649    krb5_ccache ccache = NULL;
650    krb5_error_code problem;
651    krb5_context context;
652    int ret = HTTP_INTERNAL_SERVER_ERROR;
653
654    problem = krb5_init_context(&context);
655    if (problem) {
656       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
657       return HTTP_INTERNAL_SERVER_ERROR;
658    }
659
660    problem = krb5_parse_name(context, princ_name, &princ);
661    if (problem) {
662       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
663          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
664       goto end;
665    }
666
667    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
668    if (problem) {
669       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
670          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
671       goto end;
672    }
673
674    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
675    if (GSS_ERROR(maj_stat)) {
676       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
677          "Cannot store delegated credential (%s)", 
678          get_gss_error(r->pool, min_stat, "gss_krb5_copy_ccache"));
679       goto end;
680    }
681
682    krb5_cc_close(context, ccache);
683    ccache = NULL;
684    ret = 0;
685
686 end:
687    if (princ)
688       krb5_free_principal(context, princ);
689    if (ccache)
690       krb5_cc_destroy(context, ccache);
691    krb5_free_context(context);
692    return ret;
693 }
694
695 static int
696 get_gss_creds(request_rec *r,
697               kerb_auth_config *conf,
698               gss_cred_id_t *server_creds)
699 {
700    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
701    OM_uint32 major_status, minor_status, minor_status2;
702    gss_name_t server_name = GSS_C_NO_NAME;
703
704    if (conf->service_name) {
705       input_token.value = conf->service_name;
706       input_token.length = strlen(conf->service_name) + 1;
707    }
708    else {
709       input_token.value = "khttp";
710       input_token.length = 6;
711    }
712    major_status = gss_import_name(&minor_status, &input_token,
713                                   (conf->service_name) ? 
714                                        GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE,
715                                   &server_name);
716    if (GSS_ERROR(major_status)) {
717       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
718                  "%s", get_gss_error(r->pool, minor_status,
719                  "gss_import_name() failed"));
720       return HTTP_INTERNAL_SERVER_ERROR;
721    }
722    
723    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
724                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
725                                    server_creds, NULL, NULL);
726    gss_release_name(&minor_status2, &server_name);
727    if (GSS_ERROR(major_status)) {
728       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
729                  "%s", get_gss_error(r->pool, minor_status,
730                                      "gss_acquire_cred() failed"));
731       return HTTP_INTERNAL_SERVER_ERROR;
732    }
733    
734    return 0;
735 }
736
737 static int
738 authenticate_user_gss(request_rec *r,
739                       kerb_auth_config *conf,
740                       const char *auth_line)
741 {
742   OM_uint32 major_status, minor_status, minor_status2;
743   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
744   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
745   const char *auth_param = NULL;
746   int ret;
747   gss_name_t client_name = GSS_C_NO_NAME;
748   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
749
750   if (gss_connection == NULL) {
751      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
752      if (gss_connection == NULL) {
753         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
754                    "ap_pcalloc() failed (not enough memory)");
755         ret = HTTP_INTERNAL_SERVER_ERROR;
756         goto end;
757      }
758      memset(gss_connection, 0, sizeof(*gss_connection));
759      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
760   }
761
762   if (conf->krb_5_keytab)
763      setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
764
765   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
766      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
767      if (ret)
768         goto end;
769   }
770
771   /* ap_getword() shifts parameter */
772   auth_param = ap_getword_white(r->pool, &auth_line);
773   if (auth_param == NULL) {
774      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
775                 "No Authorization parameter in request from client");
776      ret = HTTP_UNAUTHORIZED;
777      goto end;
778   }
779
780   input_token.length = ap_base64decode_len(auth_param) + 1;
781   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
782   if (input_token.value == NULL) {
783      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
784                 "ap_pcalloc() failed (not enough memory)");
785      ret = HTTP_INTERNAL_SERVER_ERROR;
786      goto end;
787   }
788   input_token.length = ap_base64decode(input_token.value, auth_param);
789
790   major_status = gss_accept_sec_context(&minor_status,
791                                         &gss_connection->context,
792                                         gss_connection->server_creds,
793                                         &input_token,
794                                         GSS_C_NO_CHANNEL_BINDINGS,
795                                         &client_name,
796                                         NULL,
797                                         &output_token,
798                                         NULL,
799                                         NULL,
800                                         &delegated_cred);
801   if (output_token.length) {
802      char *token = NULL;
803      size_t len;
804      
805      len = ap_base64encode_len(output_token.length) + 1;
806      token = ap_pcalloc(r->connection->pool, len + 1);
807      if (token == NULL) {
808         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
809                    "ap_pcalloc() failed (not enough memory)");
810         ret = HTTP_INTERNAL_SERVER_ERROR;
811         gss_release_buffer(&minor_status2, &output_token);
812         goto end;
813      }
814      ap_base64encode(token, output_token.value, output_token.length);
815      token[len] = '\0';
816      ap_table_set(r->err_headers_out, "WWW-Authenticate",
817                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
818      gss_release_buffer(&minor_status2, &output_token);
819   }
820
821   if (GSS_ERROR(major_status)) {
822      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
823                 "%s", get_gss_error(r->pool, minor_status,
824                                     "gss_accept_sec_context() failed"));
825      ret = HTTP_UNAUTHORIZED;
826      goto end;
827   }
828
829   if (major_status & GSS_S_CONTINUE_NEEDED) {
830      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
831       * iterations to establish authentication */
832      ret = HTTP_UNAUTHORIZED;
833      goto end;
834   }
835
836   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
837   gss_release_name(&minor_status, &client_name); 
838   if (GSS_ERROR(major_status)) {
839     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
840                "%s", get_gss_error(r->pool, minor_status, 
841                                    "gss_export_name() failed"));
842     ret = HTTP_INTERNAL_SERVER_ERROR;
843     goto end;
844   }
845
846   MK_AUTH_TYPE = "Negotiate";
847   MK_USER = ap_pstrdup(r->pool, output_token.value);
848
849   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
850      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
851
852   gss_release_buffer(&minor_status, &output_token);
853
854
855 #if 0
856   /* If the user comes from a realm specified by configuration don't include
857       its realm name in the username so that the authorization routine could
858       work for both Password-based and Ticket-based authentication. It's
859       administrators responsibility to include only such realm that have
860       unified principal instances, i.e. if the same principal name occures in
861       multiple realms, it must be always assigned to a single user.
862   */    
863   p = strchr(r->connection->user, '@');
864   if (p != NULL) {
865      const char *realms = conf->gss_krb5_realms;
866
867      while (realms && *realms) {
868         if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
869            *p = '\0';
870            break;
871         }
872      }
873   }
874 #endif
875
876   ret = OK;
877
878 end:
879   if (delegated_cred)
880      gss_release_cred(&minor_status, &delegated_cred);
881
882   if (output_token.length) 
883      gss_release_buffer(&minor_status, &output_token);
884
885   if (client_name != GSS_C_NO_NAME)
886      gss_release_name(&minor_status, &client_name);
887
888   return ret;
889 }
890 #endif /* KRB5 */
891
892
893 static void
894 note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf)
895 {
896    const char *auth_name = NULL;
897
898    /* get the user realm specified in .htaccess */
899    auth_name = ap_auth_name(r);
900
901    /* XXX check AuthType */
902
903    /* XXX should the WWW-Authenticate header be cleared first? */
904 #ifdef KRB5
905    if (conf->krb_method_gssapi)
906       ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
907    if (conf->krb_method_k5pass)
908       ap_table_add(r->err_headers_out, "WWW-Authenticate",
909                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
910 #endif
911
912 #ifdef KRB4
913    if (conf->krb_method_k4pass)
914       ap_table_add(r->err_headers_out, "WWW-Authenticate",
915                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
916 #endif
917 }
918
919 int kerb_authenticate_user(request_rec *r)
920 {
921    kerb_auth_config *conf = 
922       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
923                                                 &auth_kerb_module);
924    const char *auth_type = NULL;
925    const char *auth_line = NULL;
926    const char *type = NULL;
927    int use_krb5 = 0, use_krb4 = 0;
928    int ret;
929
930    /* get the type specified in .htaccess */
931    type = ap_auth_type(r);
932
933    if (type && strcasecmp(type, "Kerberos") == 0)
934       use_krb5 = use_krb4 = 1;
935    else if(type && strcasecmp(type, "KerberosV5") == 0)
936       use_krb4 = 0;
937    else if (type && strcasecmp(type, "KerberosV4") == 0)
938       use_krb5 = 0;
939    else
940       return DECLINED;
941
942    /* get what the user sent us in the HTTP header */
943    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
944    if (!auth_line) {
945       note_kerb_auth_failure(r, conf);
946       return HTTP_UNAUTHORIZED;
947    }
948    auth_type = ap_getword_white(r->pool, &auth_line);
949
950    ret = HTTP_UNAUTHORIZED;
951
952 #ifdef KRB5
953    if (use_krb5 && conf->krb_method_gssapi &&
954        strcasecmp(auth_type, "GSS-Negotiate") == 0) {
955       ret = authenticate_user_gss(r, conf, auth_line);
956    } else if (use_krb5 && conf->krb_method_k5pass &&
957               strcasecmp(auth_type, "Basic") == 0) {
958        ret = authenticate_user_krb5pwd(r, conf, auth_line);
959    }
960 #endif
961
962 #ifdef KRB4
963    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
964        strcasecmp(auth_type, "Basic") == 0)
965       ret = authenticate_user_krb4pwd(r, conf, auth_line);
966 #endif
967
968    if (ret == HTTP_UNAUTHORIZED)
969       note_kerb_auth_failure(r, conf);
970
971    return ret;
972 }
973
974
975 /*************************************************************************** 
976  Module Setup/Configuration
977  ***************************************************************************/
978 #ifdef APXS1
979 module MODULE_VAR_EXPORT auth_kerb_module = {
980         STANDARD_MODULE_STUFF,
981         NULL,                           /*      module initializer            */
982         kerb_dir_create_config,         /*      per-directory config creator  */
983         NULL,                           /*      per-directory config merger   */
984         NULL,                           /*      per-server    config creator  */
985         NULL,                           /*      per-server    config merger   */
986         kerb_auth_cmds,                 /*      command table                 */
987         NULL,                           /* [ 9] content handlers              */
988         NULL,                           /* [ 2] URI-to-filename translation   */
989         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
990         NULL,                           /* [ 6] check user_id is valid *here* */
991         NULL,                           /* [ 4] check access by host address  */
992         NULL,                           /* [ 7] MIME type checker/setter      */
993         NULL,                           /* [ 8] fixups                        */
994         NULL,                           /* [10] logger                        */
995         NULL,                           /* [ 3] header parser                 */
996         NULL,                           /*      process initialization        */
997         NULL,                           /*      process exit/cleanup          */
998         NULL                            /* [ 1] post read_request handling    */
999 };
1000 #else
1001 void kerb_register_hooks(apr_pool_t *p)
1002 {
1003    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1004 }
1005
1006 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1007 {
1008    STANDARD20_MODULE_STUFF,
1009    kerb_dir_create_config,      /* create per-dir    conf structures  */
1010    NULL,                        /* merge  per-dir    conf structures  */
1011    NULL,                        /* create per-server conf structures  */
1012    NULL,                        /* merge  per-server conf structures  */
1013    kerb_auth_cmds,              /* table of configuration directives  */
1014    kerb_register_hooks          /* register hooks                     */
1015 };
1016 #endif