--- configure.ac 2013-08-31 20:38:25.720549216 +0400 +++ configure.ac 2013-08-31 20:46:28.303115571 +0400 @@ -253,11 +253,11 @@ [WITH_OPENSSL=$withval],[WITH_OPENSSL=no]) if test "$WITH_OPENSSL" != "no"; then - use_openssl=yes - if test "$WITH_OPENSSL" != "yes"; then - CPPFLAGS="$CPPFLAGS -I$WITH_OPENSSL/include" - LDFLAGS="$LDFLAGS -L$WITH_OPENSSL/lib" - fi + use_openssl=yes + if test "$WITH_OPENSSL" != "yes"; then + CPPFLAGS="$CPPFLAGS -I$WITH_OPENSSL/include" + LDFLAGS="$LDFLAGS -L$WITH_OPENSSL/lib" + fi else use_openssl=no fi @@ -273,22 +273,61 @@ [ use_openssl=yes LDFLAGS="$LDFLAGS -L$withval" ] ) -AC_ARG_WITH(kerberos5, - AC_HELP_STRING([--with-kerberos5],[use Kerberos5 support with OpenSSL]), - [ use_kerberos=yes ], [use_kerberos=no] +dnl Check for kerberos5 +AC_MSG_CHECKING(for kerberos5) +AC_ARG_WITH(krb5, + AC_HELP_STRING([--with-kerberos5@<:@=DIR@:>@],[Use Kerberos 5]), + [WITH_KRB5=$withval],[WITH_KRB5=no]) +if test "$WITH_KRB5" != no; then + use_krb5=yes + if test "$WITH_KRB5" != "yes"; then + CPPFLAGS="$CPPFLAGS -I$WITH_KRB5/include" + LDFLAGS="$LDFLAGS -L$WITH_KRB5/lib" + fi +else + use_krb5=no +fi +AC_MSG_RESULT([$use_krb5]) + +AC_ARG_WITH(krb5-includes, + AC_HELP_STRING([--with-kerberos5-includes=DIR],[Kerberos includes]), + [ use_krb5=yes CPPFLAGS="$CPPFLAGS -I$withval" ] +) + +AC_ARG_WITH(krb5-libs, + AC_HELP_STRING([--with-kerberos5-libs=DIR],[Kerberos libraries]), + [ use_krb5=yes LDFLAGS="$LDFLAGS -L$withval" ] ) +if test "x$use_krb5" = "xyes"; then + AC_CHECK_LIB(gssapi_krb5, gss_mech_krb5, [ + AC_CHECK_HEADERS([gssapi/gssapi_krb5.h],[ + KRB5_LIB="-lresolv -lkrb5 -lgssapi_krb5" + AC_DEFINE(HAVE_KRB5, [1], [libgssapi_krb5]) + ]) + ]) + if test x$KRB5_LIB = x; then + AC_MSG_ERROR([gssapi_krb5 headers and/or libs where not found, install them or build with --without-krb5]) + fi +fi +AC_SUBST(KRB5_LIB) + if test "x$use_openssl" = "xyes"; then - if test "x$use_kerberos" != "xyes"; then + if test "x$use_krb5" != "xyes"; then CPPFLAGS="$CPPFLAGS -DOPENSSL_NO_KRB5" fi AC_CHECK_HEADERS([openssl/ssl.h]) OLDLIBS="$LIBS" - AC_CHECK_LIB(crypto, BIO_f_base64, [ - AC_CHECK_LIB(ssl, SSL_new, [ SSL_LIB="-lssl -lcrypto" - AC_DEFINE(HAVE_LIBSSL, [], [Have libssl]) ], [], [ -lcrypto "$DL_LIB" ]) - ], [], []) + AC_CHECK_LIB(crypto, BIO_f_base64, [ + AC_CHECK_LIB(ssl, SSL_new, + [ SSL_LIB="-lssl -lcrypto" + AC_DEFINE(HAVE_LIBSSL, [], [Have libssl]) + ], + [], + [ -lcrypto "$DL_LIB" ] + ) + ], [], []) LIBS="$OLDLIBS" AC_SUBST(SSL_LIB) fi --- src/Makefile.am 2012-11-17 12:32:01.000000000 +0500 +++ src/Makefile.am 2013-09-01 09:15:56.202867950 +0400 @@ -243,7 +243,7 @@ lib_LTLIBRARIES += mod_auth.la mod_auth_la_SOURCES = mod_auth.c http_auth.c mod_auth_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined -mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(common_libadd) +mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(KRB5_LIB) $(common_libadd) lib_LTLIBRARIES += mod_rewrite.la mod_rewrite_la_SOURCES = mod_rewrite.c --- src/http_auth.c 2012-05-17 05:29:24.000000000 +0400 +++ src/http_auth.c 2013-08-31 12:09:30.265879619 +0400 @@ -1200,3 +1200,574 @@ return 0; } + +#ifdef USE_KRB5 +const char *get_gss_error(buffer *msg, OM_uint32 err_maj, OM_uint32 err_min) { + OM_uint32 maj_stat, min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string; + + do { + maj_stat = gss_display_status(&min_stat, err_maj, + GSS_C_GSS_CODE, GSS_C_NO_OID, + &msg_ctx, &status_string); + if (GSS_ERROR(maj_stat)) + break; + + buffer_append_string(msg, status_string.value); + gss_release_buffer(&min_stat, &status_string); + + maj_stat = gss_display_status(&min_stat, err_min, + GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &status_string); + if (!GSS_ERROR(maj_stat)) { + buffer_append_string(msg, " ("); + buffer_append_string(msg, status_string.value); + buffer_append_string(msg, ")"); + gss_release_buffer(&min_stat, &status_string); + } + } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); + + return msg->ptr; +} + +static int create_krb5_ccache(server *srv, connection *con, mod_auth_plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache) { + int fd; + krb5_error_code problem; + krb5_ccache tmp_ccache = NULL; + buffer *ccname; + int ret = 0; + + ccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX"); + fd = mkstemp(ccname->ptr + strlen("FILE:")); + if (fd < 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", "mkstemp():", strerror(errno)); + ret = -1; + goto end; + } + close(fd); + + problem = krb5_cc_resolve(kcontext, ccname->ptr, &tmp_ccache); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_cc_resolve():", krb5_get_err_text(kcontext, problem)); + unlink(ccname->ptr + 5); + ret = -1; + goto end; + } + + problem = krb5_cc_initialize(kcontext, tmp_ccache, princ); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ssss", "krb5_cc_initialize (", ccname, ")", krb5_get_err_text(kcontext, problem)); + ret = -1; + goto end; + } + + array_set_key_value(con->environment, CONST_STR_LEN("KRB5CCNAME"), ccname->ptr + 5, ccname->used - 6); + array_set_key_value(con->request.headers, CONST_STR_LEN("X-Forwarded-Keytab"), ccname->ptr + 5, ccname->used - 6); + buffer_copy_string(p->auth_cred, ccname->ptr + 5); + + *ccache = tmp_ccache; + tmp_ccache = NULL; + +end: + if (tmp_ccache) + krb5_cc_destroy(kcontext, tmp_ccache); + buffer_free(ccname); + + return ret; +} + +static int store_gss_creds(server *srv, connection *con, mod_auth_plugin_data *p, char *princ_name, gss_cred_id_t delegated_cred) { + OM_uint32 maj_stat, min_stat; + krb5_principal princ = NULL; + krb5_ccache ccache = NULL; + krb5_error_code problem; + krb5_context context; + int ret = 0; + + problem = krb5_init_context(&context); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_init_context()", krb5_get_err_text(context, problem)); + goto end; + } + + problem = krb5_parse_name(context, princ_name, &princ); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_parse_name()", krb5_get_err_text(context, problem)); + goto end; + } + + if (create_krb5_ccache(srv, con, p, context, princ, &ccache)) + goto end; + + maj_stat = gss_krb5_copy_ccache(&min_stat, delegated_cred, ccache); + if (GSS_ERROR(maj_stat)) { + buffer *msg; + msg = buffer_init(); + log_error_write(srv, __FILE__, __LINE__, "ssss", "gss_krb5_copy_ccache (", princ_name, ")", get_gss_error(msg, maj_stat, min_stat)); + buffer_free(msg); + goto end; + } + + krb5_cc_close(context, ccache); + ccache = NULL; + ret = 1; + + end: + if (princ) + krb5_free_principal(context, princ); + if (ccache) + krb5_cc_destroy(context, ccache); + krb5_free_context(context); + + return ret; +} + +int http_auth_gssapi_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) { + data_string *realm; + realm = (data_string *)array_get_element(req, "realm"); + int ret = 0; + + buffer *t_in; + t_in = buffer_init(); + buffer_prepare_copy(t_in, strlen(realm_str)); + t_in->used = b64_pton(realm_str, (u_char *) t_in->ptr, t_in->size); + + if (t_in->used < 1) { + log_error_write(srv, __FILE__, __LINE__, "ss", "decoding GSSAPI authentication header failed", realm_str); + buffer_free(t_in); + return 0; + } + + OM_uint32 st_major, st_minor, acc_flags; + gss_buffer_desc token_s = GSS_C_EMPTY_BUFFER; + gss_buffer_desc token_in = GSS_C_EMPTY_BUFFER; + gss_buffer_desc token_out = GSS_C_EMPTY_BUFFER; + gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; + gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t context = GSS_C_NO_CONTEXT; + gss_name_t server_name = GSS_C_NO_NAME; + gss_name_t client_name = GSS_C_NO_NAME; + + buffer *ktname; + ktname = buffer_init_string("KRB5_KTNAME="); + buffer_append_string_buffer(ktname, p->conf.auth_gssapi_keytab); + putenv(ktname->ptr); + /* ktname becomes part of the environment, do not free */ + /* buffer_free(ktname); */ + + buffer *sprinc; + sprinc = buffer_init(); + buffer_copy_string_buffer(sprinc, p->conf.auth_gssapi_principal); + if (strchr(sprinc->ptr, '/') == NULL) { + buffer_append_string(sprinc, "/"); + if (strchr(con->request.http_host->ptr, ':') == NULL) { + buffer_append_string_buffer(sprinc, con->request.http_host); + } else { + buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":")); + } + } + if (strchr(sprinc->ptr, '@') == NULL) { + buffer_append_string(sprinc, "@"); + buffer_append_string_buffer(sprinc, realm->value); + } + + buffer *gss_err; + gss_err = buffer_init(); + + token_s.value = sprinc->ptr; + token_s.length = sprinc->used ? sprinc->used -1 : 0; + st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name); + if (GSS_ERROR(st_major)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "gss_import_name:", get_gss_error(gss_err, st_major, st_minor)); + goto end; + } + + memset(&token_s, 0, sizeof(token_s)); + st_major = gss_display_name(&st_minor, server_name, &token_s, NULL); + if (GSS_ERROR(st_major)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "gss_display_name:", get_gss_error(gss_err, st_major, st_minor)); + goto end; + } + + /* acquire server's own credentials */ + st_major = gss_acquire_cred(&st_minor, server_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_cred, NULL, NULL); + if (GSS_ERROR(st_major)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "gss_acquire_cred(", sprinc, "):", get_gss_error(gss_err, st_major, st_minor)); + goto end; + } + + /* accept the user's context */ + token_in.length = t_in->used; + token_in.value = t_in->ptr; + st_major = gss_accept_sec_context(&st_minor, &context, server_cred, &token_in, GSS_C_NO_CHANNEL_BINDINGS, + &client_name, NULL, &token_out, &acc_flags, NULL, &client_cred); + if (GSS_ERROR(st_major)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "gss_accept_sec_context:", get_gss_error(gss_err, st_major, st_minor)); + goto end; + } + + /* fetch the username */ + st_major = gss_display_name(&st_minor, client_name, &token_out, NULL); + if (GSS_ERROR(st_major)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "gss_display_name:", get_gss_error(gss_err, st_major, st_minor)); + goto end; + } + + /* check the allow-rules */ + if (http_auth_match_rules(srv, req, token_out.value, NULL, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match"); + goto end; + } + + if (acc_flags & GSS_C_CONF_FLAG) { + if (acc_flags & GSS_C_DELEG_FLAG) { + ret = store_gss_creds(srv, con, p, token_out.value, client_cred); + if (!ret) { + buffer_copy_string(p->auth_cred, "\0"); + } else { + buffer_copy_string(p->auth_user, token_out.value); + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", "Unable to delegate credentials for user:", token_out.value); + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", "No confidentiality for user:", token_out.value); + } + + end: + if (sprinc) + buffer_free(sprinc); + if (t_in) + buffer_free(t_in); + if (gss_err) + buffer_free(gss_err); + + if (context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&st_minor, &context, GSS_C_NO_BUFFER); + + if (client_cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&st_minor, &client_cred); + if (server_cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&st_minor, &server_cred); + + if (client_name != GSS_C_NO_NAME) + gss_release_name(&st_minor, &client_name); + if (server_name != GSS_C_NO_NAME) + gss_release_name(&st_minor, &server_name); + + if (token_s.length) + gss_release_buffer(&st_minor, &token_s); + /* if (token_in.length) + * gss_release_buffer(&st_minor, &token_in); */ + if (token_out.length) + gss_release_buffer(&st_minor, &token_out); + + return ret; +} + +static krb5_error_code verify_krb5_init_creds(server *srv, krb5_context context, krb5_creds *creds, krb5_principal ap_req_server, krb5_keytab ap_req_keytab) { + krb5_error_code ret; + krb5_data req; + krb5_ccache local_ccache = NULL; + krb5_creds *new_creds = NULL; + krb5_auth_context auth_context = NULL; + krb5_keytab keytab = NULL; + char *server_name; + + memset(&req, 0, sizeof(req)); + + if (ap_req_keytab == NULL) { + ret = krb5_kt_default(context, &keytab); + if (ret) + return ret; + } else + keytab = ap_req_keytab; + + ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_resolve() failed when verifying KDC"); + /* return ret; */ + goto end; + } + + ret = krb5_cc_initialize(context, local_ccache, creds->client); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_initialize() failed when verifying KDC"); + goto end; + } + + ret = krb5_cc_store_cred(context, local_ccache, creds); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_cc_store_cred() failed when verifying KDC"); + goto end; + } + + ret = krb5_unparse_name(context, ap_req_server, &server_name); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_unparse_name() failed when verifying KDC"); + goto end; + } + + /* log_error_write(srv, __FILE__, __LINE__, "ss", "Trying to verify authenticity of KDC using principal", server_name); */ + free(server_name); + + if (!krb5_principal_compare(context, ap_req_server, creds->server)) { + krb5_creds match_cred; + + memset(&match_cred, 0, sizeof(match_cred)); + + match_cred.client = creds->client; + match_cred.server = ap_req_server; + + ret = krb5_get_credentials(context, 0, local_ccache, &match_cred, &new_creds); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_get_credentials() failed when verifying KDC"); + goto end; + } + creds = new_creds; + } + + ret = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_mk_req_extended() failed when verifying KDC"); + goto end; + } + + krb5_auth_con_free(context, auth_context); + auth_context = NULL; + ret = krb5_auth_con_init(context, &auth_context); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_auth_con_init() failed when verifying KDC"); + goto end; + } + + /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */ + krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE); + ret = krb5_rd_req(context, &auth_context, &req, ap_req_server, keytab, 0, NULL); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "s", "krb5_rd_req() failed when verifying KDC"); + goto end; + } + + end: + krb5_free_data_contents(context, &req); + if (auth_context) + krb5_auth_con_free(context, auth_context); + if (new_creds) + krb5_free_creds(context, new_creds); + if (ap_req_keytab == NULL && keytab) + krb5_kt_close(context, keytab); + if (local_ccache) + krb5_cc_destroy(context, local_ccache); + + return ret; +} + +static int store_krb5_creds(server *srv, connection *con, mod_auth_plugin_data *p, + krb5_context kcontext, krb5_ccache delegated_cred) { + krb5_error_code problem; + krb5_principal princ; + krb5_ccache ccache; + int ret = 0; + + problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_cc_get_principal() failed:", krb5_get_err_text(kcontext, problem)); + ret = -1; + goto end; + } + + if (create_krb5_ccache(srv, con, p, kcontext, princ, &ccache)) { + ret = -1; + goto end; + } + + problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache); + if (problem) { + log_error_write(srv, __FILE__, __LINE__, "ss", "Failed to store credentials:", krb5_get_err_text(kcontext, problem)); + ret = -1; + goto end; + } + + krb5_free_principal(kcontext, princ); + krb5_cc_close(kcontext, ccache); + return 0; + + end: + if (princ) + krb5_free_principal(kcontext, princ); + if (ccache) + krb5_cc_destroy(kcontext, ccache); + return ret; +} + +int http_auth_basic_gssapi_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) { + char *pw; + buffer *username; + + username = buffer_init(); + if (!base64_decode(username, realm_str)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username); + buffer_free(username); + return 0; + } + + if (NULL == (pw = strchr(username->ptr, ':'))) { + log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username); + buffer_free(username); + return 0; + } + + *pw++ = '\0'; + if (pw == NULL || pw[0] == '\0') { + log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted"); + buffer_free(username); + return 0; + } + username->used = pw - username->ptr; + + krb5_context kcontext = NULL; + krb5_error_code code = krb5_init_context(&kcontext); + if (code) { + log_error_write(srv, __FILE__, __LINE__, "sds", "Cannot initialize Kerberos5 context (", code, ")"); + buffer_free(username); + return 0; + } + + krb5_keytab keytab = NULL; + krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab); + + buffer *sprinc; + sprinc = buffer_init(); + buffer_copy_string_buffer(sprinc, p->conf.auth_gssapi_principal); + if (strchr(sprinc->ptr, '/') == NULL) { + buffer_append_string(sprinc, "/"); + if (strchr(con->request.http_host->ptr, ':') == NULL) { + buffer_append_string_buffer(sprinc, con->request.http_host); + } else { + buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":")); + } + } + + int ret = 0; + + buffer *password = buffer_init(); + buffer_copy_string(password, pw); + + krb5_principal s_princ = NULL; + krb5_principal c_princ = NULL; + krb5_creds c_creds; + krb5_ccache c_ccache = NULL; + krb5_ccache ret_ccache = NULL; + + ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "Error parsing server name (", sprinc, "):", krb5_get_err_text(kcontext, ret)); + ret = -1; + goto end; + } + + if (strchr(username->ptr, '@') == NULL) { + data_string *realm; + realm = (data_string *)array_get_element(req, "realm"); + + BUFFER_APPEND_STRING_CONST(username, "@"); + buffer_append_string_buffer(username, realm->value); + } + + ret = krb5_parse_name(kcontext, username->ptr, &c_princ); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "Error parsing client name (", username, "):", krb5_get_err_text(kcontext, ret)); + ret = -1; + goto end; + } + + /* + * char *name = NULL; + * ret = krb5_unparse_name(kcontext, c_princ, &name); + * if (ret == 0) { + * log_error_write(srv, __FILE__, __LINE__, "sbsb", "Trying to get TGT for user:", username, "password:", password); + * free(name); + * } + */ + + memset(&c_creds, 0, sizeof(c_creds)); + ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, password->ptr, NULL, NULL, 0, NULL, NULL); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_get_init_creds_password() failed:", krb5_get_err_text(kcontext, ret)); + goto end; + } + + ret = verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "ss", "failed to verify krb5 credentials:", krb5_get_err_text(kcontext, ret)); + goto end; + } + + ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "ss", "generating new memory ccache failed:", krb5_get_err_text(kcontext, ret)); + goto end; + } + + ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_cc_initialize() failed:", krb5_get_err_text(kcontext, ret)); + goto end; + } + + ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds); + if (ret) { + log_error_write(srv, __FILE__, __LINE__, "ss", "krb5_cc_store_cred() failed:", krb5_get_err_text(kcontext, ret)); + goto end; + } + + c_ccache = ret_ccache; + ret_ccache = NULL; + + end: + krb5_free_cred_contents(kcontext, &c_creds); + if (ret_ccache) + krb5_cc_destroy(kcontext, ret_ccache); + + if (!ret && c_ccache && (ret = store_krb5_creds(srv, con, p, kcontext, c_ccache))) { + log_error_write(srv, __FILE__, __LINE__, "sb", "store_krb5_creds failed for", username); + } + + if (c_princ) + krb5_free_principal(kcontext, c_princ); + if (password) + buffer_free(password); + if (s_princ) + krb5_free_principal(kcontext, s_princ); + if (sprinc) + buffer_free(sprinc); + if (c_ccache) + krb5_cc_destroy(kcontext, c_ccache); + if (keytab) + krb5_kt_close(kcontext, keytab); + + krb5_free_context(kcontext); + + if (ret) { + /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN */ + log_error_write(srv, __FILE__, __LINE__, "sbb", "password doesn't match for", con->uri.path, username); + buffer_free(username); + return 0; + } + + /* value is our allow-rules */ + if (http_auth_match_rules(srv, req, username->ptr, NULL, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match"); + buffer_free(username); + return 0; + } + + /* remember the username */ + buffer_copy_string_buffer(p->auth_user, username); + buffer_free(username); + + return 1; +} +#endif --- src/http_auth.h 2009-01-15 03:39:05.000000000 -0500 +++ src/http_auth.h 2009-04-22 11:34:56.000000000 -0400 @@ -9,12 +9,29 @@ # include #endif +#if defined(HAVE_KRB5) +# define USE_KRB5 +# include +# include +# include +# include +# define GSS_C_NT_USER_NAME gss_nt_user_name +# define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +# define GSS_KRB5_NT_PRINCIPAL_NAME gss_nt_krb5_name +# define krb5_get_err_text(context,code) error_message(code) +#endif + typedef enum { AUTH_BACKEND_UNSET, AUTH_BACKEND_PLAIN, AUTH_BACKEND_LDAP, AUTH_BACKEND_HTPASSWD, +#ifdef USE_KRB5 + AUTH_BACKEND_HTDIGEST, + AUTH_BACKEND_GSSAPI +#else AUTH_BACKEND_HTDIGEST +#endif } auth_backend_t; typedef struct { @@ -38,6 +55,13 @@ unsigned short auth_ldap_starttls; unsigned short auth_ldap_allow_empty_pw; +#ifdef USE_KRB5 + buffer *auth_gssapi_keytab; + unsigned short auth_gssapi_passwd; + unsigned short auth_gssapi_spnego; + buffer *auth_gssapi_principal; +#endif + unsigned short auth_debug; /* generated */ @@ -57,6 +81,10 @@ buffer *auth_user; +#ifdef USE_KRB5 + buffer *auth_cred; +#endif + #ifdef USE_LDAP buffer *ldap_filter; #endif @@ -70,5 +98,10 @@ int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str); int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char hh[33]); int http_auth_match_rules(server *srv, array *req, const char *username, const char *group, const char *host); +#ifdef USE_KRB5 +int http_auth_gssapi_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str); +int http_auth_basic_gssapi_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str); + +#endif #endif --- src/keyvalue.h 2009-01-15 03:39:05.000000000 -0500 +++ src/keyvalue.h 2009-04-22 11:34:56.000000000 -0400 @@ -62,7 +62,7 @@ buffer *value; } pcre_keyvalue; -typedef enum { HTTP_AUTH_BASIC, HTTP_AUTH_DIGEST } httpauth_type; +typedef enum { HTTP_AUTH_BASIC, HTTP_AUTH_DIGEST, HTTP_AUTH_GSSAPI } httpauth_type; typedef struct { char *key; --- src/mod_auth.c 2012-04-19 08:05:52.000000000 -0400 +++ src/mod_auth.c 2013-08-31 14:14:41.638958852 -0400 @@ -34,6 +34,10 @@ p->tmp_buf = buffer_init(); +#ifdef USE_KRB5 + p->auth_cred = buffer_init(); +#endif + p->auth_user = buffer_init(); #ifdef USE_LDAP p->ldap_filter = buffer_init(); @@ -50,6 +54,11 @@ if (!p) return HANDLER_GO_ON; buffer_free(p->tmp_buf); + +#ifdef USE_KRB5 + buffer_free(p->auth_cred); +#endif + buffer_free(p->auth_user); #ifdef USE_LDAP buffer_free(p->ldap_filter); @@ -76,6 +85,11 @@ buffer_free(s->auth_ldap_filter); buffer_free(s->auth_ldap_cafile); +#ifdef USE_KRB5 + buffer_free(s->auth_gssapi_keytab); + buffer_free(s->auth_gssapi_principal); +#endif + #ifdef USE_LDAP buffer_free(s->ldap_filter_pre); buffer_free(s->ldap_filter_post); @@ -114,6 +128,12 @@ PATCH(auth_ldap_cafile); PATCH(auth_ldap_starttls); PATCH(auth_ldap_allow_empty_pw); +#ifdef USE_KRB5 + PATCH(auth_gssapi_keytab); + PATCH(auth_gssapi_passwd); + PATCH(auth_gssapi_principal); + PATCH(auth_gssapi_spnego); +#endif #ifdef USE_LDAP p->anon_conf = s; PATCH(ldap_filter_pre); @@ -169,6 +189,16 @@ PATCH(auth_ldap_bindpw); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.allow-empty-pw"))) { PATCH(auth_ldap_allow_empty_pw); +#ifdef USE_KRB5 + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.keytab"))) { + PATCH(auth_gssapi_keytab); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.passwd"))) { + PATCH(auth_gssapi_passwd); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.principal"))) { + PATCH(auth_gssapi_principal); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.gssapi.spnego"))) { + PATCH(auth_gssapi_spnego); +#endif } } } @@ -263,6 +293,10 @@ if (0 == strcmp(req_method->value->ptr, "basic")) { auth_satisfied = http_auth_basic_check(srv, con, p, req, auth_realm+1); +#ifdef USE_KRB5 + } else if ((p->conf.auth_gssapi_passwd) && (0 == strcmp(req_method->value->ptr, "gssapi"))) { + auth_satisfied = http_auth_basic_gssapi_check(srv, con, p, req, con->uri.path, auth_realm+1); +#endif } } else if ((auth_type_len == 6) && (0 == strncasecmp(http_authorization, "Digest", auth_type_len))) { @@ -277,6 +311,20 @@ return HANDLER_FINISHED; } } +#ifdef USE_KRB5 + } else if ((auth_type_len == 9) && + (0 == strncasecmp(http_authorization, "Negotiate", auth_type_len))) { + if (0 == strcmp(req_method->value->ptr, "gssapi")) { + if (p->conf.auth_gssapi_spnego) { + /* auth_satisfied = http_auth_basic_gssapi_check(srv, con, p, req, con->uri.path, auth_realm+1); + } else { */ + if (-1 == (auth_satisfied = http_auth_gssapi_check(srv, con, p, req, con->uri.path, auth_realm+1))) { + con->http_status = 400; + return HANDLER_FINISHED; + } + } + } +#endif } else { log_error_write(srv, __FILE__, __LINE__, "ss", "unknown authentification type:", @@ -310,8 +358,23 @@ buffer_append_string_len(p->tmp_buf, CONST_STR_LEN("\", qop=\"auth\"")); response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf)); +#ifdef USE_KRB5 + } else if (0 == strcmp(req_method->value->ptr, "gssapi")) { + if (p->conf.auth_gssapi_spnego) { + buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("Negotiate")); + response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf)); + } + if (p->conf.auth_gssapi_passwd) { + buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("Basic realm=\"Kerberos\"")); + response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf)); + } +#endif } else { /* evil */ + log_error_write(srv, __FILE__, __LINE__, "ss", + "unknown authentification type:", + http_authorization); + return HANDLER_ERROR; } return HANDLER_FINISHED; } else { @@ -361,6 +424,12 @@ { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ +#ifdef USE_KRB5 + { "auth.backend.gssapi.keytab", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "auth.backend.gssapi.passwd", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "auth.backend.gssapi.principal", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ + { "auth.backend.gssapi.spnego", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ +#endif { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -388,6 +457,13 @@ s->auth_ldap_starttls = 0; s->auth_debug = 0; +#ifdef USE_KRB5 + s->auth_gssapi_keytab = buffer_init(); + s->auth_gssapi_passwd = 0; + s->auth_gssapi_principal = buffer_init(); + s->auth_gssapi_spnego = 1; +#endif + s->auth_require = array_init(); #ifdef USE_LDAP @@ -411,6 +487,12 @@ cv[12].destination = s->auth_htdigest_userfile; cv[13].destination = s->auth_htpasswd_userfile; cv[14].destination = &(s->auth_debug); +#ifdef USE_KRB5 + cv[15].destination = s->auth_gssapi_keytab; + cv[16].destination = &(s->auth_gssapi_passwd); + cv[17].destination = s->auth_gssapi_principal; + cv[18].destination = &(s->auth_gssapi_spnego); +#endif p->config_storage[i] = s; ca = ((data_config *)srv->config_context->data[i])->value; @@ -428,6 +510,10 @@ s->auth_backend = AUTH_BACKEND_PLAIN; } else if (0 == strcmp(s->auth_backend_conf->ptr, "ldap")) { s->auth_backend = AUTH_BACKEND_LDAP; +#ifdef USE_KRB5 + } else if (0 == strcmp(s->auth_backend_conf->ptr, "gssapi")) { + s->auth_backend = AUTH_BACKEND_GSSAPI; +#endif } else { log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf); @@ -508,6 +594,7 @@ } else { if (0 != strcmp(method, "basic") && 0 != strcmp(method, "digest") && + 0 != strcmp(method, "gssapi") && 0 != strcmp(method, "extern")) { log_error_write(srv, __FILE__, __LINE__, "ss", "method has to be either \"basic\", \"digest\" or \"extern\" in", @@ -649,6 +736,20 @@ #endif } +#ifdef USE_KRB5 +REQUESTDONE_FUNC(mod_auth_request_done) { + UNUSED(srv); + UNUSED(con); + + mod_auth_plugin_data *p = p_d; + if (!buffer_is_empty(p->auth_cred)) { + unlink(p->auth_cred->ptr); + } + + return HANDLER_GO_ON; +} +#endif + int mod_auth_plugin_init(plugin *p); int mod_auth_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; @@ -656,6 +757,11 @@ p->init = mod_auth_init; p->set_defaults = mod_auth_set_defaults; p->handle_uri_clean = mod_auth_uri_handler; + +#ifdef USE_KRB5 + p->handle_request_done = mod_auth_request_done; +#endif + p->cleanup = mod_auth_free; p->data = NULL; --- src/server.c 2012-08-31 10:11:20.000000000 -0400 +++ src/server.c 2013-09-06 10:34:28.571389478 -0400 @@ -476,6 +476,11 @@ #else "\t- LDAP support\n" #endif +#if defined HAVE_KRB5 + "\t+ kerberos support\n" +#else + "\t- kerberos support\n" +#endif #ifdef HAVE_MEMCACHE_H "\t+ memcached support\n" #else