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