Various code rearrangements to achieve portable password verification.
[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 #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_creds my_creds;
449    krb5_data tgtname = {
450       0,
451       KRB5_TGS_NAME_SIZE,
452       KRB5_TGS_NAME
453    }
454
455    memset((char *)&my_creds, 0, sizeof(my_creds));
456    my_creds.client = principal;
457
458    if (krb5_build_principal_ext(kcontext, &server,
459                                 krb5_princ_realm(kcontext, me)->length,
460                                 krb5_princ_realm(kcontext, me)->data,
461                                 tgtname.length, tgtname.data,
462                                 krb5_princ_realm(kcontext, me)->length,
463                                 krb5_princ_realm(kcontext, me)->data,
464                                 0)) {
465         return ret;
466    }
467
468    my_creds.server = server;
469    if (krb5_timeofday(kcontext, &now))
470         return -1;
471
472    my_creds.times.starttime = 0;
473    /* XXX
474    my_creds.times.endtime = now + lifetime;
475    my_creds.times.renew_till = now + renewal;
476    */
477
478    ret = krb5_get_in_tkt_with_password(kcontext, options, 0, NULL, 0,
479                                        pass, ccache, &my_creds, 0);
480    if (ret) {
481         return ret;
482    }
483
484    return 0;
485 }
486 #endif
487
488
489 /*************************************************************************** 
490  Username/Password Validation
491  ***************************************************************************/
492 #ifdef KRB5
493 int kerb5_password_validate(request_rec *r, const char *user, const char *pass)
494 {
495         kerb_auth_config *conf =
496                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
497                                         &kerb_auth_module);
498         int ret;
499         krb5_context kcontext;
500         krb5_principal server, me;
501         krb5_creds my_creds;
502         krb5_timestamp now;
503         krb5_ccache ccache = NULL;
504         krb5_deltat lifetime = 300;     /* 5 minutes */
505         krb5_deltat renewal = 0;
506         krb5_flags options = 0;
507         krb5_data tgtname = {
508 #ifndef HEIMDAL
509                 0,
510 #endif
511                 KRB5_TGS_NAME_SIZE,
512                 KRB5_TGS_NAME
513         };
514         char *c, ccname[MAX_STRING_LEN];
515
516         if (krb5_init_context(&kcontext))
517                 return 0;
518
519         if (conf->krb_forwardable) {
520            options |= KDC_OPT_FORWARDABLE;
521         }
522
523         if (conf->krb_renewable) {
524            options |= KDC_OPT_RENEWABLE;
525            renewal = 86400;        /* 24 hours */
526         }
527
528         if (conf->krb_lifetime) {
529            lifetime = atoi(conf->krb_lifetime);
530         }
531
532         code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
533         if (code) {
534            snprintf(errstr, sizeof(errstr), "krb5_cc_gen_new(): %.100s",
535                     krb5_get_err_text(kcontext, code));
536            ap_log_reason (errstr, r->uri, r);
537            ret = SERVER_ERROR;
538            goto end;
539         }
540
541         realms = conf->krb5_auth_realm;
542         do {
543            code = 0;
544            if (realms) {
545               code = krb5_set_default_realm(kcontext, 
546                                             ap_getword_white(r->pool, &realms));
547               if (code)
548                  continue;
549            }
550
551            code = krb5_parse_name(kcontext, r->connection->user, &princ);
552            if (code)
553               continue;
554
555            code = krb5_verify_user(kcontext, princ, ccache, sent_pw,
556                                    1, "khttp");
557            if (code == 0)
558               break;
559
560            /* ap_getword_white() used above shifts the parameter, so it's not
561               needed to touch the realms variable */
562         } while (realms && *realms);
563
564         memset((char *)pass, 0, strlen(pass));
565
566         if (code) {
567            snprintf(errstr, sizeof(errstr), "Verifying krb5 password failed: %s",
568                     krb5_get_err_text(kcontext, code));
569            ap_log_reason (errstr, r->uri, r);
570            ret = HTTP_UNAUTHORIZED;
571            return 0;
572         }
573
574         if (conf->krb_save_credentials) {
575                 sprintf(ccname, "FILE:%s/k5cc_ap_%s",
576                         conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp",
577                         MK_USER);
578
579                 for (c = ccname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir :                                "/tmp") + 1; *c; c++) {
580                         if (*c == '/')
581                                 *c = '.';
582                 }
583
584                 ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
585                 if (krb5_cc_set_default_name(kcontext, ccname)) {
586                         return 0;
587                 }
588                 unlink(ccname+strlen("FILE:"));
589
590                 if (krb5_cc_resolve(kcontext, ccname, &ccache))
591                         return 0;
592
593                 problem = krb5_cc_get_principal(krb_ctx, mem_ccache, &me);
594
595                 if (krb5_cc_initialize(kcontext, ccache, me))
596                         return 0;
597
598                 problem = krb5_cc_copy_cache(krb_ctx, delegated_cred, ccache);
599                 if (problem) {
600                    return 0;
601                 }
602
603                 krb5_cc_close(krb_ctx, ccache);
604         }
605
606         return 1;
607 }
608 #endif /* KRB5 */
609
610 #ifdef KRB4
611 int kerb4_password_validate(request_rec *r, const char *user, const char *pass)
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         int lifetime = DEFAULT_TKT_LIFE;
618         char *c, *tfname;
619         char *username = NULL;
620         char *instance = NULL;
621         char *realm = NULL;
622
623         username = (char *)ap_pstrdup(r->pool, user);
624         if (!username) {
625                 return 0;
626         }
627
628         instance = strchr(username, '.');
629         if (instance) {
630                 *instance++ = '\0';
631         }
632         else {
633                 instance = "";
634         }
635
636         realm = strchr(username, '@');
637         if (realm) {
638                 *realm++ = '\0';
639         }
640         else {
641                 realm = "";
642         }
643
644         if (conf->krb_lifetime) {
645                 lifetime = atoi(conf->krb_lifetime);
646         }
647
648         if (conf->krb_force_instance) {
649                 instance = conf->krb_force_instance;
650         }
651
652         if (conf->krb_save_credentials) {
653                 tfname = (char *)malloc(sizeof(char) * MAX_STRING_LEN);
654                 sprintf(tfname, "%s/k5cc_ap_%s",
655                         conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp",
656                         MK_USER);
657
658                 if (!strcmp(instance, "")) {
659                         tfname = strcat(tfname, ".");
660                         tfname = strcat(tfname, instance);
661                 }
662
663                 if (!strcmp(realm, "")) {
664                         tfname = strcat(tfname, ".");
665                         tfname = strcat(tfname, realm);
666                 }
667
668                 for (c = tfname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir :
669                                 "/tmp") + 1; *c; c++) {
670                         if (*c == '/')
671                                 *c = '.';
672                 }
673
674                 krb_set_tkt_string(tfname);
675         }
676
677         if (!strcmp(realm, "")) {
678                 realm = (char *)malloc(sizeof(char) * (REALM_SZ + 1));
679                 ret = krb_get_lrealm(realm, 1);
680                 if (ret != KSUCCESS)
681                         return 0;
682         }
683
684         ret = krb_get_pw_in_tkt((char *)user, instance, realm, "krbtgt", realm,
685                                         lifetime, (char *)pass);
686         switch (ret) {
687                 case INTK_OK:
688                 case INTK_W_NOTALL:
689                         return 1;
690                         break;
691
692                 default:
693                         return 0;
694                         break;
695         }
696 }
697 #endif /* KRB4 */
698
699 static int
700 get_gss_creds(request_rec *r,
701               kerb_auth_config *conf,
702               gss_cred_id_t *server_creds)
703 {
704    int ret = 0;
705    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
706    OM_uint32 major_status, minor_status;
707    gss_name_t server_name = GSS_C_NO_NAME;
708
709    if (conf->service_name) {
710       input_token.value = conf->service_name;
711       input_token.length = strlen(conf->service_name) + 1;
712    }
713    else {
714       input_token.value = "khttp";
715       input_token.length = 6;
716    }
717    major_status = gss_import_name(&minor_status, &input_token,
718                                   (conf->service_name) ? 
719                                        GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE,
720                                   &server_name);
721    if (GSS_ERROR(major_status)) {
722       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
723                     "%s", get_gss_error(r->pool, minor_status,
724                     "gss_import_name() failed"));
725       ret = SERVER_ERROR;
726       goto fail;
727    }
728    
729 #ifdef KRB5
730    if (conf->krb_5_keytab)
731       setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
732 #endif
733
734    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
735                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
736                                    server_creds, NULL, NULL);
737 #ifdef KRB5
738    if (conf->krb_5_keytab)
739       unsetenv("KRB5_KTNAME");
740 #endif
741    if (GSS_ERROR(major_status)) {
742       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
743                    "%s", get_gss_error(r->pool, minor_status,
744                                        "gss_acquire_cred() failed"));
745       ret = SERVER_ERROR;
746       goto fail;
747    }
748    
749    return 0;
750
751 fail:
752    /* XXX cleanup */
753
754    return ret;
755 }
756
757 static int
758 negotiate_authenticate_user(request_rec *r,
759                             kerb_auth_config *conf,
760                             const char *auth_line)
761 {
762   OM_uint32 major_status, minor_status, minor_status2;
763   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
764   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
765   const char *auth_param = NULL;
766   krb5_context krb_ctx = NULL;
767   int ret;
768   gss_name_t client_name = GSS_C_NO_NAME;
769   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
770   char *p;
771
772   if (gss_connection == NULL) {
773      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
774      if (gss_connection == NULL) {
775         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
776                       "ap_pcalloc() failed");
777         ret = SERVER_ERROR;
778         goto end;
779      }
780      memset(gss_connection, 0, sizeof(*gss_connection));
781      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
782   }
783
784   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
785      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
786      if (ret)
787         goto end;
788   }
789
790   /* ap_getword() shifts parameter */
791   auth_param = ap_getword_white(r->pool, &auth_line);
792   if (auth_param == NULL) {
793      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
794                    "No Authorization parameter from client");
795      ret = HTTP_UNAUTHORIZED;
796      goto end;
797   }
798
799   input_token.length = ap_base64decode_len(auth_param);
800   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
801   if (input_token.value == NULL) {
802      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
803                    "Not enough memory");
804      ret = SERVER_ERROR;
805      goto end;
806   }
807   input_token.length = ap_base64decode(input_token.value, auth_param);
808
809   major_status = gss_accept_sec_context(&minor_status,
810                                         &gss_connection->context,
811                                         gss_connection->server_creds,
812                                         &input_token,
813                                         GSS_C_NO_CHANNEL_BINDINGS,
814                                         &client_name,
815                                         NULL,
816                                         &output_token,
817                                         NULL,
818                                         NULL,
819                                         &delegated_cred);
820   if (output_token.length) {
821      char *token = NULL;
822      size_t len;
823      
824      len = ap_base64encode_len(output_token.length);
825      token = ap_pcalloc(r->connection->pool, len + 1);
826      if (token == NULL) {
827         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
828                      "Not enough memory");
829         ret = SERVER_ERROR;
830         gss_release_buffer(&minor_status2, &output_token);
831         goto end;
832      }
833      ap_base64encode(token, output_token.value, output_token.length);
834      token[len] = '\0';
835      ap_table_set(r->err_headers_out, "WWW-Authenticate",
836                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
837      free(token);
838      gss_release_buffer(&minor_status2, &output_token);
839   }
840
841   if (GSS_ERROR(major_status)) {
842      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
843                    "%s", get_gss_error(r->pool, minor_status,
844                                        "gss_accept_sec_context() failed"));
845      ret = HTTP_UNAUTHORIZED;
846      goto end;
847   }
848
849   if (major_status & GSS_S_CONTINUE_NEEDED) {
850 #if 0
851      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
852                    "only one authentication iteration allowed"); 
853 #endif
854      ret = HTTP_UNAUTHORIZED;
855      goto end;
856   }
857
858   major_status = gss_export_name(&minor_status, client_name, &output_token);
859   gss_release_name(&minor_status, &client_name); 
860   if (GSS_ERROR(major_status)) {
861     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
862                   "%s", get_gss_error(r->pool, minor_status, 
863                                       "gss_export_name() failed"));
864     ret = SERVER_ERROR;
865     goto end;
866   }
867
868   r->connection->ap_auth_type = "Negotiate";
869   r->connection->user = ap_pstrdup(r->pool, output_token.value);
870 #if 0
871   /* If the user comes from a realm specified by configuration don't include
872       its realm name in the username so that the authorization routine could
873       work for both Password-based and Ticket-based authentication. It's
874       administrators responsibility to include only such realm that have
875       unified principal instances, i.e. if the same principal name occures in
876       multiple realms, it must be always assigned to a single user.
877   */    
878   p = strchr(r->connection->user, '@');
879   if (p != NULL) {
880      const char *realms = conf->gss_krb5_realms;
881
882      while (realms && *realms) {
883         if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
884            *p = '\0';
885            break;
886         }
887      }
888   }
889 #endif
890
891   gss_release_buffer(&minor_status, &output_token);
892
893 #if 0
894   /* This should be only done if afs token are requested or gss_save creds is 
895    * specified */
896   /* gss_export_cred() from the GGF GSS Extensions could be used */
897   if (delegated_cred != GSS_C_NO_CREDENTIAL &&
898       (conf->gss_save_creds || (conf->gss_krb5_cells && k_hasafs()))) { 
899      krb5_init_context(&krb_ctx);
900      do_afs_log(krb_ctx, r, delegated_cred->ccache, conf->gss_krb5_cells);
901      ret = store_krb5_creds(krb_ctx, r, conf, delegated_cred->ccache);
902      krb5_free_context(krb_ctx);
903      if (ret)
904         goto end;
905   }
906 #endif
907   ret = OK;
908
909 end:
910   if (delegated_cred)
911      gss_release_cred(&minor_status, &delegated_cred);
912
913   if (output_token.length) 
914      gss_release_buffer(&minor_status, &output_token);
915
916   if (client_name != GSS_C_NO_NAME)
917      gss_release_name(&minor_status, &client_name);
918
919   return ret;
920 }
921
922
923 /*************************************************************************** 
924  User Authentication
925  ***************************************************************************/
926 int kerb_authenticate_user(request_rec *r)
927 {
928         const char *name;               /* AuthName specified */
929         const char *type;               /* AuthType specified */
930         int KerberosV5 = 0;             /* Kerberos V5 check enabled */
931         int KerberosV4 = 0;             /* Kerberos V4 check enabled */
932         int KerberosV4first = 0;        /* Kerberos V4 check first */
933         const char *sent_pw;            /* Password sent by browser */
934         int res;                        /* Response holder */
935         int retcode;                    /* Return code holder */
936         const char *t;                  /* Decoded auth_line */
937         const char *authtype;           /* AuthType to send back to browser */
938         const char *auth_line = MK_TABLE_GET(r->headers_in,
939                                         (r->proxyreq == MK_PROXY)
940                                                 ? "Proxy-Authorization"
941                                                 : "Authorization");
942         kerb_auth_config *conf =
943                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
944                                         &kerb_auth_module);
945
946         type = ap_auth_type(r);
947
948         if (type != NULL) {
949 #ifdef KRB5
950                 if ((strncasecmp(type, "KerberosV5", 10) == 0) ||
951                     (strncasecmp(conf->krb_auth_type, "KerberosV5", 10) == 0)) {
952                         KerberosV5 = 1;
953                 }
954 #endif /* KRB5 */
955
956 #ifdef KRB4
957                 if ((strncasecmp(type, "KerberosV4", 10) == 0) ||
958                     (strncasecmp(conf->krb_auth_type, "KerberosV4", 10) == 0)) {
959                         KerberosV4 = 1;
960                 }
961 #endif /* KRB4 */
962
963 #if defined(KRB5) && defined(KRB4)
964                 if ((strncasecmp(type, "KerberosDualV5V4", 15) == 0) ||
965                     (strncasecmp(conf->krb_auth_type, "KerberosDualV5V4", 15) == 0)) {
966                         KerberosV5 = 1;
967                         KerberosV4 = 1;
968                 }
969
970                 if ((strncasecmp(type, "KerberosDualV4V5", 15) == 0) ||
971                     (strncasecmp(conf->krb_auth_type, "KerberosDualV4V5", 15) == 0)) {
972                         KerberosV5 = 1;
973                         KerberosV4 = 1;
974                         KerberosV4first = 1;
975                 }
976 #endif /* KRB5 && KRB4 */
977         }
978
979         if (!KerberosV4 && !KerberosV5) {
980                 if (conf->krb_authoritative) {
981                         return HTTP_UNAUTHORIZED;
982                 }
983                 else {
984                         return DECLINED;
985                 }
986         }
987
988         name = ap_auth_name(r);
989         if (!name) {
990                 return HTTP_INTERNAL_SERVER_ERROR;
991         }
992
993         if (!auth_line) {
994                 MK_TABLE_SET(r->err_headers_out, "WWW-Authenticate",
995                         (char *)ap_pstrcat(r->pool,
996                         "Basic realm=\"", name, "\"", NULL));
997                 return HTTP_UNAUTHORIZED;
998         }
999
1000         type = ap_getword_white(r->pool, &auth_line);
1001         t = ap_pbase64decode(r->pool, auth_line);
1002         MK_USER = ap_getword_nulls(r->pool, &t, ':');
1003         MK_AUTH_TYPE = "Kerberos";
1004         sent_pw = ap_getword_white(r->pool, &t);
1005
1006         retcode = DECLINED;
1007
1008 #ifdef KRB5
1009         if (KerberosV5 && !KerberosV4first && retcode != OK) {
1010                 MK_AUTH_TYPE = "KerberosV5";
1011                 if (kerb5_password_validate(r, MK_USER, sent_pw)) {
1012                         retcode = OK;
1013                 }
1014                 else {
1015                         retcode = conf->krb_fail_status;
1016                 }
1017         }
1018 #endif /* KRB5 */
1019
1020 #ifdef KRB4
1021         if (KerberosV4 && retcode != OK) {
1022                 MK_AUTH_TYPE = "KerberosV4";
1023                 if (kerb4_password_validate(r, MK_USER, sent_pw)) {
1024                         retcode = OK;
1025                 }
1026                 else {
1027                         retcode = conf->krb_fail_status;
1028                 }
1029         }
1030 #endif /* KRB4 */
1031
1032 #if defined(KRB5) && defined(KRB4)
1033         if (KerberosV5 && KerberosV4first && retcode != OK) {
1034                 MK_AUTH_TYPE = "KerberosV5";
1035                 if (kerb5_password_validate(r, MK_USER, sent_pw)) {
1036                         retcode = OK;
1037                 }
1038                 else {
1039                         retcode = conf->krb_fail_status;
1040                 }
1041         }
1042 #endif /* KRB5 && KRB4 */
1043
1044         if (conf->krb_authoritative && retcode == DECLINED) {
1045                 return HTTP_UNAUTHORIZED;
1046         }
1047         else {
1048                 return retcode;
1049         }
1050 }
1051
1052
1053
1054
1055 /*************************************************************************** 
1056  Access Verification
1057  ***************************************************************************/
1058 int kerb_check_user_access(request_rec *r)
1059 {
1060         register int x;
1061         const char *t, *w;
1062         const MK_ARRAY_HEADER *reqs_arr = ap_requires(r);
1063         require_line *reqs;
1064         kerb_auth_config *conf =
1065                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
1066                                                 &kerb_auth_module);
1067
1068         if (reqs_arr == NULL) {
1069                 return OK;
1070         }
1071         reqs = (require_line *)reqs_arr->elts;
1072
1073         for (x = 0; x < reqs_arr->nelts; x++) {
1074                 t = reqs[x].requirement;
1075                 w = ap_getword_white(r->pool, &t);
1076                 if (strcmp(w, "realm") == 0) {
1077                         while (t[0] != '\0') {
1078                                 w = ap_getword_conf(r->pool, &t);
1079                                 if (strcmp(MK_USER, w) == 0) {
1080                                         return OK;
1081                                 }
1082                         }
1083                 }
1084         }
1085
1086         return DECLINED;
1087 }
1088
1089
1090
1091
1092 /*************************************************************************** 
1093  Module Setup/Configuration
1094  ***************************************************************************/
1095 #ifdef APXS1
1096 module MODULE_VAR_EXPORT kerb_auth_module = {
1097         STANDARD_MODULE_STUFF,
1098         NULL,                           /*      module initializer            */
1099         kerb_dir_config,                /*      per-directory config creator  */
1100         NULL,                           /*      per-directory config merger   */
1101         NULL,                           /*      per-server    config creator  */
1102         NULL,                           /*      per-server    config merger   */
1103         kerb_auth_cmds,                 /*      command table                 */
1104         NULL,                           /* [ 9] content handlers              */
1105         NULL,                           /* [ 2] URI-to-filename translation   */
1106         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
1107         kerb_check_user_access,         /* [ 6] check user_id is valid *here* */
1108         NULL,                           /* [ 4] check access by host address  */
1109         NULL,                           /* [ 7] MIME type checker/setter      */
1110         NULL,                           /* [ 8] fixups                        */
1111         NULL,                           /* [10] logger                        */
1112         NULL,                           /* [ 3] header parser                 */
1113         NULL,                           /*      process initialization        */
1114         NULL,                           /*      process exit/cleanup          */
1115         NULL                            /* [ 1] post read_request handling    */
1116 #ifdef EAPI
1117         ,                               /*            EAPI Additions          */
1118         NULL,                           /* EAPI add module                    */
1119         NULL,                           /* EAPI remove module                 */
1120         NULL,                           /* EAPI rewrite command               */
1121         NULL                            /* EAPI new connection                */
1122 #endif /* EAPI */
1123 };
1124 #else
1125 #ifdef APXS2
1126 void kerb_register_hooks(apr_pool_t *p)
1127 {
1128         ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1129         ap_hook_access_checker(kerb_check_user_access, NULL, NULL, APR_HOOK_MIDDLE);
1130 }
1131
1132 module AP_MODULE_DECLARE_DATA kerb_auth_module =
1133 {
1134         STANDARD20_MODULE_STUFF,
1135         kerb_dir_config,                /* create per-dir    conf structures  */
1136         NULL,                           /* merge  per-dir    conf structures  */
1137         NULL,                           /* create per-server conf structures  */
1138         NULL,                           /* merge  per-server conf structures  */
1139         kerb_auth_cmds,                 /* table of configuration directives  */
1140         kerb_register_hooks             /* register hooks                     */
1141 };
1142 #endif /* APXS2 */
1143 #endif /* APXS1 */