Defined macro 'command' to ease the kerb_auth_cmds structure (unified for apache1...
[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
202
203 /*************************************************************************** 
204  Auth Configuration Parsers
205  ***************************************************************************/
206 static const char *kerb_set_fail_slot(cmd_parms *cmd, void *struct_ptr,
207                                         const char *arg)
208 {
209         int offset = (int) (long) cmd->info;
210         if (!strncasecmp(arg, "unauthorized", 12))
211                 *(int *) ((char *)struct_ptr + offset) = HTTP_UNAUTHORIZED;
212         else if (!strncasecmp(arg, "forbidden", 9))
213                 *(int *) ((char *)struct_ptr + offset) = HTTP_FORBIDDEN;
214         else if (!strncasecmp(arg, "declined", 8))
215                 *(int *) ((char *)struct_ptr + offset) = DECLINED;
216         else
217                 return "KrbAuthFailStatus must be Forbidden, Unauthorized, or Declined.";
218         return NULL;
219 }
220
221
222 #ifndef HEIMDAL
223 krb5_error_code
224 krb5_verify_user(krb5_context context, krb5_principal principal,
225                  krb5_ccache ccache, const char *password, krb5_boolean secure,
226                  const char *service)
227 {
228    int ret;
229    krb5_context kcontext;
230    krb5_principal server, client;
231    krb5_timestamp now;
232    krb5_creds my_creds;
233    krb5_flags options = 0;
234    krb5_principal me = NULL;
235    krb5_data tgtname = {
236       0,
237       KRB5_TGS_NAME_SIZE,
238       KRB5_TGS_NAME
239    };
240
241    memset((char *)&my_creds, 0, sizeof(my_creds));
242    my_creds.client = principal;
243
244    if (krb5_build_principal_ext(kcontext, &server,
245                                 krb5_princ_realm(kcontext, me)->length,
246                                 krb5_princ_realm(kcontext, me)->data,
247                                 tgtname.length, tgtname.data,
248                                 krb5_princ_realm(kcontext, me)->length,
249                                 krb5_princ_realm(kcontext, me)->data,
250                                 0)) {
251         return ret;
252    }
253
254    my_creds.server = server;
255    if (krb5_timeofday(kcontext, &now))
256         return -1;
257
258    my_creds.times.starttime = 0;
259    /* XXX
260    my_creds.times.endtime = now + lifetime;
261    my_creds.times.renew_till = now + renewal;
262    */
263
264    ret = krb5_get_in_tkt_with_password(kcontext, options, 0, NULL, 0,
265                                        password, ccache, &my_creds, 0);
266    if (ret) {
267         return ret;
268    }
269
270    return 0;
271 }
272 #endif
273
274
275 /*************************************************************************** 
276  Username/Password Validation
277  ***************************************************************************/
278 #ifdef KRB5
279 static void
280 krb5_cache_cleanup(void *data)
281 {
282    krb5_context context;
283    krb5_ccache  cache;
284    krb5_error_code problem;
285    char *cache_name = (char *) data;
286
287    problem = krb5_init_context(&context);
288    if (problem) {
289       ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "krb5_init_context() failed");
290       return;
291    }
292
293    problem = krb5_cc_resolve(context, cache_name, &cache);
294    if (problem) {
295       ap_log_error(APLOG_MARK, APLOG_ERR, NULL, 
296                    "krb5_cc_resolve() failed (%s: %s)",
297                    cache_name, krb5_get_err_text(context, problem)); 
298       return;
299    }
300
301    krb5_cc_destroy(context, cache);
302    krb5_free_context(context);
303 }
304
305 static int
306 create_krb5_ccache(krb5_context kcontext,
307                    request_rec *r,
308                    kerb_auth_config *conf,
309                    krb5_principal princ,
310                    krb5_ccache *ccache)
311 {
312         char *c, ccname[MAX_STRING_LEN];
313         krb5_error_code problem;
314         char errstr[1024];
315         int ret;
316         krb5_ccache tmp_ccache = NULL;
317
318         snprintf(ccname, sizeof(ccname), "FILE:%s/k5cc_ap_%s",
319                 conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp",
320                 MK_USER);
321
322         for (c = ccname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir :
323              "/tmp") + 1; *c; c++) {
324                 if (*c == '/')
325                         *c = '.';
326         }
327
328 #if 0
329         /* not sure what's the purpose of this call here */
330         problem = krb5_cc_set_default_name(kcontext, ccname);
331         if (problem) {
332                 snprintf(errstr, sizeof(errstr),
333                            "krb5_cc_set_default_name() failed: %s",
334                            krb5_get_err_text(kcontext, problem));
335                 ap_log_reason (errstr, r->uri, r);
336                 ret = SERVER_ERROR;
337                 goto end;
338           }
339
340 #endif
341
342 #if 0
343         /* XXX Dan: Why is this done? Cleanup? But the file would not be
344          * accessible from another processes (CGI) */
345         unlink(ccname+strlen("FILE:"));
346 #endif
347
348         problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache);
349         if (problem) {
350                 snprintf(errstr, sizeof(errstr),
351                          "krb5_cc_resolve() failed: %s",
352                          krb5_get_err_text(kcontext, problem));
353                 ap_log_reason (errstr, r->uri, r);
354                 ret = SERVER_ERROR;
355                 goto end;
356         }
357
358         problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
359         if (problem) {
360                 snprintf(errstr, sizeof(errstr),
361                          "krb5_cc_initialize() failed: %s",
362                          krb5_get_err_text(kcontext, problem));
363                 ap_log_reason (errstr, r->uri, r);
364                 ret = SERVER_ERROR;
365                 goto end;
366         }
367
368         ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname);
369         ap_register_cleanup(r->pool, ccname,
370                             krb5_cache_cleanup, ap_null_cleanup);
371
372         *ccache = tmp_ccache;
373         tmp_ccache = NULL;
374
375         ret = OK;
376
377 end:
378         if (tmp_ccache)
379            krb5_cc_destroy(kcontext, tmp_ccache);
380
381         return ret; /* XXX */
382 }
383
384 static int
385 store_krb5_creds(krb5_context kcontext,
386                  request_rec *r,
387                  kerb_auth_config *conf,
388                  krb5_ccache delegated_cred)
389 {
390    char errstr[1024];
391    krb5_error_code problem;
392    krb5_principal princ;
393    krb5_ccache ccache;
394    int ret;
395
396    problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
397    if (problem) {
398       snprintf(errstr, sizeof(errstr), "krb5_cc_get_principal() failed: %s",
399                krb5_get_err_text(kcontext, problem));
400       return SERVER_ERROR;
401    }
402
403    ret = create_krb5_ccache(kcontext, r, conf, princ, &ccache);
404    if (ret) {
405       krb5_free_principal(kcontext, princ);
406       return ret;
407    }
408
409    problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache);
410    krb5_free_principal(kcontext, princ);
411    if (problem) {
412       snprintf(errstr, sizeof(errstr), "krb5_cc_copy_cache() failed: %s",
413                krb5_get_err_text(kcontext, problem));
414       krb5_cc_destroy(kcontext, ccache);
415       return SERVER_ERROR;
416    }
417
418    krb5_cc_close(kcontext, ccache);
419    return OK;
420 }
421
422 int authenticate_user_krb5pwd(request_rec *r,
423                               kerb_auth_config *conf,
424                               const char *auth_line)
425 {
426    const char      *sent_pw = NULL; 
427    const char      *realms = NULL;
428    krb5_context    kcontext;
429    krb5_error_code code;
430    krb5_principal  client = NULL;
431    krb5_ccache     ccache = NULL;
432    int             ret;
433
434    code = krb5_init_context(&kcontext);
435    if (code) {
436       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO, r,
437                     "Cannot initialize Kerberos5 context (%d)", code);
438       return SERVER_ERROR;
439    }
440
441    sent_pw = ap_uudecode(r->pool, auth_line);
442    r->connection->user = ap_getword (r->pool, &sent_pw, ':');
443    r->connection->ap_auth_type = "Basic";
444
445    /* do not allow user to override realm setting of server */
446    if (strchr(r->connection->user,'@')) {
447       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO, r,
448                    "specifying realm in user name is prohibited");
449       ret = HTTP_UNAUTHORIZED;
450       goto end;
451    } 
452
453 #ifdef HEIMDAL
454    code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
455 #else
456    code = krb5_mcc_generate_new(kcontext, &ccache);
457 #endif
458    if (code) {
459       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO, r, 
460                     "Cannot generate new ccache: %s",
461                     krb5_get_err_text(kcontext, code));
462       ret = SERVER_ERROR;
463       goto end;
464    }
465
466    realms = conf->krb_auth_realms;
467    do {
468       if (realms && krb5_set_default_realm(kcontext,
469                                            ap_getword_white(r->pool, &realms)))
470          continue;
471
472       code = krb5_parse_name(kcontext, r->connection->user, &client);
473       if (code)
474          continue;
475
476       code = krb5_verify_user(kcontext, client, ccache, sent_pw, 1, "khttp");
477       krb5_free_principal(kcontext, client);
478       if (code == 0)
479          break;
480
481       /* ap_getword_white() used above shifts the parameter, so it's not
482          needed to touch the realms variable */
483    } while (realms && *realms);
484
485    memset((char *)sent_pw, 0, strlen(sent_pw));
486
487    if (code) {
488       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO, r,
489                     "Verifying krb5 password failed: %s",
490                     krb5_get_err_text(kcontext, code));
491       ret = HTTP_UNAUTHORIZED;
492       goto end;
493    }
494
495    if (conf->krb_save_credentials) {
496       ret = store_krb5_creds(kcontext, r, conf, ccache);
497       if (ret) /* Ignore error ?? */
498          goto end;
499    }
500
501    ret = OK;
502
503 end:
504    if (client)
505       krb5_free_principal(kcontext, client);
506    if (ccache)
507       krb5_cc_destroy(kcontext, ccache);
508    krb5_free_context(kcontext);
509
510    return ret;
511 }
512 #endif /* KRB5 */
513
514 #ifdef KRB4
515 int kerb4_password_validate(request_rec *r, const char *user, const char *pass)
516 {
517         kerb_auth_config *conf =
518                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
519                                         &kerb_auth_module);
520         int ret;
521         int lifetime = DEFAULT_TKT_LIFE;
522         char *c, *tfname;
523         char *username = NULL;
524         char *instance = NULL;
525         char *realm = NULL;
526
527         username = (char *)ap_pstrdup(r->pool, user);
528         if (!username) {
529                 return 0;
530         }
531
532         instance = strchr(username, '.');
533         if (instance) {
534                 *instance++ = '\0';
535         }
536         else {
537                 instance = "";
538         }
539
540         realm = strchr(username, '@');
541         if (realm) {
542                 *realm++ = '\0';
543         }
544         else {
545                 realm = "";
546         }
547
548         if (conf->krb_lifetime) {
549                 lifetime = atoi(conf->krb_lifetime);
550         }
551
552         if (conf->krb_force_instance) {
553                 instance = conf->krb_force_instance;
554         }
555
556         if (conf->krb_save_credentials) {
557                 tfname = (char *)malloc(sizeof(char) * MAX_STRING_LEN);
558                 sprintf(tfname, "%s/k5cc_ap_%s",
559                         conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp",
560                         MK_USER);
561
562                 if (!strcmp(instance, "")) {
563                         tfname = strcat(tfname, ".");
564                         tfname = strcat(tfname, instance);
565                 }
566
567                 if (!strcmp(realm, "")) {
568                         tfname = strcat(tfname, ".");
569                         tfname = strcat(tfname, realm);
570                 }
571
572                 for (c = tfname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir :
573                                 "/tmp") + 1; *c; c++) {
574                         if (*c == '/')
575                                 *c = '.';
576                 }
577
578                 krb_set_tkt_string(tfname);
579         }
580
581         if (!strcmp(realm, "")) {
582                 realm = (char *)malloc(sizeof(char) * (REALM_SZ + 1));
583                 ret = krb_get_lrealm(realm, 1);
584                 if (ret != KSUCCESS)
585                         return 0;
586         }
587
588         ret = krb_get_pw_in_tkt((char *)user, instance, realm, "krbtgt", realm,
589                                         lifetime, (char *)pass);
590         switch (ret) {
591                 case INTK_OK:
592                 case INTK_W_NOTALL:
593                         return 1;
594                         break;
595
596                 default:
597                         return 0;
598                         break;
599         }
600 }
601 #endif /* KRB4 */
602
603
604
605
606 /*************************************************************************** 
607  GSSAPI Validation
608  ***************************************************************************/
609 #ifdef GSSAPI
610 static const char *
611 get_gss_error(pool *p, OM_uint32 error_status, char *prefix)
612 {
613    OM_uint32 maj_stat, min_stat;
614    OM_uint32 msg_ctx = 0;
615    gss_buffer_desc status_string;
616    char buf[1024];
617    size_t len;
618
619    snprintf(buf, sizeof(buf), "%s: ", prefix);
620    len = strlen(buf);
621    do {
622       maj_stat = gss_display_status (&min_stat,
623                                      error_status,
624                                      GSS_C_MECH_CODE,
625                                      GSS_C_NO_OID,
626                                      &msg_ctx,
627                                      &status_string);
628       if (sizeof(buf) > len + status_string.length + 1) {
629          sprintf(buf+len, "%s:", (char*) status_string.value);
630          len += status_string.length;
631       }
632       gss_release_buffer(&min_stat, &status_string);
633    } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
634
635    return (ap_pstrdup(p, buf));
636 }
637
638 static int
639 get_gss_creds(request_rec *r,
640               kerb_auth_config *conf,
641               gss_cred_id_t *server_creds)
642 {
643    int ret = 0;
644    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
645    OM_uint32 major_status, minor_status;
646    gss_name_t server_name = GSS_C_NO_NAME;
647
648    if (conf->service_name) {
649       input_token.value = conf->service_name;
650       input_token.length = strlen(conf->service_name) + 1;
651    }
652    else {
653       input_token.value = "khttp";
654       input_token.length = 6;
655    }
656    major_status = gss_import_name(&minor_status, &input_token,
657                                   (conf->service_name) ? 
658                                        GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE,
659                                   &server_name);
660    if (GSS_ERROR(major_status)) {
661       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
662                     "%s", get_gss_error(r->pool, minor_status,
663                     "gss_import_name() failed"));
664       ret = SERVER_ERROR;
665       goto fail;
666    }
667    
668 #ifdef KRB5
669    if (conf->krb_5_keytab)
670       setenv("KRB5_KTNAME", conf->krb_5_keytab, 1);
671 #endif
672
673    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
674                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT,
675                                    server_creds, NULL, NULL);
676 #ifdef KRB5
677    if (conf->krb_5_keytab)
678       unsetenv("KRB5_KTNAME");
679 #endif
680    if (GSS_ERROR(major_status)) {
681       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r,
682                    "%s", get_gss_error(r->pool, minor_status,
683                                        "gss_acquire_cred() failed"));
684       ret = SERVER_ERROR;
685       goto fail;
686    }
687    
688    return 0;
689
690 fail:
691    /* XXX cleanup */
692
693    return ret;
694 }
695
696 static int
697 authenticate_user_gss(request_rec *r,
698                       kerb_auth_config *conf,
699                       const char *auth_line)
700 {
701   OM_uint32 major_status, minor_status, minor_status2;
702   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
703   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
704   const char *auth_param = NULL;
705   int ret;
706   gss_name_t client_name = GSS_C_NO_NAME;
707   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
708
709   if (gss_connection == NULL) {
710      gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection));
711      if (gss_connection == NULL) {
712         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
713                       "ap_pcalloc() failed (not enough memory)");
714         ret = SERVER_ERROR;
715         goto end;
716      }
717      memset(gss_connection, 0, sizeof(*gss_connection));
718      ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup);
719   }
720
721   if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) {
722      ret = get_gss_creds(r, conf, &gss_connection->server_creds);
723      if (ret)
724         goto end;
725   }
726
727   /* ap_getword() shifts parameter */
728   auth_param = ap_getword_white(r->pool, &auth_line);
729   if (auth_param == NULL) {
730      ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
731                    "No Authorization parameter in request from client");
732      ret = HTTP_UNAUTHORIZED;
733      goto end;
734   }
735
736   input_token.length = ap_base64decode_len(auth_param);
737   input_token.value = ap_pcalloc(r->connection->pool, input_token.length);
738   if (input_token.value == NULL) {
739      ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
740                    "ap_pcalloc() failed (not enough memory)");
741      ret = SERVER_ERROR;
742      goto end;
743   }
744   input_token.length = ap_base64decode(input_token.value, auth_param);
745
746   major_status = gss_accept_sec_context(&minor_status,
747                                         &gss_connection->context,
748                                         gss_connection->server_creds,
749                                         &input_token,
750                                         GSS_C_NO_CHANNEL_BINDINGS,
751                                         &client_name,
752                                         NULL,
753                                         &output_token,
754                                         NULL,
755                                         NULL,
756                                         &delegated_cred);
757   if (output_token.length) {
758      char *token = NULL;
759      size_t len;
760      
761      len = ap_base64encode_len(output_token.length);
762      token = ap_pcalloc(r->connection->pool, len + 1);
763      if (token == NULL) {
764         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
765                      "ap_pcalloc() failed (not enough memory)");
766         ret = SERVER_ERROR;
767         gss_release_buffer(&minor_status2, &output_token);
768         goto end;
769      }
770      ap_base64encode(token, output_token.value, output_token.length);
771      token[len] = '\0';
772      ap_table_set(r->err_headers_out, "WWW-Authenticate",
773                   ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
774      gss_release_buffer(&minor_status2, &output_token);
775   }
776
777   if (GSS_ERROR(major_status)) {
778      ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
779                    "%s", get_gss_error(r->pool, minor_status,
780                                        "gss_accept_sec_context() failed"));
781      ret = HTTP_UNAUTHORIZED;
782      goto end;
783   }
784
785   if (major_status & GSS_S_CONTINUE_NEEDED) {
786      /* Some GSSAPI mechanism (eg GSI from Globus) may require multiple 
787       * iterations to establish authentication */
788      ret = HTTP_UNAUTHORIZED;
789      goto end;
790   }
791
792   major_status = gss_export_name(&minor_status, client_name, &output_token);
793   gss_release_name(&minor_status, &client_name); 
794   if (GSS_ERROR(major_status)) {
795     ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
796                   "%s", get_gss_error(r->pool, minor_status, 
797                                       "gss_export_name() failed"));
798     ret = SERVER_ERROR;
799     goto end;
800   }
801
802   r->connection->ap_auth_type = "Negotiate";
803   r->connection->user = ap_pstrdup(r->pool, output_token.value);
804 #if 0
805   /* If the user comes from a realm specified by configuration don't include
806       its realm name in the username so that the authorization routine could
807       work for both Password-based and Ticket-based authentication. It's
808       administrators responsibility to include only such realm that have
809       unified principal instances, i.e. if the same principal name occures in
810       multiple realms, it must be always assigned to a single user.
811   */    
812   p = strchr(r->connection->user, '@');
813   if (p != NULL) {
814      const char *realms = conf->gss_krb5_realms;
815
816      while (realms && *realms) {
817         if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
818            *p = '\0';
819            break;
820         }
821      }
822   }
823 #endif
824
825   gss_release_buffer(&minor_status, &output_token);
826
827 #if 0
828   /* This should be only done if afs token are requested or gss_save creds is 
829    * specified */
830   /* gss_export_cred() from the GGF GSS Extensions could be used */
831   if (delegated_cred != GSS_C_NO_CREDENTIAL &&
832       (conf->gss_save_creds || (conf->gss_krb5_cells && k_hasafs()))) { 
833      krb5_init_context(&krb_ctx);
834      do_afs_log(krb_ctx, r, delegated_cred->ccache, conf->gss_krb5_cells);
835      ret = store_krb5_creds(krb_ctx, r, conf, delegated_cred->ccache);
836      krb5_free_context(krb_ctx);
837      if (ret)
838         goto end;
839   }
840 #endif
841   ret = OK;
842
843 end:
844   if (delegated_cred)
845      gss_release_cred(&minor_status, &delegated_cred);
846
847   if (output_token.length) 
848      gss_release_buffer(&minor_status, &output_token);
849
850   if (client_name != GSS_C_NO_NAME)
851      gss_release_name(&minor_status, &client_name);
852
853   return ret;
854 }
855 #endif /* GSSAPI */
856
857
858 static void
859 note_auth_failure(request_rec *r, const kerb_auth_config *conf)
860 {
861    const char *auth_type = NULL;
862    const char *auth_name = NULL;
863
864    /* get the type specified in .htaccess */
865    auth_type = ap_auth_type(r);
866
867    /* get the user realm specified in .htaccess */
868    auth_name = ap_auth_name(r);
869
870    /* XXX should the WWW-Authenticate header be cleared first? */
871 #ifdef KRB5
872    if (conf->krb_method_gssapi)
873       ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
874 #endif
875    if (auth_type && strncasecmp(auth_type, "KerberosV5", 10) == 0)
876       ap_table_add(r->err_headers_out, "WWW-Authenticate",
877                    ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
878 }
879
880
881
882 /*************************************************************************** 
883  User Authentication
884  ***************************************************************************/
885 int kerb_authenticate_user(request_rec *r)
886 {
887    kerb_auth_config *conf = 
888       (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
889                                                 &kerb_auth_module);
890    const char *auth_type = NULL;
891    const char *auth_line = NULL;
892    const char *type = NULL;
893    int ret;
894
895    /* get the type specified in .htaccess */
896    type = ap_auth_type(r);
897
898    if (!conf->krb_auth_enable &&
899        (type == NULL || (strncasecmp(type, "Kerberos", 8) != 0)))
900       return DECLINED;
901
902    /* get what the user sent us in the HTTP header */
903    auth_line = MK_TABLE_GET(r->headers_in, "Authorization");
904    if (!auth_line) {
905       note_auth_failure(r, conf);
906       return HTTP_UNAUTHORIZED;
907    }
908    auth_type = ap_getword_white(r->pool, &auth_line);
909
910    ret = HTTP_UNAUTHORIZED;
911
912    /* XXX Support for AuthType=Kerberos */
913
914 #ifdef KRB5
915    if (conf->krb_method_gssapi &&
916        strcasecmp(auth_type, "GSS-Negotiate") == 0) {
917       ret = authenticate_user_gss(r, conf, auth_line);
918    } else if (conf->krb_method_k5pass &&
919               strcasecmp(auth_type, "Basic") == 0) {
920        ret = authenticate_user_krb5pwd(r, conf, auth_line);
921    }
922 #endif
923
924 #ifdef KRB4
925    if (ret == HTTP_UNAUTHORIZED && conf->krb_method_k4pass &&
926        strcasecmp(auth_type, "Basic") == 0)
927       ret = authenticate_user_krb4pwd(r, conf, auth_line);
928 #endif
929
930    if (ret == HTTP_UNAUTHORIZED)
931       note_auth_failure(r, conf);
932
933    return ret;
934 }
935
936
937 #if 0
938 int kerb_check_user_access(request_rec *r)
939 {
940         register int x;
941         const char *t, *w;
942         const MK_ARRAY_HEADER *reqs_arr = ap_requires(r);
943         require_line *reqs;
944         kerb_auth_config *conf =
945                 (kerb_auth_config *)ap_get_module_config(r->per_dir_config,
946                                                 &kerb_auth_module);
947
948         if (reqs_arr == NULL) {
949                 return OK;
950         }
951         reqs = (require_line *)reqs_arr->elts;
952
953         for (x = 0; x < reqs_arr->nelts; x++) {
954                 t = reqs[x].requirement;
955                 w = ap_getword_white(r->pool, &t);
956                 if (strcmp(w, "realm") == 0) {
957                         while (t[0] != '\0') {
958                                 w = ap_getword_conf(r->pool, &t);
959                                 if (strcmp(MK_USER, w) == 0) {
960                                         return OK;
961                                 }
962                         }
963                 }
964         }
965
966         return DECLINED;
967 }
968 #endif
969
970
971
972
973 /*************************************************************************** 
974  Module Setup/Configuration
975  ***************************************************************************/
976 #ifdef APXS1
977 module MODULE_VAR_EXPORT kerb_auth_module = {
978         STANDARD_MODULE_STUFF,
979         NULL,                           /*      module initializer            */
980         kerb_dir_create_config,         /*      per-directory config creator  */
981         NULL,                           /*      per-directory config merger   */
982         NULL,                           /*      per-server    config creator  */
983         NULL,                           /*      per-server    config merger   */
984         kerb_auth_cmds,                 /*      command table                 */
985         NULL,                           /* [ 9] content handlers              */
986         NULL,                           /* [ 2] URI-to-filename translation   */
987         kerb_authenticate_user,         /* [ 5] check/validate user_id        */
988         NULL,                           /* [ 6] check user_id is valid *here* */
989         NULL,                           /* [ 4] check access by host address  */
990         NULL,                           /* [ 7] MIME type checker/setter      */
991         NULL,                           /* [ 8] fixups                        */
992         NULL,                           /* [10] logger                        */
993         NULL,                           /* [ 3] header parser                 */
994         NULL,                           /*      process initialization        */
995         NULL,                           /*      process exit/cleanup          */
996         NULL                            /* [ 1] post read_request handling    */
997 };
998 #else
999 void kerb_register_hooks(apr_pool_t *p)
1000 {
1001    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
1002 }
1003
1004 module AP_MODULE_DECLARE_DATA kerb_auth_module =
1005 {
1006    STANDARD20_MODULE_STUFF,
1007    kerb_dir_create_config,      /* create per-dir    conf structures  */
1008    NULL,                        /* merge  per-dir    conf structures  */
1009    NULL,                        /* create per-server conf structures  */
1010    NULL,                        /* merge  per-server conf structures  */
1011    kerb_auth_cmds,              /* table of configuration directives  */
1012    kerb_register_hooks          /* register hooks                     */
1013 };
1014 #endif