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