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_WWW_AUTHENTICATE, 257 CONST_STR_LEN("WWW-Authenticate"), 258 CONST_STR_LEN("Negotiate")); 259 return HANDLER_FINISHED; 260 } 261 262 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) 263 { 264 OM_uint32 maj_stat, min_stat; 265 krb5_principal princ = NULL; 266 krb5_ccache ccache = NULL; 267 krb5_error_code problem; 268 krb5_context context; 269 270 problem = krb5_init_context(&context); 271 if (problem) { 272 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem); 273 return 0; 274 } 275 276 problem = krb5_parse_name(context, princ_name, &princ); 277 if (problem) { 278 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem); 279 goto end; 280 } 281 282 if (mod_authn_gssapi_create_krb5_ccache(r, p, context, princ, &ccache)) 283 goto end; 284 285 maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache); 286 if (GSS_ERROR(maj_stat)) { 287 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat); 288 goto end; 289 } 290 291 krb5_cc_close(context, ccache); 292 krb5_free_principal(context, princ); 293 krb5_free_context(context); 294 return 1; 295 296 end: 297 if (princ) 298 krb5_free_principal(context, princ); 299 if (ccache) 300 krb5_cc_destroy(context, ccache); 301 krb5_free_context(context); 302 303 return 0; 304 } 305 306 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) 307 { 308 OM_uint32 st_major, st_minor, acc_flags; 309 gss_buffer_desc token_s = GSS_C_EMPTY_BUFFER; 310 gss_buffer_desc token_in = GSS_C_EMPTY_BUFFER; 311 gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER; 312 gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; 313 gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL; 314 gss_ctx_id_t context = GSS_C_NO_CONTEXT; 315 gss_name_t server_name = GSS_C_NO_NAME; 316 gss_name_t client_name = GSS_C_NO_NAME; 317 318 buffer *sprinc; 319 handler_t rc = HANDLER_UNSET; 320 321 buffer *t_in = buffer_init(); 322 if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) { 323 log_error(r->conf.errh, __FILE__, __LINE__, "decoding GSSAPI authentication header failed %s", realm_str); 324 buffer_free(t_in); 325 return mod_authn_gssapi_send_400_bad_request(r); 326 } 327 328 mod_authn_gssapi_patch_config(r, p); 329 330 { 331 /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 332 * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */ 333 /* ??? Should KRB5_KTNAME go into r->env instead ??? */ 334 /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */ 335 buffer ktname; 336 memset(&ktname, 0, sizeof(ktname)); 337 buffer_copy_string_len(&ktname, CONST_STR_LEN("KRB5_KTNAME=")); 338 buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab); 339 putenv(ktname.ptr); 340 /* ktname.ptr becomes part of the environment, do not free */ 341 } 342 343 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal); 344 if (strchr(sprinc->ptr, '/') == NULL) { 345 /*(copy HTTP Host, omitting port if port is present)*/ 346 /* ??? Should r->server_name be used if http_host not present? 347 * ??? What if r->server_name is not set? 348 * ??? Will this work below if IPv6 provided in Host? probably not */ 349 if (!buffer_is_empty(r->http_host)) { 350 buffer_append_string_len(sprinc, CONST_STR_LEN("/")); 351 buffer_append_string_len(sprinc, r->http_host->ptr, strcspn(r->http_host->ptr, ":")); 352 } 353 } 354 if (strchr(sprinc->ptr, '@') == NULL) { 355 buffer_append_string_len(sprinc, CONST_STR_LEN("@")); 356 buffer_append_string_buffer(sprinc, require->realm); 357 } 358 /*#define GSS_C_NT_USER_NAME gss_nt_user_name*/ 359 /*#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name*/ 360 #define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name 361 362 token_s.value = sprinc->ptr; 363 token_s.length = buffer_string_length(sprinc); 364 st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name); 365 if (GSS_ERROR(st_major)) { 366 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_import_name", NULL, st_major, st_minor); 367 goto end; 368 } 369 370 memset(&token_s, 0, sizeof(token_s)); 371 st_major = gss_display_name(&st_minor, server_name, &token_s, NULL); 372 if (GSS_ERROR(st_major)) { 373 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 374 goto end; 375 } 376 377 /* acquire server's own credentials */ 378 st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL); 379 if (GSS_ERROR(st_major)) { 380 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor); 381 goto end; 382 } 383 384 /* accept the user's context */ 385 token_in.length = buffer_string_length(t_in); 386 token_in.value = t_in->ptr; 387 st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS, 388 &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred); 389 if (GSS_ERROR(st_major)) { 390 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_accept_sec_context", NULL, st_major, st_minor); 391 goto end; 392 } 393 394 /* fetch the username */ 395 st_major = gss_display_name(&st_minor, client_name, &token_out, NULL); 396 if (GSS_ERROR(st_major)) { 397 mod_authn_gssapi_log_gss_error(r->conf.errh, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor); 398 goto end; 399 } 400 401 if (!(acc_flags & GSS_C_CONF_FLAG)) { 402 log_error(r->conf.errh, __FILE__, __LINE__, "No confidentiality for user: %s", (char *)token_out.value); 403 goto end; 404 } 405 406 /* check the allow-rules */ 407 if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) { 408 goto end; 409 } 410 411 if (p->conf.auth_gssapi_store_creds) { 412 if (!(acc_flags & GSS_C_DELEG_FLAG)) { 413 log_error(r->conf.errh, __FILE__, __LINE__, "Unable to delegate credentials for user: %s", (char *)token_out.value); 414 goto end; 415 } 416 else if (!mod_authn_gssapi_store_gss_creds(r, p, token_out.value, client_cred)) { 417 rc = mod_authn_gssapi_send_500_server_error(r); 418 goto end; 419 } 420 } 421 422 http_auth_setenv(r, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI")); 423 rc = HANDLER_GO_ON; /* success */ 424 425 end: 426 buffer_free(t_in); 427 buffer_free(sprinc); 428 429 if (context != GSS_C_NO_CONTEXT) 430 gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER); 431 432 if (client_cred != GSS_C_NO_CREDENTIAL) 433 gss_release_cred(&st_minor, &client_cred); 434 if (server_cred != GSS_C_NO_CREDENTIAL) 435 gss_release_cred(&st_minor, &server_cred); 436 437 if (client_name != GSS_C_NO_NAME) 438 gss_release_name(&st_minor, &client_name); 439 if (server_name != GSS_C_NO_NAME) 440 gss_release_name(&st_minor, &server_name); 441 442 if (token_s.length) 443 gss_release_buffer(&st_minor, &token_s); 444 /* if (token_in.length) 445 * gss_release_buffer(&st_minor, &token_in); */ 446 if (token_out.length) 447 gss_release_buffer(&st_minor, &token_out); 448 449 return rc != HANDLER_UNSET ? rc : mod_authn_gssapi_send_401_unauthorized_negotiate(r); 450 } 451 452 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) 453 { 454 const buffer *vb = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization")); 455 456 UNUSED(backend); 457 if (NULL == vb) { 458 return mod_authn_gssapi_send_401_unauthorized_negotiate(r); 459 } 460 461 if (!buffer_eq_icase_ssn(vb->ptr, CONST_STR_LEN("Negotiate "))) { 462 return mod_authn_gssapi_send_400_bad_request(r); 463 } 464 465 return mod_authn_gssapi_check_spnego(r, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1); 466 } 467 468 /* 469 * HTTP auth Basic realm="kerberos" 470 */ 471 472 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) 473 { 474 krb5_error_code ret; 475 krb5_data req; 476 krb5_ccache local_ccache = NULL; 477 krb5_creds *new_creds = NULL; 478 krb5_auth_context auth_context = NULL; 479 krb5_keytab keytab = NULL; 480 char *server_name; 481 482 memset(&req, 0, sizeof(req)); 483 484 if (ap_req_keytab == NULL) { 485 ret = krb5_kt_default(context, &keytab); 486 if (ret) 487 return ret; 488 } else 489 keytab = ap_req_keytab; 490 491 ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache); 492 if (ret) { 493 log_error(errh, __FILE__, __LINE__, "krb5_cc_resolve() failed when verifying KDC"); 494 /* return ret; */ 495 goto end; 496 } 497 498 ret = krb5_cc_initialize(context, local_ccache, creds->client); 499 if (ret) { 500 log_error(errh, __FILE__, __LINE__, "krb5_cc_initialize() failed when verifying KDC"); 501 goto end; 502 } 503 504 ret = krb5_cc_store_cred(context, local_ccache, creds); 505 if (ret) { 506 log_error(errh, __FILE__, __LINE__, "krb5_cc_store_cred() failed when verifying KDC"); 507 goto end; 508 } 509 510 ret = krb5_unparse_name(context, ap_req_server, &server_name); 511 if (ret) { 512 log_error(errh, __FILE__, __LINE__, "krb5_unparse_name() failed when verifying KDC"); 513 goto end; 514 } 515 krb5_free_unparsed_name(context, server_name); 516 517 if (!krb5_principal_compare(context, ap_req_server, creds->server)) { 518 krb5_creds match_cred; 519 520 memset(&match_cred, 0, sizeof(match_cred)); 521 522 match_cred.client = creds->client; 523 match_cred.server = ap_req_server; 524 525 ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds); 526 if (ret) { 527 log_error(errh, __FILE__, __LINE__, "krb5_get_credentials() failed when verifying KDC"); 528 goto end; 529 } 530 creds = new_creds; 531 } 532 533 ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req); 534 if (ret) { 535 log_error(errh, __FILE__, __LINE__, "krb5_mk_req_extended() failed when verifying KDC"); 536 goto end; 537 } 538 539 krb5_auth_con_free(context, auth_context); 540 auth_context = NULL; 541 ret = krb5_auth_con_init(context, &auth_context); 542 if (ret) { 543 log_error(errh, __FILE__, __LINE__, "krb5_auth_con_init() failed when verifying KDC"); 544 goto end; 545 } 546 547 /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */ 548 krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); 549 ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL); 550 if (ret) { 551 log_error(errh, __FILE__, __LINE__, "krb5_rd_req() failed when verifying KDC"); 552 goto end; 553 } 554 555 end: 556 krb5_free_data_contents(context, &req); 557 if (auth_context) 558 krb5_auth_con_free(context, auth_context); 559 if (new_creds) 560 krb5_free_creds(context, new_creds); 561 if (ap_req_keytab == NULL && keytab) 562 krb5_kt_close(context, keytab); 563 if (local_ccache) 564 krb5_cc_destroy(context, local_ccache); 565 566 return ret; 567 } 568 569 static int mod_authn_gssapi_store_krb5_creds(request_st * const r, plugin_data * const p, 570 krb5_context kcontext, krb5_ccache delegated_cred) 571 { 572 krb5_error_code problem; 573 krb5_principal princ = NULL; 574 krb5_ccache ccache = NULL; 575 576 problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ); 577 if (problem) { 578 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem); 579 goto end; 580 } 581 582 if (mod_authn_gssapi_create_krb5_ccache(r, p, kcontext, princ, &ccache)) { 583 goto end; 584 } 585 586 problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache); 587 if (problem) { 588 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem); 589 goto end; 590 } 591 592 krb5_free_principal(kcontext, princ); 593 krb5_cc_close(kcontext, ccache); 594 return 0; 595 596 end: 597 if (princ) 598 krb5_free_principal(kcontext, princ); 599 if (ccache) 600 krb5_cc_destroy(kcontext, ccache); 601 return -1; 602 } 603 604 static handler_t mod_authn_gssapi_send_401_unauthorized_basic (request_st * const r) 605 { 606 r->http_status = 401; 607 r->handler_module = NULL; 608 http_header_response_set(r, HTTP_HEADER_WWW_AUTHENTICATE, 609 CONST_STR_LEN("WWW-Authenticate"), 610 CONST_STR_LEN("Basic realm=\"Kerberos\"")); 611 return HANDLER_FINISHED; 612 } 613 614 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) 615 { 616 krb5_context kcontext = NULL; 617 krb5_keytab keytab = NULL; 618 krb5_principal s_princ = NULL; 619 krb5_principal c_princ = NULL; 620 krb5_creds c_creds; 621 krb5_ccache c_ccache = NULL; 622 krb5_ccache ret_ccache = NULL; 623 krb5_error_code code; 624 int ret; 625 buffer *sprinc; 626 buffer *user_at_realm = NULL; 627 plugin_data * const p = (plugin_data *)p_d; 628 629 if (*pw == '\0') { 630 log_error(r->conf.errh, __FILE__, __LINE__, "Empty passwords are not accepted"); 631 return mod_authn_gssapi_send_401_unauthorized_basic(r); 632 } 633 634 mod_authn_gssapi_patch_config(r, p); 635 636 code = krb5_init_context(&kcontext); 637 if (code) { 638 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_init_context(): %d", code); 639 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 640 } 641 642 if (buffer_string_is_empty(p->conf.auth_gssapi_keytab)) { 643 log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend.gssapi.keytab not configured"); 644 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 645 } 646 647 code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); 648 if (code) { 649 log_error(r->conf.errh, __FILE__, __LINE__, "krb5_kt_resolve(): %d %s", code, p->conf.auth_gssapi_keytab->ptr); 650 return mod_authn_gssapi_send_401_unauthorized_basic(r); /*(well, should be 500)*/ 651 } 652 653 sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal); 654 if (strchr(sprinc->ptr, '/') == NULL) { 655 /*(copy HTTP Host, omitting port if port is present)*/ 656 /* ??? Should r->server_name be used if http_host not present? 657 * ??? What if r->server_name is not set? 658 * ??? Will this work below if IPv6 provided in Host? probably not */ 659 if (!buffer_is_empty(r->http_host)) { 660 buffer_append_string_len(sprinc, CONST_STR_LEN("/")); 661 buffer_append_string_len(sprinc, r->http_host->ptr, strcspn(r->http_host->ptr, ":")); 662 } 663 } 664 665 /*(init c_creds before anything which might krb5_free_cred_contents())*/ 666 memset(&c_creds, 0, sizeof(c_creds)); 667 668 ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ); 669 if (ret) { 670 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret); 671 ret = -1; 672 goto end; 673 } 674 675 if (strchr(username->ptr, '@') == NULL) { 676 user_at_realm = buffer_init_buffer(username); 677 BUFFER_APPEND_STRING_CONST(user_at_realm, "@"); 678 buffer_append_string_buffer(user_at_realm, require->realm); 679 } 680 681 ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ); 682 if (ret) { 683 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); 684 if (user_at_realm) buffer_free(user_at_realm); 685 ret = -1; 686 goto end; 687 } 688 if (user_at_realm) buffer_free(user_at_realm); 689 /* XXX: if the qualified username with @realm should be in REMOTE_USER, 690 * then http_auth_backend_t basic interface needs to change to pass 691 * modifiable buffer *username, but note that const char *pw follows 692 * in the truncated buffer *username, so pw would need to be copied 693 * before modifying buffer *username */ 694 695 /* 696 * char *name = NULL; 697 * ret = krb5_unparse_name(kcontext, c_princ, &name); 698 * if (ret == 0) { 699 * log_error(r->conf.errh, __FILE__, __LINE__, "Trying to get TGT for user: %s password: %s", username->ptr, pw); 700 * } 701 * krb5_free_unparsed_name(kcontext, name); 702 */ 703 704 ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL); 705 if (ret) { 706 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret); 707 goto end; 708 } 709 710 ret = mod_authn_gssapi_verify_krb5_init_creds(kcontext, &c_creds, s_princ, keytab, r->conf.errh); 711 if (ret) { 712 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret); 713 goto end; 714 } 715 716 if (!p->conf.auth_gssapi_store_creds) goto end; 717 718 ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache); 719 if (ret) { 720 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret); 721 goto end; 722 } 723 724 ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ); 725 if (ret) { 726 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret); 727 goto end; 728 } 729 730 ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds); 731 if (ret) { 732 mod_authn_gssapi_log_krb5_error(r->conf.errh, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, kcontext, ret); 733 goto end; 734 } 735 736 c_ccache = ret_ccache; 737 ret_ccache = NULL; 738 739 end: 740 741 krb5_free_cred_contents(kcontext, &c_creds); 742 if (ret_ccache) 743 krb5_cc_destroy(kcontext, ret_ccache); 744 745 if (!ret && c_ccache && (ret = mod_authn_gssapi_store_krb5_creds(r, p, kcontext, c_ccache))) { 746 log_error(r->conf.errh, __FILE__, __LINE__, "mod_authn_gssapi_store_krb5_creds failed for %s", username->ptr); 747 } 748 749 buffer_free(sprinc); 750 if (c_princ) 751 krb5_free_principal(kcontext, c_princ); 752 if (s_princ) 753 krb5_free_principal(kcontext, s_princ); 754 if (c_ccache) 755 krb5_cc_destroy(kcontext, c_ccache); 756 if (keytab) 757 krb5_kt_close(kcontext, keytab); 758 759 krb5_free_context(kcontext); 760 761 if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){ 762 return HANDLER_GO_ON; 763 } 764 else { 765 /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */ 766 log_error(r->conf.errh, __FILE__, __LINE__, 767 "password doesn't match for %s username: %s IP: %s", 768 r->uri.path.ptr, username->ptr, r->con->dst_addr_buf->ptr); 769 return mod_authn_gssapi_send_401_unauthorized_basic(r); 770 } 771 } 772 773 774 REQUEST_FUNC(mod_authn_gssapi_handle_reset) { 775 plugin_data *p = (plugin_data *)p_d; 776 buffer *kccname = (buffer *)r->plugin_ctx[p->id]; 777 if (NULL != kccname) { 778 r->plugin_ctx[p->id] = NULL; 779 unlink(kccname->ptr+sizeof("FILE:")-1); 780 buffer_free(kccname); 781 } 782 783 return HANDLER_GO_ON; 784 } 785 786 int mod_authn_gssapi_plugin_init(plugin *p); 787 int mod_authn_gssapi_plugin_init(plugin *p) { 788 p->version = LIGHTTPD_VERSION_ID; 789 p->name = "authn_gssapi"; 790 p->init = mod_authn_gssapi_init; 791 p->set_defaults= mod_authn_gssapi_set_defaults; 792 p->handle_request_reset = mod_authn_gssapi_handle_reset; 793 794 return 0; 795 } 796