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