+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
+
+static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
+ apr_pool_t *temp, server_rec *s)
+{
+ /* FIXME: create mutex to deal with connections and contexts ? */
+ mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ mag_post_config_session();
+ ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
+
+ return OK;
+}
+
+static int mag_pre_connection(conn_rec *c, void *csd)
+{
+ struct mag_conn *mc;
+
+ mc = mag_new_conn_ctx(c->pool);
+ ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
+ return OK;
+}
+
+static apr_status_t mag_conn_destroy(void *ptr)
+{
+ struct mag_conn *mc = (struct mag_conn *)ptr;
+ uint32_t min;
+
+ if (mc->ctx) {
+ (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
+ }
+ return APR_SUCCESS;
+}
+
+struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool)
+{
+ struct mag_conn *mc;
+
+ mc = apr_pcalloc(pool, sizeof(struct mag_conn));
+ apr_pool_create(&mc->pool, pool);
+ /* register the context in the memory pool, so it can be freed
+ * when the connection/request is terminated */
+ apr_pool_cleanup_register(mc->pool, (void *)mc,
+ mag_conn_destroy, apr_pool_cleanup_null);
+
+ return mc;
+}
+
+static void mag_conn_clear(struct mag_conn *mc)
+{
+ (void)mag_conn_destroy(mc);
+ apr_pool_t *temp;
+
+ apr_pool_clear(mc->pool);
+ temp = mc->pool;
+ memset(mc, 0, sizeof(struct mag_conn));
+ mc->pool = temp;
+}
+
+static bool mag_conn_is_https(conn_rec *c)
+{
+ if (mag_is_https) {
+ if (mag_is_https(c)) return true;
+ }
+
+ return false;
+}
+
+static bool mag_acquire_creds(request_rec *req,
+ struct mag_config *cfg,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t *creds,
+ gss_OID_set *actual_mechs)
+{
+ uint32_t maj, min;
+#ifdef HAVE_CRED_STORE
+ gss_const_key_value_set_t store = cfg->cred_store;
+
+ maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ desired_mechs, cred_usage, store, creds,
+ actual_mechs, NULL);
+#else
+ maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ desired_mechs, cred_usage, creds,
+ actual_mechs, NULL);
+#endif
+
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
+ mag_error(req, "gss_acquire_cred[_from]() "
+ "failed to get server creds",
+ maj, min));
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef HAVE_CRED_STORE
+static char *escape(apr_pool_t *pool, const char *name,
+ char find, const char *replace)
+{
+ char *escaped = NULL;
+ char *namecopy;
+ char *n;
+ char *p;
+
+ namecopy = apr_pstrdup(pool, name);
+
+ p = strchr(namecopy, find);
+ if (!p) return namecopy;
+
+ /* first segment */
+ n = namecopy;
+ while (p) {
+ /* terminate previous segment */
+ *p = '\0';
+ if (escaped) {
+ escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
+ } else {
+ escaped = apr_pstrcat(pool, n, replace, NULL);
+ }
+ /* move to next segment */
+ n = p + 1;
+ p = strchr(n, find);
+ }
+ /* append last segment if any */
+ if (*n) {
+ escaped = apr_pstrcat(pool, escaped, n, NULL);
+ }
+
+ return escaped;
+}
+
+static char *mag_gss_name_to_ccache_name(request_rec *req,
+ char *dir, const char *gss_name)
+{
+ char *escaped;
+
+ /* We need to escape away '/', we can't have path separators in
+ * a ccache file name */
+ /* first double escape the esacping char (~) if any */
+ escaped = escape(req->pool, gss_name, '~', "~~");
+ /* then escape away the separator (/) if any */
+ escaped = escape(req->pool, escaped, '/', "~");
+
+ return apr_psprintf(req->pool, "%s/%s", dir, escaped);
+}
+
+static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
+{
+ apr_status_t status;
+ apr_finfo_t finfo;
+ char *value;
+
+ status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
+ if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ /* set the file cache anyway, but warn */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
+ "KRB5CCNAME file (%s) lookup failed!", ccname);
+ }
+
+ value = apr_psprintf(req->pool, "FILE:%s", ccname);
+ apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
+}
+
+static void mag_store_deleg_creds(request_rec *req,
+ char *dir, char *clientname,
+ gss_cred_id_t delegated_cred,
+ char **ccachefile)
+{
+ gss_key_value_element_desc element;
+ gss_key_value_set_desc store;
+ char *ccname;
+ uint32_t maj, min;
+ element.key = "ccache";
+ store.elements = &element;
+ store.count = 1;
+
+ ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
+ element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
+
+ maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
+ GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
+ mag_error(req, "failed to store delegated creds",
+ maj, min));
+ }
+
+ *ccachefile = ccname;
+}
+#endif
+
+static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
+ gss_buffer_t value)
+{
+ char *auth_header_value;
+
+ auth_header_value = ap_getword_white(pool, auth_header);
+ if (!auth_header_value) return false;
+ value->length = apr_base64_decode_len(auth_header_value) + 1;
+ value->value = apr_pcalloc(pool, value->length);
+ if (!value->value) return false;
+ value->length = apr_base64_decode(value->value, auth_header_value);
+
+ return true;
+}
+
+static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
+{
+ if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
+
+ for (int i = 0; i < cfg->allowed_mechs->count; i++) {
+ if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#define AUTH_TYPE_NEGOTIATE 0
+#define AUTH_TYPE_BASIC 1
+#define AUTH_TYPE_RAW_NTLM 2
+const char *auth_types[] = {
+ "Negotiate",
+ "Basic",
+ "NTLM",
+ NULL
+};
+
+static void mag_set_req_data(request_rec *req,
+ struct mag_config *cfg,
+ struct mag_conn *mc)
+{
+ apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
+ apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
+ apr_psprintf(req->pool,
+ "%ld", (long)mc->expiration));
+ req->ap_auth_type = apr_pstrdup(req->pool,
+ auth_types[mc->auth_type]);
+ req->user = apr_pstrdup(req->pool, mc->user_name);
+ if (cfg->deleg_ccache_dir && mc->delegated) {
+ char *ccname;
+ ccname = mag_gss_name_to_ccache_name(req,
+ cfg->deleg_ccache_dir,
+ mc->gss_name);
+ if (ccname) {
+ mag_set_KRB5CCANME(req, ccname);
+ }
+ }
+}
+
+static bool mag_auth_basic(request_rec *req,
+ struct mag_config *cfg,
+ gss_buffer_desc ba_user,
+ gss_buffer_desc ba_pwd,
+ gss_cred_id_t acquired_cred,
+ gss_cred_usage_t cred_usage,
+ gss_name_t *client,
+ gss_OID *mech_type,
+ gss_cred_id_t *delegated_cred,
+ uint32_t *vtime)
+{
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ const char *user_ccache = NULL;
+ const char *orig_ccache = NULL;
+ long long unsigned int rndname;
+ apr_status_t rs;
+#endif
+ gss_name_t user = GSS_C_NO_NAME;
+ gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
+ gss_name_t server = GSS_C_NO_NAME;
+ gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t server_ctx = GSS_C_NO_CONTEXT;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
+ uint32_t init_flags = 0;
+ uint32_t maj, min;
+ bool ret = false;
+
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ rs = apr_generate_random_bytes((unsigned char *)(&rndname),
+ sizeof(long long unsigned int));
+ if (rs != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "Failed to generate random ccache name");
+ goto done;
+ }
+ user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
+ maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "In Basic Auth, %s",
+ mag_error(req, "gss_krb5_ccache_name() "
+ "failed", maj, min));
+ goto done;
+ }
+#endif
+
+ maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &user);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "In Basic Auth, %s",
+ mag_error(req, "gss_import_name() failed",
+ maj, min));
+ goto done;
+ }
+
+ maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
+ GSS_C_INDEFINITE,
+ cfg->allowed_mechs,
+ GSS_C_INITIATE,
+ &user_cred, NULL, NULL);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "In Basic Auth, %s",
+ mag_error(req, "gss_acquire_cred_with_password() "
+ "failed", maj, min));
+ goto done;
+ }
+
+ if (cred_usage == GSS_C_BOTH) {
+ /* If GSS_C_BOTH is used then inquire_cred will return the client
+ * name instead of the SPN of the server credentials. Therefore we
+ * need to acquire a different set of credential setting
+ * GSS_C_ACCEPT explicitly */
+ if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
+ GSS_C_ACCEPT, &server_cred, NULL)) {
+ goto done;
+ }
+ } else {
+ server_cred = acquired_cred;
+ }
+ maj = gss_inquire_cred(&min, server_cred, &server,
+ NULL, NULL, NULL);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "%s", mag_error(req, "gss_inquired_cred_() "
+ "failed", maj, min));
+ goto done;
+ }
+
+#ifdef HAVE_CRED_STORE
+ if (cfg->deleg_ccache_dir) {
+ /* delegate ourselves credentials so we store them as requested */
+ init_flags |= GSS_C_DELEG_FLAG;
+ }
+#endif
+
+ do {
+ /* output and input are inverted here, this is intentional */
+ maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
+ GSS_C_NO_OID, init_flags, 300,
+ GSS_C_NO_CHANNEL_BINDINGS, &output,
+ NULL, &input, NULL, NULL);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "%s", mag_error(req, "gss_init_sec_context() "
+ "failed", maj, min));
+ goto done;
+ }
+ gss_release_buffer(&min, &output);
+ maj = gss_accept_sec_context(&min, &server_ctx, acquired_cred,
+ &input, GSS_C_NO_CHANNEL_BINDINGS,
+ client, mech_type, &output, NULL,
+ vtime, delegated_cred);
+ if (GSS_ERROR(maj)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "%s", mag_error(req, "gss_accept_sec_context()"
+ " failed", maj, min));
+ goto done;
+ }
+ gss_release_buffer(&min, &input);
+ } while (maj == GSS_S_CONTINUE_NEEDED);
+
+ ret = true;
+
+done:
+ gss_release_buffer(&min, &output);
+ gss_release_buffer(&min, &input);
+ gss_release_name(&min, &server);
+ if (server_cred != acquired_cred) {
+ gss_release_cred(&min, &server_cred);
+ }
+ gss_delete_sec_context(&min, &server_ctx, GSS_C_NO_BUFFER);
+ gss_release_name(&min, &user);
+ gss_release_cred(&min, &user_cred);
+ gss_delete_sec_context(&min, &user_ctx, GSS_C_NO_BUFFER);
+#ifdef HAVE_GSS_KRB5_CCACHE_NAME
+ if (user_ccache != NULL) {
+ maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
+ if (maj != GSS_S_COMPLETE) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
+ "Failed to restore per-thread ccache, %s",
+ mag_error(req, "gss_krb5_ccache_name() "
+ "failed", maj, min));
+ }
+ }
+#endif
+ return ret;
+}
+
+