diff --git a/README.md b/README.md index ab01a61..f9e5e33 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ token = retries = 3 attribute = email cache_duration = 30 +#cache_per_rhost verify = /etc/ssl/ca.crt ``` @@ -103,6 +104,7 @@ verify = /etc/ssl/ca.crt - `token` is the complete HTTP `Authorization` header, including `Bearer` - `retries` is the number of verification code retries allowed - `cache_duration` is the time the server should respond with a cached answer instead of reauthenticating the user, in seconds +- `cache_per_rhost`, if activated, signals that caching should take place per remote host, so that connecting from a different IP address requires reauthentication - `verify` alternative SSL CA, for debug purposes ## Locking yourself out diff --git a/pam-weblogin.conf.sample b/pam-weblogin.conf.sample index 53a3207..67a8d61 100644 --- a/pam-weblogin.conf.sample +++ b/pam-weblogin.conf.sample @@ -3,4 +3,5 @@ token = Bearer retries = 3 attribute = email cache_duration = 30 +#cache_per_rhost verify = /etc/ssl/ca.crt diff --git a/server/weblogin_daemon.py b/server/weblogin_daemon.py index dbe95d4..679c5fa 100755 --- a/server/weblogin_daemon.py +++ b/server/weblogin_daemon.py @@ -71,9 +71,9 @@ def pop_auth(session_id): auths.pop(session_id, None) -def pop_cached(user_id): - logging.debug(f"pop cached {user_id}") - cached.pop(user_id, None) +def pop_cached(cache_id): + logging.debug(f"pop cached {cache_id}") + cached.pop(cache_id, None) def session_id(length=8): @@ -133,13 +133,16 @@ def start(): logging.debug(f"/pam-weblogin/start\n <- {data}") user_id = data.get('user_id') + rhost = data.get('rhost') + cache_per_rhost = data.get('cache_per_rhost') + cache_id = (user_id, rhost) if cache_per_rhost else (user_id, None) attribute = data.get('attribute') cache_duration = data.get('cache_duration', 0) new_session_id = session_id() url = os.environ.get("URL", config['url']).rstrip('/') session_url = url + "/pam-weblogin/login/" + new_session_id qr_code = create_qr(session_url) - cache = cached.get(user_id, False) + cache = cached.get(cache_id, False) displayname = user_id or 'weblogin' # The Smart Shell testcase @@ -158,6 +161,7 @@ def start(): } auths[new_session_id]['user_id'] = user_id auths[new_session_id]['attribute'] = attribute + auths[new_session_id]['cache_id'] = cache_id auths[new_session_id]['code'] = new_code auths[new_session_id]['cache_duration'] = cache_duration Timer(timeout, pop_auth, [new_session_id]).start() @@ -198,6 +202,7 @@ def check_pin(): attribute = this_auth.get('attribute') code = this_auth.get('code') cache_duration = this_auth.get('cache_duration') + cache_id = this_auth.get('cache_id') if rcode == code: reply = { 'result': 'SUCCESS', @@ -244,9 +249,9 @@ def check_pin(): ], 'info': f'User {user_id} has authenticated successfully ({attribute})' } - cached[user_id] = True + cached[cache_id] = True pop_auth(session_id) - Timer(int(cache_duration), pop_cached, [user_id]).start() + Timer(int(cache_duration), pop_cached, [cache_id]).start() else: reply = { 'result': 'FAIL', diff --git a/src/config.c b/src/config.c index b46d08f..191fd24 100644 --- a/src/config.c +++ b/src/config.c @@ -101,6 +101,7 @@ Config *getConfig(const char *filename) cfg->token = NULL; cfg->attribute = NULL; cfg->cache_duration = DEFAULT_CACHE_DURATION; + cfg->cache_per_rhost = false; cfg->retries = DEFAULT_RETRIES; cfg->pam_user = false; @@ -138,12 +139,17 @@ Config *getConfig(const char *filename) char *val = strchr(key, '='); if (val == NULL) { - /* Check for bare pam_user config */ + /* Check for bare boolean flags in config */ if (!strcmp(key, "pam_user")) { cfg->pam_user = true; log_message(LOG_DEBUG, "pam_user"); } + else if (!strcmp(key, "cache_per_rhost")) + { + cfg->cache_per_rhost = true; + log_message(LOG_DEBUG, "cache_per_rhost"); + } else { log_message(LOG_ERR, "Configuration line: %d: missing '=' symbol, skipping line", lineno); diff --git a/src/config.h b/src/config.h index 063afbe..c51acf0 100644 --- a/src/config.h +++ b/src/config.h @@ -12,6 +12,7 @@ typedef struct char *attribute; bool pam_user; unsigned int cache_duration; + bool cache_per_rhost; unsigned int retries; } Config; diff --git a/src/pam_weblogin.c b/src/pam_weblogin.c index 6dc6dbc..c582083 100644 --- a/src/pam_weblogin.c +++ b/src/pam_weblogin.c @@ -49,6 +49,13 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, UNUSED int flags, int arg log_message(LOG_ERR, "Error getting user"); return PAM_SYSTEM_ERR; } + /* Get RHOST. This should always be valid since this PAM module is used with SSH. */ + const char *rhost; + if (pam_get_item(pamh, PAM_RHOST, (const void **)(&rhost)) != PAM_SUCCESS || !rhost) + { + log_message(LOG_ERR, "Error getting rhost"); + return PAM_SYSTEM_ERR; + } /* Check if debug argument was given */ if (argc == 2 && strcmp(argv[1], "debug") == 0) @@ -79,6 +86,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, UNUSED int flags, int arg log_message(LOG_INFO, "cfg->cache_duration: '%d'\n", cfg->cache_duration); log_message(LOG_INFO, "cfg->retries: '%d'\n", cfg->retries); */ + log_message(LOG_INFO, "Starting for user %s from %s", username, rhost); authorization = str_printf("Authorization: %s", cfg->token); @@ -90,12 +98,12 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, UNUSED int flags, int arg char *data = NULL; if (!cfg->pam_user) // We check local user against remote user based on attribute { - data = str_printf("{\"user_id\":\"%s\",\"attribute\":\"%s\",\"cache_duration\":\"%d\",\"GIT_COMMIT\":\"%s\",\"JSONPARSER_GIT_COMMIT\":\"%s\"}", - username, cfg->attribute, cfg->cache_duration, TOSTR(GIT_COMMIT), TOSTR(JSONPARSER_GIT_COMMIT)); + data = str_printf("{\"user_id\":\"%s\",\"attribute\":\"%s\",\"rhost\":\"%s\",\"cache_duration\":\"%d\",\"cache_per_rhost\":\"%s\",\"GIT_COMMIT\":\"%s\",\"JSONPARSER_GIT_COMMIT\":\"%s\"}", + username, cfg->attribute, rhost, cfg->cache_duration, cfg->cache_per_rhost ? "true" : "false", TOSTR(GIT_COMMIT), TOSTR(JSONPARSER_GIT_COMMIT)); } else // We get local user from remote user based on attribute { - data = str_printf("{\"attribute\":\"%s\",\"cache_duration\":\"%d\",\"GIT_COMMIT\":\"%s\",\"JSONPARSER_GIT_COMMIT\":\"%s\"}", - cfg->attribute, cfg->cache_duration, TOSTR(GIT_COMMIT), TOSTR(JSONPARSER_GIT_COMMIT)); + data = str_printf("{\"attribute\":\"%s\",\"rhost\":\"%s\",\"cache_duration\":\"%d\",\"cache_per_rhost\":\"%s\",\"GIT_COMMIT\":\"%s\",\"JSONPARSER_GIT_COMMIT\":\"%s\"}", + cfg->attribute, rhost, cfg->cache_duration, cfg->cache_per_rhost ? "true" : "false", TOSTR(GIT_COMMIT), TOSTR(JSONPARSER_GIT_COMMIT)); } /* Request auth session_id/challenge */ diff --git a/tests/pam-weblogin.conf_1 b/tests/pam-weblogin.conf_1 index 95b75a3..5981c2b 100644 --- a/tests/pam-weblogin.conf_1 +++ b/tests/pam-weblogin.conf_1 @@ -8,6 +8,7 @@ retries = 3 attribute = uid # cache_duration=60 cache_duration=60 +#cache_per_rhost #pam_user pam_user nonsense diff --git a/tests/pam-weblogin.conf_2 b/tests/pam-weblogin.conf_2 index f133557..48b3d7e 100644 --- a/tests/pam-weblogin.conf_2 +++ b/tests/pam-weblogin.conf_2 @@ -8,6 +8,7 @@ retries = 3 attribute = uid # cache_duration=60 cache_duration=0 +cache_per_rhost #pam_user pam_user nonsense diff --git a/tests/test_config.c b/tests/test_config.c index 908e5ab..7c55b4c 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -20,6 +20,7 @@ START_TEST (test_getConfig_1) ck_assert_int_eq(cfg->retries, 3); ck_assert_str_eq(cfg->attribute, "uid"); ck_assert_int_eq(cfg->cache_duration, 60); + ck_assert(!cfg->cache_per_rhost); ck_assert(cfg->pam_user); } END_TEST @@ -35,6 +36,7 @@ START_TEST (test_getConfig_2) ck_assert_int_eq(cfg->retries, 3); ck_assert_str_eq(cfg->attribute, "uid"); ck_assert_int_eq(cfg->cache_duration, 0); + ck_assert(cfg->cache_per_rhost); ck_assert(cfg->pam_user); } END_TEST