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 /* check the allow-rules */ 415 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) { 416 goto end; 417 } 418 419 if (p->conf.auth_gssapi_store_creds) { 420 if (!(acc_flags & GSS_C_CONF_FLAG)) 421 log_error(r->conf.errh, __FILE__, __LINE__, "No confidentiality for user: %s", (char *)token_out.value); 422 if (!(acc_flags & GSS_C_DELEG_FLAG)) { 423 log_error(r->conf.errh, __FILE__, __LINE__, "Unable to delegate credentials for user: %s", (char *)token_out.value); 424 goto end; 425 } 426 else if (!mod_authn_gssapi_store_gss_creds(r, p, token_out.value, client_cred)) { 427 rc = mod_authn_gssapi_send_500_server_error(r); 428 goto end; 429 } 430 } 431 432 http_auth_setenv(r, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI")); 433 rc = HANDLER_GO_ON; /* success */ 434 435 end: 436 buffer_free(t_in); 437 buffer_free(sprinc); 438 439 if (context != GSS_C_NO_CONTEXT) 440 gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER); 441 442 if (client_cred != GSS_C_NO_CREDENTIAL) 443 gss_release_cred(&st_minor, &client_cred); 444 if (server_cred != GSS_C_NO_CREDENTIAL) 445 gss_release_cred(&st_minor, &server_cred); 446 447 if (client_name != GSS_C_NO_NAME) 448 gss_release_name(&st_minor, &client_name); 449 if (server_name != GSS_C_NO_NAME) 450 gss_release_name(&st_minor, &server_name); 451 452 if (token_s.length) 453 gss_release_buffer(&st_minor, &token_s); 454 /* if (token_in.length) 455 * gss_release_buffer(&st_minor, &token_in); */ 456 if (token_out.length) 457 gss_release_buffer(&st_minor, &token_out); 458 459 return rc != HANDLER_ERROR ? rc : mod_authn_gssapi_send_401_unauthorized_negotiate(r); 460 } 461 462 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) 463 { 464 const buffer *vb = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 465 466 UNUSED(backend); 467 if (NULL == vb) { 468 return mod_authn_gssapi_send_401_unauthorized_negotiate(r); 469 } 470 471 if (!buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Negotiate "))) { 472 return mod_authn_gssapi_send_400_bad_request(r); 473 } 474 475 return mod_authn_gssapi_check_spnego(r, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1); 476 } 477 478 /* 479 * HTTP auth Basic realm="kerberos" 480 */ 481 482 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) 483 { 484 krb5_error_code ret; 485 krb5_data req; 486 krb5_ccache local_ccache = NULL; 487 krb5_creds *new_creds = NULL; 488 krb5_auth_context auth_context = NULL; 489 krb5_keytab keytab = NULL; 490 char *server_name; 491 492 memset(&req, 0, sizeof(req)); 493 494 if (ap_req_keytab == NULL) { 495 ret = krb5_kt_default(context, &keytab); 496 if (ret) 497 return ret; 498 } else 499 keytab = ap_req_keytab; 500 501 ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache); 502 if (ret) { 503 log_error(errh, __FILE__, __LINE__, "krb5_cc_resolve() failed when verifying KDC"); 504 /* return ret; */ 505 goto end; 506 } 507 508 ret = krb5_cc_initialize(context, local_ccache, creds->client); 509 if (ret) { 510 log_error(errh, __FILE__, __LINE__, "krb5_cc_initialize() failed when verifying KDC"); 511 goto end; 512 } 513 514 ret = krb5_cc_store_cred(context, local_ccache, creds); 515 if (ret) { 516 log_error(errh, __FILE__, __LINE__, "krb5_cc_store_cred() failed when verifying KDC"); 517 goto end; 518 } 519 520 ret = krb5_unparse_name(context, ap_req_server, &server_name); 521 if (ret) { 522 log_error(errh, __FILE__, __LINE__, "krb5_unparse_name() failed when verifying KDC"); 523 goto end; 524 } 525 krb5_free_unparsed_name(context, server_name); 526 527 if (!krb5_principal_compare(context, ap_req_server, creds->server)) { 528 krb5_creds match_cred; 529 530 memset(&match_cred, 0, sizeof(match_cred)); 531 532 match_cred.client = creds->client; 533 match_cred.server = ap_req_server; 534 535 ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds); 536 if (ret) { 537 log_error(errh, __FILE__, __LINE__, "krb5_get_credentials() failed when verifying KDC"); 538 goto end; 539 } 540 creds = new_creds; 541 } 542 543 ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req); 544 if (ret) { 545 log_error(errh, __FILE__, __LINE__, "krb5_mk_req_extended() failed when verifying KDC"); 546 goto end; 547 } 548 549 krb5_auth_con_free(context, auth_context); 550 auth_context = NULL; 551 ret = krb5_auth_con_init(context, &auth_context); 552 if (ret) { 553 log_error(errh, __FILE__, __LINE__, "krb5_auth_con_init() failed when verifying KDC"); 554 goto end; 555 } 556 557 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */ 558 krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); 559 ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL); 560 if (ret) { 561 log_error(errh, __FILE__, __LINE__, "krb5_rd_req() failed when verifying KDC"); 562 goto end; 563 } 564 565 end: 566 krb5_free_data_contents(context, &req); 567 if (auth_context) 568 krb5_auth_con_free(context, auth_context); 569 if (new_creds) 570 krb5_free_creds(context, new_creds); 571 if (ap_req_keytab == NULL && keytab) 572 krb5_kt_close(context, keytab); 573 if (local_ccache) 574 krb5_cc_destroy(context, local_ccache); 575 576 return ret; 577 } 578 579 static int mod_authn_gssapi_store_krb5_creds(request_st * const r, plugin_data * const p, 580 krb5_context kcontext, krb5_ccache delegated_cred) 581 { 582 krb5_error_code problem; 583 krb5_principal princ = NULL; 584 krb5_ccache ccache = NULL; 585 586 problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ); 587 if (problem) { 588 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem); 589 goto end; 590 } 591 592 if (mod_authn_gssapi_create_krb5_ccache(r, p, kcontext, princ, &ccache)) { 593 goto end; 594 } 595 596 problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache); 597 if (problem) { 598 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem); 599 goto end; 600 } 601 602 krb5_free_principal(kcontext, princ); 603 krb5_cc_close(kcontext, ccache); 604 return 0; 605 606 end: 607 if (princ) 608 krb5_free_principal(kcontext, princ); 609 if (ccache) 610 krb5_cc_destroy(kcontext, ccache); 611 return -1; 612 } 613 614 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (request_st * const r) 615 { 616 r->http_status = 401; 617 r->handler_module = NULL; 618 http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE, 619 CONST_STR_LEN("WWW-Authenticate"), 620 CONST_STR_LEN("Basic realm=\"Kerberos\"")); 621 return HANDLER_FINISHED; 622 } 623 624 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) 625 { 626 krb5_context kcontext = NULL; 627 krb5_keytab keytab = NULL; 628 krb5_principal s_princ = NULL; 629 krb5_principal c_princ = NULL; 630 krb5_creds c_creds; 631 krb5_ccache c_ccache = NULL; 632 krb5_ccache ret_ccache = NULL; 633 krb5_error_code code; 634 int ret; 635 buffer *sprinc; 636 buffer *user_at_realm = NULL; 637 plugin_data * const p = (plugin_data *)p_d; 638 639 if (*pw == '\0') { 640 log_error(r->conf.errh, __FILE__, __LINE__, "Empty passwords are not accepted"); 641 return mod_authn_gssapi_send_401_unauthorized_basic(r); 642 } 643 644 mod_authn_gssapi_patch_config(r, p); 645 646 code = krb5_init_context(&kcontext); 647 if (code) { 648 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context(): %d", code); 649 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 650 } 651 652 if (!p->conf.auth_gssapi_keytab) { 653 log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend.gssapi.keytab not configured"); 654 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 655 } 656 657 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 658 if (code) { 659 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_kt_resolve(): %d %s", code, p->conf.auth_gssapi_keytab->ptr); 660 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 661 } 662 663 sprinc = buffer_init(); 664 if (p->conf.auth_gssapi_principal) 665 buffer_copy_buffer(sprinc, p->conf.auth_gssapi_principal); 666 if (strchr(sprinc->ptr, '/') == NULL) { 667 /*(copy HTTP Host, omitting port if port is present)*/ 668 /* ??? Should r->server_name be used if http_host not present? 669 * ??? What if r->server_name is not set? 670 * ??? Will this work below if IPv6 provided in Host? probably not */ 671 if (r->http_host && !buffer_is_blank(r->http_host)) 672 buffer_append_str2(sprinc, CONST_STR_LEN("/"), 673 r->http_host->ptr, 674 strcspn(r->http_host->ptr, ":")); 675 } 676 677 /*(init c_creds before anything which might krb5_free_cred_contents())*/ 678 memset(&c_creds, 0, sizeof(c_creds)); 679 680 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ); 681 if (ret) { 682 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret); 683 ret = -1; 684 goto end; 685 } 686 687 if (strchr(username->ptr, '@') == NULL) { 688 buffer_copy_buffer((user_at_realm = buffer_init()), username); 689 buffer_append_str2(user_at_realm, CONST_STR_LEN("@"), 690 BUF_PTR_LEN(require->realm)); 691 } 692 693 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ); 694 if (ret) { 695 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); 696 if (user_at_realm) buffer_free(user_at_realm); 697 ret = -1; 698 goto end; 699 } 700 if (user_at_realm) buffer_free(user_at_realm); 701 /* XXX: if the qualified username with @realm should be in REMOTE_USER, 702 * then http_auth_backend_t basic interface needs to change to pass 703 * modifiable buffer *username, but note that const char *pw follows 704 * in the truncated buffer *username, so pw would need to be copied 705 * before modifying buffer *username */ 706 707 /* 708 * char *name = NULL; 709 * ret = krb5_unparse_name(kcontext, c_princ, &name); 710 * if (ret == 0) { 711 * log_error(r->conf.errh, __FILE__, __LINE__, "Trying to get TGT for user: %s password: %s", username->ptr, pw); 712 * } 713 * krb5_free_unparsed_name(kcontext, name); 714 */ 715 716 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL); 717 if (ret) { 718 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret); 719 goto end; 720 } 721 722 ret = mod_authn_gssapi_verify_krb5_init_creds(kcontext, &c_creds, s_princ, keytab, r->conf.errh); 723 if (ret) { 724 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret); 725 goto end; 726 } 727 728 if (!p->conf.auth_gssapi_store_creds) goto end; 729 730 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache); 731 if (ret) { 732 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret); 733 goto end; 734 } 735 736 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ); 737 if (ret) { 738 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret); 739 goto end; 740 } 741 742 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds); 743 if (ret) { 744 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret); 745 goto end; 746 } 747 748 c_ccache = ret_ccache; 749 ret_ccache = NULL; 750 751 end: 752 753 krb5_free_cred_contents(kcontext, &c_creds); 754 if (ret_ccache) 755 krb5_cc_destroy(kcontext, ret_ccache); 756 757 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(r, p, kcontext, c_ccache))) { 758 log_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_store_krb5_creds failed for %s", username->ptr); 759 } 760 761 buffer_free(sprinc); 762 if (c_princ) 763 krb5_free_principal(kcontext, c_princ); 764 if (s_princ) 765 krb5_free_principal(kcontext, s_princ); 766 if (c_ccache) 767 krb5_cc_destroy(kcontext, c_ccache); 768 if (keytab) 769 krb5_kt_close(kcontext, keytab); 770 771 krb5_free_context(kcontext); 772 773 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){ 774 return HANDLER_GO_ON; 775 } 776 else { 777 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */ 778 log_error(r->conf.errh, __FILE__, __LINE__, 779 "password doesn't match for %s username: %s IP: %s", 780 r->uri.path.ptr, username->ptr, r->con->dst_addr_buf.ptr); 781 return mod_authn_gssapi_send_401_unauthorized_basic(r); 782 } 783 } 784 785 786 REQUEST_FUNC(mod_authn_gssapi_handle_reset) { 787 plugin_data *p = (plugin_data *)p_d; 788 buffer * const ccname = (buffer *)r->plugin_ctx[p->id]; 789 if (NULL != ccname) { 790 r->plugin_ctx[p->id] = NULL; 791 unlink(ccname->ptr); 792 } 793 794 return HANDLER_GO_ON; 795 } 796 797 798 __attribute_cold__ 799 int mod_authn_gssapi_plugin_init(plugin *p); 800 int mod_authn_gssapi_plugin_init(plugin *p) { 801 p->version = LIGHTTPD_VERSION_ID; 802 p->name = "authn_gssapi"; 803 p->init = mod_authn_gssapi_init; 804 p->set_defaults= mod_authn_gssapi_set_defaults; 805 p->handle_request_reset = mod_authn_gssapi_handle_reset; 806 807 return 0; 808 } 809