- added setting flag KRB5_TC_OPENCLOSE to make krb5_initialize happy
[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         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    return OK;
647 }
648
649 static int
650 store_gss_creds(request_rec *r, kerb_auth_config *conf, char *princ_name,
651                 gss_cred_id_t delegated_cred)
652 {
653    OM_uint32 maj_stat, min_stat;
654    krb5_principal princ = NULL;
655    krb5_ccache ccache = NULL;
656    krb5_error_code problem;
657    krb5_context context;
658    int ret = HTTP_INTERNAL_SERVER_ERROR;
659
660    problem = krb5_init_context(&context);
661    if (problem) {
662       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Cannot initialize krb5 context");
663       return HTTP_INTERNAL_SERVER_ERROR;
664    }
665
666    problem = krb5_parse_name(context, princ_name, &princ);
667    if (problem) {
668       log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
669          "Cannot parse delegated username (%s)", krb5_get_err_text(context, problem));
670       goto end;
671    }
672
673    problem = create_krb5_ccache(context, r, conf, princ, &ccache);
674    if (problem) {
675       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
676          "Cannot create krb5 ccache (%s)", krb5_get_err_text(context, problem));
677       goto end;
678    }
679
680    maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache);
681    if (GSS_ERROR(maj_stat)) {
682       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
683          "Cannot store delegated credential (%s)", 
684          get_gss_error(r->pool, min_stat, "gss_krb5_copy_ccache"));
685       goto end;
686    }
687
688    krb5_cc_close(context, ccache);
689    ccache = NULL;
690    ret = 0;
691
692 end:
693    if (princ)
694       krb5_free_principal(context, princ);
695    if (ccache)
696       krb5_cc_destroy(context, ccache);
697    krb5_free_context(context);
698    return ret;
699 }
700
701 static int
702 get_gss_creds(request_rec *r,
703               kerb_auth_config *conf,
704               gss_cred_id_t *server_creds)
705 {
706    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
707    OM_uint32 major_status, minor_status, minor_status2;
708    gss_name_t server_name = GSS_C_NO_NAME;
709
710    if (conf->service_name) {
711       input_token.value = conf->service_name;
712       input_token.length = strlen(conf->service_name) + 1;
713    }
714    else {
715       input_token.value = "khttp";
716       input_token.length = 6;
717    }
718    major_status = gss_import_name(&minor_status, &input_token,
719                                   (conf->service_name) ? 
720                                        GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE,
721                                   &server_name);
722    if (GSS_ERROR(major_status)) {
723       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
724                  "%s", get_gss_error(r->pool, minor_status,
725                  "gss_import_name() failed"));
726       return HTTP_INTERNAL_SERVER_ERROR;
727    }
728    
729    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
730                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
731                                    server_creds, NULL, NULL);
732    gss_release_name(&minor_status2, &server_name);
733    if (GSS_ERROR(major_status)) {
734       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
735                  "%s", get_gss_error(r->pool, minor_status,
736                                      "gss_acquire_cred() failed"));
737       return HTTP_INTERNAL_SERVER_ERROR;
738    }
739    
740    return 0;
741 }
742
743 static int
744 authenticate_user_gss(request_rec *r,
745                       kerb_auth_config *conf,
746                       const char *auth_line)
747 {
748   OM_uint32 major_status, minor_status, minor_status2;
749   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
750   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
751   const char *auth_param = NULL;
752   int ret;
753   gss_name_t client_name = GSS_C_NO_NAME;
754   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
755   static int initial_return = HTTP_UNAUTHORIZED;
756
757   /* needed to work around replay caches */
758   if (!ap_is_initial_req(r))
759      return initial_return;
760   initial_return = HTTP_UNAUTHORIZED;
761
762   if (gss_connection == NULL) {
763      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
764      if (gss_connection == NULL) {
765         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
766                    "ap_pcalloc() failed (not enough memory)");
767         ret = HTTP_INTERNAL_SERVER_ERROR;
768         goto end;
769      }
770      memset(gss_connection, 0, sizeof(*gss_connection));
771      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
772   }
773
774   if (conf->krb_5_keytab)
775      setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
776
777   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
778      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
779      if (ret)
780         goto end;
781   }
782
783   /* ap_getword() shifts parameter */
784   auth_param = ap_getword_white(r->pool, &auth_line);
785   if (auth_param == NULL) {
786      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
787                 "No Authorization parameter in request from client");
788      ret = HTTP_UNAUTHORIZED;
789      goto end;
790   }
791
792   input_token.length = ap_base64decode_len(auth_param) + 1;
793   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
794   if (input_token.value == NULL) {
795      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
796                 "ap_pcalloc() failed (not enough memory)");
797      ret = HTTP_INTERNAL_SERVER_ERROR;
798      goto end;
799   }
800   input_token.length = ap_base64decode(input_token.value, auth_param);
801
802   major_status = gss_accept_sec_context(&minor_status,
803                                         &gss_connection->context,
804                                         gss_connection->server_creds,
805                                         &input_token,
806                                         GSS_C_NO_CHANNEL_BINDINGS,
807                                         &client_name,
808                                         NULL,
809                                         &output_token,
810                                         NULL,
811                                         NULL,
812                                         &delegated_cred);
813   if (output_token.length) {
814      char *token = NULL;
815      size_t len;
816      
817      len = ap_base64encode_len(output_token.length) + 1;
818      token = ap_pcalloc(r->connection->pool, len + 1);
819      if (token == NULL) {
820         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
821                    "ap_pcalloc() failed (not enough memory)");
822         ret = HTTP_INTERNAL_SERVER_ERROR;
823         gss_release_buffer(&minor_status2, &output_token);
824         goto end;
825      }
826      ap_base64encode(token, output_token.value, output_token.length);
827      token[len] = '\0';
828      ap_table_set(r->err_headers_out, "WWW-Authenticate",
829                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
830      gss_release_buffer(&minor_status2, &output_token);
831   }
832
833   if (GSS_ERROR(major_status)) {
834      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
835                 "%s", get_gss_error(r->pool, minor_status,
836                                     "gss_accept_sec_context() failed"));
837      ret = HTTP_UNAUTHORIZED;
838      goto end;
839   }
840
841   if (major_status & GSS_S_CONTINUE_NEEDED) {
842      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
843       * iterations to establish authentication */
844      ret = HTTP_UNAUTHORIZED;
845      goto end;
846   }
847
848   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
849   gss_release_name(&minor_status, &client_name); 
850   if (GSS_ERROR(major_status)) {
851     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
852                "%s", get_gss_error(r->pool, minor_status, 
853                                    "gss_export_name() failed"));
854     ret = HTTP_INTERNAL_SERVER_ERROR;
855     goto end;
856   }
857
858   MK_AUTH_TYPE = "Negotiate";
859   MK_USER = ap_pstrdup(r->pool, output_token.value);
860
861   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
862      store_gss_creds(r, conf, (char *)output_token.value, delegated_cred);
863
864   gss_release_buffer(&minor_status, &output_token);
865
866
867 #if 0
868   /* If the user comes from a realm specified by configuration don't include
869       its realm name in the username so that the authorization routine could
870       work for both Password-based and Ticket-based authentication. It's
871       administrators responsibility to include only such realm that have
872       unified principal instances, i.e. if the same principal name occures in
873       multiple realms, it must be always assigned to a single user.
874   */    
875   p = strchr(r->connection->user, '@');
876   if (p != NULL) {
877      const char *realms = conf->gss_krb5_realms;
878
879      while (realms && *realms) {
880         if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
881            *p = '\0';
882            break;
883         }
884      }
885   }
886 #endif
887
888   ret = OK;
889
890 end:
891   if (delegated_cred)
892      gss_release_cred(&minor_status, &delegated_cred);
893
894   if (output_token.length) 
895      gss_release_buffer(&minor_status, &output_token);
896
897   if (client_name != GSS_C_NO_NAME)
898      gss_release_name(&minor_status, &client_name);
899
900   initial_return = ret;
901   return ret;
902 }
903 #endif /* KRB5 */
904
905
906 static void
907 note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf,
908                        int use_krb4, int use_krb5)
909 {
910    const char *auth_name = NULL;
911    int set_basic = 0;
912
913    /* get the user realm specified in .htaccess */
914    auth_name = ap_auth_name(r);
915
916    /* XXX should the WWW-Authenticate header be cleared first? */
917 #ifdef KRB5
918    if (use_krb5 && conf->krb_method_gssapi)
919       ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
920    if (use_krb5 && conf->krb_method_k5pass) {
921       ap_table_add(r->err_headers_out, "WWW-Authenticate",
922                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
923       set_basic = 1;
924    }
925 #endif
926
927 #ifdef KRB4
928    if (use_krb4 && conf->krb_method_k4pass && !set_basic)
929       ap_table_add(r->err_headers_out, "WWW-Authenticate",
930                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
931 #endif
932 }
933
934 int kerb_authenticate_user(request_rec *r)
935 {
936    kerb_auth_config *conf = 
937       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
938                                                 &auth_kerb_module);
939    const char *auth_type = NULL;
940    const char *auth_line = NULL;
941    const char *type = NULL;
942    int use_krb5 = 0, use_krb4 = 0;
943    int ret;
944
945    /* get the type specified in .htaccess */
946    type = ap_auth_type(r);
947
948    if (type && strcasecmp(type, "Kerberos") == 0)
949       use_krb5 = use_krb4 = 1;
950    else if(type && strcasecmp(type, "KerberosV5") == 0)
951       use_krb4 = 0;
952    else if(type && strcasecmp(type, "KerberosV4") == 0)
953       use_krb5 = 0;
954    else
955       return DECLINED;
956
957    /* get what the user sent us in the HTTP header */
958    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
959    if (!auth_line) {
960       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
961       return HTTP_UNAUTHORIZED;
962    }
963    auth_type = ap_getword_white(r->pool, &auth_line);
964
965    ret = HTTP_UNAUTHORIZED;
966
967 #ifdef KRB5
968    if (use_krb5 && conf->krb_method_gssapi &&
969        strcasecmp(auth_type, "GSS-Negotiate") == 0) {
970       ret = authenticate_user_gss(r, conf, auth_line);
971    } else if (use_krb5 && conf->krb_method_k5pass &&
972               strcasecmp(auth_type, "Basic") == 0) {
973        ret = authenticate_user_krb5pwd(r, conf, auth_line);
974    }
975 #endif
976
977 #ifdef KRB4
978    if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
979        strcasecmp(auth_type, "Basic") == 0)
980       ret = authenticate_user_krb4pwd(r, conf, auth_line);
981 #endif
982
983    if (ret == HTTP_UNAUTHORIZED)
984       note_kerb_auth_failure(r, conf, use_krb4, use_krb5);
985
986    return ret;
987 }
988
989
990 /*************************************************************************** 
991  Module Setup/Configuration
992  ***************************************************************************/
993 #ifdef APXS1
994 module MODULE_VAR_EXPORT auth_kerb_module = {
995         STANDARD_MODULE_STUFF,
996         NULL,                           /*      module initializer            */
997         kerb_dir_create_config,         /*      per-directory config creator  */
998         NULL,                           /*      per-directory config merger   */
999         NULL,                           /*      per-server    config creator  */
1000         NULL,                           /*      per-server    config merger   */
1001         kerb_auth_cmds,                 /*      command table                 */
1002         NULL,                           /* [ 9] content handlers              */
1003         NULL,                           /* [ 2] URI-to-filename translation   */
1004         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1005         NULL,                           /* [ 6] check user_id is valid *here* */
1006         NULL,                           /* [ 4] check access by host address  */
1007         NULL,                           /* [ 7] MIME type checker/setter      */
1008         NULL,                           /* [ 8] fixups                        */
1009         NULL,                           /* [10] logger                        */
1010         NULL,                           /* [ 3] header parser                 */
1011         NULL,                           /*      process initialization        */
1012         NULL,                           /*      process exit/cleanup          */
1013         NULL                            /* [ 1] post read_request handling    */
1014 };
1015 #else
1016 void kerb_register_hooks(apr_pool_t *p)
1017 {
1018    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1019 }
1020
1021 module AP_MODULE_DECLARE_DATA auth_kerb_module =
1022 {
1023    STANDARD20_MODULE_STUFF,
1024    kerb_dir_create_config,      /* create per-dir    conf structures  */
1025    NULL,                        /* merge  per-dir    conf structures  */
1026    NULL,                        /* create per-server conf structures  */
1027    NULL,                        /* merge  per-server conf structures  */
1028    kerb_auth_cmds,              /* table of configuration directives  */
1029    kerb_register_hooks          /* register hooks                     */
1030 };
1031 #endif