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