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