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