- krb5_verify_user() made really work
[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_creds creds;
321    krb5_principal server = NULL;
322    krb5_error_code ret;
323    krb5_verify_init_creds_opt opt;
324
325    memset(&creds, 0, sizeof(creds));
326
327    ret = krb5_get_init_creds_password(context, &creds, principal, password,
328                                       krb5_prompter_posix, NULL, 0, NULL, NULL);
329    if (ret)
330       return ret;
331
332    ret = krb5_sname_to_principal(context, NULL, service, 
333                                  KRB5_NT_SRV_HST, &server);
334    if (ret)
335       goto end;
336
337    krb5_verify_init_creds_opt_init(&opt);
338    krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, secure);
339
340    ret = krb5_verify_init_creds(context, &creds, server, NULL, NULL, &opt);
341    if (ret)
342       goto end;
343
344    if (ccache) {
345       ret = krb5_cc_initialize(context, ccache, principal);
346       if (ret == 0)
347          ret = krb5_cc_store_cred(context, ccache, &creds);
348       krb5_cc_close(context, ccache);
349    }
350
351 end:
352    krb5_free_creds_contents(context, &creds);
353    if (server)
354       krb5_free_principal(context, service);
355    return ret;
356 }
357 #endif
358
359
360 static int
361 krb5_cache_cleanup(void *data)
362 {
363    krb5_context context;
364    krb5_ccache  cache;
365    krb5_error_code problem;
366    char *cache_name = (char *) data;
367
368    problem = krb5_init_context(&context);
369    if (problem) {
370       /* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "krb5_init_context() failed"); */
371       return HTTP_INTERNAL_SERVER_ERROR;
372    }
373
374    problem = krb5_cc_resolve(context, cache_name, &cache);
375    if (problem) {
376       /* log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
377                 "krb5_cc_resolve() failed (%s: %s)",
378                 cache_name, krb5_get_err_text(context, problem)); */
379       return HTTP_INTERNAL_SERVER_ERROR;
380    }
381
382    krb5_cc_destroy(context, cache);
383    krb5_free_context(context);
384    return OK;
385 }
386
387 static int
388 create_krb5_ccache(krb5_context kcontext,
389                    request_rec *r,
390                    kerb_auth_config *conf,
391                    krb5_principal princ,
392                    krb5_ccache *ccache)
393 {
394    char *ccname;
395    krb5_error_code problem;
396    int ret;
397    krb5_ccache tmp_ccache = NULL;
398
399 #ifdef HEIMDAL
400    problem = krb5_cc_gen_new(kcontext, &krb5_fcc_ops, &tmp_ccache);
401 #else
402    problem = krb5_fcc_generate_new(kcontext, &tmp_ccache);
403 #endif
404    if (problem) {
405       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
406                  "Cannot create file for new krb5 ccache: %s",
407                  krb5_get_err_text(kcontext, problem));
408       ret = HTTP_INTERNAL_SERVER_ERROR;
409       goto end;
410    }
411
412    ccname = ap_pstrdup(r->pool, krb5_cc_get_name(kcontext, tmp_ccache));
413
414    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
415    if (problem) {
416       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
417                  "Cannot create krb5 ccache %s: krb5_cc_initialize() failed: %s",
418                  ccname, krb5_get_err_text(kcontext, problem));
419       ret = HTTP_INTERNAL_SERVER_ERROR;
420       goto end;
421    }
422
423    ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
424    ap_register_cleanup(r->pool, ccname,
425                        krb5_cache_cleanup, ap_null_cleanup);
426
427    *ccache = tmp_ccache;
428    tmp_ccache = NULL;
429
430    ret = OK;
431
432 end:
433    if (tmp_ccache)
434       krb5_cc_destroy(kcontext, tmp_ccache);
435
436    return ret;
437 }
438
439 static int
440 store_krb5_creds(krb5_context kcontext,
441                  request_rec *r,
442                  kerb_auth_config *conf,
443                  krb5_ccache delegated_cred)
444 {
445    char errstr[1024];
446    krb5_error_code problem;
447    krb5_principal princ;
448    krb5_ccache ccache;
449    int ret;
450
451    problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
452    if (problem) {
453       snprintf(errstr, sizeof(errstr), "krb5_cc_get_principal() failed: %s",
454                krb5_get_err_text(kcontext, problem));
455       return HTTP_INTERNAL_SERVER_ERROR;
456    }
457
458    ret = create_krb5_ccache(kcontext, r, conf, princ, &ccache);
459    if (ret) {
460       krb5_free_principal(kcontext, princ);
461       return ret;
462    }
463
464 #ifdef HEIMDAL
465    problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache);
466 #else
467    problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
468 #endif
469    krb5_free_principal(kcontext, princ);
470    if (problem) {
471       snprintf(errstr, sizeof(errstr), "Failed to store credentials: %s",
472                krb5_get_err_text(kcontext, problem));
473       krb5_cc_destroy(kcontext, ccache);
474       return HTTP_INTERNAL_SERVER_ERROR;
475    }
476
477    krb5_cc_close(kcontext, ccache);
478    return OK;
479 }
480
481
482 int authenticate_user_krb5pwd(request_rec *r,
483                               kerb_auth_config *conf,
484                               const char *auth_line)
485 {
486    const char      *sent_pw = NULL; 
487    const char      *sent_name = NULL;
488    const char      *realms = NULL;
489    const char      *service_name = NULL;
490    krb5_context    kcontext = NULL;
491    krb5_error_code code;
492    krb5_principal  client = NULL;
493    krb5_ccache     ccache = NULL;
494    int             ret;
495    char *name = NULL;
496
497    code = krb5_init_context(&kcontext);
498    if (code) {
499       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
500                  "Cannot initialize Kerberos5 context (%d)", code);
501       return HTTP_INTERNAL_SERVER_ERROR;
502    }
503
504    sent_pw = ap_pbase64decode(r->pool, auth_line);
505    sent_name = ap_getword (r->pool, &sent_pw, ':');
506    /* do not allow user to override realm setting of server */
507    if (strchr(sent_name, '@')) {
508       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
509                  "specifying realm in user name is prohibited");
510       ret = HTTP_UNAUTHORIZED;
511       goto end;
512    } 
513
514 #ifdef HEIMDAL
515    code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
516 #else
517    code = krb5_mcc_generate_new(kcontext, &ccache);
518 #endif
519    if (code) {
520       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
521                  "Cannot generate new ccache: %s",
522                  krb5_get_err_text(kcontext, code));
523       ret = HTTP_INTERNAL_SERVER_ERROR;
524       goto end;
525    }
526
527    if (conf->krb_5_keytab)
528       setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
529       /* kcontext->default_keytab = conf->krb_5_keytab; */
530
531    if (conf->service_name) {
532       char *p;
533       service_name = ap_pstrdup(r->pool, conf->service_name);
534       if ((p=strchr(service_name, '/')))
535          *p = '\0';
536    } else
537       service_name = "khttp";
538
539    realms = conf->krb_auth_realms;
540    do {
541       if (realms && (code = krb5_set_default_realm(kcontext,
542                                            ap_getword_white(r->pool, &realms))))
543          continue;
544
545       if (client) {
546          krb5_free_principal(kcontext, client);
547          client = NULL;
548       }
549       code = krb5_parse_name(kcontext, sent_name, &client);
550       if (code)
551          continue;
552
553       code = krb5_verify_user(kcontext, client, ccache, sent_pw, 1, 
554                               service_name);
555       if (code == 0)
556          break;
557
558       /* ap_getword_white() used above shifts the parameter, so it's not
559          needed to touch the realms variable */
560    } while (realms && *realms);
561
562    memset((char *)sent_pw, 0, strlen(sent_pw));
563
564    if (code) {
565       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
566                  "Verifying krb5 password failed: %s",
567                  krb5_get_err_text(kcontext, code));
568       ret = HTTP_UNAUTHORIZED;
569       goto end;
570    }
571
572    code = krb5_unparse_name(kcontext, client, &name);
573    if (code) {
574       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s",
575                  krb5_get_err_text(kcontext, code));
576       ret = HTTP_UNAUTHORIZED;
577       goto end;
578    }
579    MK_USER = ap_pstrdup (r->pool, name);
580    MK_AUTH_TYPE = "Basic";
581    free(name);
582
583    if (conf->krb_save_credentials)
584       store_krb5_creds(kcontext, r, conf, ccache);
585
586    ret = OK;
587
588 end:
589    if (client)
590       krb5_free_principal(kcontext, client);
591    if (ccache)
592       krb5_cc_destroy(kcontext, ccache);
593    krb5_free_context(kcontext);
594
595    return ret;
596 }
597
598 /*********************************************************************
599  * GSSAPI Authentication
600  ********************************************************************/
601
602 static const char *
603 get_gss_error(MK_POOL *p, OM_uint32 error_status, char *prefix)
604 {
605    OM_uint32 maj_stat, min_stat;
606    OM_uint32 msg_ctx = 0;
607    gss_buffer_desc status_string;
608    char buf[1024];
609    size_t len;
610
611    snprintf(buf, sizeof(buf), "%s", prefix);
612    len = strlen(buf);
613    do {
614       maj_stat = gss_display_status (&min_stat,
615                                      error_status,
616                                      GSS_C_MECH_CODE,
617                                      GSS_C_NO_OID,
618                                      &msg_ctx,
619                                      &status_string);
620       if (sizeof(buf) > len + status_string.length + 1) {
621          sprintf(buf+len, ": %s", (char*) status_string.value);
622          len += status_string.length;
623       }
624       gss_release_buffer(&min_stat, &status_string);
625    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
626
627    return (ap_pstrdup(p, buf));
628 }
629
630 static int
631 cleanup_gss_connection(void *data)
632 {
633    OM_uint32 minor_status;
634    gss_connection_t *gss_conn = (gss_connection_t *)data;
635
636    if (data == NULL)
637       return OK;
638    if (gss_conn->context != GSS_C_NO_CONTEXT)
639       gss_delete_sec_context(&minor_status, &gss_conn->context,
640                              GSS_C_NO_BUFFER);
641    if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL)
642       gss_release_cred(&minor_status, &gss_conn->server_creds);
643
644    return OK;
645 }
646
647 static int
648 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
649                 gss_cred_id_t delegated_cred)
650 {
651    OM_uint32 maj_stat, min_stat;
652    krb5_principal princ = NULL;
653    krb5_ccache ccache = NULL;
654    krb5_error_code problem;
655    krb5_context context;
656    int ret = HTTP_INTERNAL_SERVER_ERROR;
657
658    problem = krb5_init_context(&context);
659    if (problem) {
660       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
661       return HTTP_INTERNAL_SERVER_ERROR;
662    }
663
664    problem = krb5_parse_name(context, princ_name, &princ);
665    if (problem) {
666       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
667          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
668       goto end;
669    }
670
671    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
672    if (problem) {
673       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
674          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
675       goto end;
676    }
677
678    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
679    if (GSS_ERROR(maj_stat)) {
680       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
681          "Cannot store delegated credential (%s)", 
682          get_gss_error(r->pool, min_stat, "gss_krb5_copy_ccache"));
683       goto end;
684    }
685
686    krb5_cc_close(context, ccache);
687    ccache = NULL;
688    ret = 0;
689
690 end:
691    if (princ)
692       krb5_free_principal(context, princ);
693    if (ccache)
694       krb5_cc_destroy(context, ccache);
695    krb5_free_context(context);
696    return ret;
697 }
698
699 static int
700 get_gss_creds(request_rec *r,
701               kerb_auth_config *conf,
702               gss_cred_id_t *server_creds)
703 {
704    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
705    OM_uint32 major_status, minor_status, minor_status2;
706    gss_name_t server_name = GSS_C_NO_NAME;
707
708    if (conf->service_name) {
709       input_token.value = conf->service_name;
710       input_token.length = strlen(conf->service_name) + 1;
711    }
712    else {
713       input_token.value = "khttp";
714       input_token.length = 6;
715    }
716    major_status = gss_import_name(&minor_status, &input_token,
717                                   (conf->service_name) ? 
718                                        GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE,
719                                   &server_name);
720    if (GSS_ERROR(major_status)) {
721       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
722                  "%s", get_gss_error(r->pool, minor_status,
723                  "gss_import_name() failed"));
724       return HTTP_INTERNAL_SERVER_ERROR;
725    }
726    
727    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
728                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
729                                    server_creds, NULL, NULL);
730    gss_release_name(&minor_status2, &server_name);
731    if (GSS_ERROR(major_status)) {
732       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
733                  "%s", get_gss_error(r->pool, minor_status,
734                                      "gss_acquire_cred() failed"));
735       return HTTP_INTERNAL_SERVER_ERROR;
736    }
737    
738    return 0;
739 }
740
741 static int
742 authenticate_user_gss(request_rec *r,
743                       kerb_auth_config *conf,
744                       const char *auth_line)
745 {
746   OM_uint32 major_status, minor_status, minor_status2;
747   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
748   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
749   const char *auth_param = NULL;
750   int ret;
751   gss_name_t client_name = GSS_C_NO_NAME;
752   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
753   static int initial_return = HTTP_UNAUTHORIZED;
754
755   /* needed to work around replay caches */
756   if (!ap_is_initial_req(r))
757      return initial_return;
758   initial_return = HTTP_UNAUTHORIZED;
759
760   if (gss_connection == NULL) {
761      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
762      if (gss_connection == NULL) {
763         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
764                    "ap_pcalloc() failed (not enough memory)");
765         ret = HTTP_INTERNAL_SERVER_ERROR;
766         goto end;
767      }
768      memset(gss_connection, 0, sizeof(*gss_connection));
769      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
770   }
771
772   if (conf->krb_5_keytab)
773      setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
774
775   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
776      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
777      if (ret)
778         goto end;
779   }
780
781   /* ap_getword() shifts parameter */
782   auth_param = ap_getword_white(r->pool, &auth_line);
783   if (auth_param == NULL) {
784      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
785                 "No Authorization parameter in request from client");
786      ret = HTTP_UNAUTHORIZED;
787      goto end;
788   }
789
790   input_token.length = ap_base64decode_len(auth_param) + 1;
791   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
792   if (input_token.value == NULL) {
793      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
794                 "ap_pcalloc() failed (not enough memory)");
795      ret = HTTP_INTERNAL_SERVER_ERROR;
796      goto end;
797   }
798   input_token.length = ap_base64decode(input_token.value, auth_param);
799
800   major_status = gss_accept_sec_context(&minor_status,
801                                         &gss_connection->context,
802                                         gss_connection->server_creds,
803                                         &input_token,
804                                         GSS_C_NO_CHANNEL_BINDINGS,
805                                         &client_name,
806                                         NULL,
807                                         &output_token,
808                                         NULL,
809                                         NULL,
810                                         &delegated_cred);
811   if (output_token.length) {
812      char *token = NULL;
813      size_t len;
814      
815      len = ap_base64encode_len(output_token.length) + 1;
816      token = ap_pcalloc(r->connection->pool, len + 1);
817      if (token == NULL) {
818         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
819                    "ap_pcalloc() failed (not enough memory)");
820         ret = HTTP_INTERNAL_SERVER_ERROR;
821         gss_release_buffer(&minor_status2, &output_token);
822         goto end;
823      }
824      ap_base64encode(token, output_token.value, output_token.length);
825      token[len] = '\0';
826      ap_table_set(r->err_headers_out, "WWW-Authenticate",
827                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
828      gss_release_buffer(&minor_status2, &output_token);
829   }
830
831   if (GSS_ERROR(major_status)) {
832      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
833                 "%s", get_gss_error(r->pool, minor_status,
834                                     "gss_accept_sec_context() failed"));
835      ret = HTTP_UNAUTHORIZED;
836      goto end;
837   }
838
839   if (major_status & GSS_S_CONTINUE_NEEDED) {
840      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
841       * iterations to establish authentication */
842      ret = HTTP_UNAUTHORIZED;
843      goto end;
844   }
845
846   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
847   gss_release_name(&minor_status, &client_name); 
848   if (GSS_ERROR(major_status)) {
849     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
850                "%s", get_gss_error(r->pool, minor_status, 
851                                    "gss_export_name() failed"));
852     ret = HTTP_INTERNAL_SERVER_ERROR;
853     goto end;
854   }
855
856   MK_AUTH_TYPE = "Negotiate";
857   MK_USER = ap_pstrdup(r->pool, output_token.value);
858
859   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
860      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
861
862   gss_release_buffer(&minor_status, &output_token);
863
864
865 #if 0
866   /* If the user comes from a realm specified by configuration don't include
867       its realm name in the username so that the authorization routine could
868       work for both Password-based and Ticket-based authentication. It's
869       administrators responsibility to include only such realm that have
870       unified principal instances, i.e. if the same principal name occures in
871       multiple realms, it must be always assigned to a single user.
872   */    
873   p = strchr(r->connection->user, '@');
874   if (p != NULL) {
875      const char *realms = conf->gss_krb5_realms;
876
877      while (realms && *realms) {
878         if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
879            *p = '\0';
880            break;
881         }
882      }
883   }
884 #endif
885
886   ret = OK;
887
888 end:
889   if (delegated_cred)
890      gss_release_cred(&minor_status, &delegated_cred);
891
892   if (output_token.length) 
893      gss_release_buffer(&minor_status, &output_token);
894
895   if (client_name != GSS_C_NO_NAME)
896      gss_release_name(&minor_status, &client_name);
897
898   initial_return = ret;
899   return ret;
900 }
901 #endif /* KRB5 */
902
903
904 static void
905 note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf,
906                        int use_krb4, int use_krb5)
907 {
908    const char *auth_name = NULL;
909    int set_basic = 0;
910
911    /* get the user realm specified in .htaccess */
912    auth_name = ap_auth_name(r);
913
914    /* XXX should the WWW-Authenticate header be cleared first? */
915 #ifdef KRB5
916    if (use_krb5 && conf->krb_method_gssapi)
917       ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
918    if (use_krb5 && conf->krb_method_k5pass) {
919       ap_table_add(r->err_headers_out, "WWW-Authenticate",
920                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
921       set_basic = 1;
922    }
923 #endif
924
925 #ifdef KRB4
926    if (use_krb4 && conf->krb_method_k4pass && !set_basic)
927       ap_table_add(r->err_headers_out, "WWW-Authenticate",
928                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
929 #endif
930 }
931
932 int kerb_authenticate_user(request_rec *r)
933 {
934    kerb_auth_config *conf = 
935       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
936                                                 &auth_kerb_module);
937    const char *auth_type = NULL;
938    const char *auth_line = NULL;
939    const char *type = NULL;
940    int use_krb5 = 0, use_krb4 = 0;
941    int ret;
942
943    /* get the type specified in .htaccess */
944    type = ap_auth_type(r);
945
946    if (type && strcasecmp(type, "Kerberos") == 0)
947       use_krb5 = use_krb4 = 1;
948    else if(type && strcasecmp(type, "KerberosV5") == 0)
949       use_krb4 = 0;
950    else if(type && strcasecmp(type, "KerberosV4") == 0)
951       use_krb5 = 0;
952    else
953       return DECLINED;
954
955    /* get what the user sent us in the HTTP header */
956    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
957    if (!auth_line) {
958       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
959       return HTTP_UNAUTHORIZED;
960    }
961    auth_type = ap_getword_white(r->pool, &auth_line);
962
963    ret = HTTP_UNAUTHORIZED;
964
965 #ifdef KRB5
966    if (use_krb5 && conf->krb_method_gssapi &&
967        strcasecmp(auth_type, "GSS-Negotiate") == 0) {
968       ret = authenticate_user_gss(r, conf, auth_line);
969    } else if (use_krb5 && conf->krb_method_k5pass &&
970               strcasecmp(auth_type, "Basic") == 0) {
971        ret = authenticate_user_krb5pwd(r, conf, auth_line);
972    }
973 #endif
974
975 #ifdef KRB4
976    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
977        strcasecmp(auth_type, "Basic") == 0)
978       ret = authenticate_user_krb4pwd(r, conf, auth_line);
979 #endif
980
981    if (ret == HTTP_UNAUTHORIZED)
982       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
983
984    return ret;
985 }
986
987
988 /*************************************************************************** 
989  Module Setup/Configuration
990  ***************************************************************************/
991 #ifdef APXS1
992 module MODULE_VAR_EXPORT auth_kerb_module = {
993         STANDARD_MODULE_STUFF,
994         NULL,                           /*      module initializer            */
995         kerb_dir_create_config,         /*      per-directory config creator  */
996         NULL,                           /*      per-directory config merger   */
997         NULL,                           /*      per-server    config creator  */
998         NULL,                           /*      per-server    config merger   */
999         kerb_auth_cmds,                 /*      command table                 */
1000         NULL,                           /* [ 9] content handlers              */
1001         NULL,                           /* [ 2] URI-to-filename translation   */
1002         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1003         NULL,                           /* [ 6] check user_id is valid *here* */
1004         NULL,                           /* [ 4] check access by host address  */
1005         NULL,                           /* [ 7] MIME type checker/setter      */
1006         NULL,                           /* [ 8] fixups                        */
1007         NULL,                           /* [10] logger                        */
1008         NULL,                           /* [ 3] header parser                 */
1009         NULL,                           /*      process initialization        */
1010         NULL,                           /*      process exit/cleanup          */
1011         NULL                            /* [ 1] post read_request handling    */
1012 };
1013 #else
1014 void kerb_register_hooks(apr_pool_t *p)
1015 {
1016    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1017 }
1018
1019 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1020 {
1021    STANDARD20_MODULE_STUFF,
1022    kerb_dir_create_config,      /* create per-dir    conf structures  */
1023    NULL,                        /* merge  per-dir    conf structures  */
1024    NULL,                        /* create per-server conf structures  */
1025    NULL,                        /* merge  per-server conf structures  */
1026    kerb_auth_cmds,              /* table of configuration directives  */
1027    kerb_register_hooks          /* register hooks                     */
1028 };
1029 #endif