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