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