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 "fdevent.h" 35 #include "http_header.h" 36 #include "log.h" 37 #include "plugin.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 /* process and validate config directives 123 * (init i to 0 if global context; to 1 to skip empty global context) */ 124 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 125 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 126 for (; -1 != cpv->k_id; ++cpv) { 127 switch (cpv->k_id) { 128 case 0: /* auth.backend.gssapi.keytab */ 129 if (buffer_is_blank(cpv->v.b)) 130 cpv->v.b = NULL; 131 break; 132 case 1: /* auth.backend.gssapi.principal */ 133 case 2: /* auth.backend.gssapi.store-creds */ 134 default:/* should not happen */ 135 break; 136 } 137 } 138 } 139 140 /* default enabled for backwards compatibility; disable in future */ 141 p->defaults.auth_gssapi_store_creds = 1; 142 143 /* initialize p->defaults from global config context */ 144 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 145 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 146 if (-1 != cpv->k_id) 147 mod_authn_gssapi_merge_config(&p->defaults, cpv); 148 } 149 150 return HANDLER_GO_ON; 151 } 152 153 __attribute_cold__ 154 static handler_t mod_authn_gssapi_send_400_bad_request (request_st * const r) 155 { 156 r->http_status = 400; 157 r->handler_module = NULL; 158 return HANDLER_FINISHED; 159 } 160 161 __attribute_cold__ 162 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) 163 { 164 buffer * const msg = buffer_init(); 165 OM_uint32 maj_stat, min_stat; 166 OM_uint32 msg_ctx = 0; 167 gss_buffer_desc status_string; 168 169 buffer_copy_string(msg, func); 170 buffer_append_char(msg, '('); 171 if (extra) buffer_append_string(msg, extra); 172 buffer_append_string_len(msg, CONST_STR_LEN("):")); 173 174 do { 175 maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE, 176 GSS_C_NO_OID, &msg_ctx, &status_string); 177 if (GSS_ERROR(maj_stat)) 178 break; 179 180 buffer_append_string(msg, status_string.value); 181 gss_release_buffer(&min_stat, &status_string); 182 183 maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE, 184 GSS_C_NULL_OID, &msg_ctx, &status_string); 185 if (!GSS_ERROR(maj_stat)) { 186 buffer_append_string_len(msg, CONST_STR_LEN(" (")); 187 buffer_append_string(msg, status_string.value); 188 buffer_append_char(msg, ')'); 189 gss_release_buffer(&min_stat, &status_string); 190 } 191 } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); 192 193 log_error(errh, file, line, "%s", msg->ptr); 194 buffer_free(msg); 195 } 196 197 __attribute_cold__ 198 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) 199 { 200 UNUSED(context); 201 /*(extra might be NULL)*/ 202 log_error(errh, file, line, 203 "%s (%s): %s", func, extra ? extra : "", error_message(code)); 204 } 205 206 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) 207 { 208 char kccname[] = "FILE:/tmp/krb5cc_gssapi_XXXXXX"; 209 char * const ccname = kccname + sizeof("FILE:")-1; 210 /*(future: might consider using server.upload-dirs instead of /tmp)*/ 211 int fd = fdevent_mkostemp(ccname, 0); 212 if (fd < 0) { 213 log_perror(r->conf.errh, __FILE__, __LINE__, "mkstemp(): %s", ccname); 214 return -1; 215 } 216 close(fd); 217 218 do { 219 krb5_error_code problem; 220 221 problem = krb5_cc_resolve(kcontext, kccname, ccache); 222 if (problem) { 223 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem); 224 break; 225 } 226 227 problem = krb5_cc_initialize(kcontext, *ccache, princ); 228 if (problem) { 229 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", kccname, kcontext, problem); 230 break; 231 } 232 233 buffer * const vb = http_header_env_set_ptr(r, CONST_STR_LEN("KRB5CCNAME")); 234 r->plugin_ctx[p->id] = vb; 235 const size_t ccnamelen = sizeof(kccname)-sizeof("FILE:"); 236 buffer_copy_string_len(vb, ccname, ccnamelen); 237 http_header_request_set(r, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen); 238 239 return 0; 240 241 } while (0); 242 243 if (*ccache) { 244 krb5_cc_destroy(kcontext, *ccache); 245 *ccache = NULL; 246 } 247 unlink(ccname); 248 249 return -1; 250 } 251 252 /* 253 * HTTP auth Negotiate 254 */ 255 256 __attribute_cold__ 257 static handler_t mod_authn_gssapi_send_500_server_error (request_st * const r) 258 { 259 r->http_status = 500; 260 r->handler_module = NULL; 261 return HANDLER_FINISHED; 262 } 263 264 static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (request_st * const r) 265 { 266 r->http_status = 401; 267 r->handler_module = NULL; 268 http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE, 269 CONST_STR_LEN("WWW-Authenticate"), 270 CONST_STR_LEN("Negotiate")); 271 return HANDLER_FINISHED; 272 } 273 274 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) 275 { 276 OM_uint32 maj_stat, min_stat; 277 krb5_principal princ = NULL; 278 krb5_ccache ccache = NULL; 279 krb5_error_code problem; 280 krb5_context context; 281 282 problem = krb5_init_context(&context); 283 if (problem) { 284 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem); 285 return 0; 286 } 287 288 problem = krb5_parse_name(context, princ_name, &princ); 289 if (problem) { 290 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem); 291 goto end; 292 } 293 294 if (mod_authn_gssapi_create_krb5_ccache(r, p, context, princ, &ccache)) 295 goto end; 296 297 maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache); 298 if (GSS_ERROR(maj_stat)) { 299 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat); 300 goto end; 301 } 302 303 krb5_cc_close(context, ccache); 304 krb5_free_principal(context, princ); 305 krb5_free_context(context); 306 return 1; 307 308 end: 309 if (princ) 310 krb5_free_principal(context, princ); 311 if (ccache) 312 krb5_cc_destroy(context, ccache); 313 krb5_free_context(context); 314 315 return 0; 316 } 317 318 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) 319 { 320 OM_uint32 st_major, st_minor, acc_flags; 321 gss_buffer_desc token_s = GSS_C_EMPTY_BUFFER; 322 gss_buffer_desc token_in = GSS_C_EMPTY_BUFFER; 323 gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER; 324 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; 325 gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL; 326 gss_ctx_id_t context = GSS_C_NO_CONTEXT; 327 gss_name_t server_name = GSS_C_NO_NAME; 328 gss_name_t client_name = GSS_C_NO_NAME; 329 330 buffer *sprinc; 331 handler_t rc = HANDLER_ERROR; 332 333 buffer *t_in = buffer_init(); 334 if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) { 335 log_error(r->conf.errh, __FILE__, __LINE__, "decoding GSSAPI authentication header failed %s", realm_str); 336 buffer_free(t_in); 337 return mod_authn_gssapi_send_400_bad_request(r); 338 } 339 340 mod_authn_gssapi_patch_config(r, p); 341 342 if (p->conf.auth_gssapi_keytab) { 343 /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 344 * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */ 345 /* ??? Should KRB5_KTNAME go into r->env instead ??? */ 346 /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */ 347 buffer ktname; 348 memset(&ktname, 0, sizeof(ktname)); 349 buffer_copy_string_len(&ktname, CONST_STR_LEN("KRB5_KTNAME=")); 350 buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab); 351 putenv(ktname.ptr); 352 /* ktname.ptr becomes part of the environment, do not free */ 353 } 354 355 sprinc = buffer_init(); 356 if (p->conf.auth_gssapi_principal) 357 buffer_copy_buffer(sprinc, p->conf.auth_gssapi_principal); 358 if (strchr(sprinc->ptr, '/') == NULL) { 359 /*(copy HTTP Host, omitting port if port is present)*/ 360 /* ??? Should r->server_name be used if http_host not present? 361 * ??? What if r->server_name is not set? 362 * ??? Will this work below if IPv6 provided in Host? probably not */ 363 if (r->http_host && !buffer_is_blank(r->http_host)) 364 buffer_append_str2(sprinc, CONST_STR_LEN("/"), 365 r->http_host->ptr, 366 strcspn(r->http_host->ptr, ":")); 367 } 368 if (strchr(sprinc->ptr, '@') == NULL) 369 buffer_append_str2(sprinc, CONST_STR_LEN("@"), 370 BUF_PTR_LEN(require->realm)); 371 /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/ 372 /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/ 373 #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name 374 375 token_s.value = sprinc->ptr; 376 token_s.length = buffer_clen(sprinc); 377 st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name); 378 if (GSS_ERROR(st_major)) { 379 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor); 380 goto end; 381 } 382 383 memset(&token_s, 0, sizeof(token_s)); 384 st_major = gss_display_name(&st_minor, server_name, &token_s, NULL); 385 if (GSS_ERROR(st_major)) { 386 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 387 goto end; 388 } 389 390 /* acquire server's own credentials */ 391 st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL); 392 if (GSS_ERROR(st_major)) { 393 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor); 394 goto end; 395 } 396 397 /* accept the user's context */ 398 token_in.length = buffer_clen(t_in); 399 token_in.value = t_in->ptr; 400 st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS, 401 &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred); 402 if (GSS_ERROR(st_major)) { 403 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor); 404 goto end; 405 } 406 407 /* fetch the username */ 408 st_major = gss_display_name(&st_minor, client_name, &token_out, NULL); 409 if (GSS_ERROR(st_major)) { 410 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 411 goto end; 412 } 413 414 if (!(acc_flags & GSS_C_CONF_FLAG)) { 415 log_error(r->conf.errh, __FILE__, __LINE__, "No confidentiality for user: %s", (char *)token_out.value); 416 goto end; 417 } 418 419 /* check the allow-rules */ 420 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) { 421 goto end; 422 } 423 424 if (p->conf.auth_gssapi_store_creds) { 425 if (!(acc_flags & GSS_C_DELEG_FLAG)) { 426 log_error(r->conf.errh, __FILE__, __LINE__, "Unable to delegate credentials for user: %s", (char *)token_out.value); 427 goto end; 428 } 429 else if (!mod_authn_gssapi_store_gss_creds(r, p, token_out.value, client_cred)) { 430 rc = mod_authn_gssapi_send_500_server_error(r); 431 goto end; 432 } 433 } 434 435 http_auth_setenv(r, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI")); 436 rc = HANDLER_GO_ON; /* success */ 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 rc != HANDLER_ERROR ? rc : mod_authn_gssapi_send_401_unauthorized_negotiate(r); 463 } 464 465 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) 466 { 467 const buffer *vb = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 468 469 UNUSED(backend); 470 if (NULL == vb) { 471 return mod_authn_gssapi_send_401_unauthorized_negotiate(r); 472 } 473 474 if (!buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Negotiate "))) { 475 return mod_authn_gssapi_send_400_bad_request(r); 476 } 477 478 return mod_authn_gssapi_check_spnego(r, (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(krb5_context context, krb5_creds *creds, krb5_principal ap_req_server, krb5_keytab ap_req_keytab, log_error_st *errh) 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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(errh, __FILE__, __LINE__, "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(request_st * const r, plugin_data * const 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(r->conf.errh, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem); 592 goto end; 593 } 594 595 if (mod_authn_gssapi_create_krb5_ccache(r, 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(r->conf.errh, __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 (request_st * const r) 618 { 619 r->http_status = 401; 620 r->handler_module = NULL; 621 http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE, 622 CONST_STR_LEN("WWW-Authenticate"), 623 CONST_STR_LEN("Basic realm=\"Kerberos\"")); 624 return HANDLER_FINISHED; 625 } 626 627 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) 628 { 629 krb5_context kcontext = NULL; 630 krb5_keytab keytab = NULL; 631 krb5_principal s_princ = NULL; 632 krb5_principal c_princ = NULL; 633 krb5_creds c_creds; 634 krb5_ccache c_ccache = NULL; 635 krb5_ccache ret_ccache = NULL; 636 krb5_error_code code; 637 int ret; 638 buffer *sprinc; 639 buffer *user_at_realm = NULL; 640 plugin_data * const p = (plugin_data *)p_d; 641 642 if (*pw == '\0') { 643 log_error(r->conf.errh, __FILE__, __LINE__, "Empty passwords are not accepted"); 644 return mod_authn_gssapi_send_401_unauthorized_basic(r); 645 } 646 647 mod_authn_gssapi_patch_config(r, p); 648 649 code = krb5_init_context(&kcontext); 650 if (code) { 651 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context(): %d", code); 652 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 653 } 654 655 if (!p->conf.auth_gssapi_keytab) { 656 log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend.gssapi.keytab not configured"); 657 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 658 } 659 660 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 661 if (code) { 662 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_kt_resolve(): %d %s", code, p->conf.auth_gssapi_keytab->ptr); 663 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 664 } 665 666 sprinc = buffer_init(); 667 if (p->conf.auth_gssapi_principal) 668 buffer_copy_buffer(sprinc, p->conf.auth_gssapi_principal); 669 if (strchr(sprinc->ptr, '/') == NULL) { 670 /*(copy HTTP Host, omitting port if port is present)*/ 671 /* ??? Should r->server_name be used if http_host not present? 672 * ??? What if r->server_name is not set? 673 * ??? Will this work below if IPv6 provided in Host? probably not */ 674 if (r->http_host && !buffer_is_blank(r->http_host)) 675 buffer_append_str2(sprinc, CONST_STR_LEN("/"), 676 r->http_host->ptr, 677 strcspn(r->http_host->ptr, ":")); 678 } 679 680 /*(init c_creds before anything which might krb5_free_cred_contents())*/ 681 memset(&c_creds, 0, sizeof(c_creds)); 682 683 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ); 684 if (ret) { 685 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret); 686 ret = -1; 687 goto end; 688 } 689 690 if (strchr(username->ptr, '@') == NULL) { 691 buffer_copy_buffer((user_at_realm = buffer_init()), username); 692 buffer_append_str2(user_at_realm, CONST_STR_LEN("@"), 693 BUF_PTR_LEN(require->realm)); 694 } 695 696 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ); 697 if (ret) { 698 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); 699 if (user_at_realm) buffer_free(user_at_realm); 700 ret = -1; 701 goto end; 702 } 703 if (user_at_realm) buffer_free(user_at_realm); 704 /* XXX: if the qualified username with @realm should be in REMOTE_USER, 705 * then http_auth_backend_t basic interface needs to change to pass 706 * modifiable buffer *username, but note that const char *pw follows 707 * in the truncated buffer *username, so pw would need to be copied 708 * before modifying buffer *username */ 709 710 /* 711 * char *name = NULL; 712 * ret = krb5_unparse_name(kcontext, c_princ, &name); 713 * if (ret == 0) { 714 * log_error(r->conf.errh, __FILE__, __LINE__, "Trying to get TGT for user: %s password: %s", username->ptr, pw); 715 * } 716 * krb5_free_unparsed_name(kcontext, name); 717 */ 718 719 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL); 720 if (ret) { 721 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret); 722 goto end; 723 } 724 725 ret = mod_authn_gssapi_verify_krb5_init_creds(kcontext, &c_creds, s_princ, keytab, r->conf.errh); 726 if (ret) { 727 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret); 728 goto end; 729 } 730 731 if (!p->conf.auth_gssapi_store_creds) goto end; 732 733 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache); 734 if (ret) { 735 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret); 736 goto end; 737 } 738 739 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ); 740 if (ret) { 741 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret); 742 goto end; 743 } 744 745 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds); 746 if (ret) { 747 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret); 748 goto end; 749 } 750 751 c_ccache = ret_ccache; 752 ret_ccache = NULL; 753 754 end: 755 756 krb5_free_cred_contents(kcontext, &c_creds); 757 if (ret_ccache) 758 krb5_cc_destroy(kcontext, ret_ccache); 759 760 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(r, p, kcontext, c_ccache))) { 761 log_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_store_krb5_creds failed for %s", username->ptr); 762 } 763 764 buffer_free(sprinc); 765 if (c_princ) 766 krb5_free_principal(kcontext, c_princ); 767 if (s_princ) 768 krb5_free_principal(kcontext, s_princ); 769 if (c_ccache) 770 krb5_cc_destroy(kcontext, c_ccache); 771 if (keytab) 772 krb5_kt_close(kcontext, keytab); 773 774 krb5_free_context(kcontext); 775 776 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){ 777 return HANDLER_GO_ON; 778 } 779 else { 780 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */ 781 log_error(r->conf.errh, __FILE__, __LINE__, 782 "password doesn't match for %s username: %s IP: %s", 783 r->uri.path.ptr, username->ptr, r->con->dst_addr_buf.ptr); 784 return mod_authn_gssapi_send_401_unauthorized_basic(r); 785 } 786 } 787 788 789 REQUEST_FUNC(mod_authn_gssapi_handle_reset) { 790 plugin_data *p = (plugin_data *)p_d; 791 buffer * const ccname = (buffer *)r->plugin_ctx[p->id]; 792 if (NULL != ccname) { 793 r->plugin_ctx[p->id] = NULL; 794 unlink(ccname->ptr); 795 } 796 797 return HANDLER_GO_ON; 798 } 799 800 int mod_authn_gssapi_plugin_init(plugin *p); 801 int mod_authn_gssapi_plugin_init(plugin *p) { 802 p->version = LIGHTTPD_VERSION_ID; 803 p->name = "authn_gssapi"; 804 p->init = mod_authn_gssapi_init; 805 p->set_defaults= mod_authn_gssapi_set_defaults; 806 p->handle_request_reset = mod_authn_gssapi_handle_reset; 807 808 return 0; 809 } 810