diff options
Diffstat (limited to 'src/mod_auth_gssapi.c')
-rw-r--r-- | src/mod_auth_gssapi.c | 247 |
1 files changed, 232 insertions, 15 deletions
diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c index 7f4077b..240d47a 100644 --- a/src/mod_auth_gssapi.c +++ b/src/mod_auth_gssapi.c @@ -24,16 +24,23 @@ #include <stdbool.h> #include <stdint.h> +#include <time.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> +#include <apr_strings.h> +#include <apr_base64.h> +#define APR_WANT_STRFUNC +#include "apr_want.h" + #include <httpd.h> #include <http_core.h> #include <http_connection.h> #include <http_log.h> #include <http_request.h> -#include <apr_strings.h> -#include <apr_base64.h> +#include <mod_session.h> +#include "crypto.h" + module AP_MODULE_DECLARE_DATA auth_gssapi_module; @@ -43,7 +50,9 @@ struct mag_config { bool ssl_only; bool map_to_local; bool gss_conn_ctx; + bool use_sessions; gss_key_value_set_desc cred_store; + struct seal_key *mag_skey; }; static char *mag_status(request_rec *req, int type, uint32_t err) @@ -88,23 +97,56 @@ static char *mag_error(request_rec *req, const char *msg, } static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_load) *mag_sess_load_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_get) *mag_sess_get_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_set) *mag_sess_set_fn = NULL; -static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log, +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_sess_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); + mag_sess_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); + mag_sess_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set); return OK; } +static apr_status_t mag_session_load(request_rec *req, session_rec **sess) +{ + if (mag_sess_load_fn) { + return mag_sess_load_fn(req, sess); + } + return DECLINED; +} + +static apr_status_t mag_session_get(request_rec *req, session_rec *sess, + const char *key, const char **value) +{ + if (mag_sess_get_fn) { + return mag_sess_get_fn(req, sess, key, value); + } + return DECLINED; +} + +static apr_status_t mag_session_set(request_rec *req, session_rec *sess, + const char *key, const char *value) +{ + if (mag_sess_set_fn) { + return mag_sess_set_fn(req, sess, key, value); + } + return DECLINED; +} + struct mag_conn { apr_pool_t *parent; gss_ctx_id_t ctx; bool established; - char *user_name; - char *gss_name; + const char *user_name; + const char *gss_name; + time_t expiration; }; static int mag_pre_connection(conn_rec *c, void *csd) @@ -131,6 +173,150 @@ static apr_status_t mag_conn_destroy(void *ptr) return APR_SUCCESS; } +#define MIN_EXPIRATION_TIME 300 /* 5 minutes validity minimum */ + +#define MAG_BEARER_KEY "MagBearerToken" + +static void mag_check_session(request_rec *req, + struct mag_config *cfg, struct mag_conn **conn) +{ + struct mag_conn *mc; + apr_status_t rc; + session_rec *sess = NULL; + const char *sessval = NULL; + int declen; + struct databuf ctxbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + char *next, *last; + time_t expiration; + + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, req, + "Sessions not available, no cookies!"); + return; + } + + mc = *conn; + if (!mc) { + mc = apr_pcalloc(req->pool, sizeof(struct mag_conn)); + if (!mc) return; + + mc->parent = req->pool; + *conn = mc; + } + + rc = mag_session_get(req, sess, MAG_BEARER_KEY, &sessval); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to get session data!"); + return; + } + if (!sessval) { + /* no session established, just return */ + return; + } + + if (!cfg->mag_skey) { + /* we do not have a key, just return */ + return; + } + + /* decode it */ + declen = apr_base64_decode_len(sessval); + cipherbuf.value = apr_palloc(req->pool, declen); + if (!cipherbuf.value) return; + cipherbuf.length = (int)apr_base64_decode((char *)cipherbuf.value, sessval); + + rc = UNSEAL_BUFFER(req->pool, cfg->mag_skey, &cipherbuf, &ctxbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to unseal session data!"); + return; + } + + /* get time */ + next = apr_strtok((char *)ctxbuf.value, ":", &last); + expiration = (time_t)apr_atoi64(next); + if (expiration < time(NULL)) { + /* credentials fully expired, return nothing */ + return; + } + + /* user name is next */ + next = apr_strtok(NULL, ":", &last); + mc->user_name = apr_pstrdup(mc->parent, next); + if (!mc->user_name) return; + + /* gssapi name (often a principal) is last. + * (because it may contain the separator as a valid char we + * just read last as is, without further tokenizing */ + mc->gss_name = apr_pstrdup(mc->parent, last); + if (!mc->gss_name) return; + + /* OK we have a valid token */ + mc->established = true; +} + +static void mag_attempt_session(request_rec *req, + struct mag_config *cfg, struct mag_conn *mc) +{ + session_rec *sess = NULL; + char *sessval = NULL; + struct databuf plainbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + struct databuf ctxbuf = { 0 }; + apr_status_t rc; + + /* we save the session only if the authentication is established */ + + if (!mc->established) return; + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, req, + "Sessions not available, can't send cookies!"); + return; + } + + if (!cfg->mag_skey) { + rc = SEAL_KEY_CREATE(&cfg->mag_skey); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to create sealing key!"); + return; + } + } + + sessval = apr_psprintf(req->pool, "%ld:%s:%s", + (long)mc->expiration, mc->user_name, mc->gss_name); + if (!sessval) return; + + plainbuf.length = strlen(sessval) + 1; + plainbuf.value = (unsigned char *)sessval; + + rc = SEAL_BUFFER(req->pool, cfg->mag_skey, &plainbuf, &cipherbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to seal session data!"); + return; + } + + ctxbuf.length = apr_base64_encode_len(cipherbuf.length); + ctxbuf.value = apr_pcalloc(req->pool, ctxbuf.length); + if (!ctxbuf.value) return; + + ctxbuf.length = apr_base64_encode((char *)ctxbuf.value, + (char *)cipherbuf.value, + cipherbuf.length); + + rc = mag_session_set(req, sess, MAG_BEARER_KEY, (char *)ctxbuf.value); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to set session data!"); + return; + } +} + static bool mag_conn_is_https(conn_rec *c) { if (mag_is_https) { @@ -156,6 +342,7 @@ static int mag_auth(request_rec *req) gss_name_t client = GSS_C_NO_NAME; gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL; uint32_t flags; + uint32_t vtime; uint32_t maj, min; char *reply; size_t replen; @@ -169,6 +356,11 @@ static int mag_auth(request_rec *req) return DECLINED; } + /* ignore auth for subrequests */ + if (!ap_is_initial_req(req)) { + return OK; + } + cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module); if (cfg->ssl_only) { @@ -184,11 +376,24 @@ static int mag_auth(request_rec *req) req->connection->conn_config, &auth_gssapi_module); if (!mc) { - return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, + "Failed to retrieve connection context!"); + goto done; } + } + + /* if available, session always supersedes connection bound data */ + mag_check_session(req, cfg, &mc); + + if (mc) { + /* register the context in the memory pool, so it can be freed + * when the connection/request is terminated */ + apr_pool_userdata_set(mc, "mag_conn_ptr", + mag_conn_destroy, mc->parent); + if (mc->established) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, - "Connection bound pre-authentication found."); + "Already established context found!"); apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name); req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate"); req->user = apr_pstrdup(req->pool, mc->user_name); @@ -217,7 +422,7 @@ static int mag_auth(request_rec *req) maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL, &input, GSS_C_NO_CHANNEL_BINDINGS, - &client, &mech_type, &output, &flags, NULL, + &client, &mech_type, &output, &flags, &vtime, &delegated_cred); if (GSS_ERROR(maj)) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, @@ -226,19 +431,17 @@ static int mag_auth(request_rec *req) goto done; } - /* register the context in the connection pool, so it can be freed - * when the connection is terminated */ - apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent); - if (maj == GSS_S_CONTINUE_NEEDED) { - if (!cfg->gss_conn_ctx) { + if (!mc) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, - "Mechanism needs continuation but " - "GssapiConnectionBound is off."); + "Mechanism needs continuation but neither " + "GssapiConnectionBound nor " + "GssapiUseSessions are available"); gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER); gss_release_buffer(&min, &output); output.length = 0; } + /* auth not complete send token and wait next packet */ goto done; } @@ -281,6 +484,11 @@ static int mag_auth(request_rec *req) mc->user_name = apr_pstrdup(mc->parent, req->user); mc->gss_name = apr_pstrdup(mc->parent, clientname); mc->established = true; + if (vtime == GSS_C_INDEFINITE || vtime < MIN_EXPIRATION_TIME) { + vtime = MIN_EXPIRATION_TIME; + } + mc->expiration = time(NULL) + vtime; + mag_attempt_session(req, cfg, mc); } ret = OK; @@ -341,6 +549,13 @@ static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on) return NULL; } +static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on) +{ + struct mag_config *cfg = (struct mag_config *)mconfig; + cfg->use_sessions = on ? true : false; + return NULL; +} + static const char *mag_cred_store(cmd_parms *parms, void *mconfig, const char *w) { @@ -394,6 +609,8 @@ static const command_rec mag_commands[] = { "Work only if connection is SSL Secured"), AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG, "Authentication is bound to the TCP connection"), + AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG, + "Authentication uses mod_sessions to hold status"), AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG, "Credential Store"), { NULL } |