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