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