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