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