1 #include "first.h" 2 3 /* mod_authn_gssapi 4 * 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) 8 * 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. 13 * 14 * LIMITATIONS: 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 17 * 18 * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS: 19 * - Kerberos auth is synchronous and blocks waiting for response 20 * TODO: attempt async? 21 */ 22 23 #include "plugin.h" 24 25 #include <krb5.h> 26 #include <gssapi.h> 27 #include <gssapi/gssapi_krb5.h> 28 29 #include "http_auth.h" 30 #include "http_header.h" 31 #include "base.h" 32 #include "log.h" 33 #include "md5.h" 34 #include "base64.h" 35 36 #include <errno.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <unistd.h> 40 41 typedef struct { 42 buffer *auth_gssapi_keytab; 43 buffer *auth_gssapi_principal; 44 } plugin_config; 45 46 typedef struct { 47 PLUGIN_DATA; 48 plugin_config **config_storage; 49 plugin_config conf; 50 } plugin_data; 51 52 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); 53 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); 54 55 INIT_FUNC(mod_authn_gssapi_init) { 56 static http_auth_scheme_t http_auth_scheme_gssapi = 57 { "gssapi", mod_authn_gssapi_check, NULL }; 58 static http_auth_backend_t http_auth_backend_gssapi = 59 { "gssapi", mod_authn_gssapi_basic, NULL, NULL }; 60 plugin_data *p = calloc(1, sizeof(*p)); 61 62 /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */ 63 http_auth_scheme_gssapi.p_d = p; 64 http_auth_scheme_set(&http_auth_scheme_gssapi); 65 http_auth_backend_gssapi.p_d = p; 66 http_auth_backend_set(&http_auth_backend_gssapi); 67 68 return p; 69 } 70 71 FREE_FUNC(mod_authn_gssapi_free) { 72 plugin_data *p = p_d; 73 74 UNUSED(srv); 75 76 if (!p) return HANDLER_GO_ON; 77 78 if (p->config_storage) { 79 size_t i; 80 for (i = 0; i < srv->config_context->used; i++) { 81 plugin_config *s = p->config_storage[i]; 82 83 if (NULL == s) continue; 84 85 buffer_free(s->auth_gssapi_keytab); 86 buffer_free(s->auth_gssapi_principal); 87 88 free(s); 89 } 90 free(p->config_storage); 91 } 92 93 free(p); 94 95 return HANDLER_GO_ON; 96 } 97 98 SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults) { 99 plugin_data *p = p_d; 100 size_t i; 101 config_values_t cv[] = { 102 { "auth.backend.gssapi.keytab", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, 103 { "auth.backend.gssapi.principal", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, 104 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 105 }; 106 107 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 108 109 for (i = 0; i < srv->config_context->used; i++) { 110 data_config const* config = (data_config const*)srv->config_context->data[i]; 111 plugin_config *s; 112 113 s = calloc(1, sizeof(plugin_config)); 114 115 s->auth_gssapi_keytab = buffer_init(); 116 s->auth_gssapi_principal = buffer_init(); 117 118 cv[0].destination = s->auth_gssapi_keytab; 119 cv[1].destination = s->auth_gssapi_principal; 120 121 p->config_storage[i] = s; 122 123 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 124 return HANDLER_ERROR; 125 } 126 } 127 128 return HANDLER_GO_ON; 129 } 130 131 #define PATCH(x) \ 132 p->conf.x = s->x; 133 static int mod_authn_gssapi_patch_connection(server *srv, connection *con, plugin_data *p) 134 { 135 size_t i, j; 136 plugin_config *s = p->config_storage[0]; 137 138 PATCH(auth_gssapi_keytab); 139 PATCH(auth_gssapi_principal); 140 141 /* skip the first, the global context */ 142 for (i = 1; i < srv->config_context->used; i++) { 143 data_config *dc = (data_config *)srv->config_context->data[i]; 144 s = p->config_storage[i]; 145 146 /* condition didn't match */ 147 if (!config_check_cond(srv, con, dc)) continue; 148 149 /* merge config */ 150 for (j = 0; j < dc->value->used; j++) { 151 data_unset *du = dc->value->data[j]; 152 153 if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.keytab"))) { 154 PATCH(auth_gssapi_keytab); 155 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.principal"))) { 156 PATCH(auth_gssapi_principal); 157 } 158 } 159 } 160 161 return 0; 162 } 163 #undef PATCH 164 165 static handler_t mod_authn_gssapi_send_400_bad_request (server *srv, connection *con) 166 { 167 UNUSED(srv); 168 con->http_status = 400; 169 con->mode = DIRECT; 170 return HANDLER_FINISHED; 171 } 172 173 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) 174 { 175 buffer * const msg = buffer_init_string(func); 176 OM_uint32 maj_stat, min_stat; 177 OM_uint32 msg_ctx = 0; 178 gss_buffer_desc status_string; 179 180 buffer_append_string_len(msg, CONST_STR_LEN("(")); 181 if (extra) buffer_append_string(msg, extra); 182 buffer_append_string_len(msg, CONST_STR_LEN("):")); 183 184 do { 185 maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE, 186 GSS_C_NO_OID, &msg_ctx, &status_string); 187 if (GSS_ERROR(maj_stat)) 188 break; 189 190 buffer_append_string(msg, status_string.value); 191 gss_release_buffer(&min_stat, &status_string); 192 193 maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE, 194 GSS_C_NULL_OID, &msg_ctx, &status_string); 195 if (!GSS_ERROR(maj_stat)) { 196 buffer_append_string_len(msg, CONST_STR_LEN(" (")); 197 buffer_append_string(msg, status_string.value); 198 buffer_append_string_len(msg, CONST_STR_LEN(")")); 199 gss_release_buffer(&min_stat, &status_string); 200 } 201 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); 202 203 log_error_write(srv, file, line, "b", msg); 204 buffer_free(msg); 205 } 206 207 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) 208 { 209 UNUSED(context); 210 /*(extra might be NULL)*/ 211 log_error_write(srv, file, line, "sssss", func, "(", extra, "):", 212 error_message(code)); 213 } 214 215 static int mod_authn_gssapi_create_krb5_ccache(server *srv, connection *con, plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache) 216 { 217 buffer * const kccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX"); 218 char * const ccname = kccname->ptr + sizeof("FILE:")-1; 219 const size_t ccnamelen = buffer_string_length(kccname)-(sizeof("FILE:")-1); 220 /*(future: might consider using server.upload-dirs instead of /tmp)*/ 221 #ifdef __COVERITY__ 222 /* POSIX-2008 requires mkstemp create file with 0600 perms */ 223 umask(0600); 224 #endif 225 /* coverity[secure_temp : FALSE] */ 226 int fd = mkstemp(ccname); 227 if (fd < 0) { 228 log_error_write(srv, __FILE__, __LINE__, "sss", "mkstemp():", ccname, strerror(errno)); 229 buffer_free(kccname); 230 return -1; 231 } 232 close(fd); 233 234 do { 235 krb5_error_code problem; 236 237 problem = krb5_cc_resolve(kcontext, kccname->ptr, ccache); 238 if (problem) { 239 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem); 240 break; 241 } 242 243 problem = krb5_cc_initialize(kcontext, *ccache, princ); 244 if (problem) { 245 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", kccname->ptr, kcontext, problem); 246 break; 247 } 248 249 con->plugin_ctx[p->id] = kccname; 250 251 http_header_env_set(con, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen); 252 http_header_request_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen); 253 254 return 0; 255 256 } while (0); 257 258 if (*ccache) { 259 krb5_cc_destroy(kcontext, *ccache); 260 *ccache = NULL; 261 } 262 unlink(ccname); 263 buffer_free(kccname); 264 265 return -1; 266 } 267 268 /* 269 * HTTP auth Negotiate 270 */ 271 272 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (connection *con) 273 { 274 con->http_status = 401; 275 con->mode = DIRECT; 276 http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate")); 277 return HANDLER_FINISHED; 278 } 279 280 static int mod_authn_gssapi_store_gss_creds(server *srv, connection *con, plugin_data *p, char *princ_name, gss_cred_id_t delegated_cred) 281 { 282 OM_uint32 maj_stat, min_stat; 283 krb5_principal princ = NULL; 284 krb5_ccache ccache = NULL; 285 krb5_error_code problem; 286 krb5_context context; 287 288 problem = krb5_init_context(&context); 289 if (problem) { 290 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem); 291 return 0; 292 } 293 294 problem = krb5_parse_name(context, princ_name, &princ); 295 if (problem) { 296 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem); 297 goto end; 298 } 299 300 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, context, princ, &ccache)) 301 goto end; 302 303 maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache); 304 if (GSS_ERROR(maj_stat)) { 305 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat); 306 goto end; 307 } 308 309 krb5_cc_close(context, ccache); 310 krb5_free_principal(context, princ); 311 krb5_free_context(context); 312 return 1; 313 314 end: 315 if (princ) 316 krb5_free_principal(context, princ); 317 if (ccache) 318 krb5_cc_destroy(context, ccache); 319 krb5_free_context(context); 320 321 return 0; 322 } 323 324 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) 325 { 326 OM_uint32 st_major, st_minor, acc_flags; 327 gss_buffer_desc token_s = GSS_C_EMPTY_BUFFER; 328 gss_buffer_desc token_in = GSS_C_EMPTY_BUFFER; 329 gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER; 330 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; 331 gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL; 332 gss_ctx_id_t context = GSS_C_NO_CONTEXT; 333 gss_name_t server_name = GSS_C_NO_NAME; 334 gss_name_t client_name = GSS_C_NO_NAME; 335 336 buffer *sprinc; 337 int ret = 0; 338 339 buffer *t_in = buffer_init(); 340 if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) { 341 log_error_write(srv, __FILE__, __LINE__, "ss", "decoding GSSAPI authentication header failed", realm_str); 342 buffer_free(t_in); 343 return mod_authn_gssapi_send_400_bad_request(srv, con); 344 } 345 346 mod_authn_gssapi_patch_connection(srv, con, p); 347 348 { 349 /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 350 * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */ 351 /* ??? Should KRB5_KTNAME go into con->environment instead ??? */ 352 /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */ 353 buffer ktname; 354 memset(&ktname, 0, sizeof(ktname)); 355 buffer_copy_string(&ktname, "KRB5_KTNAME="); 356 buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab); 357 putenv(ktname.ptr); 358 /* ktname.ptr becomes part of the environment, do not free */ 359 } 360 361 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal); 362 if (strchr(sprinc->ptr, '/') == NULL) { 363 /*(copy HTTP Host, omitting port if port is present)*/ 364 /* ??? Should con->server_name be used if http_host not present? 365 * ??? What if con->server_name is not set? 366 * ??? Will this work below if IPv6 provided in Host? probably not */ 367 if (!buffer_is_empty(con->request.http_host)) { 368 buffer_append_string_len(sprinc, CONST_STR_LEN("/")); 369 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":")); 370 } 371 } 372 if (strchr(sprinc->ptr, '@') == NULL) { 373 buffer_append_string_len(sprinc, CONST_STR_LEN("@")); 374 buffer_append_string_buffer(sprinc, require->realm); 375 } 376 /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/ 377 /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/ 378 #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name 379 380 token_s.value = sprinc->ptr; 381 token_s.length = buffer_string_length(sprinc); 382 st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name); 383 if (GSS_ERROR(st_major)) { 384 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor); 385 goto end; 386 } 387 388 memset(&token_s, 0, sizeof(token_s)); 389 st_major = gss_display_name(&st_minor, server_name, &token_s, NULL); 390 if (GSS_ERROR(st_major)) { 391 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 392 goto end; 393 } 394 395 /* acquire server's own credentials */ 396 st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL); 397 if (GSS_ERROR(st_major)) { 398 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor); 399 goto end; 400 } 401 402 /* accept the user's context */ 403 token_in.length = buffer_string_length(t_in); 404 token_in.value = t_in->ptr; 405 st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS, 406 &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred); 407 if (GSS_ERROR(st_major)) { 408 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor); 409 goto end; 410 } 411 412 /* fetch the username */ 413 st_major = gss_display_name(&st_minor, client_name, &token_out, NULL); 414 if (GSS_ERROR(st_major)) { 415 mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 416 goto end; 417 } 418 419 if (!(acc_flags & GSS_C_CONF_FLAG)) { 420 log_error_write(srv, __FILE__, __LINE__, "ss", "No confidentiality for user:", token_out.value); 421 goto end; 422 } 423 424 if (!(acc_flags & GSS_C_DELEG_FLAG)) { 425 log_error_write(srv, __FILE__, __LINE__, "ss", "Unable to delegate credentials for user:", token_out.value); 426 goto end; 427 } 428 429 /* check the allow-rules */ 430 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) { 431 goto end; 432 } 433 434 ret = mod_authn_gssapi_store_gss_creds(srv, con, p, token_out.value, client_cred); 435 if (ret) 436 http_auth_setenv(con, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI")); 437 438 end: 439 buffer_free(t_in); 440 buffer_free(sprinc); 441 442 if (context != GSS_C_NO_CONTEXT) 443 gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER); 444 445 if (client_cred != GSS_C_NO_CREDENTIAL) 446 gss_release_cred(&st_minor, &client_cred); 447 if (server_cred != GSS_C_NO_CREDENTIAL) 448 gss_release_cred(&st_minor, &server_cred); 449 450 if (client_name != GSS_C_NO_NAME) 451 gss_release_name(&st_minor, &client_name); 452 if (server_name != GSS_C_NO_NAME) 453 gss_release_name(&st_minor, &server_name); 454 455 if (token_s.length) 456 gss_release_buffer(&st_minor, &token_s); 457 /* if (token_in.length) 458 * gss_release_buffer(&st_minor, &token_in); */ 459 if (token_out.length) 460 gss_release_buffer(&st_minor, &token_out); 461 462 return ret ? HANDLER_GO_ON : mod_authn_gssapi_send_401_unauthorized_negotiate(con); 463 } 464 465 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) 466 { 467 buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 468 469 UNUSED(backend); 470 if (NULL == vb) { 471 return mod_authn_gssapi_send_401_unauthorized_negotiate(con); 472 } 473 474 if (0 != strncasecmp(vb->ptr, "Negotiate ", sizeof("Negotiate ")-1)) { 475 return mod_authn_gssapi_send_400_bad_request(srv, con); 476 } 477 478 return mod_authn_gssapi_check_spnego(srv, con, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1); 479 } 480 481 /* 482 * HTTP auth Basic realm="kerberos" 483 */ 484 485 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) 486 { 487 krb5_error_code ret; 488 krb5_data req; 489 krb5_ccache local_ccache = NULL; 490 krb5_creds *new_creds = NULL; 491 krb5_auth_context auth_context = NULL; 492 krb5_keytab keytab = NULL; 493 char *server_name; 494 495 memset(&req, 0, sizeof(req)); 496 497 if (ap_req_keytab == NULL) { 498 ret = krb5_kt_default(context, &keytab); 499 if (ret) 500 return ret; 501 } else 502 keytab = ap_req_keytab; 503 504 ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache); 505 if (ret) { 506 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_resolve() failed when verifying KDC"); 507 /* return ret; */ 508 goto end; 509 } 510 511 ret = krb5_cc_initialize(context, local_ccache, creds->client); 512 if (ret) { 513 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_initialize() failed when verifying KDC"); 514 goto end; 515 } 516 517 ret = krb5_cc_store_cred(context, local_ccache, creds); 518 if (ret) { 519 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_store_cred() failed when verifying KDC"); 520 goto end; 521 } 522 523 ret = krb5_unparse_name(context, ap_req_server, &server_name); 524 if (ret) { 525 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_unparse_name() failed when verifying KDC"); 526 goto end; 527 } 528 krb5_free_unparsed_name(context, server_name); 529 530 if (!krb5_principal_compare(context, ap_req_server, creds->server)) { 531 krb5_creds match_cred; 532 533 memset(&match_cred, 0, sizeof(match_cred)); 534 535 match_cred.client = creds->client; 536 match_cred.server = ap_req_server; 537 538 ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds); 539 if (ret) { 540 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_get_credentials() failed when verifying KDC"); 541 goto end; 542 } 543 creds = new_creds; 544 } 545 546 ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req); 547 if (ret) { 548 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_mk_req_extended() failed when verifying KDC"); 549 goto end; 550 } 551 552 krb5_auth_con_free(context, auth_context); 553 auth_context = NULL; 554 ret = krb5_auth_con_init(context, &auth_context); 555 if (ret) { 556 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_auth_con_init() failed when verifying KDC"); 557 goto end; 558 } 559 560 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */ 561 krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); 562 ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL); 563 if (ret) { 564 log_error_write(srv, __FILE__, __LINE__, "s", "krb5_rd_req() failed when verifying KDC"); 565 goto end; 566 } 567 568 end: 569 krb5_free_data_contents(context, &req); 570 if (auth_context) 571 krb5_auth_con_free(context, auth_context); 572 if (new_creds) 573 krb5_free_creds(context, new_creds); 574 if (ap_req_keytab == NULL && keytab) 575 krb5_kt_close(context, keytab); 576 if (local_ccache) 577 krb5_cc_destroy(context, local_ccache); 578 579 return ret; 580 } 581 582 static int mod_authn_gssapi_store_krb5_creds(server *srv, connection *con, plugin_data *p, 583 krb5_context kcontext, krb5_ccache delegated_cred) 584 { 585 krb5_error_code problem; 586 krb5_principal princ = NULL; 587 krb5_ccache ccache = NULL; 588 589 problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ); 590 if (problem) { 591 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem); 592 goto end; 593 } 594 595 if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, kcontext, princ, &ccache)) { 596 goto end; 597 } 598 599 problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache); 600 if (problem) { 601 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem); 602 goto end; 603 } 604 605 krb5_free_principal(kcontext, princ); 606 krb5_cc_close(kcontext, ccache); 607 return 0; 608 609 end: 610 if (princ) 611 krb5_free_principal(kcontext, princ); 612 if (ccache) 613 krb5_cc_destroy(kcontext, ccache); 614 return -1; 615 } 616 617 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (connection *con) 618 { 619 con->http_status = 401; 620 con->mode = DIRECT; 621 http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\"")); 622 return HANDLER_FINISHED; 623 } 624 625 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) 626 { 627 krb5_context kcontext = NULL; 628 krb5_keytab keytab = NULL; 629 krb5_principal s_princ = NULL; 630 krb5_principal c_princ = NULL; 631 krb5_creds c_creds; 632 krb5_ccache c_ccache = NULL; 633 krb5_ccache ret_ccache = NULL; 634 krb5_error_code code; 635 int ret; 636 buffer *sprinc; 637 buffer *user_at_realm = NULL; 638 plugin_data * const p = (plugin_data *)p_d; 639 640 if (*pw == '\0') { 641 log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted"); 642 return mod_authn_gssapi_send_401_unauthorized_basic(con); 643 } 644 645 mod_authn_gssapi_patch_connection(srv, con, p); 646 647 code = krb5_init_context(&kcontext); 648 if (code) { 649 log_error_write(srv, __FILE__, __LINE__, "sd", "krb5_init_context():", code); 650 return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/ 651 } 652 653 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 654 if (code) { 655 log_error_write(srv, __FILE__, __LINE__, "sdb", "krb5_kt_resolve():", code, p->conf.auth_gssapi_keytab); 656 return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/ 657 } 658 659 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal); 660 if (strchr(sprinc->ptr, '/') == NULL) { 661 /*(copy HTTP Host, omitting port if port is present)*/ 662 /* ??? Should con->server_name be used if http_host not present? 663 * ??? What if con->server_name is not set? 664 * ??? Will this work below if IPv6 provided in Host? probably not */ 665 if (!buffer_is_empty(con->request.http_host)) { 666 buffer_append_string_len(sprinc, CONST_STR_LEN("/")); 667 buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":")); 668 } 669 } 670 671 /*(init c_creds before anything which might krb5_free_cred_contents())*/ 672 memset(&c_creds, 0, sizeof(c_creds)); 673 674 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ); 675 if (ret) { 676 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret); 677 ret = -1; 678 goto end; 679 } 680 681 if (strchr(username->ptr, '@') == NULL) { 682 user_at_realm = buffer_init_buffer(username); 683 BUFFER_APPEND_STRING_CONST(user_at_realm, "@"); 684 buffer_append_string_buffer(user_at_realm, require->realm); 685 } 686 687 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ); 688 if (ret) { 689 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret); 690 if (user_at_realm) buffer_free(user_at_realm); 691 ret = -1; 692 goto end; 693 } 694 if (user_at_realm) buffer_free(user_at_realm); 695 /* XXX: if the qualified username with @realm should be in REMOTE_USER, 696 * then http_auth_backend_t basic interface needs to change to pass 697 * modifiable buffer *username, but note that const char *pw follows 698 * in the truncated buffer *username, so pw would need to be copied 699 * before modifying buffer *username */ 700 701 /* 702 * char *name = NULL; 703 * ret = krb5_unparse_name(kcontext, c_princ, &name); 704 * if (ret == 0) { 705 * log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw); 706 * } 707 * krb5_free_unparsed_name(kcontext, name); 708 */ 709 710 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL); 711 if (ret) { 712 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret); 713 goto end; 714 } 715 716 ret = mod_authn_gssapi_verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab); 717 if (ret) { 718 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret); 719 goto end; 720 } 721 722 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache); 723 if (ret) { 724 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret); 725 goto end; 726 } 727 728 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ); 729 if (ret) { 730 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret); 731 goto end; 732 } 733 734 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds); 735 if (ret) { 736 mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret); 737 goto end; 738 } 739 740 c_ccache = ret_ccache; 741 ret_ccache = NULL; 742 743 end: 744 745 krb5_free_cred_contents(kcontext, &c_creds); 746 if (ret_ccache) 747 krb5_cc_destroy(kcontext, ret_ccache); 748 749 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(srv, con, p, kcontext, c_ccache))) { 750 log_error_write(srv, __FILE__, __LINE__, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username); 751 } 752 753 buffer_free(sprinc); 754 if (c_princ) 755 krb5_free_principal(kcontext, c_princ); 756 if (s_princ) 757 krb5_free_principal(kcontext, s_princ); 758 if (c_ccache) 759 krb5_cc_destroy(kcontext, c_ccache); 760 if (keytab) 761 krb5_kt_close(kcontext, keytab); 762 763 krb5_free_context(kcontext); 764 765 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){ 766 return HANDLER_GO_ON; 767 } 768 else { 769 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */ 770 log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf); 771 return mod_authn_gssapi_send_401_unauthorized_basic(con); 772 } 773 } 774 775 776 CONNECTION_FUNC(mod_authn_gssapi_handle_reset) { 777 plugin_data *p = (plugin_data *)p_d; 778 buffer *kccname = (buffer *)con->plugin_ctx[p->id]; 779 if (NULL != kccname) { 780 con->plugin_ctx[p->id] = NULL; 781 unlink(kccname->ptr+sizeof("FILE:")-1); 782 buffer_free(kccname); 783 } 784 785 UNUSED(srv); 786 return HANDLER_GO_ON; 787 } 788 789 int mod_authn_gssapi_plugin_init(plugin *p); 790 int mod_authn_gssapi_plugin_init(plugin *p) { 791 p->version = LIGHTTPD_VERSION_ID; 792 p->name = buffer_init_string("authn_gssapi"); 793 p->init = mod_authn_gssapi_init; 794 p->set_defaults= mod_authn_gssapi_set_defaults; 795 p->cleanup = mod_authn_gssapi_free; 796 p->connection_reset = mod_authn_gssapi_handle_reset; 797 798 p->data = NULL; 799 800 return 0; 801 } 802