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