summaryrefslogtreecommitdiff
path: root/data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c')
-rw-r--r--data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c b/data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c
new file mode 100644
index 000000000..6ed61ee92
--- /dev/null
+++ b/data/lighttpd/lighttpd-1.4.53/src/mod_authn_gssapi.c
@@ -0,0 +1,801 @@
+#include "first.h"
+
+/* mod_authn_gssapi
+ *
+ * - provides http_auth_backend_t "gssapi" for HTTP auth Basic realm="Kerberos"
+ * - provides http_auth_scheme_t "Negotiate"
+ * - (does not provide http_auth_backend_t for HTTP auth Digest)
+ *
+ * Note: Credentials cache (KRB5CCNAME) is exported into CGI and SSI environment
+ * as well as passed to FastCGI and SCGI (useful if on same machine
+ * and running under same user account with access to KRB5CCNAME file).
+ * Credentials are clean up at the end of each request.
+ *
+ * LIMITATIONS:
+ * - no rate limiting of auth requests, so remote attacker can send many auth
+ * requests very quickly if attempting brute force password cracking attack
+ *
+ * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
+ * - Kerberos auth is synchronous and blocks waiting for response
+ * TODO: attempt async?
+ */
+
+#include "plugin.h"
+
+#include <krb5.h>
+#include <gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+
+#include "http_auth.h"
+#include "http_header.h"
+#include "base.h"
+#include "log.h"
+#include "md5.h"
+#include "base64.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+typedef struct {
+ buffer *auth_gssapi_keytab;
+ buffer *auth_gssapi_principal;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+static handler_t mod_authn_gssapi_check(server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
+static handler_t mod_authn_gssapi_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw);
+
+INIT_FUNC(mod_authn_gssapi_init) {
+ static http_auth_scheme_t http_auth_scheme_gssapi =
+ { "gssapi", mod_authn_gssapi_check, NULL };
+ static http_auth_backend_t http_auth_backend_gssapi =
+ { "gssapi", mod_authn_gssapi_basic, NULL, NULL };
+ plugin_data *p = calloc(1, sizeof(*p));
+
+ /* register http_auth_scheme_gssapi and http_auth_backend_gssapi */
+ http_auth_scheme_gssapi.p_d = p;
+ http_auth_scheme_set(&http_auth_scheme_gssapi);
+ http_auth_backend_gssapi.p_d = p;
+ http_auth_backend_set(&http_auth_backend_gssapi);
+
+ return p;
+}
+
+FREE_FUNC(mod_authn_gssapi_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (NULL == s) continue;
+
+ buffer_free(s->auth_gssapi_keytab);
+ buffer_free(s->auth_gssapi_principal);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_authn_gssapi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+ config_values_t cv[] = {
+ { "auth.backend.gssapi.keytab", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.gssapi.principal", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ data_config const* config = (data_config const*)srv->config_context->data[i];
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+
+ s->auth_gssapi_keytab = buffer_init();
+ s->auth_gssapi_principal = buffer_init();
+
+ cv[0].destination = s->auth_gssapi_keytab;
+ cv[1].destination = s->auth_gssapi_principal;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_authn_gssapi_patch_connection(server *srv, connection *con, plugin_data *p)
+{
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(auth_gssapi_keytab);
+ PATCH(auth_gssapi_principal);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ 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.principal"))) {
+ PATCH(auth_gssapi_principal);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_authn_gssapi_send_400_bad_request (server *srv, connection *con)
+{
+ UNUSED(srv);
+ con->http_status = 400;
+ con->mode = DIRECT;
+ return HANDLER_FINISHED;
+}
+
+static void mod_authn_gssapi_log_gss_error(server *srv, const char *file, unsigned int line, const char *func, const char *extra, OM_uint32 err_maj, OM_uint32 err_min)
+{
+ buffer * const msg = buffer_init_string(func);
+ OM_uint32 maj_stat, min_stat;
+ OM_uint32 msg_ctx = 0;
+ gss_buffer_desc status_string;
+
+ buffer_append_string_len(msg, CONST_STR_LEN("("));
+ if (extra) buffer_append_string(msg, extra);
+ buffer_append_string_len(msg, CONST_STR_LEN("):"));
+
+ 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_len(msg, CONST_STR_LEN(" ("));
+ buffer_append_string(msg, status_string.value);
+ buffer_append_string_len(msg, CONST_STR_LEN(")"));
+ gss_release_buffer(&min_stat, &status_string);
+ }
+ } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
+
+ log_error_write(srv, file, line, "b", msg);
+ buffer_free(msg);
+}
+
+static void mod_authn_gssapi_log_krb5_error(server *srv, const char *file, unsigned int line, const char *func, const char *extra, krb5_context context, int code)
+{
+ UNUSED(context);
+ /*(extra might be NULL)*/
+ log_error_write(srv, file, line, "sssss", func, "(", extra, "):",
+ error_message(code));
+}
+
+static int mod_authn_gssapi_create_krb5_ccache(server *srv, connection *con, plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache)
+{
+ buffer * const kccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX");
+ char * const ccname = kccname->ptr + sizeof("FILE:")-1;
+ const size_t ccnamelen = buffer_string_length(kccname)-(sizeof("FILE:")-1);
+ /*(future: might consider using server.upload-dirs instead of /tmp)*/
+ #ifdef __COVERITY__
+ /* POSIX-2008 requires mkstemp create file with 0600 perms */
+ umask(0600);
+ #endif
+ /* coverity[secure_temp : FALSE] */
+ int fd = mkstemp(ccname);
+ if (fd < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sss", "mkstemp():", ccname, strerror(errno));
+ buffer_free(kccname);
+ return -1;
+ }
+ close(fd);
+
+ do {
+ krb5_error_code problem;
+
+ problem = krb5_cc_resolve(kcontext, kccname->ptr, ccache);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem);
+ break;
+ }
+
+ problem = krb5_cc_initialize(kcontext, *ccache, princ);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", kccname->ptr, kcontext, problem);
+ break;
+ }
+
+ con->plugin_ctx[p->id] = kccname;
+
+ http_header_env_set(con, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen);
+ http_header_request_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen);
+
+ return 0;
+
+ } while (0);
+
+ if (*ccache) {
+ krb5_cc_destroy(kcontext, *ccache);
+ *ccache = NULL;
+ }
+ unlink(ccname);
+ buffer_free(kccname);
+
+ return -1;
+}
+
+/*
+ * HTTP auth Negotiate
+ */
+
+static handler_t mod_authn_gssapi_send_401_unauthorized_negotiate (connection *con)
+{
+ con->http_status = 401;
+ con->mode = DIRECT;
+ http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Negotiate"));
+ return HANDLER_FINISHED;
+}
+
+static int mod_authn_gssapi_store_gss_creds(server *srv, connection *con, 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;
+
+ problem = krb5_init_context(&context);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_init_context", NULL, context, problem);
+ return 0;
+ }
+
+ problem = krb5_parse_name(context, princ_name, &princ);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", NULL, context, problem);
+ goto end;
+ }
+
+ if (mod_authn_gssapi_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)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_krb5_copy_ccache", princ_name, maj_stat, min_stat);
+ goto end;
+ }
+
+ krb5_cc_close(context, ccache);
+ krb5_free_principal(context, princ);
+ krb5_free_context(context);
+ return 1;
+
+ end:
+ if (princ)
+ krb5_free_principal(context, princ);
+ if (ccache)
+ krb5_cc_destroy(context, ccache);
+ krb5_free_context(context);
+
+ return 0;
+}
+
+static handler_t mod_authn_gssapi_check_spnego(server *srv, connection *con, plugin_data *p, const http_auth_require_t *require, const char *realm_str)
+{
+ 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 *sprinc;
+ int ret = 0;
+
+ buffer *t_in = buffer_init();
+ if (!buffer_append_base64_decode(t_in, realm_str, strlen(realm_str), BASE64_STANDARD)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "decoding GSSAPI authentication header failed", realm_str);
+ buffer_free(t_in);
+ return mod_authn_gssapi_send_400_bad_request(srv, con);
+ }
+
+ mod_authn_gssapi_patch_connection(srv, con, p);
+
+ {
+ /* ??? Should code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
+ * be used, instead of putenv() of KRB5_KTNAME=...? See mod_authn_gssapi_basic() */
+ /* ??? Should KRB5_KTNAME go into con->environment instead ??? */
+ /* ??? Should KRB5_KTNAME be added to mod_authn_gssapi_basic(), too? */
+ buffer ktname;
+ memset(&ktname, 0, sizeof(ktname));
+ buffer_copy_string(&ktname, "KRB5_KTNAME=");
+ buffer_append_string_buffer(&ktname, p->conf.auth_gssapi_keytab);
+ putenv(ktname.ptr);
+ /* ktname.ptr becomes part of the environment, do not free */
+ }
+
+ sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
+ if (strchr(sprinc->ptr, '/') == NULL) {
+ /*(copy HTTP Host, omitting port if port is present)*/
+ /* ??? Should con->server_name be used if http_host not present?
+ * ??? What if con->server_name is not set?
+ * ??? Will this work below if IPv6 provided in Host? probably not */
+ if (!buffer_is_empty(con->request.http_host)) {
+ buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
+ buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
+ }
+ }
+ if (strchr(sprinc->ptr, '@') == NULL) {
+ buffer_append_string_len(sprinc, CONST_STR_LEN("@"));
+ buffer_append_string_buffer(sprinc, require->realm);
+ }
+ /*#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
+
+ token_s.value = sprinc->ptr;
+ token_s.length = buffer_string_length(sprinc);
+ st_major = gss_import_name(&st_minor, &token_s, (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, &server_name);
+ if (GSS_ERROR(st_major)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_import_name", NULL, 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)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, 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)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_acquire_cred", sprinc->ptr, st_major, st_minor);
+ goto end;
+ }
+
+ /* accept the user's context */
+ token_in.length = buffer_string_length(t_in);
+ 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)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_accept_sec_context", NULL, 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)) {
+ mod_authn_gssapi_log_gss_error(srv, __FILE__, __LINE__, "gss_display_name", NULL, st_major, st_minor);
+ goto end;
+ }
+
+ if (!(acc_flags & GSS_C_CONF_FLAG)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "No confidentiality for user:", token_out.value);
+ goto end;
+ }
+
+ if (!(acc_flags & GSS_C_DELEG_FLAG)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "Unable to delegate credentials for user:", token_out.value);
+ goto end;
+ }
+
+ /* check the allow-rules */
+ if (!http_auth_match_rules(require, token_out.value, NULL, NULL)) {
+ goto end;
+ }
+
+ ret = mod_authn_gssapi_store_gss_creds(srv, con, p, token_out.value, client_cred);
+ if (ret)
+ http_auth_setenv(con, token_out.value, token_out.length, CONST_STR_LEN("GSSAPI"));
+
+ end:
+ buffer_free(t_in);
+ buffer_free(sprinc);
+
+ 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 ? HANDLER_GO_ON : mod_authn_gssapi_send_401_unauthorized_negotiate(con);
+}
+
+static handler_t mod_authn_gssapi_check (server *srv, connection *con, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend)
+{
+ buffer *vb = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
+
+ UNUSED(backend);
+ if (NULL == vb) {
+ return mod_authn_gssapi_send_401_unauthorized_negotiate(con);
+ }
+
+ if (0 != strncasecmp(vb->ptr, "Negotiate ", sizeof("Negotiate ")-1)) {
+ return mod_authn_gssapi_send_400_bad_request(srv, con);
+ }
+
+ return mod_authn_gssapi_check_spnego(srv, con, (plugin_data *)p_d, require, vb->ptr+sizeof("Negotiate ")-1);
+}
+
+/*
+ * HTTP auth Basic realm="kerberos"
+ */
+
+static krb5_error_code mod_authn_gssapi_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;
+ }
+ krb5_free_unparsed_name(context, 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 mod_authn_gssapi_store_krb5_creds(server *srv, connection *con, plugin_data *p,
+ krb5_context kcontext, krb5_ccache delegated_cred)
+{
+ krb5_error_code problem;
+ krb5_principal princ = NULL;
+ krb5_ccache ccache = NULL;
+
+ problem = krb5_cc_get_principal(kcontext, delegated_cred, &princ);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_get_principal", NULL, kcontext, problem);
+ goto end;
+ }
+
+ if (mod_authn_gssapi_create_krb5_ccache(srv, con, p, kcontext, princ, &ccache)) {
+ goto end;
+ }
+
+ problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache);
+ if (problem) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_copy_creds", NULL, kcontext, problem);
+ 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 -1;
+}
+
+static handler_t mod_authn_gssapi_send_401_unauthorized_basic (connection *con)
+{
+ con->http_status = 401;
+ con->mode = DIRECT;
+ http_header_response_set(con, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_STR_LEN("Basic realm=\"Kerberos\""));
+ return HANDLER_FINISHED;
+}
+
+static handler_t mod_authn_gssapi_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw)
+{
+ krb5_context kcontext = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_principal s_princ = NULL;
+ krb5_principal c_princ = NULL;
+ krb5_creds c_creds;
+ krb5_ccache c_ccache = NULL;
+ krb5_ccache ret_ccache = NULL;
+ krb5_error_code code;
+ int ret;
+ buffer *sprinc;
+ buffer *user_at_realm = NULL;
+ plugin_data * const p = (plugin_data *)p_d;
+
+ if (*pw == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Empty passwords are not accepted");
+ return mod_authn_gssapi_send_401_unauthorized_basic(con);
+ }
+
+ mod_authn_gssapi_patch_connection(srv, con, p);
+
+ code = krb5_init_context(&kcontext);
+ if (code) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "krb5_init_context():", code);
+ return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
+ }
+
+ code = krb5_kt_resolve(kcontext, p->conf.auth_gssapi_keytab->ptr, &keytab);
+ if (code) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb", "krb5_kt_resolve():", code, p->conf.auth_gssapi_keytab);
+ return mod_authn_gssapi_send_401_unauthorized_basic(con); /*(well, should be 500)*/
+ }
+
+ sprinc = buffer_init_buffer(p->conf.auth_gssapi_principal);
+ if (strchr(sprinc->ptr, '/') == NULL) {
+ /*(copy HTTP Host, omitting port if port is present)*/
+ /* ??? Should con->server_name be used if http_host not present?
+ * ??? What if con->server_name is not set?
+ * ??? Will this work below if IPv6 provided in Host? probably not */
+ if (!buffer_is_empty(con->request.http_host)) {
+ buffer_append_string_len(sprinc, CONST_STR_LEN("/"));
+ buffer_append_string_len(sprinc, con->request.http_host->ptr, strcspn(con->request.http_host->ptr, ":"));
+ }
+ }
+
+ /*(init c_creds before anything which might krb5_free_cred_contents())*/
+ memset(&c_creds, 0, sizeof(c_creds));
+
+ ret = krb5_parse_name(kcontext, sprinc->ptr, &s_princ);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", sprinc->ptr, kcontext, ret);
+ ret = -1;
+ goto end;
+ }
+
+ if (strchr(username->ptr, '@') == NULL) {
+ user_at_realm = buffer_init_buffer(username);
+ BUFFER_APPEND_STRING_CONST(user_at_realm, "@");
+ buffer_append_string_buffer(user_at_realm, require->realm);
+ }
+
+ ret = krb5_parse_name(kcontext, (user_at_realm ? user_at_realm->ptr : username->ptr), &c_princ);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_parse_name", (user_at_realm ? user_at_realm->ptr : username->ptr), kcontext, ret);
+ if (user_at_realm) buffer_free(user_at_realm);
+ ret = -1;
+ goto end;
+ }
+ if (user_at_realm) buffer_free(user_at_realm);
+ /* XXX: if the qualified username with @realm should be in REMOTE_USER,
+ * then http_auth_backend_t basic interface needs to change to pass
+ * modifiable buffer *username, but note that const char *pw follows
+ * in the truncated buffer *username, so pw would need to be copied
+ * before modifying buffer *username */
+
+ /*
+ * char *name = NULL;
+ * ret = krb5_unparse_name(kcontext, c_princ, &name);
+ * if (ret == 0) {
+ * log_error_write(srv, __FILE__, __LINE__, "sbss", "Trying to get TGT for user:", username, "password:", pw);
+ * }
+ * krb5_free_unparsed_name(kcontext, name);
+ */
+
+ ret = krb5_get_init_creds_password(kcontext, &c_creds, c_princ, pw, NULL, NULL, 0, NULL, NULL);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_get_init_creds_password", NULL, kcontext, ret);
+ goto end;
+ }
+
+ ret = mod_authn_gssapi_verify_krb5_init_creds(srv, kcontext, &c_creds, s_princ, keytab);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "mod_authn_gssapi_verify_krb5_init_creds", NULL, kcontext, ret);
+ goto end;
+ }
+
+ ret = krb5_cc_resolve(kcontext, "MEMORY:", &ret_ccache);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, ret);
+ goto end;
+ }
+
+ ret = krb5_cc_initialize(kcontext, ret_ccache, c_princ);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", NULL, kcontext, ret);
+ goto end;
+ }
+
+ ret = krb5_cc_store_cred(kcontext, ret_ccache, &c_creds);
+ if (ret) {
+ mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_store_cred", NULL, 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 = mod_authn_gssapi_store_krb5_creds(srv, con, p, kcontext, c_ccache))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "mod_authn_gssapi_store_krb5_creds failed for", username);
+ }
+
+ buffer_free(sprinc);
+ if (c_princ)
+ krb5_free_principal(kcontext, c_princ);
+ if (s_princ)
+ krb5_free_principal(kcontext, s_princ);
+ if (c_ccache)
+ krb5_cc_destroy(kcontext, c_ccache);
+ if (keytab)
+ krb5_kt_close(kcontext, keytab);
+
+ krb5_free_context(kcontext);
+
+ if (0 == ret && http_auth_match_rules(require,username->ptr,NULL,NULL)){
+ return HANDLER_GO_ON;
+ }
+ else {
+ /* ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN or no authz rules match */
+ log_error_write(srv, __FILE__, __LINE__, "sbsBsB", "password doesn't match for", con->uri.path, "username:", username, ", IP:", con->dst_addr_buf);
+ return mod_authn_gssapi_send_401_unauthorized_basic(con);
+ }
+}
+
+
+CONNECTION_FUNC(mod_authn_gssapi_handle_reset) {
+ plugin_data *p = (plugin_data *)p_d;
+ buffer *kccname = (buffer *)con->plugin_ctx[p->id];
+ if (NULL != kccname) {
+ con->plugin_ctx[p->id] = NULL;
+ unlink(kccname->ptr+sizeof("FILE:")-1);
+ buffer_free(kccname);
+ }
+
+ UNUSED(srv);
+ return HANDLER_GO_ON;
+}
+
+int mod_authn_gssapi_plugin_init(plugin *p);
+int mod_authn_gssapi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("authn_gssapi");
+ p->init = mod_authn_gssapi_init;
+ p->set_defaults= mod_authn_gssapi_set_defaults;
+ p->cleanup = mod_authn_gssapi_free;
+ p->connection_reset = mod_authn_gssapi_handle_reset;
+
+ p->data = NULL;
+
+ return 0;
+}