5 * - provides http_auth_backend_t "gssapi" for HTTP auth Basic realm="Kerberos"
6 * - provides http_auth_scheme_t "Negotiate"
7 * - (does not provide http_auth_backend_t for HTTP auth Digest)
9 * Note: Credentials cache (KRB5CCNAME) is exported into CGI and SSI environment
10 * as well as passed to FastCGI and SCGI (useful if on same machine
11 * and running under same user account with access to KRB5CCNAME file).
12 * Credentials are clean up at the end of each request.
15 * - no rate limiting of auth requests, so remote attacker can send many auth
16 * requests very quickly if attempting brute force password cracking attack
18 * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
19 * - Kerberos auth is synchronous and blocks waiting for response
20 * TODO: attempt async?
27 #include <gssapi/gssapi_krb5.h>
29 #include "http_auth.h"
30 #include "http_header.h"
42 buffer
*auth_gssapi_keytab
;
43 buffer
*auth_gssapi_principal
;
44 unsigned short int auth_gssapi_store_creds
;
49 plugin_config
**config_storage
;
53 static handler_t
mod_authn_gssapi_check(server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
);
54 static handler_t
mod_authn_gssapi_basic(server
*srv
, connection
*con
, void *p_d
, const http_auth_require_t
*require
, const buffer
*username
, const char *pw
);
56 INIT_FUNC(mod_authn_gssapi_init
) {
57 static http_auth_scheme_t http_auth_scheme_gssapi
=
58 { "gssapi", mod_authn_gssapi_check
, NULL
};
59 static http_auth_backend_t http_auth_backend_gssapi
=
60 { "gssapi", mod_authn_gssapi_basic
, NULL
, NULL
};
61 plugin_data
*p
= calloc(1, sizeof(*p
));
63 /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */
64 http_auth_scheme_gssapi
.p_d
= p
;
65 http_auth_scheme_set(&http_auth_scheme_gssapi
);
66 http_auth_backend_gssapi
.p_d
= p
;
67 http_auth_backend_set(&http_auth_backend_gssapi
);
72 FREE_FUNC(mod_authn_gssapi_free
) {
77 if (!p
) return HANDLER_GO_ON
;
79 if (p
->config_storage
) {
81 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
82 plugin_config
*s
= p
->config_storage
[i
];
84 if (NULL
== s
) continue;
86 buffer_free(s
->auth_gssapi_keytab
);
87 buffer_free(s
->auth_gssapi_principal
);
91 free(p
->config_storage
);
99 SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults
) {
100 plugin_data
*p
= p_d
;
102 config_values_t cv
[] = {
103 { "auth.backend.gssapi.keytab", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
},
104 { "auth.backend.gssapi.principal", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
},
105 { "auth.backend.gssapi.store-creds",NULL
, T_CONFIG_BOOLEAN
,T_CONFIG_SCOPE_CONNECTION
},
106 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
109 p
->config_storage
= calloc(srv
->config_context
->used
, sizeof(plugin_config
*));
111 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
112 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
115 s
= calloc(1, sizeof(plugin_config
));
117 s
->auth_gssapi_keytab
= buffer_init();
118 s
->auth_gssapi_principal
= buffer_init();
120 cv
[0].destination
= s
->auth_gssapi_keytab
;
121 cv
[1].destination
= s
->auth_gssapi_principal
;
122 cv
[2].destination
= &s
->auth_gssapi_store_creds
;
123 /* default enabled for backwards compatibility; disable in future */
124 s
->auth_gssapi_store_creds
= 1;
126 p
->config_storage
[i
] = s
;
128 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
129 return HANDLER_ERROR
;
133 return HANDLER_GO_ON
;
138 static int mod_authn_gssapi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
)
141 plugin_config
*s
= p
->config_storage
[0];
143 PATCH(auth_gssapi_keytab
);
144 PATCH(auth_gssapi_principal
);
145 PATCH(auth_gssapi_store_creds
);
147 /* skip the first, the global context */
148 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
149 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
150 s
= p
->config_storage
[i
];
152 /* condition didn't match */
153 if (!config_check_cond(srv
, con
, dc
)) continue;
156 for (j
= 0; j
< dc
->value
->used
; j
++) {
157 data_unset
*du
= dc
->value
->data
[j
];
159 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.gssapi.keytab"))) {
160 PATCH(auth_gssapi_keytab
);
161 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.gssapi.principal"))) {
162 PATCH(auth_gssapi_principal
);
163 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("auth.backend.gssapi.store-creds"))) {
164 PATCH(auth_gssapi_store_creds
);
173 static handler_t
mod_authn_gssapi_send_400_bad_request (server
*srv
, connection
*con
)
176 con
->http_status
= 400;
178 return HANDLER_FINISHED
;
181 static void mod_authn_gssapi_log_gss_error(server
*srv
, const char *file
, unsigned int line
, const char *func
, const char *extra
, OM_uint32 err_maj
, OM_uint32 err_min
)
183 buffer
* const msg
= buffer_init_string(func
);
184 OM_uint32 maj_stat
, min_stat
;
185 OM_uint32 msg_ctx
= 0;
186 gss_buffer_desc status_string
;
188 buffer_append_string_len(msg
, CONST_STR_LEN("("));
189 if (extra
) buffer_append_string(msg
, extra
);
190 buffer_append_string_len(msg
, CONST_STR_LEN("):"));
193 maj_stat
= gss_display_status(&min_stat
, err_maj
, GSS_C_GSS_CODE
,
194 GSS_C_NO_OID
, &msg_ctx
, &status_string
);
195 if (GSS_ERROR(maj_stat
))
198 buffer_append_string(msg
, status_string
.value
);
199 gss_release_buffer(&min_stat
, &status_string
);
201 maj_stat
= gss_display_status(&min_stat
, err_min
, GSS_C_MECH_CODE
,
202 GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
203 if (!GSS_ERROR(maj_stat
)) {
204 buffer_append_string_len(msg
, CONST_STR_LEN(" ("));
205 buffer_append_string(msg
, status_string
.value
);
206 buffer_append_string_len(msg
, CONST_STR_LEN(")"));
207 gss_release_buffer(&min_stat
, &status_string
);
209 } while (!GSS_ERROR(maj_stat
) && msg_ctx
!= 0);
211 log_error_write(srv
, file
, line
, "b", msg
);
215 static void mod_authn_gssapi_log_krb5_error(server
*srv
, const char *file
, unsigned int line
, const char *func
, const char *extra
, krb5_context context
, int code
)
218 /*(extra might be NULL)*/
219 log_error_write(srv
, file
, line
, "sssss", func
, "(", extra
, "):",
220 error_message(code
));
223 static int mod_authn_gssapi_create_krb5_ccache(server
*srv
, connection
*con
, plugin_data
*p
, krb5_context kcontext
, krb5_principal princ
, krb5_ccache
*ccache
)
225 buffer
* const kccname
= buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX");
226 char * const ccname
= kccname
->ptr
+ sizeof("FILE:")-1;
227 const size_t ccnamelen
= buffer_string_length(kccname
)-(sizeof("FILE:")-1);
228 /*(future: might consider using server.upload-dirs instead of /tmp)*/
230 /* POSIX-2008 requires mkstemp create file with 0600 perms */
233 /* coverity[secure_temp : FALSE] */
234 int fd
= mkstemp(ccname
);
236 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "mkstemp():", ccname
, strerror(errno
));
237 buffer_free(kccname
);
243 krb5_error_code problem
;
245 problem
= krb5_cc_resolve(kcontext
, kccname
->ptr
, ccache
);
247 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_resolve", NULL
, kcontext
, problem
);
251 problem
= krb5_cc_initialize(kcontext
, *ccache
, princ
);
253 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_initialize", kccname
->ptr
, kcontext
, problem
);
257 con
->plugin_ctx
[p
->id
] = kccname
;
259 http_header_env_set(con
, CONST_STR_LEN("KRB5CCNAME"), ccname
, ccnamelen
);
260 http_header_request_set(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("X-Forwarded-Keytab"), ccname
, ccnamelen
);
267 krb5_cc_destroy(kcontext
, *ccache
);
271 buffer_free(kccname
);
277 * HTTP auth Negotiate
280 static handler_t
mod_authn_gssapi_send_500_server_error (connection
*con
)
282 con
->http_status
= 500;
284 return HANDLER_FINISHED
;
287 static handler_t
mod_authn_gssapi_send_401_unauthorized_negotiate (connection
*con
)
289 con
->http_status
= 401;
291 http_header_response_set(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate"));
292 return HANDLER_FINISHED
;
295 static int mod_authn_gssapi_store_gss_creds(server
*srv
, connection
*con
, plugin_data
*p
, char *princ_name
, gss_cred_id_t delegated_cred
)
297 OM_uint32 maj_stat
, min_stat
;
298 krb5_principal princ
= NULL
;
299 krb5_ccache ccache
= NULL
;
300 krb5_error_code problem
;
301 krb5_context context
;
303 problem
= krb5_init_context(&context
);
305 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_init_context", NULL
, context
, problem
);
309 problem
= krb5_parse_name(context
, princ_name
, &princ
);
311 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_parse_name", NULL
, context
, problem
);
315 if (mod_authn_gssapi_create_krb5_ccache(srv
, con
, p
, context
, princ
, &ccache
))
318 maj_stat
= gss_krb5_copy_ccache(&min_stat
, delegated_cred
, ccache
);
319 if (GSS_ERROR(maj_stat
)) {
320 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_krb5_copy_ccache", princ_name
, maj_stat
, min_stat
);
324 krb5_cc_close(context
, ccache
);
325 krb5_free_principal(context
, princ
);
326 krb5_free_context(context
);
331 krb5_free_principal(context
, princ
);
333 krb5_cc_destroy(context
, ccache
);
334 krb5_free_context(context
);
339 static handler_t
mod_authn_gssapi_check_spnego(server
*srv
, connection
*con
, plugin_data
*p
, const http_auth_require_t
*require
, const char *realm_str
)
341 OM_uint32 st_major
, st_minor
, acc_flags
;
342 gss_buffer_desc token_s
= GSS_C_EMPTY_BUFFER
;
343 gss_buffer_desc token_in
= GSS_C_EMPTY_BUFFER
;
344 gss_buffer_desc token_out
= GSS_C_EMPTY_BUFFER
;
345 gss_cred_id_t server_cred
= GSS_C_NO_CREDENTIAL
;
346 gss_cred_id_t client_cred
= GSS_C_NO_CREDENTIAL
;
347 gss_ctx_id_t context
= GSS_C_NO_CONTEXT
;
348 gss_name_t server_name
= GSS_C_NO_NAME
;
349 gss_name_t client_name
= GSS_C_NO_NAME
;
352 handler_t rc
= HANDLER_UNSET
;
354 buffer
*t_in
= buffer_init();
355 if (!buffer_append_base64_decode(t_in
, realm_str
, strlen(realm_str
), BASE64_STANDARD
)) {
356 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "decoding GSSAPI authentication header failed", realm_str
);
358 return mod_authn_gssapi_send_400_bad_request(srv
, con
);
361 mod_authn_gssapi_patch_connection(srv
, con
, p
);
364 /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
365 * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */
366 /* ??? Should KRB5_KTNAME go into con->environment instead ??? */
367 /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */
369 memset(&ktname
, 0, sizeof(ktname
));
370 buffer_copy_string(&ktname
, "KRB5_KTNAME=");
371 buffer_append_string_buffer(&ktname
, p
->conf
.auth_gssapi_keytab
);
373 /* ktname.ptr becomes part of the environment, do not free */
376 sprinc
= buffer_init_buffer(p
->conf
.auth_gssapi_principal
);
377 if (strchr(sprinc
->ptr
, '/') == NULL
) {
378 /*(copy HTTP Host, omitting port if port is present)*/
379 /* ??? Should con->server_name be used if http_host not present?
380 * ??? What if con->server_name is not set?
381 * ??? Will this work below if IPv6 provided in Host? probably not */
382 if (!buffer_is_empty(con
->request
.http_host
)) {
383 buffer_append_string_len(sprinc
, CONST_STR_LEN("/"));
384 buffer_append_string_len(sprinc
, con
->request
.http_host
->ptr
, strcspn(con
->request
.http_host
->ptr
, ":"));
387 if (strchr(sprinc
->ptr
, '@') == NULL
) {
388 buffer_append_string_len(sprinc
, CONST_STR_LEN("@"));
389 buffer_append_string_buffer(sprinc
, require
->realm
);
391 /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/
392 /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/
393 #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name
395 token_s
.value
= sprinc
->ptr
;
396 token_s
.length
= buffer_string_length(sprinc
);
397 st_major
= gss_import_name(&st_minor
, &token_s
, (gss_OID
) GSS_KRB5_NT_PRINCIPAL_NAME
, &server_name
);
398 if (GSS_ERROR(st_major
)) {
399 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_import_name", NULL
, st_major
, st_minor
);
403 memset(&token_s
, 0, sizeof(token_s
));
404 st_major
= gss_display_name(&st_minor
, server_name
, &token_s
, NULL
);
405 if (GSS_ERROR(st_major
)) {
406 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_display_name", NULL
, st_major
, st_minor
);
410 /* acquire server's own credentials */
411 st_major
= gss_acquire_cred(&st_minor
, server_name
, GSS_C_INDEFINITE
, GSS_C_NO_OID_SET
, GSS_C_ACCEPT
, &server_cred
, NULL
, NULL
);
412 if (GSS_ERROR(st_major
)) {
413 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_acquire_cred", sprinc
->ptr
, st_major
, st_minor
);
417 /* accept the user's context */
418 token_in
.length
= buffer_string_length(t_in
);
419 token_in
.value
= t_in
->ptr
;
420 st_major
= gss_accept_sec_context(&st_minor
, &context
, server_cred
, &token_in
, GSS_C_NO_CHANNEL_BINDINGS
,
421 &client_name
, NULL
, &token_out
, &acc_flags
, NULL
, &client_cred
);
422 if (GSS_ERROR(st_major
)) {
423 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_accept_sec_context", NULL
, st_major
, st_minor
);
427 /* fetch the username */
428 st_major
= gss_display_name(&st_minor
, client_name
, &token_out
, NULL
);
429 if (GSS_ERROR(st_major
)) {
430 mod_authn_gssapi_log_gss_error(srv
, __FILE__
, __LINE__
, "gss_display_name", NULL
, st_major
, st_minor
);
434 if (!(acc_flags
& GSS_C_CONF_FLAG
)) {
435 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "No confidentiality for user:", token_out
.value
);
439 /* check the allow-rules */
440 if (!http_auth_match_rules(require
, token_out
.value
, NULL
, NULL
)) {
444 if (p
->conf
.auth_gssapi_store_creds
) {
445 if (!(acc_flags
& GSS_C_DELEG_FLAG
)) {
446 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "Unable to delegate credentials for user:", token_out
.value
);
449 else if (!mod_authn_gssapi_store_gss_creds(srv
, con
, p
, token_out
.value
, client_cred
)) {
450 rc
= mod_authn_gssapi_send_500_server_error(con
);
455 http_auth_setenv(con
, token_out
.value
, token_out
.length
, CONST_STR_LEN("GSSAPI"));
456 rc
= HANDLER_GO_ON
; /* success */
462 if (context
!= GSS_C_NO_CONTEXT
)
463 gss_delete_sec_context(&st_minor
, &context
, GSS_C_NO_BUFFER
);
465 if (client_cred
!= GSS_C_NO_CREDENTIAL
)
466 gss_release_cred(&st_minor
, &client_cred
);
467 if (server_cred
!= GSS_C_NO_CREDENTIAL
)
468 gss_release_cred(&st_minor
, &server_cred
);
470 if (client_name
!= GSS_C_NO_NAME
)
471 gss_release_name(&st_minor
, &client_name
);
472 if (server_name
!= GSS_C_NO_NAME
)
473 gss_release_name(&st_minor
, &server_name
);
476 gss_release_buffer(&st_minor
, &token_s
);
477 /* if (token_in.length)
478 * gss_release_buffer(&st_minor, &token_in); */
479 if (token_out
.length
)
480 gss_release_buffer(&st_minor
, &token_out
);
482 return rc
!= HANDLER_UNSET
? rc
: mod_authn_gssapi_send_401_unauthorized_negotiate(con
);
485 static handler_t
mod_authn_gssapi_check (server
*srv
, connection
*con
, void *p_d
, const struct http_auth_require_t
*require
, const struct http_auth_backend_t
*backend
)
487 buffer
*vb
= http_header_request_get(con
, HTTP_HEADER_AUTHORIZATION
, CONST_STR_LEN("Authorization"));
491 return mod_authn_gssapi_send_401_unauthorized_negotiate(con
);
494 if (!buffer_eq_icase_ssn(vb
->ptr
, CONST_STR_LEN("Negotiate "))) {
495 return mod_authn_gssapi_send_400_bad_request(srv
, con
);
498 return mod_authn_gssapi_check_spnego(srv
, con
, (plugin_data
*)p_d
, require
, vb
->ptr
+sizeof("Negotiate ")-1);
502 * HTTP auth Basic realm="kerberos"
505 static krb5_error_code
mod_authn_gssapi_verify_krb5_init_creds(server
*srv
, krb5_context context
, krb5_creds
*creds
, krb5_principal ap_req_server
, krb5_keytab ap_req_keytab
)
509 krb5_ccache local_ccache
= NULL
;
510 krb5_creds
*new_creds
= NULL
;
511 krb5_auth_context auth_context
= NULL
;
512 krb5_keytab keytab
= NULL
;
515 memset(&req
, 0, sizeof(req
));
517 if (ap_req_keytab
== NULL
) {
518 ret
= krb5_kt_default(context
, &keytab
);
522 keytab
= ap_req_keytab
;
524 ret
= krb5_cc_resolve(context
, "MEMORY:", &local_ccache
);
526 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_cc_resolve() failed when verifying KDC");
531 ret
= krb5_cc_initialize(context
, local_ccache
, creds
->client
);
533 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_cc_initialize() failed when verifying KDC");
537 ret
= krb5_cc_store_cred(context
, local_ccache
, creds
);
539 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_cc_store_cred() failed when verifying KDC");
543 ret
= krb5_unparse_name(context
, ap_req_server
, &server_name
);
545 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_unparse_name() failed when verifying KDC");
548 krb5_free_unparsed_name(context
, server_name
);
550 if (!krb5_principal_compare(context
, ap_req_server
, creds
->server
)) {
551 krb5_creds match_cred
;
553 memset(&match_cred
, 0, sizeof(match_cred
));
555 match_cred
.client
= creds
->client
;
556 match_cred
.server
= ap_req_server
;
558 ret
= krb5_get_credentials(context
, 0, local_ccache
, &match_cred
, &new_creds
);
560 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_get_credentials() failed when verifying KDC");
566 ret
= krb5_mk_req_extended(context
, &auth_context
, 0, NULL
, creds
, &req
);
568 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_mk_req_extended() failed when verifying KDC");
572 krb5_auth_con_free(context
, auth_context
);
574 ret
= krb5_auth_con_init(context
, &auth_context
);
576 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_auth_con_init() failed when verifying KDC");
580 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
581 krb5_auth_con_setflags(context
, auth_context
, KRB5_AUTH_CONTEXT_DO_SEQUENCE
);
582 ret
= krb5_rd_req(context
, &auth_context
, &req
, ap_req_server
, keytab
, 0, NULL
);
584 log_error_write(srv
, __FILE__
, __LINE__
, "s", "krb5_rd_req() failed when verifying KDC");
589 krb5_free_data_contents(context
, &req
);
591 krb5_auth_con_free(context
, auth_context
);
593 krb5_free_creds(context
, new_creds
);
594 if (ap_req_keytab
== NULL
&& keytab
)
595 krb5_kt_close(context
, keytab
);
597 krb5_cc_destroy(context
, local_ccache
);
602 static int mod_authn_gssapi_store_krb5_creds(server
*srv
, connection
*con
, plugin_data
*p
,
603 krb5_context kcontext
, krb5_ccache delegated_cred
)
605 krb5_error_code problem
;
606 krb5_principal princ
= NULL
;
607 krb5_ccache ccache
= NULL
;
609 problem
= krb5_cc_get_principal(kcontext
, delegated_cred
, &princ
);
611 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_get_principal", NULL
, kcontext
, problem
);
615 if (mod_authn_gssapi_create_krb5_ccache(srv
, con
, p
, kcontext
, princ
, &ccache
)) {
619 problem
= krb5_cc_copy_creds(kcontext
, delegated_cred
, ccache
);
621 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_copy_creds", NULL
, kcontext
, problem
);
625 krb5_free_principal(kcontext
, princ
);
626 krb5_cc_close(kcontext
, ccache
);
631 krb5_free_principal(kcontext
, princ
);
633 krb5_cc_destroy(kcontext
, ccache
);
637 static handler_t
mod_authn_gssapi_send_401_unauthorized_basic (connection
*con
)
639 con
->http_status
= 401;
641 http_header_response_set(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\""));
642 return HANDLER_FINISHED
;
645 static handler_t
mod_authn_gssapi_basic(server
*srv
, connection
*con
, void *p_d
, const http_auth_require_t
*require
, const buffer
*username
, const char *pw
)
647 krb5_context kcontext
= NULL
;
648 krb5_keytab keytab
= NULL
;
649 krb5_principal s_princ
= NULL
;
650 krb5_principal c_princ
= NULL
;
652 krb5_ccache c_ccache
= NULL
;
653 krb5_ccache ret_ccache
= NULL
;
654 krb5_error_code code
;
657 buffer
*user_at_realm
= NULL
;
658 plugin_data
* const p
= (plugin_data
*)p_d
;
661 log_error_write(srv
, __FILE__
, __LINE__
, "s", "Empty passwords are not accepted");
662 return mod_authn_gssapi_send_401_unauthorized_basic(con
);
665 mod_authn_gssapi_patch_connection(srv
, con
, p
);
667 code
= krb5_init_context(&kcontext
);
669 log_error_write(srv
, __FILE__
, __LINE__
, "sd", "krb5_init_context():", code
);
670 return mod_authn_gssapi_send_401_unauthorized_basic(con
); /*(well, should be 500)*/
673 code
= krb5_kt_resolve(kcontext
, p
->conf
.auth_gssapi_keytab
->ptr
, &keytab
);
675 log_error_write(srv
, __FILE__
, __LINE__
, "sdb", "krb5_kt_resolve():", code
, p
->conf
.auth_gssapi_keytab
);
676 return mod_authn_gssapi_send_401_unauthorized_basic(con
); /*(well, should be 500)*/
679 sprinc
= buffer_init_buffer(p
->conf
.auth_gssapi_principal
);
680 if (strchr(sprinc
->ptr
, '/') == NULL
) {
681 /*(copy HTTP Host, omitting port if port is present)*/
682 /* ??? Should con->server_name be used if http_host not present?
683 * ??? What if con->server_name is not set?
684 * ??? Will this work below if IPv6 provided in Host? probably not */
685 if (!buffer_is_empty(con
->request
.http_host
)) {
686 buffer_append_string_len(sprinc
, CONST_STR_LEN("/"));
687 buffer_append_string_len(sprinc
, con
->request
.http_host
->ptr
, strcspn(con
->request
.http_host
->ptr
, ":"));
691 /*(init c_creds before anything which might krb5_free_cred_contents())*/
692 memset(&c_creds
, 0, sizeof(c_creds
));
694 ret
= krb5_parse_name(kcontext
, sprinc
->ptr
, &s_princ
);
696 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_parse_name", sprinc
->ptr
, kcontext
, ret
);
701 if (strchr(username
->ptr
, '@') == NULL
) {
702 user_at_realm
= buffer_init_buffer(username
);
703 BUFFER_APPEND_STRING_CONST(user_at_realm
, "@");
704 buffer_append_string_buffer(user_at_realm
, require
->realm
);
707 ret
= krb5_parse_name(kcontext
, (user_at_realm
? user_at_realm
->ptr
: username
->ptr
), &c_princ
);
709 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_parse_name", (user_at_realm
? user_at_realm
->ptr
: username
->ptr
), kcontext
, ret
);
710 if (user_at_realm
) buffer_free(user_at_realm
);
714 if (user_at_realm
) buffer_free(user_at_realm
);
715 /* XXX: if the qualified username with @realm should be in REMOTE_USER,
716 * then http_auth_backend_t basic interface needs to change to pass
717 * modifiable buffer *username, but note that const char *pw follows
718 * in the truncated buffer *username, so pw would need to be copied
719 * before modifying buffer *username */
723 * ret = krb5_unparse_name(kcontext, c_princ, &name);
725 * log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw);
727 * krb5_free_unparsed_name(kcontext, name);
730 ret
= krb5_get_init_creds_password(kcontext
, &c_creds
, c_princ
, pw
, NULL
, NULL
, 0, NULL
, NULL
);
732 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_get_init_creds_password", NULL
, kcontext
, ret
);
736 ret
= mod_authn_gssapi_verify_krb5_init_creds(srv
, kcontext
, &c_creds
, s_princ
, keytab
);
738 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "mod_authn_gssapi_verify_krb5_init_creds", NULL
, kcontext
, ret
);
742 if (!p
->conf
.auth_gssapi_store_creds
) goto end
;
744 ret
= krb5_cc_resolve(kcontext
, "MEMORY:", &ret_ccache
);
746 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_resolve", NULL
, kcontext
, ret
);
750 ret
= krb5_cc_initialize(kcontext
, ret_ccache
, c_princ
);
752 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_initialize", NULL
, kcontext
, ret
);
756 ret
= krb5_cc_store_cred(kcontext
, ret_ccache
, &c_creds
);
758 mod_authn_gssapi_log_krb5_error(srv
, __FILE__
, __LINE__
, "krb5_cc_store_cred", NULL
, kcontext
, ret
);
762 c_ccache
= ret_ccache
;
767 krb5_free_cred_contents(kcontext
, &c_creds
);
769 krb5_cc_destroy(kcontext
, ret_ccache
);
771 if (!ret
&& c_ccache
&& (ret
= mod_authn_gssapi_store_krb5_creds(srv
, con
, p
, kcontext
, c_ccache
))) {
772 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username
);
777 krb5_free_principal(kcontext
, c_princ
);
779 krb5_free_principal(kcontext
, s_princ
);
781 krb5_cc_destroy(kcontext
, c_ccache
);
783 krb5_kt_close(kcontext
, keytab
);
785 krb5_free_context(kcontext
);
787 if (0 == ret
&& http_auth_match_rules(require
,username
->ptr
,NULL
,NULL
)){
788 return HANDLER_GO_ON
;
791 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
792 log_error_write(srv
, __FILE__
, __LINE__
, "sbsBsB", "password doesn't match for", con
->uri
.path
, "username:", username
, ", IP:", con
->dst_addr_buf
);
793 return mod_authn_gssapi_send_401_unauthorized_basic(con
);
798 CONNECTION_FUNC(mod_authn_gssapi_handle_reset
) {
799 plugin_data
*p
= (plugin_data
*)p_d
;
800 buffer
*kccname
= (buffer
*)con
->plugin_ctx
[p
->id
];
801 if (NULL
!= kccname
) {
802 con
->plugin_ctx
[p
->id
] = NULL
;
803 unlink(kccname
->ptr
+sizeof("FILE:")-1);
804 buffer_free(kccname
);
808 return HANDLER_GO_ON
;
811 int mod_authn_gssapi_plugin_init(plugin
*p
);
812 int mod_authn_gssapi_plugin_init(plugin
*p
) {
813 p
->version
= LIGHTTPD_VERSION_ID
;
814 p
->name
= buffer_init_string("authn_gssapi");
815 p
->init
= mod_authn_gssapi_init
;
816 p
->set_defaults
= mod_authn_gssapi_set_defaults
;
817 p
->cleanup
= mod_authn_gssapi_free
;
818 p
->connection_reset
= mod_authn_gssapi_handle_reset
;