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