From 355f40b0437a39f0e177ac24d561494fa657924b Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sat, 9 May 2020 22:47:53 +0200 Subject: [PATCH 01/17] add authy authentication --- Makefile.am | 3 +- configure.ac | 2 + src/pam_authy.c | 221 ++++++++++++++++++++++++++ src/pam_authy.h | 23 +++ src/pam_google_authenticator.c | 279 +++++++++++++++++++++++++++++++++ 5 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 src/pam_authy.c create mode 100644 src/pam_authy.h diff --git a/Makefile.am b/Makefile.am index 6be2379..198d820 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,8 +29,9 @@ google_authenticator_SOURCES = \ pam_google_authenticator_la_SOURCES = \ src/pam_google_authenticator.c \ + src/pam_authy.c \ $(CORE_SRC) -pam_google_authenticator_la_LIBADD = -lpam +pam_google_authenticator_la_LIBADD = -lpam -lcurl -ljansson pam_google_authenticator_la_CFLAGS = $(AM_CFLAGS) pam_google_authenticator_la_LDFLAGS = $(AM_LDFLAGS) $(MODULES_LDFLAGS) -export-symbols-regex "pam_sm_(setcred|open_session|authenticate)" diff --git a/configure.ac b/configure.ac index f88f863..41b9fc5 100644 --- a/configure.ac +++ b/configure.ac @@ -37,6 +37,8 @@ AS_IF([test "x$ac_cv_header_security_pam_modules_h" = "xno" \ AC_MSG_ERROR([Unable to find the PAM library or the PAM header files]) ]) +PKG_CHECK_MODULES(LIBCURL, libcurl) + AC_MSG_CHECKING([whether certain PAM functions require const arguments]) AC_LANG_PUSH(C) # Force test to bail if const isn't needed diff --git a/src/pam_authy.c b/src/pam_authy.c new file mode 100644 index 0000000..553a49b --- /dev/null +++ b/src/pam_authy.c @@ -0,0 +1,221 @@ +#define _GNU_SOURCE + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +#include "pam_authy.h" + +#if 0 +typedef struct { + char *memory; + size_t size; +} mblock_t; + +static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, + void *user_mem) +{ + size_t realsize = size * nmemb; + mblock_t *mem = (mblock_t *)user_mem; + + mem->memory = realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory == NULL) { + return -ENOMEM; + } + + memcpy(&(mem->memory[mem->size]), content, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +static authy_rc_t authy_check_aproval(char *api_key, char *uuid) +{ + CURL *curl = NULL; + CURLcode res; + authy_rc_t rc; + mblock_t buffer = {0}; + struct curl_slist *headers = NULL; + char *url = NULL, *xheader = NULL, *str = NULL; + json_t *payload = NULL, *jt = NULL; + + curl = curl_easy_init(); + if (!curl) { + rc = AUTHY_CLIENT_ERROR; + goto exit_err; + } + + asprintf(&url, "https://api.authy.com/onetouch/json/approval_requests/%s", + uuid); + asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + headers = curl_slist_append(headers, xheader); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + rc = AUTHY_OK; + + payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); + jt = json_object_get(payload, "approval_request"); + if (!jt) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + str = (char *)json_string_value(json_object_get(jt, "status")); + if (!str) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + if (!strcmp(str, "pending")) { + rc = AUTHY_PENDING; + } else if (!strcmp(str, "expired")) { + rc = AUTHY_EXPIRED; + } else if (!strcmp(str, "denied")) { + rc = AUTHY_DENIED; + } else if (!strcmp(str, "approved")) { + rc = AUTHY_APPROVED; + } + +exit_err: + if (buffer.memory) + free(buffer.memory); + + if (jt) + free(jt); + + if (str) + free(str); + + if (payload) + free(payload); + + if (url) + free(url); + + if (curl) + curl_easy_cleanup(curl); + + return rc; +} + +static authy_rc_t authy_post_aproval(long authy_id, char *api_key, int timeout, char **uuid) +{ + CURL *curl = NULL; + CURLcode res; + authy_rc_t rc; + mblock_t buffer = {0}; + struct curl_slist *headers = NULL; + char *url = NULL, *xheader = NULL, *str = NULL; + json_t *payload = NULL, *jt = NULL; + + curl = curl_easy_init(); + if (!curl) { + rc = AUTHY_CLIENT_ERROR; + goto exit_err; + } + + asprintf(&url, "https://api.authy.com/onetouch/json/users/%ld/approval_requests", + authy_id); + asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + headers = curl_slist_append(headers, xheader); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + //curl_easy_setopt(curl, CURLOPT_POSTFIELDS, temp_string); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + rc = AUTHY_OK; + + payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); + + jt = json_object_get(payload, "approval_request"); + if (!jt) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + str = (char *)json_string_value(json_object_get(jt, "uuid")); + if (!str) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + asprintf(uuid, "%s", str); + +exit_err: + if (buffer.memory) + free(buffer.memory); + + if (jt) + free(jt); + + if (str) + free(str); + + if (payload) + free(payload); + + if (url) + free(url); + + if (curl) + curl_easy_cleanup(curl); + + return rc; +} + +authy_rc_t authy_login(long authy_id, char *api_key, int timeout) +{ + time_t start_time; + authy_rc_t rc; + char *uuid = NULL; + + rc = authy_post_aproval(authy_id, api_key, 30, &uuid); + if (rc != AUTHY_OK) { + goto exit_err; + } + + start_time = time(NULL); + do { + rc = authy_check_aproval(api_key, uuid); + switch (rc) { + case AUTHY_DENIED: + case AUTHY_EXPIRED: + case AUTHY_APPROVED: + goto exit_err; + default: + break; + } + sleep(1); + } while ((start_time + timeout) < time(NULL)); + rc = AUTHY_EXPIRED; + +exit_err: + if (uuid) + free(uuid); + + return rc; +} +#endif diff --git a/src/pam_authy.h b/src/pam_authy.h new file mode 100644 index 0000000..d5bb052 --- /dev/null +++ b/src/pam_authy.h @@ -0,0 +1,23 @@ +#ifndef AUTHY_H +#define AUTHY_H + +typedef enum { + AUTHY_CONTINUE = -1, /* continue authentication */ + AUTHY_OK = 0, /* great success! */ + AUTHY_FAIL, /* nice try */ + AUTHY_ABORT, /* give up */ + AUTHY_LIB_ERROR, /* unexpected library error */ + AUTHY_CONN_ERROR, /* problem connecting */ + AUTHY_CLIENT_ERROR, /* you screwed up */ + AUTHY_SERVER_ERROR, /* we screwed up */ + AUTHY_FAIL_SAFE_ALLOW, /* preauth fails in failsafe mode */ + AUTHY_FAIL_SECURE_DENY, /* preauth fails in failsecure mode */ + AUTHY_APPROVED, + AUTHY_DENIED, + AUTHY_PENDING, + AUTHY_EXPIRED, +} authy_rc_t; + +//authy_rc_t authy_login(long authy_id, char *api_key, int timeout); + +#endif /* AUTHY_H */ diff --git a/src/pam_google_authenticator.c b/src/pam_google_authenticator.c index 790fed8..e2b0124 100644 --- a/src/pam_google_authenticator.c +++ b/src/pam_google_authenticator.c @@ -52,10 +52,12 @@ #include "hmac.h" #include "sha1.h" #include "util.h" +#include "pam_authy.h" #define MODULE_NAME "pam_google_authenticator" #define SECRET "~/.google_authenticator" #define CODE_PROMPT "Verification code: " +#define AUTHY_PROMPT "Confirm login request" #define PWCODE_PROMPT "Password & verification code: " typedef struct Params { @@ -74,6 +76,7 @@ typedef struct Params { int allowed_perm; time_t grace_period; int allow_readonly; + int enable_authy; } Params; static char oom; @@ -136,6 +139,234 @@ static void log_message(int priority, pam_handle_t *pamh, } } +#include +#include +#include + +pam_handle_t *_pamh_g; +typedef struct { + char *memory; + size_t size; +} mblock_t; + +static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, + void *user_mem) +{ + size_t realsize = size * nmemb; + mblock_t *mem = (mblock_t *)user_mem; + + mem->memory = realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory == NULL) { + return -ENOMEM; + } + + memcpy(&(mem->memory[mem->size]), content, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +static authy_rc_t authy_check_aproval(char *api_key, char *uuid) +{ + CURL *curl = NULL; + CURLcode res; + authy_rc_t rc; + mblock_t buffer = {0}; + struct curl_slist *headers = NULL; + char *url = NULL, *xheader = NULL, *str = NULL; + json_t *payload = NULL, *jt = NULL; + + curl = curl_easy_init(); + if (!curl) { + rc = AUTHY_CLIENT_ERROR; + goto exit_err; + } + + asprintf(&url, "https://api.authy.com/onetouch/json/approval_requests/%s", + uuid); + asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + headers = curl_slist_append(headers, xheader); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + + log_message(LOG_INFO, _pamh_g, "url: %s\n", url); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + log_message(LOG_ERR, _pamh_g, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + rc = AUTHY_OK; + + log_message(LOG_INFO, _pamh_g, "curl: %s\n", (char*)buffer.memory); + payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); + jt = json_object_get(payload, "approval_request"); + if (!jt) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + str = (char *)json_string_value(json_object_get(jt, "status")); + if (!str) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + if (!strcmp(str, "pending")) { + rc = AUTHY_PENDING; + } else if (!strcmp(str, "expired")) { + rc = AUTHY_EXPIRED; + } else if (!strcmp(str, "denied")) { + rc = AUTHY_DENIED; + } else if (!strcmp(str, "approved")) { + rc = AUTHY_APPROVED; + } + +exit_err: + if (buffer.memory) + free(buffer.memory); + + if (jt) + free(jt); + + if (str) + free(str); + + if (payload) + free(payload); + + if (url) + free(url); + + if (curl) + curl_easy_cleanup(curl); + + return rc; +} + +static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) +{ + CURL *curl = NULL; + CURLcode res; + authy_rc_t rc; + mblock_t buffer = {0}; + struct curl_slist *headers = NULL; + char *url = NULL, *xheader = NULL, *str = NULL; + json_t *payload = NULL, *jt = NULL; + char *data = NULL; + const char *username; + + curl = curl_easy_init(); + if (!curl) { + rc = AUTHY_CLIENT_ERROR; + goto exit_err; + } + + pam_get_user(pamh, &username, NULL); + + asprintf(&url, "https://api.authy.com/onetouch/json/users/%ld/approval_requests", + authy_id); + asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + headers = curl_slist_append(headers, xheader); + asprintf(&data, "message=Login authentication"); + asprintf(&data, "%s&details=%s", data, username); + asprintf(&data, "%s&seconds_to_expire=%d", data, timeout); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + + log_message(LOG_INFO, _pamh_g, "url: %s\n", url); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + log_message(LOG_ERR, _pamh_g, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + rc = AUTHY_OK; + + log_message(LOG_INFO, _pamh_g, "curl: %s\n", (char*)buffer.memory); + payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); + + jt = json_object_get(payload, "approval_request"); + if (!jt) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + + str = (char *)json_string_value(json_object_get(jt, "uuid")); + if (!str) { + rc = AUTHY_CONN_ERROR; + goto exit_err; + } + asprintf(uuid, "%s", str); + +exit_err: + if (buffer.memory) + free(buffer.memory); + + if (jt) + free(jt); + + if (str) + free(str); + + if (payload) + free(payload); + + if (url) + free(url); + + if (data) + free(data); + + if (curl) + curl_easy_cleanup(curl); + + return rc; +} + +authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout) +{ + time_t start_time; + authy_rc_t rc; + char *uuid = NULL; + + rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); + if (rc != AUTHY_OK) { + goto exit_err; + } + + start_time = time(NULL); + do { + rc = authy_check_aproval(api_key, uuid); + switch (rc) { + case AUTHY_DENIED: + case AUTHY_EXPIRED: + case AUTHY_APPROVED: + goto exit_err; + case AUTHY_PENDING: + default: + break; + } + sleep(1); + } while ((start_time + timeout) > time(NULL)); + rc = AUTHY_EXPIRED; + +exit_err: + if (uuid) + free(uuid); + + return rc; +} + static int converse(pam_handle_t *pamh, int nargs, PAM_CONST struct pam_message **message, struct pam_response **response) { @@ -910,6 +1141,38 @@ static long get_hotp_counter(pam_handle_t *pamh, const char *buf) { return counter; } +static long get_cfg_value_long(pam_handle_t *pamh, const char *buf, char *parm) { + if (!buf) { + return -1; + } + const char *cfg_val_str = get_cfg_value(pamh, parm, buf); + if (cfg_val_str == &oom) { + // Out of memory. This is a fatal error + return -1; + } + + long cfg_val = 0; + if (cfg_val_str) { + cfg_val = strtoll(cfg_val_str, NULL, 10); + } + free((void *)cfg_val_str); + + return cfg_val; +} + +static const char *get_cfg_value_char(pam_handle_t *pamh, const char *buf, char *parm) { + if (!buf) { + return NULL; + } + const char *cfg_val_str = get_cfg_value(pamh, parm, buf); + if (cfg_val_str == &oom) { + // Out of memory. This is a fatal error + return NULL; + } + + return cfg_val_str; +} + static int rate_limit(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { const char *value = get_cfg_value(pamh, "RATE_LIMIT", *buf); @@ -1781,6 +2044,8 @@ static int parse_args(pam_handle_t *pamh, int argc, const char **argv, params->nullok = NULLOK; } else if (!strcmp(argv[i], "allow_readonly")) { params->allow_readonly = 1; + } else if (!strcmp(argv[i], "enable_authy")) { + params->enable_authy = 1; } else if (!strcmp(argv[i], "echo-verification-code") || !strcmp(argv[i], "echo_verification_code")) { params->echocode = PAM_PROMPT_ECHO_ON; @@ -1890,6 +2155,20 @@ static int google_authenticator(pam_handle_t *pamh, log_message(LOG_WARNING , pamh, "No secret configured for user %s, asking for code anyway.", username); } + if (params.enable_authy) { + authy_rc_t arc; + long authy_id = get_cfg_value_long(pamh, buf, "AUTHY_ID"); + char *api_key = (char *)get_cfg_value_char(pamh, buf, "AUTHY_API_KEY"); + log_message(LOG_INFO, pamh, + "authy auth, id: %ld, api_key: %s", authy_id, api_key); + arc = authy_login(pamh, authy_id, api_key, 60); + log_message(LOG_INFO, pamh, "authy_login result: %d\n", arc); + if (arc == AUTHY_APPROVED) { + rc = PAM_SUCCESS; + goto out; + } + } + int must_advance_counter = 0; char *pw = NULL, *saved_pw = NULL; for (int mode = 0; mode < 4; ++mode) { From eef6d6f305f09a662fcbd1c2ee86597c8bb21445 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sun, 10 May 2020 12:59:35 +0200 Subject: [PATCH 02/17] authy: move authy calls to pam_authy --- Makefile.am | 1 + src/pam_authy.c | 68 ++++++-- src/pam_authy.h | 15 +- src/pam_google_authenticator.c | 296 +-------------------------------- src/pam_util.c | 72 ++++++++ src/pam_util.h | 9 + 6 files changed, 144 insertions(+), 317 deletions(-) create mode 100644 src/pam_util.c create mode 100644 src/pam_util.h diff --git a/Makefile.am b/Makefile.am index 198d820..62558a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ google_authenticator_SOURCES = \ pam_google_authenticator_la_SOURCES = \ src/pam_google_authenticator.c \ src/pam_authy.c \ + src/pam_util.c \ $(CORE_SRC) pam_google_authenticator_la_LIBADD = -lpam -lcurl -ljansson pam_google_authenticator_la_CFLAGS = $(AM_CFLAGS) diff --git a/src/pam_authy.c b/src/pam_authy.c index 553a49b..5bacafe 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -6,12 +6,16 @@ #include #include #include +#include #include #include +#include +#include + +#include "pam_util.h" #include "pam_authy.h" -#if 0 typedef struct { char *memory; size_t size; @@ -35,7 +39,7 @@ static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, return realsize; } -static authy_rc_t authy_check_aproval(char *api_key, char *uuid) +static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *uuid) { CURL *curl = NULL; CURLcode res; @@ -47,7 +51,8 @@ static authy_rc_t authy_check_aproval(char *api_key, char *uuid) curl = curl_easy_init(); if (!curl) { - rc = AUTHY_CLIENT_ERROR; + log_message(LOG_ERR, pamh, "authy_err: curl init failed\n"); + rc = AUTHY_LIB_ERROR; goto exit_err; } @@ -62,8 +67,8 @@ static authy_rc_t authy_check_aproval(char *api_key, char *uuid) res = curl_easy_perform(curl); if (res != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); + log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)\n", + res, curl_easy_strerror(res)); rc = AUTHY_CONN_ERROR; goto exit_err; } @@ -72,6 +77,7 @@ static authy_rc_t authy_check_aproval(char *api_key, char *uuid) payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); jt = json_object_get(payload, "approval_request"); if (!jt) { + log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing\n"); rc = AUTHY_CONN_ERROR; goto exit_err; } @@ -114,7 +120,7 @@ static authy_rc_t authy_check_aproval(char *api_key, char *uuid) return rc; } -static authy_rc_t authy_post_aproval(long authy_id, char *api_key, int timeout, char **uuid) +static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) { CURL *curl = NULL; CURLcode res; @@ -123,42 +129,55 @@ static authy_rc_t authy_post_aproval(long authy_id, char *api_key, int timeout, struct curl_slist *headers = NULL; char *url = NULL, *xheader = NULL, *str = NULL; json_t *payload = NULL, *jt = NULL; + char *data = NULL; + char hostname[128] = { 0 }; + const char *username; curl = curl_easy_init(); if (!curl) { - rc = AUTHY_CLIENT_ERROR; + log_message(LOG_ERR, pamh, "authy_err: curl init failed\n"); + rc = AUTHY_LIB_ERROR; goto exit_err; } + pam_get_user(pamh, &username, NULL); + if (gethostname(hostname, sizeof(hostname)-1)) { + strcpy(hostname, "unix"); + } + asprintf(&url, "https://api.authy.com/onetouch/json/users/%ld/approval_requests", authy_id); asprintf(&xheader, "X-Authy-API-Key: %s", api_key); headers = curl_slist_append(headers, xheader); + asprintf(&data, "message=Login authentication"); + asprintf(&data, "%s&details=%s at %s", data, username, hostname); + asprintf(&data, "%s&seconds_to_expire=%d", data, timeout); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); - //curl_easy_setopt(curl, CURLOPT_POSTFIELDS, temp_string); res = curl_easy_perform(curl); if (res != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); + log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)\n", + res, curl_easy_strerror(res)); rc = AUTHY_CONN_ERROR; goto exit_err; } rc = AUTHY_OK; payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); - jt = json_object_get(payload, "approval_request"); if (!jt) { + log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing\n"); rc = AUTHY_CONN_ERROR; goto exit_err; } - str = (char *)json_string_value(json_object_get(jt, "uuid")); if (!str) { + log_message(LOG_ERR, pamh, "authy_err: 'uuid' field missing\n"); rc = AUTHY_CONN_ERROR; goto exit_err; } @@ -180,42 +199,57 @@ static authy_rc_t authy_post_aproval(long authy_id, char *api_key, int timeout, if (url) free(url); + if (data) + free(data); + if (curl) curl_easy_cleanup(curl); return rc; } -authy_rc_t authy_login(long authy_id, char *api_key, int timeout) +authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout) { time_t start_time; authy_rc_t rc; char *uuid = NULL; + char *err_str = NULL; - rc = authy_post_aproval(authy_id, api_key, 30, &uuid); + log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request\n"); + rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); if (rc != AUTHY_OK) { + log_message(LOG_ERR, pamh, "authy_err: Push Authentication request failed\n"); goto exit_err; } + log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval\n"); start_time = time(NULL); do { - rc = authy_check_aproval(api_key, uuid); + rc = authy_check_aproval(pamh, api_key, uuid); switch (rc) { case AUTHY_DENIED: + err_str = "denied"; + goto exit_err; case AUTHY_EXPIRED: + err_str = "expired"; + goto exit_err; case AUTHY_APPROVED: + log_message(LOG_INFO, pamh, "authy_dbg: Authentication approved\n"); goto exit_err; default: break; } sleep(1); - } while ((start_time + timeout) < time(NULL)); + } while ((start_time + timeout + 5) > time(NULL)); rc = AUTHY_EXPIRED; + err_str = "expired (pam timeout)"; exit_err: + if (err_str) + log_message(LOG_ERR, pamh, "authy_err: Authentication %s\n", err_str); + if (uuid) free(uuid); return rc; } -#endif diff --git a/src/pam_authy.h b/src/pam_authy.h index d5bb052..960ce32 100644 --- a/src/pam_authy.h +++ b/src/pam_authy.h @@ -2,22 +2,15 @@ #define AUTHY_H typedef enum { - AUTHY_CONTINUE = -1, /* continue authentication */ - AUTHY_OK = 0, /* great success! */ - AUTHY_FAIL, /* nice try */ - AUTHY_ABORT, /* give up */ - AUTHY_LIB_ERROR, /* unexpected library error */ - AUTHY_CONN_ERROR, /* problem connecting */ - AUTHY_CLIENT_ERROR, /* you screwed up */ - AUTHY_SERVER_ERROR, /* we screwed up */ - AUTHY_FAIL_SAFE_ALLOW, /* preauth fails in failsafe mode */ - AUTHY_FAIL_SECURE_DENY, /* preauth fails in failsecure mode */ + AUTHY_OK = 0, + AUTHY_LIB_ERROR, + AUTHY_CONN_ERROR, AUTHY_APPROVED, AUTHY_DENIED, AUTHY_PENDING, AUTHY_EXPIRED, } authy_rc_t; -//authy_rc_t authy_login(long authy_id, char *api_key, int timeout); +authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout); #endif /* AUTHY_H */ diff --git a/src/pam_google_authenticator.c b/src/pam_google_authenticator.c index e2b0124..5f752f6 100644 --- a/src/pam_google_authenticator.c +++ b/src/pam_google_authenticator.c @@ -52,9 +52,9 @@ #include "hmac.h" #include "sha1.h" #include "util.h" +#include "pam_util.h" #include "pam_authy.h" -#define MODULE_NAME "pam_google_authenticator" #define SECRET "~/.google_authenticator" #define CODE_PROMPT "Verification code: " #define AUTHY_PROMPT "Confirm login request" @@ -83,290 +83,6 @@ static char oom; static const char* nobody = "nobody"; -#if defined(DEMO) || defined(TESTING) -static char* error_msg = NULL; - -const char *get_error_msg(void) __attribute__((visibility("default"))); -const char *get_error_msg(void) { - if (!error_msg) { - return ""; - } - return error_msg; -} -#endif - -static void log_message(int priority, pam_handle_t *pamh, - const char *format, ...) { - char *service = NULL; - if (pamh) - pam_get_item(pamh, PAM_SERVICE, (void *)&service); - if (!service) - service = ""; - - char logname[80]; - snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service); - - va_list args; - va_start(args, format); -#if !defined(DEMO) && !defined(TESTING) - openlog(logname, LOG_CONS | LOG_PID, LOG_AUTHPRIV); - vsyslog(priority, format, args); - closelog(); -#else - if (!error_msg) { - error_msg = strdup(""); - } - { - char buf[1000]; - vsnprintf(buf, sizeof buf, format, args); - const int newlen = strlen(error_msg) + 1 + strlen(buf) + 1; - char* n = malloc(newlen); - if (n) { - snprintf(n, newlen, "%s%s%s", error_msg, strlen(error_msg)?"\n":"",buf); - free(error_msg); - error_msg = n; - } else { - fprintf(stderr, "Failed to malloc %d bytes for log data.\n", newlen); - } - } -#endif - - va_end(args); - - if (priority == LOG_EMERG) { - // Something really bad happened. There is no way we can proceed safely. - _exit(1); - } -} - -#include -#include -#include - -pam_handle_t *_pamh_g; -typedef struct { - char *memory; - size_t size; -} mblock_t; - -static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, - void *user_mem) -{ - size_t realsize = size * nmemb; - mblock_t *mem = (mblock_t *)user_mem; - - mem->memory = realloc(mem->memory, mem->size + realsize + 1); - if (mem->memory == NULL) { - return -ENOMEM; - } - - memcpy(&(mem->memory[mem->size]), content, realsize); - mem->size += realsize; - mem->memory[mem->size] = 0; - - return realsize; -} - -static authy_rc_t authy_check_aproval(char *api_key, char *uuid) -{ - CURL *curl = NULL; - CURLcode res; - authy_rc_t rc; - mblock_t buffer = {0}; - struct curl_slist *headers = NULL; - char *url = NULL, *xheader = NULL, *str = NULL; - json_t *payload = NULL, *jt = NULL; - - curl = curl_easy_init(); - if (!curl) { - rc = AUTHY_CLIENT_ERROR; - goto exit_err; - } - - asprintf(&url, "https://api.authy.com/onetouch/json/approval_requests/%s", - uuid); - asprintf(&xheader, "X-Authy-API-Key: %s", api_key); - headers = curl_slist_append(headers, xheader); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); - - log_message(LOG_INFO, _pamh_g, "url: %s\n", url); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - log_message(LOG_ERR, _pamh_g, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - rc = AUTHY_OK; - - log_message(LOG_INFO, _pamh_g, "curl: %s\n", (char*)buffer.memory); - payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); - jt = json_object_get(payload, "approval_request"); - if (!jt) { - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - - str = (char *)json_string_value(json_object_get(jt, "status")); - if (!str) { - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - - if (!strcmp(str, "pending")) { - rc = AUTHY_PENDING; - } else if (!strcmp(str, "expired")) { - rc = AUTHY_EXPIRED; - } else if (!strcmp(str, "denied")) { - rc = AUTHY_DENIED; - } else if (!strcmp(str, "approved")) { - rc = AUTHY_APPROVED; - } - -exit_err: - if (buffer.memory) - free(buffer.memory); - - if (jt) - free(jt); - - if (str) - free(str); - - if (payload) - free(payload); - - if (url) - free(url); - - if (curl) - curl_easy_cleanup(curl); - - return rc; -} - -static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) -{ - CURL *curl = NULL; - CURLcode res; - authy_rc_t rc; - mblock_t buffer = {0}; - struct curl_slist *headers = NULL; - char *url = NULL, *xheader = NULL, *str = NULL; - json_t *payload = NULL, *jt = NULL; - char *data = NULL; - const char *username; - - curl = curl_easy_init(); - if (!curl) { - rc = AUTHY_CLIENT_ERROR; - goto exit_err; - } - - pam_get_user(pamh, &username, NULL); - - asprintf(&url, "https://api.authy.com/onetouch/json/users/%ld/approval_requests", - authy_id); - asprintf(&xheader, "X-Authy-API-Key: %s", api_key); - headers = curl_slist_append(headers, xheader); - asprintf(&data, "message=Login authentication"); - asprintf(&data, "%s&details=%s", data, username); - asprintf(&data, "%s&seconds_to_expire=%d", data, timeout); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); - - log_message(LOG_INFO, _pamh_g, "url: %s\n", url); - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - log_message(LOG_ERR, _pamh_g, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - rc = AUTHY_OK; - - log_message(LOG_INFO, _pamh_g, "curl: %s\n", (char*)buffer.memory); - payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); - - jt = json_object_get(payload, "approval_request"); - if (!jt) { - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - - str = (char *)json_string_value(json_object_get(jt, "uuid")); - if (!str) { - rc = AUTHY_CONN_ERROR; - goto exit_err; - } - asprintf(uuid, "%s", str); - -exit_err: - if (buffer.memory) - free(buffer.memory); - - if (jt) - free(jt); - - if (str) - free(str); - - if (payload) - free(payload); - - if (url) - free(url); - - if (data) - free(data); - - if (curl) - curl_easy_cleanup(curl); - - return rc; -} - -authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout) -{ - time_t start_time; - authy_rc_t rc; - char *uuid = NULL; - - rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); - if (rc != AUTHY_OK) { - goto exit_err; - } - - start_time = time(NULL); - do { - rc = authy_check_aproval(api_key, uuid); - switch (rc) { - case AUTHY_DENIED: - case AUTHY_EXPIRED: - case AUTHY_APPROVED: - goto exit_err; - case AUTHY_PENDING: - default: - break; - } - sleep(1); - } while ((start_time + timeout) > time(NULL)); - rc = AUTHY_EXPIRED; - -exit_err: - if (uuid) - free(uuid); - - return rc; -} - static int converse(pam_handle_t *pamh, int nargs, PAM_CONST struct pam_message **message, struct pam_response **response) { @@ -1151,7 +867,7 @@ static long get_cfg_value_long(pam_handle_t *pamh, const char *buf, char *parm) return -1; } - long cfg_val = 0; + long cfg_val = -1; if (cfg_val_str) { cfg_val = strtoll(cfg_val_str, NULL, 10); } @@ -2158,10 +1874,12 @@ static int google_authenticator(pam_handle_t *pamh, if (params.enable_authy) { authy_rc_t arc; long authy_id = get_cfg_value_long(pamh, buf, "AUTHY_ID"); + long authy_timeout = get_cfg_value_long(pamh, buf, "AUTHY_TIMEOUT"); + if (authy_timeout < 0) { + authy_timeout = 30; + } char *api_key = (char *)get_cfg_value_char(pamh, buf, "AUTHY_API_KEY"); - log_message(LOG_INFO, pamh, - "authy auth, id: %ld, api_key: %s", authy_id, api_key); - arc = authy_login(pamh, authy_id, api_key, 60); + arc = authy_login(pamh, authy_id, api_key, authy_timeout); log_message(LOG_INFO, pamh, "authy_login result: %d\n", arc); if (arc == AUTHY_APPROVED) { rc = PAM_SUCCESS; diff --git a/src/pam_util.c b/src/pam_util.c new file mode 100644 index 0000000..bee4a89 --- /dev/null +++ b/src/pam_util.c @@ -0,0 +1,72 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pam_util.h" + +#if defined(DEMO) || defined(TESTING) +static char* error_msg = NULL; + +const char *get_error_msg(void) __attribute__((visibility("default"))); +const char *get_error_msg(void) +{ + if (!error_msg) { + return ""; + } + return error_msg; +} +#endif + +void log_message(int priority, pam_handle_t *pamh, + const char *format, ...) +{ + char *service = NULL; + if (pamh) + pam_get_item(pamh, PAM_SERVICE, (void *)&service); + if (!service) + service = ""; + + char logname[80]; + snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service); + + va_list args; + va_start(args, format); +#if !defined(DEMO) && !defined(TESTING) + openlog(logname, LOG_CONS | LOG_PID, LOG_AUTHPRIV); + vsyslog(priority, format, args); + closelog(); +#else + if (!error_msg) { + error_msg = strdup(""); + } + { + char buf[1000]; + vsnprintf(buf, sizeof buf, format, args); + const int newlen = strlen(error_msg) + 1 + strlen(buf) + 1; + char* n = malloc(newlen); + if (n) { + snprintf(n, newlen, "%s%s%s", error_msg, strlen(error_msg)?"\n":"",buf); + free(error_msg); + error_msg = n; + } else { + fprintf(stderr, "Failed to malloc %d bytes for log data.\n", newlen); + } + } +#endif + + va_end(args); + + if (priority == LOG_EMERG) { + // Something really bad happened. There is no way we can proceed safely. + _exit(1); + } +} + diff --git a/src/pam_util.h b/src/pam_util.h new file mode 100644 index 0000000..bcbea37 --- /dev/null +++ b/src/pam_util.h @@ -0,0 +1,9 @@ +#ifndef _PAM_UTIL_H_ +#define _PAM_UTIL_H_ + +#define MODULE_NAME "pam_google_authenticator" + +void log_message(int priority, pam_handle_t *pamh, + const char *format, ...); + +#endif /* _PAM_UTIL_H_ */ From 2b733f3c83c17de0f0417eb4b841bf76e401ddc8 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sun, 10 May 2020 18:13:22 +0200 Subject: [PATCH 03/17] add README.authy.md --- README.authy.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 README.authy.md diff --git a/README.authy.md b/README.authy.md new file mode 100644 index 0000000..62e1dca --- /dev/null +++ b/README.authy.md @@ -0,0 +1,69 @@ +# Google Authenticator PAM module with Authy push notification + +PAM authentication using Authy (https://authy.com/) push notifications. + +This repository is a fork of the "Google Authenticator PAM module" +https://github.com/google/google-authenticator-libpam with an extra +extension to send Authy push notification. + +## Build, install, setup +See https://github.com/google/google-authenticator-libpam + +Authy extension requires the following extra dependencies: +- libjansson +- libcurl + +If running Debian-based distro, do: +``` +sudo apt install libjansson-dev libcurl4-gnutls-dev +``` + +## Extra PAM module options +### enable_authy +If set, Authy extension is enabled. PAM module sends push notification +to the Authy authenticator on user login. If authentication passes, +login is granted. On failure, the classic Google OTP is used. + +## Extra .google_authenticator fields +To setup Authy authentication, generate .google_authenticator file as described +in https://github.com/google/google-authenticator-libpam and add the following +extra fields. + +### AUTHY_ID +See https://support.authy.com/hc/en-us/articles/360016449054-Find-your-Authy-ID +how to find out your Authy ID. + +Example: +``` +" AUTHY_ID 123456789" +``` +### AUTHY_API_KEY +See: https://www.twilio.com/docs/authy/twilioauth-sdk/quickstart/obtain-authy-api-key + +Obtaining an Authy API Key: +1. Create a Twilio account: https://www.twilio.com/try-twilio +2. Create an Authy application in the Twilio Console. +3. Once you've created a new Authy application, copy the API Key for +Production available in the Settings page of your Authy application. + +Example: +``` +" AUTHY_API_KEY aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpP +``` + +### .google_authenticator example +``` +00000000000000000000000000 +" RESETTING_TIME_SKEW 52968321+21 52968328+21 52968508+20 +" RATE_LIMIT 3 30 1589055237 +" WINDOW_SIZE 17 +" TOTP_AUTH +" AUTHY_ID 000000000 +" AUTHY_API_KEY xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +11111111 +22222222 +33333333 +" +``` + +### TODO: add Authy fields generation in google-authenticator tool From f7459d28a99c95bc9ea1fd8acd6a474711ba036b Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Mon, 11 May 2020 00:05:03 +0200 Subject: [PATCH 04/17] authy: make authy feature optional (disabled by default) --- Makefile.am | 6 ++++-- configure.ac | 35 ++++++++++++++++++++++++++++++++-- src/pam_google_authenticator.c | 4 ++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 62558a3..2221e0a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,10 +29,12 @@ google_authenticator_SOURCES = \ pam_google_authenticator_la_SOURCES = \ src/pam_google_authenticator.c \ - src/pam_authy.c \ src/pam_util.c \ $(CORE_SRC) -pam_google_authenticator_la_LIBADD = -lpam -lcurl -ljansson +if ENABLE_AUTHY +pam_google_authenticator_la_SOURCES += src/pam_authy.c +endif +pam_google_authenticator_la_LIBADD = -lpam pam_google_authenticator_la_CFLAGS = $(AM_CFLAGS) pam_google_authenticator_la_LDFLAGS = $(AM_LDFLAGS) $(MODULES_LDFLAGS) -export-symbols-regex "pam_sm_(setcred|open_session|authenticate)" diff --git a/configure.ac b/configure.ac index 41b9fc5..5b6a1a1 100644 --- a/configure.ac +++ b/configure.ac @@ -37,8 +37,6 @@ AS_IF([test "x$ac_cv_header_security_pam_modules_h" = "xno" \ AC_MSG_ERROR([Unable to find the PAM library or the PAM header files]) ]) -PKG_CHECK_MODULES(LIBCURL, libcurl) - AC_MSG_CHECKING([whether certain PAM functions require const arguments]) AC_LANG_PUSH(C) # Force test to bail if const isn't needed @@ -73,6 +71,38 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ AC_LANG_POP(C) +AC_ARG_ENABLE([authy], + [AC_HELP_STRING([--enable-authy], + [enable Authy push authentcation [default=no]])], + [ + xenable_authy=$enableval + AC_DEFINE(ENABLE_AUTHY, [1], [Enable Authy extension]) + ], + [xenable_authy=no]) +AM_CONDITIONAL([ENABLE_AUTHY], [false]) + +AC_CHECK_LIB([curl], [curl_global_init], + [ + LIBS="$LIBS -lcurl" + have_curl=yes + ],[ have_curl=no ],[]) +AC_CHECK_HEADERS([curl/curl.h]) + +AC_CHECK_LIB([jansson], [json_loads], + [ + LIBS="$LIBS -ljansson" + have_jansson=yes + ],[ have_jansson=no ],[]) +AC_CHECK_HEADERS([jansson.h]) + +if test "x${xenable_authy}" == "x${enableval}"; then + if test "x${have_jansson}" == "xno"; then + AC_MSG_ERROR([libjannson is missing]) + fi + if test "x${have_jansson}" == "xno" || test "x${have_curl}" == "xno"; then + AC_MSG_ERROR([libcurl is missing]) + fi +fi AC_SEARCH_LIBS([dlopen], [dl]) @@ -83,6 +113,7 @@ AC_OUTPUT echo " $PACKAGE_NAME version $PACKAGE_VERSION + Authy..........: $xenable_authy Prefix.........: $prefix Debug Build....: $debug C Compiler.....: $CC $CFLAGS $CPPFLAGS diff --git a/src/pam_google_authenticator.c b/src/pam_google_authenticator.c index 5f752f6..0b5dbf2 100644 --- a/src/pam_google_authenticator.c +++ b/src/pam_google_authenticator.c @@ -857,6 +857,7 @@ static long get_hotp_counter(pam_handle_t *pamh, const char *buf) { return counter; } +#ifdef ENABLE_AUTHY static long get_cfg_value_long(pam_handle_t *pamh, const char *buf, char *parm) { if (!buf) { return -1; @@ -888,6 +889,7 @@ static const char *get_cfg_value_char(pam_handle_t *pamh, const char *buf, char return cfg_val_str; } +#endif /* ENABLE_AUTHY */ static int rate_limit(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { @@ -1871,6 +1873,7 @@ static int google_authenticator(pam_handle_t *pamh, log_message(LOG_WARNING , pamh, "No secret configured for user %s, asking for code anyway.", username); } +#ifdef ENABLE_AUTHY if (params.enable_authy) { authy_rc_t arc; long authy_id = get_cfg_value_long(pamh, buf, "AUTHY_ID"); @@ -1886,6 +1889,7 @@ static int google_authenticator(pam_handle_t *pamh, goto out; } } +#endif /* ENABLE_AUTHY */ int must_advance_counter = 0; char *pw = NULL, *saved_pw = NULL; From c27a6a5a9b59634bcd8f690f35e01c6c7e06b897 Mon Sep 17 00:00:00 2001 From: krzole Date: Mon, 11 May 2020 16:03:11 +0200 Subject: [PATCH 05/17] authy: update README.authy.md --- README.authy.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.authy.md b/README.authy.md index 62e1dca..d51546c 100644 --- a/README.authy.md +++ b/README.authy.md @@ -1,10 +1,6 @@ -# Google Authenticator PAM module with Authy push notification - -PAM authentication using Authy (https://authy.com/) push notifications. - -This repository is a fork of the "Google Authenticator PAM module" -https://github.com/google/google-authenticator-libpam with an extra -extension to send Authy push notification. +# Authy push authentication for Google Authenticator PAM module +This extension uses Authy (https://authy.com/) to authenticate +using push notifications. ## Build, install, setup See https://github.com/google/google-authenticator-libpam From 90562d3278968453306112e197b7e14f7a60cd91 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 20:11:07 +0200 Subject: [PATCH 06/17] authy: remove newlines from log_message() calls in pam_authy.c --- src/pam_authy.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 5bacafe..19270cb 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -51,7 +51,7 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u curl = curl_easy_init(); if (!curl) { - log_message(LOG_ERR, pamh, "authy_err: curl init failed\n"); + log_message(LOG_ERR, pamh, "authy_err: curl init failed"); rc = AUTHY_LIB_ERROR; goto exit_err; } @@ -67,7 +67,7 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u res = curl_easy_perform(curl); if (res != CURLE_OK) { - log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)\n", + log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", res, curl_easy_strerror(res)); rc = AUTHY_CONN_ERROR; goto exit_err; @@ -77,7 +77,7 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); jt = json_object_get(payload, "approval_request"); if (!jt) { - log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing\n"); + log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing"); rc = AUTHY_CONN_ERROR; goto exit_err; } @@ -135,7 +135,7 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap curl = curl_easy_init(); if (!curl) { - log_message(LOG_ERR, pamh, "authy_err: curl init failed\n"); + log_message(LOG_ERR, pamh, "authy_err: curl init failed"); rc = AUTHY_LIB_ERROR; goto exit_err; } @@ -161,7 +161,7 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap res = curl_easy_perform(curl); if (res != CURLE_OK) { - log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)\n", + log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", res, curl_easy_strerror(res)); rc = AUTHY_CONN_ERROR; goto exit_err; @@ -171,13 +171,13 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap payload = json_loads(buffer.memory, JSON_DECODE_ANY, NULL); jt = json_object_get(payload, "approval_request"); if (!jt) { - log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing\n"); + log_message(LOG_ERR, pamh, "authy_err: 'approval_request' field missing"); rc = AUTHY_CONN_ERROR; goto exit_err; } str = (char *)json_string_value(json_object_get(jt, "uuid")); if (!str) { - log_message(LOG_ERR, pamh, "authy_err: 'uuid' field missing\n"); + log_message(LOG_ERR, pamh, "authy_err: 'uuid' field missing"); rc = AUTHY_CONN_ERROR; goto exit_err; } @@ -215,14 +215,14 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim char *uuid = NULL; char *err_str = NULL; - log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request\n"); + log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request"); rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); if (rc != AUTHY_OK) { - log_message(LOG_ERR, pamh, "authy_err: Push Authentication request failed\n"); + log_message(LOG_ERR, pamh, "authy_err: Push Authentication request failed"); goto exit_err; } - log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval\n"); + log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); start_time = time(NULL); do { rc = authy_check_aproval(pamh, api_key, uuid); @@ -234,7 +234,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim err_str = "expired"; goto exit_err; case AUTHY_APPROVED: - log_message(LOG_INFO, pamh, "authy_dbg: Authentication approved\n"); + log_message(LOG_INFO, pamh, "authy_dbg: Authentication approved"); goto exit_err; default: break; @@ -246,7 +246,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim exit_err: if (err_str) - log_message(LOG_ERR, pamh, "authy_err: Authentication %s\n", err_str); + log_message(LOG_ERR, pamh, "authy_err: Authentication %s", err_str); if (uuid) free(uuid); From a7c61a111050095949cbe60df3941543eef3c7aa Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:05:07 +0200 Subject: [PATCH 07/17] authy: remove asprintf() calls --- src/pam_authy.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 19270cb..9d3fb45 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -46,7 +46,7 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u authy_rc_t rc; mblock_t buffer = {0}; struct curl_slist *headers = NULL; - char *url = NULL, *xheader = NULL, *str = NULL; + char *str = NULL; json_t *payload = NULL, *jt = NULL; curl = curl_easy_init(); @@ -56,9 +56,13 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u goto exit_err; } - asprintf(&url, "https://api.authy.com/onetouch/json/approval_requests/%s", + char url[80]; + snprintf(url, sizeof(url), + "https://api.authy.com/onetouch/json/approval_requests/%s", uuid); - asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + char xheader[40]; + snprintf(xheader, sizeof(xheader), "X-Authy-API-Key: %s", api_key); + headers = curl_slist_append(headers, xheader); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, url); @@ -111,9 +115,6 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u if (payload) free(payload); - if (url) - free(url); - if (curl) curl_easy_cleanup(curl); @@ -127,9 +128,8 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap authy_rc_t rc; mblock_t buffer = {0}; struct curl_slist *headers = NULL; - char *url = NULL, *xheader = NULL, *str = NULL; + char *str = NULL; json_t *payload = NULL, *jt = NULL; - char *data = NULL; char hostname[128] = { 0 }; const char *username; @@ -145,13 +145,20 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap strcpy(hostname, "unix"); } - asprintf(&url, "https://api.authy.com/onetouch/json/users/%ld/approval_requests", + char url[80]; + snprintf(url, sizeof(url), + "https://api.authy.com/onetouch/json/users/%ld/approval_requests", authy_id); - asprintf(&xheader, "X-Authy-API-Key: %s", api_key); + + char xheader[40]; + snprintf(xheader, sizeof(xheader), "X-Authy-API-Key: %s", api_key); + + char data[170]; + snprintf(data, sizeof(data), "message=Login authentication&" \ + "details=%s at %s&seconds_to_expire=%d", + username, hostname, timeout); + headers = curl_slist_append(headers, xheader); - asprintf(&data, "message=Login authentication"); - asprintf(&data, "%s&details=%s at %s", data, username, hostname); - asprintf(&data, "%s&seconds_to_expire=%d", data, timeout); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); @@ -175,13 +182,12 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap rc = AUTHY_CONN_ERROR; goto exit_err; } - str = (char *)json_string_value(json_object_get(jt, "uuid")); - if (!str) { + *uuid = (char *)json_string_value(json_object_get(jt, "uuid")); + if (!*uuid) { log_message(LOG_ERR, pamh, "authy_err: 'uuid' field missing"); rc = AUTHY_CONN_ERROR; goto exit_err; } - asprintf(uuid, "%s", str); exit_err: if (buffer.memory) @@ -196,12 +202,6 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap if (payload) free(payload); - if (url) - free(url); - - if (data) - free(data); - if (curl) curl_easy_cleanup(curl); From f39faed33a1e9320e578d1fa11326c8c186a0eed Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:14:11 +0200 Subject: [PATCH 08/17] authy: dont check for NULL pointers before freeing them --- src/pam_authy.c | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 9d3fb45..823b14f 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -103,17 +103,10 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u } exit_err: - if (buffer.memory) - free(buffer.memory); - - if (jt) - free(jt); - - if (str) - free(str); - - if (payload) - free(payload); + free(buffer.memory); + free(jt); + free(str); + free(payload); if (curl) curl_easy_cleanup(curl); @@ -190,17 +183,10 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap } exit_err: - if (buffer.memory) - free(buffer.memory); - - if (jt) - free(jt); - - if (str) - free(str); - - if (payload) - free(payload); + free(buffer.memory); + free(jt); + free(str); + free(payload); if (curl) curl_easy_cleanup(curl); @@ -215,14 +201,14 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim char *uuid = NULL; char *err_str = NULL; - log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request"); + log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request"); rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); if (rc != AUTHY_OK) { log_message(LOG_ERR, pamh, "authy_err: Push Authentication request failed"); goto exit_err; } - log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); + log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); start_time = time(NULL); do { rc = authy_check_aproval(pamh, api_key, uuid); @@ -248,8 +234,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim if (err_str) log_message(LOG_ERR, pamh, "authy_err: Authentication %s", err_str); - if (uuid) - free(uuid); + free(uuid); return rc; } From 09989ab54f091a542c548dc30642d2535f45f34a Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:20:08 +0200 Subject: [PATCH 09/17] authy: check for pam_get_user() failure --- src/pam_authy.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 823b14f..79af14a 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -133,7 +133,14 @@ static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *ap goto exit_err; } - pam_get_user(pamh, &username, NULL); + if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS || + !username || + !*username) { + log_message(LOG_ERR, pamh, "pam_get_user() failed to get a user name"); + rc = AUTHY_LIB_ERROR; + goto exit_err; + } + if (gethostname(hostname, sizeof(hostname)-1)) { strcpy(hostname, "unix"); } From e17765dafd3073ed969d939482c307ef90117b8d Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:21:09 +0200 Subject: [PATCH 10/17] authy: fix typo in the function name --- src/pam_authy.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 79af14a..9e6ee26 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -39,7 +39,7 @@ static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, return realsize; } -static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *uuid) +static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char *uuid) { CURL *curl = NULL; CURLcode res; @@ -114,7 +114,7 @@ static authy_rc_t authy_check_aproval(pam_handle_t *pamh, char *api_key, char *u return rc; } -static authy_rc_t authy_post_aproval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) +static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) { CURL *curl = NULL; CURLcode res; @@ -209,7 +209,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim char *err_str = NULL; log_message(LOG_INFO, pamh, "authy_dbg: Sending Authy authentication push request"); - rc = authy_post_aproval(pamh, authy_id, api_key, 30, &uuid); + rc = authy_post_approval(pamh, authy_id, api_key, 30, &uuid); if (rc != AUTHY_OK) { log_message(LOG_ERR, pamh, "authy_err: Push Authentication request failed"); goto exit_err; @@ -218,7 +218,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); start_time = time(NULL); do { - rc = authy_check_aproval(pamh, api_key, uuid); + rc = authy_check_approval(pamh, api_key, uuid); switch (rc) { case AUTHY_DENIED: err_str = "denied"; From c0546102daadb85fa82f354f48bd8967791cd7e2 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:46:05 +0200 Subject: [PATCH 11/17] authy: reduce variable scope --- src/pam_authy.c | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index 9e6ee26..c378581 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -29,7 +29,7 @@ static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, mem->memory = realloc(mem->memory, mem->size + realsize + 1); if (mem->memory == NULL) { - return -ENOMEM; + return -ENOMEM; } memcpy(&(mem->memory[mem->size]), content, realsize); @@ -41,15 +41,11 @@ static size_t ctrl_curl_receive(void *content, size_t size, size_t nmemb, static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char *uuid) { - CURL *curl = NULL; - CURLcode res; - authy_rc_t rc; - mblock_t buffer = {0}; - struct curl_slist *headers = NULL; - char *str = NULL; json_t *payload = NULL, *jt = NULL; + mblock_t buffer = {0}; + authy_rc_t rc; - curl = curl_easy_init(); + CURL *curl = curl_easy_init(); if (!curl) { log_message(LOG_ERR, pamh, "authy_err: curl init failed"); rc = AUTHY_LIB_ERROR; @@ -60,15 +56,17 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * snprintf(url, sizeof(url), "https://api.authy.com/onetouch/json/approval_requests/%s", uuid); + char xheader[40]; snprintf(xheader, sizeof(xheader), "X-Authy-API-Key: %s", api_key); - headers = curl_slist_append(headers, xheader); + struct curl_slist *headers = curl_slist_append(headers, xheader); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + CURLcode res; res = curl_easy_perform(curl); if (res != CURLE_OK) { log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", @@ -86,7 +84,7 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * goto exit_err; } - str = (char *)json_string_value(json_object_get(jt, "status")); + char *str = (char *)json_string_value(json_object_get(jt, "status")); if (!str) { rc = AUTHY_CONN_ERROR; goto exit_err; @@ -105,7 +103,6 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * exit_err: free(buffer.memory); free(jt); - free(str); free(payload); if (curl) @@ -116,23 +113,18 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *api_key, int timeout, char **uuid) { - CURL *curl = NULL; - CURLcode res; - authy_rc_t rc; - mblock_t buffer = {0}; - struct curl_slist *headers = NULL; - char *str = NULL; json_t *payload = NULL, *jt = NULL; - char hostname[128] = { 0 }; - const char *username; + mblock_t buffer = {0}; + authy_rc_t rc; - curl = curl_easy_init(); + CURL *curl = curl_easy_init(); if (!curl) { log_message(LOG_ERR, pamh, "authy_err: curl init failed"); rc = AUTHY_LIB_ERROR; goto exit_err; } + const char *username; if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS || !username || !*username) { @@ -141,6 +133,7 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a goto exit_err; } + char hostname[128] = {0}; if (gethostname(hostname, sizeof(hostname)-1)) { strcpy(hostname, "unix"); } @@ -158,7 +151,7 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a "details=%s at %s&seconds_to_expire=%d", username, hostname, timeout); - headers = curl_slist_append(headers, xheader); + struct curl_slist *headers = curl_slist_append(headers, xheader); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); @@ -166,6 +159,7 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + CURLcode res; res = curl_easy_perform(curl); if (res != CURLE_OK) { log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", @@ -192,7 +186,6 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a exit_err: free(buffer.memory); free(jt); - free(str); free(payload); if (curl) @@ -203,7 +196,6 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout) { - time_t start_time; authy_rc_t rc; char *uuid = NULL; char *err_str = NULL; @@ -216,7 +208,7 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim } log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); - start_time = time(NULL); + time_t start_time = time(NULL); do { rc = authy_check_approval(pamh, api_key, uuid); switch (rc) { From 013618e9c73bc5b614d644246c9e4add481e1abb Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 22:57:18 +0200 Subject: [PATCH 12/17] authy: use clock_gettime() to check the timeout --- src/pam_authy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index c378581..bebb98c 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -208,7 +208,8 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim } log_message(LOG_INFO, pamh, "authy_dbg: Waiting for Authy authentication approval"); - time_t start_time = time(NULL); + struct timespec start_time, now; + clock_gettime(CLOCK_MONOTONIC, &start_time); do { rc = authy_check_approval(pamh, api_key, uuid); switch (rc) { @@ -225,7 +226,8 @@ authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int tim break; } sleep(1); - } while ((start_time + timeout + 5) > time(NULL)); + clock_gettime(CLOCK_MONOTONIC, &now); + } while ((start_time.tv_sec + timeout + 5) > now.tv_sec); rc = AUTHY_EXPIRED; err_str = "expired (pam timeout)"; From 82941d0d85019f2d8f390d2d3ef44712e6159110 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 23:27:22 +0200 Subject: [PATCH 13/17] authy: fix too small string sizes --- src/pam_authy.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pam_authy.c b/src/pam_authy.c index bebb98c..7b04f5b 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -52,15 +52,16 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * goto exit_err; } - char url[80]; + char url[120]; snprintf(url, sizeof(url), "https://api.authy.com/onetouch/json/approval_requests/%s", uuid); - char xheader[40]; + char xheader[64]; snprintf(xheader, sizeof(xheader), "X-Authy-API-Key: %s", api_key); - struct curl_slist *headers = curl_slist_append(headers, xheader); + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, xheader); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); @@ -69,7 +70,7 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * CURLcode res; res = curl_easy_perform(curl); if (res != CURLE_OK) { - log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", + log_message(LOG_ERR, pamh, "authy_err: curl call failed: %d (%s)", res, curl_easy_strerror(res)); rc = AUTHY_CONN_ERROR; goto exit_err; @@ -143,15 +144,16 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a "https://api.authy.com/onetouch/json/users/%ld/approval_requests", authy_id); - char xheader[40]; + char xheader[64]; snprintf(xheader, sizeof(xheader), "X-Authy-API-Key: %s", api_key); - char data[170]; + char data[200]; snprintf(data, sizeof(data), "message=Login authentication&" \ "details=%s at %s&seconds_to_expire=%d", username, hostname, timeout); - struct curl_slist *headers = curl_slist_append(headers, xheader); + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, xheader); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(data)); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); From 89ddccb4dee31b8e5d932ed817c2e9ad408b5a84 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Fri, 15 May 2020 20:09:11 +0200 Subject: [PATCH 14/17] authy: auto-enable authy support if dependencies are met --- configure.ac | 51 +++++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/configure.ac b/configure.ac index 5b6a1a1..95d3753 100644 --- a/configure.ac +++ b/configure.ac @@ -71,39 +71,26 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ AC_LANG_POP(C) +AC_CHECK_LIB([curl], [curl_global_init], [have_curl=yes]) +AC_CHECK_LIB([jansson], [json_loads], [have_jansson=yes]) + AC_ARG_ENABLE([authy], - [AC_HELP_STRING([--enable-authy], - [enable Authy push authentcation [default=no]])], - [ - xenable_authy=$enableval - AC_DEFINE(ENABLE_AUTHY, [1], [Enable Authy extension]) - ], - [xenable_authy=no]) -AM_CONDITIONAL([ENABLE_AUTHY], [false]) - -AC_CHECK_LIB([curl], [curl_global_init], - [ - LIBS="$LIBS -lcurl" - have_curl=yes - ],[ have_curl=no ],[]) -AC_CHECK_HEADERS([curl/curl.h]) - -AC_CHECK_LIB([jansson], [json_loads], - [ - LIBS="$LIBS -ljansson" - have_jansson=yes - ],[ have_jansson=no ],[]) -AC_CHECK_HEADERS([jansson.h]) - -if test "x${xenable_authy}" == "x${enableval}"; then - if test "x${have_jansson}" == "xno"; then - AC_MSG_ERROR([libjannson is missing]) - fi - if test "x${have_jansson}" == "xno" || test "x${have_curl}" == "xno"; then - AC_MSG_ERROR([libcurl is missing]) - fi -fi + [AC_HELP_STRING([--disable-authy], + [enable Authy push authentcation [default=auto]])], + [:], [enable_authy=check]) + +AS_IF([test "x${enable_authy}" = "xcheck"], [ + AS_IF([test "x${have_jansson}" = "xyes" && test "x${have_curl}" = "xyes"], + [enable_authy=yes], [enable_authy=no]) +]) +AM_CONDITIONAL([ENABLE_AUTHY], [test x${enable_authy} = xyes]) +AS_IF([test "x${enable_authy}" = "xyes"], [ + AS_IF([test "x${have_jansson}" != "xyes"], [AC_MSG_ERROR([libjannson is missing])]) + AS_IF([test "x${have_curl}" != "xyes"], [AC_MSG_ERROR([libcurl is missing])]) + AC_DEFINE(ENABLE_AUTHY, [1], [Define to 1 to enable Authy extension.]) + LIBS="$LIBS -ljansson -lcurl" +]) AC_SEARCH_LIBS([dlopen], [dl]) @@ -113,7 +100,7 @@ AC_OUTPUT echo " $PACKAGE_NAME version $PACKAGE_VERSION - Authy..........: $xenable_authy + Authy..........: $enable_authy Prefix.........: $prefix Debug Build....: $debug C Compiler.....: $CC $CFLAGS $CPPFLAGS From 60ffea7689cd67743d8a3e51b82bde2ff8562d68 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sat, 16 May 2020 01:10:27 +0200 Subject: [PATCH 15/17] authy: add client-side timeouts with CURLOPT_TIMEOUT option --- src/pam_authy.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pam_authy.c b/src/pam_authy.c index 7b04f5b..84e61be 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -66,6 +66,7 @@ static authy_rc_t authy_check_approval(pam_handle_t *pamh, char *api_key, char * curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); CURLcode res; res = curl_easy_perform(curl); @@ -160,6 +161,7 @@ static authy_rc_t authy_post_approval(pam_handle_t *pamh, long authy_id, char *a curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctrl_curl_receive); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); CURLcode res; res = curl_easy_perform(curl); From ebfb63cd489e954c57805b882169f623dfacad21 Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sat, 16 May 2020 01:31:08 +0200 Subject: [PATCH 16/17] authy: add a dummy pam_no_authy.c file pam_no_authy.c contains a dummy authy_login() implementation that always fails. It is linked, when Authy support is disabled, limiting the number of 'ifdefs' in the pam_google_authenticator.c code. --- Makefile.am | 2 ++ src/pam_authy.h | 1 + src/pam_google_authenticator.c | 4 ---- src/pam_no_authy.c | 36 ++++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/pam_no_authy.c diff --git a/Makefile.am b/Makefile.am index 2221e0a..8649394 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,8 @@ pam_google_authenticator_la_SOURCES = \ $(CORE_SRC) if ENABLE_AUTHY pam_google_authenticator_la_SOURCES += src/pam_authy.c +else +pam_google_authenticator_la_SOURCES += src/pam_no_authy.c endif pam_google_authenticator_la_LIBADD = -lpam pam_google_authenticator_la_CFLAGS = $(AM_CFLAGS) diff --git a/src/pam_authy.h b/src/pam_authy.h index 960ce32..b73e5cd 100644 --- a/src/pam_authy.h +++ b/src/pam_authy.h @@ -9,6 +9,7 @@ typedef enum { AUTHY_DENIED, AUTHY_PENDING, AUTHY_EXPIRED, + AUTHY_NO_SUPPORT, } authy_rc_t; authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout); diff --git a/src/pam_google_authenticator.c b/src/pam_google_authenticator.c index 0b5dbf2..5f752f6 100644 --- a/src/pam_google_authenticator.c +++ b/src/pam_google_authenticator.c @@ -857,7 +857,6 @@ static long get_hotp_counter(pam_handle_t *pamh, const char *buf) { return counter; } -#ifdef ENABLE_AUTHY static long get_cfg_value_long(pam_handle_t *pamh, const char *buf, char *parm) { if (!buf) { return -1; @@ -889,7 +888,6 @@ static const char *get_cfg_value_char(pam_handle_t *pamh, const char *buf, char return cfg_val_str; } -#endif /* ENABLE_AUTHY */ static int rate_limit(pam_handle_t *pamh, const char *secret_filename, int *updated, char **buf) { @@ -1873,7 +1871,6 @@ static int google_authenticator(pam_handle_t *pamh, log_message(LOG_WARNING , pamh, "No secret configured for user %s, asking for code anyway.", username); } -#ifdef ENABLE_AUTHY if (params.enable_authy) { authy_rc_t arc; long authy_id = get_cfg_value_long(pamh, buf, "AUTHY_ID"); @@ -1889,7 +1886,6 @@ static int google_authenticator(pam_handle_t *pamh, goto out; } } -#endif /* ENABLE_AUTHY */ int must_advance_counter = 0; char *pw = NULL, *saved_pw = NULL; diff --git a/src/pam_no_authy.c b/src/pam_no_authy.c new file mode 100644 index 0000000..3511da3 --- /dev/null +++ b/src/pam_no_authy.c @@ -0,0 +1,36 @@ +/* + * Google authenticator extension for Authy push notifications + * + * Copyright 2020 Krzysztof Olejarczyk + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include "config.h" +#include +#include + +#include +#include + +#include "pam_authy.h" + +authy_rc_t authy_login(pam_handle_t *pamh, long authy_id, char *api_key, int timeout) +{ + return AUTHY_NO_SUPPORT; +} From 7adaa1a87077a119d3af854fa431fd9bf5533baa Mon Sep 17 00:00:00 2001 From: Krzysztof Olejarczyk Date: Sat, 16 May 2020 01:36:27 +0200 Subject: [PATCH 17/17] authy: add licence header --- src/pam_authy.c | 21 +++++++++++++++++++++ src/pam_authy.h | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/pam_authy.c b/src/pam_authy.c index 84e61be..ceca6b6 100644 --- a/src/pam_authy.c +++ b/src/pam_authy.c @@ -1,3 +1,24 @@ +/* + * Google authenticator extension for Authy push notifications + * + * Copyright 2020 Krzysztof Olejarczyk + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #define _GNU_SOURCE #include "config.h" diff --git a/src/pam_authy.h b/src/pam_authy.h index b73e5cd..ead6824 100644 --- a/src/pam_authy.h +++ b/src/pam_authy.h @@ -1,3 +1,24 @@ +/* + * Google authenticator extension for Authy push notifications + * + * Copyright 2020 Krzysztof Olejarczyk + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #ifndef AUTHY_H #define AUTHY_H