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