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