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