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