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