From eda645f7e0af2dcb02a13e1a1ce6c56049c30b83 Mon Sep 17 00:00:00 2001
From: Maxime de Roucy
Date: Sun, 4 Jan 2015 12:47:03 +0100
Subject: [PATCH 1/3] usersfile : rewrite
modify the usersfile inplace to keep owner and permissions
---
NEWS | 3 +
liboath/errors.c | 54 ++--
liboath/oath.h.in | 71 +++--
liboath/tests/tst_usersfile.c | 2 +-
liboath/totp.c | 6 +-
liboath/usersfile.c | 701 ++++++++++++++++++++++++------------------
pam_oath/README | 2 +-
7 files changed, 481 insertions(+), 358 deletions(-)
diff --git a/NEWS b/NEWS
index 559d9d8..d84f8bf 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,9 @@ Copyright (C) 2009-2014 Simon Josefsson. Licensed under the GPLv3+.
* Version 2.6.0 (unreleased)
+** liboath: usersfile is now updated inplace, so there is no owner/permission
+modifications.
+
** liboath: Support TOTP with HMAC-SHA256 and HMAC-SHA512.
This adds new APIs oath_totp_generate2, oath_totp_validate4 and
oath_totp_validate4_callback.
diff --git a/liboath/errors.c b/liboath/errors.c
index 87292e1..1257bad 100644
--- a/liboath/errors.c
+++ b/liboath/errors.c
@@ -31,32 +31,34 @@ typedef struct
} err_t;
static const err_t errors[] = {
- ERR (OATH_OK, "Successful return"),
- ERR (OATH_CRYPTO_ERROR, "Internal error in crypto functions"),
- ERR (OATH_INVALID_DIGITS, "Unsupported number of OTP digits"),
- ERR (OATH_PRINTF_ERROR, "Error from system printf call"),
- ERR (OATH_INVALID_HEX, "Hex string is invalid"),
- ERR (OATH_TOO_SMALL_BUFFER, "The output buffer is too small"),
- ERR (OATH_INVALID_OTP, "The OTP is not valid"),
- ERR (OATH_REPLAYED_OTP, "The OTP has been replayed"),
- ERR (OATH_BAD_PASSWORD, "The password does not match"),
- ERR (OATH_INVALID_COUNTER, "The counter value is corrupt"),
- ERR (OATH_INVALID_TIMESTAMP, "The timestamp is corrupt"),
- ERR (OATH_NO_SUCH_FILE, "The supplied filename does not exist"),
- ERR (OATH_UNKNOWN_USER, "Cannot find information about user"),
- ERR (OATH_FILE_SEEK_ERROR, "System error when seeking in file"),
- ERR (OATH_FILE_CREATE_ERROR, "System error when creating file"),
- ERR (OATH_FILE_LOCK_ERROR, "System error when locking file"),
- ERR (OATH_FILE_RENAME_ERROR, "System error when renaming file"),
- ERR (OATH_FILE_UNLINK_ERROR, "System error when removing file"),
- ERR (OATH_TIME_ERROR, "System error for time manipulation"),
- ERR (OATH_STRCMP_ERROR, "A strcmp callback returned an error"),
- ERR (OATH_INVALID_BASE32, "Base32 string is invalid"),
- ERR (OATH_BASE32_OVERFLOW, "Base32 encoding would overflow"),
- ERR (OATH_MALLOC_ERROR, "Memory allocation failed"),
- ERR (OATH_FILE_FLUSH_ERROR, "System error when flushing file buffer"),
- ERR (OATH_FILE_SYNC_ERROR, "System error when syncing file to disk"),
- ERR (OATH_FILE_CLOSE_ERROR, "System error when closing file")
+ ERR (OATH_OK, " Successful return"),
+ ERR (OATH_CRYPTO_ERROR, " Internal error in crypto functions"),
+ ERR (OATH_INVALID_DIGITS, " Unsupported number of OTP digits"),
+ ERR (OATH_INVALID_HEX, " Hex string is invalid"),
+ ERR (OATH_TOO_SMALL_BUFFER, " The output buffer is too small"),
+ ERR (OATH_INVALID_OTP, " The OTP is not valid"),
+ ERR (OATH_REPLAYED_OTP, " The OTP has been replayed"),
+ ERR (OATH_BAD_PASSWORD, " The password does not match"),
+ ERR (OATH_INVALID_COUNTER, " The counter value is corrupt"),
+ ERR (OATH_INVALID_TIMESTAMP, " The timestamp is corrupt"),
+ ERR (OATH_UNKNOWN_USER, " Cannot find information about user"),
+ ERR (OATH_WRONG_TOKEN_TYPE, " Bad formated token type"),
+ ERR (OATH_NO_USERNAME, " Can't read username from file"),
+ ERR (OATH_NO_PASSWORD, " Can't read password from file"),
+ ERR (OATH_NO_SECRET, " Can't read secret key from file"),
+ ERR (OATH_PRINTF_ERROR, " Error from system *printf call"),
+ ERR (OATH_FILE_OPEN_ERROR, " System error when opening file"),
+ ERR (OATH_FILE_READ_ERROR, " System error when reading in file"),
+ ERR (OATH_FILE_WRITE_ERROR, " System error when writting in file"),
+ ERR (OATH_FILE_TELL_ERROR, " Error from system ftell call"),
+ ERR (OATH_FILE_SEEK_ERROR, " System error when seeking in file"),
+ ERR (OATH_FILE_TRUNCATE_ERROR, " System error when truncating file"),
+ ERR (OATH_FILE_LOCK_ERROR, " System error when locking file"),
+ ERR (OATH_TIME_ERROR, " System error for time manipulation"),
+ ERR (OATH_STRCMP_ERROR, " A strcmp callback returned an error"),
+ ERR (OATH_INVALID_BASE32, " Base32 string is invalid"),
+ ERR (OATH_BASE32_OVERFLOW, " Base32 encoding would overflow"),
+ ERR (OATH_MALLOC_ERROR, " Memory allocation failed")
};
/**
diff --git a/liboath/oath.h.in b/liboath/oath.h.in
index 173592a..a6bb8d1 100644
--- a/liboath/oath.h.in
+++ b/liboath/oath.h.in
@@ -68,7 +68,6 @@ extern "C"
* @OATH_OK: Successful return
* @OATH_CRYPTO_ERROR: Internal error in crypto functions
* @OATH_INVALID_DIGITS: Unsupported number of OTP digits
- * @OATH_PRINTF_ERROR: Error from system printf call
* @OATH_INVALID_HEX: Hex string is invalid
* @OATH_TOO_SMALL_BUFFER: The output buffer is too small
* @OATH_INVALID_OTP: The OTP is not valid
@@ -76,22 +75,26 @@ extern "C"
* @OATH_BAD_PASSWORD: The password does not match
* @OATH_INVALID_COUNTER: The counter value is corrupt
* @OATH_INVALID_TIMESTAMP: The timestamp is corrupt
- * @OATH_NO_SUCH_FILE: The supplied filename does not exist
* @OATH_UNKNOWN_USER: Cannot find information about user
+ * @OATH_WRONG_TOKEN_TYPE: Bad formated token type
+ * @OATH_NO_USERNAME: Can't read username from file
+ * @OATH_NO_PASSWORD: Can't read password from file
+ * @OATH_NO_SECRET: Can't read secret key from file
+ * @OATH_PRINTF_ERROR: Error from system *printf call
+ * @OATH_FILE_OPEN_ERROR: System error when opening file
+ * @OATH_FILE_READ_ERROR: System error when reading in file
+ * @OATH_FILE_WRITE_ERROR: System error when writting in file
+ * @OATH_FILE_TELL_ERROR: Error from system ftell call
* @OATH_FILE_SEEK_ERROR: System error when seeking in file
- * @OATH_FILE_CREATE_ERROR: System error when creating file
+ * @OATH_FILE_TRUNCATE_ERROR: System error when truncating file
* @OATH_FILE_LOCK_ERROR: System error when locking file
- * @OATH_FILE_RENAME_ERROR: System error when renaming file
- * @OATH_FILE_UNLINK_ERROR: System error when removing file
* @OATH_TIME_ERROR: System error for time manipulation
* @OATH_STRCMP_ERROR: A strcmp callback returned an error
* @OATH_INVALID_BASE32: Base32 string is invalid
* @OATH_BASE32_OVERFLOW: Base32 encoding would overflow
* @OATH_MALLOC_ERROR: Memory allocation failed
- * @OATH_FILE_FLUSH_ERROR: System error when flushing file buffer
- * @OATH_FILE_SYNC_ERROR: System error when syncing file to disk
- * @OATH_FILE_CLOSE_ERROR: System error when closing file
* @OATH_LAST_ERROR: Meta-error indicating the last error code, for use
+ *
* when iterating over all error codes or similar.
*
* Return codes for OATH functions. All return codes are negative
@@ -106,32 +109,34 @@ typedef enum
OATH_OK = 0,
OATH_CRYPTO_ERROR = -1,
OATH_INVALID_DIGITS = -2,
- OATH_PRINTF_ERROR = -3,
- OATH_INVALID_HEX = -4,
- OATH_TOO_SMALL_BUFFER = -5,
- OATH_INVALID_OTP = -6,
- OATH_REPLAYED_OTP = -7,
- OATH_BAD_PASSWORD = -8,
- OATH_INVALID_COUNTER = -9,
- OATH_INVALID_TIMESTAMP = -10,
- OATH_NO_SUCH_FILE = -11,
- OATH_UNKNOWN_USER = -12,
- OATH_FILE_SEEK_ERROR = -13,
- OATH_FILE_CREATE_ERROR = -14,
- OATH_FILE_LOCK_ERROR = -15,
- OATH_FILE_RENAME_ERROR = -16,
- OATH_FILE_UNLINK_ERROR = -17,
- OATH_TIME_ERROR = -18,
- OATH_STRCMP_ERROR = -19,
- OATH_INVALID_BASE32 = -20,
- OATH_BASE32_OVERFLOW = -21,
- OATH_MALLOC_ERROR = -22,
- OATH_FILE_FLUSH_ERROR = -23,
- OATH_FILE_SYNC_ERROR = -24,
- OATH_FILE_CLOSE_ERROR = -25,
+ OATH_INVALID_HEX = -3,
+ OATH_TOO_SMALL_BUFFER = -4,
+ OATH_INVALID_OTP = -5,
+ OATH_REPLAYED_OTP = -6,
+ OATH_BAD_PASSWORD = -7,
+ OATH_INVALID_COUNTER = -8,
+ OATH_INVALID_TIMESTAMP = -9,
+ OATH_UNKNOWN_USER = -10,
+ OATH_WRONG_TOKEN_TYPE = -11,
+ OATH_NO_USERNAME = -12,
+ OATH_NO_PASSWORD = -13,
+ OATH_NO_SECRET = -14,
+ OATH_PRINTF_ERROR = -15,
+ OATH_FILE_OPEN_ERROR = -16,
+ OATH_FILE_READ_ERROR = -17,
+ OATH_FILE_WRITE_ERROR = -18,
+ OATH_FILE_TELL_ERROR = -19,
+ OATH_FILE_SEEK_ERROR = -20,
+ OATH_FILE_TRUNCATE_ERROR = -21,
+ OATH_FILE_LOCK_ERROR = -22,
+ OATH_TIME_ERROR = -23,
+ OATH_STRCMP_ERROR = -24,
+ OATH_INVALID_BASE32 = -25,
+ OATH_BASE32_OVERFLOW = -26,
+ OATH_MALLOC_ERROR = -27,
/* When adding anything here, update OATH_LAST_ERROR, errors.c
- and tests/tst_errors.c. */
- OATH_LAST_ERROR = -25
+ * and tests/tst_errors.c. */
+ OATH_LAST_ERROR = -27
} oath_rc;
/* Global */
diff --git a/liboath/tests/tst_usersfile.c b/liboath/tests/tst_usersfile.c
index b018707..615e376 100644
--- a/liboath/tests/tst_usersfile.c
+++ b/liboath/tests/tst_usersfile.c
@@ -53,7 +53,7 @@ main (void)
rc = oath_authenticate_usersfile ("no-such-file", "joe", "755224",
0, "1234", &last_otp);
- if (rc != OATH_NO_SUCH_FILE)
+ if (rc != OATH_FILE_OPEN_ERROR)
{
printf ("oath_authenticate_usersfile[1]: %s (%d)\n",
oath_strerror_name (rc), rc);
diff --git a/liboath/totp.c b/liboath/totp.c
index 0d796ae..2b3be04 100644
--- a/liboath/totp.c
+++ b/liboath/totp.c
@@ -162,8 +162,10 @@ oath_totp_validate (const char *secret,
unsigned time_step_size,
time_t start_offset, size_t window, const char *otp)
{
- return oath_totp_validate3 (secret, secret_length, now, time_step_size,
- start_offset, window, NULL, NULL, otp);
+ return oath_totp_validate4_callback (secret, secret_length, now,
+ time_step_size, start_offset,
+ strlen (otp), window, NULL, NULL, 0,
+ _oath_strcmp_callback, (void *) otp);
}
/**
diff --git a/liboath/usersfile.c b/liboath/usersfile.c
index 8dfeffa..816053d 100644
--- a/liboath/usersfile.c
+++ b/liboath/usersfile.c
@@ -31,8 +31,25 @@
#include /* For errno. */
#include /* For S_IRUSR, S_IWUSR. */
+#define IF_ERROR_GOTO(test, rc_value, goto_label) if (test) { rc = rc_value; goto goto_label; }
+
+static const char *whitespace = " \t\r\n";
+#define TIME_FORMAT_STRING "%Y-%m-%dT%H:%M:%SL"
+#define TIME_BUFFER_SIZE 30
+#define BUFFER_SIZE 1024
+
+/*
+ * parse_usersfile :
+ * @str: string with token type to parse
+ * @digits: output variable holding the length of OTP (6, 7 or 8)
+ * @totpstepsize: output variable holding the interval (in second) of TOTP
+ *
+ * internal fonction
+ *
+ * Returns: 0 on success, -1 on error
+ **/
static int
-parse_type (const char *str, unsigned *digits, unsigned *totpstepsize)
+parse_type (const char *str, unsigned int *digits, unsigned int *totpstepsize)
{
*totpstepsize = 0;
if (strcmp (str, "HOTP/E/6") == 0
@@ -72,59 +89,127 @@ parse_type (const char *str, unsigned *digits, unsigned *totpstepsize)
return 0;
}
-static const char *whitespace = " \t\r\n";
-#define TIME_FORMAT_STRING "%Y-%m-%dT%H:%M:%SL"
+/* compute the timestamp */
+static int
+compute_timestamp (char timestamp[])
+{
+ struct tm now;
+ time_t t;
+ size_t l;
+
+ if (time (&t) == (time_t) - 1)
+ return OATH_TIME_ERROR;
+
+ if (localtime_r (&t, &now) == NULL)
+ return OATH_TIME_ERROR;
+
+ l = strftime (timestamp, TIME_BUFFER_SIZE, TIME_FORMAT_STRING, &now);
+ if (l != 20)
+ return OATH_TIME_ERROR;
+
+ return OATH_OK;
+}
+
+
+/*
+ * parse_usersfile :
+ * @username: string with name of user
+ * @otp: string with one-time password to authenticate
+ * @window: how many past/future OTPs to search
+ * @passwd: string with password, or NULL if password checking is disabled
+ * @usersfile_fd: input file descriptor for usersfile
+ * @last_otp_timestamp: output variable holding last successful authentication timestamp
+ * @old_log_start: output variable holding the file position of the beginning of the "log" of the validation line
+ * @old_log_end: output variable holding the file position of the end of the "log" of the validation line
+ *
+ * internal fonction
+ *
+ * by "log" (old_log_start/end) I mean the end of line that should contain a moving_factor, an otp and a timestamp.
+ *
+ * Returns: %OATH_OK on success, negative value on error (see oath.h)
+ **/
static int
parse_usersfile (const char *username,
const char *otp,
- size_t window,
+ const size_t window,
const char *passwd,
- time_t * last_otp,
- FILE * infh,
- char **lineptr, size_t * n, uint64_t * new_moving_factor,
- size_t * skipped_users)
+ FILE * usersfile_fd,
+ time_t * last_otp_timestamp,
+ off_t * old_log_start,
+ off_t * old_log_end, char *new_log_buffer)
{
- int bad_password = 0;
+ int rc = OATH_OK;
+
+ // to record the fact we read a line matching the username
+ int matching_user_line = 0;
+ // to record the fact we read a line matching the username and the passwd
+ int matching_user_and_passwd_line = 0;
- *skipped_users = 0;
+ char *line_buffer = NULL;
+ size_t line_buffer_size = 0;
+ ssize_t line_size = 0;
- while (getline (lineptr, n, infh) != -1)
+ // for each line of userfile
+ while ((line_size =
+ getline (&line_buffer, &line_buffer_size, usersfile_fd)) != -1)
{
+ // used internally by strtok_r in order to maintain context
+ // between successive calls that parse the same string.
char *saveptr;
- char *p = strtok_r (*lineptr, whitespace, &saveptr);
- unsigned digits, totpstepsize;
- char secret[32];
- size_t secret_length = sizeof (secret);
- uint64_t start_moving_factor = 0;
- int rc = 0;
- char *prev_otp = NULL;
- if (p == NULL)
+ // read the first token/element of the line
+ char *p = strtok_r (line_buffer, whitespace, &saveptr);
+
+ if (p == NULL || *p == '#')
+ // blank line or comment
continue;
- /* Read token type */
+ // read token type
+ unsigned int digits, totpstepsize;
if (parse_type (p, &digits, &totpstepsize) != 0)
- continue;
+ {
+ // wrong formated token type
+ rc = OATH_WRONG_TOKEN_TYPE;
+ break;
+ }
- /* Read username */
+ // read username
p = strtok_r (NULL, whitespace, &saveptr);
- if (p == NULL || strcmp (p, username) != 0)
+ if (p == NULL)
+ {
+ // there is no username in the current line of usersfile
+ rc = OATH_NO_USERNAME;
+ break;
+ }
+
+ if (strcmp (p, username) != 0)
+ // username doesn't match
continue;
- /* Read password. */
+ // we record the fact we read a line matching the username
+ matching_user_line = 1;
+
+ // read password
p = strtok_r (NULL, whitespace, &saveptr);
+
if (passwd)
{
+ // password checking is enabled
+
if (p == NULL)
- continue;
+ {
+ // there is no password in the usersfile
+ rc = OATH_NO_PASSWORD;
+ break;
+ }
+
if (strcmp (p, "-") == 0)
{
if (*passwd != '\0')
- {
- bad_password = 1;
- rc = OATH_BAD_PASSWORD;
- }
+ // the user supply a non empty password but
+ // there is no password ("-") in the current usersfile line
+ rc = OATH_BAD_PASSWORD;
}
else if (strcmp (p, "+") == 0)
{
@@ -132,285 +217,235 @@ parse_usersfile (const char *username,
}
else if (strcmp (p, passwd) != 0)
{
- bad_password = 1;
+ // the password supply by the user doesn't match the one in the
+ // current usersfile line
rc = OATH_BAD_PASSWORD;
}
+
if (rc == OATH_BAD_PASSWORD)
{
- (*skipped_users)++;
+ // the user supply a non empty password but there is no password ("-") in the current usersfile line
+ // or
+ // the password supply by the user doesn't match the one in the current usersfile line
+
+ // we continue because a user can have multiple password
+ rc = OATH_OK;
continue;
}
- bad_password = 0;
+
+ // we record the fact we read a line matching the username and the passwd
+ matching_user_and_passwd_line = 1;
}
- /* Read key. */
+ // read secret
p = strtok_r (NULL, whitespace, &saveptr);
if (p == NULL)
- continue;
+ {
+ // there is no secret key in the current line of usersfile
+ rc = OATH_NO_SECRET;
+ break;
+ }
+
+ // convert the secret key in binary format
+ char secret[32];
+ size_t secret_length = sizeof (secret);
rc = oath_hex2bin (p, secret, &secret_length);
if (rc != OATH_OK)
- return rc;
+ // the secret key can't be converted the binary format
+ break;
+
+ // record the size of the "log" of the current line
+ long old_log_size = line_size - strlen (p) - (p - line_buffer);
+
+ // read (optional) moving factor
+ unsigned long long start_moving_factor = 0;
- /* Read (optional) moving factor. */
p = strtok_r (NULL, whitespace, &saveptr);
- if (p && *p)
+ if (p)
{
+ // the current line contain a moving factor
+
+ // convert the string to unsigned long long
char *endptr;
- unsigned long long int ull = strtoull (p, &endptr, 10);
+ start_moving_factor = strtoull (p, &endptr, 10);
if (endptr && *endptr != '\0')
- return OATH_INVALID_COUNTER;
- start_moving_factor = ull;
+ {
+ // the moving factor is bad formated and
+ // can't be converted to unsigned long long
+ rc = OATH_INVALID_COUNTER;
+ break;
+ }
}
- /* Read (optional) last OTP */
- prev_otp = strtok_r (NULL, whitespace, &saveptr);
+ // read (optional) last OTP
+ char *last_otp = NULL;
+ last_otp = strtok_r (NULL, whitespace, &saveptr);
- /* Read (optional) last_otp */
+ // Read (optional) last OTP timestamp
p = strtok_r (NULL, whitespace, &saveptr);
- if (p)
+ if (p && last_otp_timestamp)
{
+ // the current line contain a timestamp
+ // and the caller off the function want to get it
+
+ // we convert it to tm
struct tm tm;
char *ts;
-
ts = strptime (p, TIME_FORMAT_STRING, &tm);
if (ts == NULL || *ts != '\0')
- return OATH_INVALID_TIMESTAMP;
+ {
+ // the timestamp is bad formatted
+ rc = OATH_INVALID_TIMESTAMP;
+ break;
+ }
tm.tm_isdst = -1;
- if (last_otp)
+
+ // convert the tm into time_t and
+ // store it in last_otp_timestamp
+ *last_otp_timestamp = mktime (&tm);
+ if (*last_otp_timestamp == (time_t) - 1)
{
- *last_otp = mktime (&tm);
- if (*last_otp == (time_t) - 1)
- return OATH_INVALID_TIMESTAMP;
+ // the tm is wrong
+ rc = OATH_INVALID_TIMESTAMP;
+ break;
}
}
- if (prev_otp && strcmp (prev_otp, otp) == 0)
- return OATH_REPLAYED_OTP;
+ if (last_otp && strcmp (last_otp, otp) == 0)
+ {
+ // the OTP supply by the user is the
+ // same that is stored in usersfile
+ rc = OATH_REPLAYED_OTP;
+ break;
+ }
if (totpstepsize == 0)
- rc = oath_hotp_validate (secret, secret_length,
- start_moving_factor, window, otp);
- else if (prev_otp)
{
- int prev_otp_pos, this_otp_pos, tmprc;
+ // token type algorithm is HOTP
+
+ // check if the suppied OTP is valid
+ rc = oath_hotp_validate (secret, secret_length,
+ start_moving_factor, window, otp);
+ }
+ else if (last_otp)
+ {
+ // token type algorithm is TOTP
+ // and the current line contain a "log"
+
+ // check if the suppied OTP is valid
+ int totp_position;
rc = oath_totp_validate2 (secret, secret_length,
time (NULL), totpstepsize, 0, window,
- &this_otp_pos, otp);
- if (rc == OATH_INVALID_OTP)
+ &totp_position, otp);
+
+ if (rc == OATH_OK)
{
- (*skipped_users)++;
- continue;
+ // the supplied OTP is valide
+ // but it may have been already by played
+ // since it's valide for a periode of time in which a new
+ // OTP could have been played :
+ //
+ // OTP1 → OTP2 → OTP1(replay)
+ // OTP1 validity |--------------------------|
+ //
+ // in that case OTP1(replay) should be rejected
+
+ // get the time validity of the last recorded OTP
+ int last_totp_position, tmprc;
+ tmprc = oath_totp_validate2 (secret, secret_length,
+ time (NULL), totpstepsize, 0,
+ window, &last_totp_position,
+ last_otp);
+
+ if (tmprc >= 0 && last_totp_position >= totp_position)
+ {
+ // last recorded otp is newer than the one supplied by the user
+ rc = OATH_REPLAYED_OTP;
+ break;
+ }
}
- if (rc < 0)
- return rc;
- tmprc = oath_totp_validate2 (secret, secret_length,
- time (NULL), totpstepsize, 0, window,
- &prev_otp_pos, prev_otp);
- if (tmprc >= 0 && prev_otp_pos >= this_otp_pos)
- return OATH_REPLAYED_OTP;
}
else
- rc = oath_totp_validate (secret, secret_length,
- time (NULL), totpstepsize, 0, window, otp);
- if (rc == OATH_INVALID_OTP)
{
- (*skipped_users)++;
- continue;
- }
- if (rc < 0)
- return rc;
- *new_moving_factor = start_moving_factor + rc;
- return OATH_OK;
- }
-
- if (*skipped_users)
- {
- if (bad_password)
- return OATH_BAD_PASSWORD;
- else
- return OATH_INVALID_OTP;
- }
-
- return OATH_UNKNOWN_USER;
-}
-
-static int
-update_usersfile2 (const char *username,
- const char *otp,
- FILE * infh,
- FILE * outfh,
- char **lineptr,
- size_t * n, char *timestamp, uint64_t new_moving_factor,
- size_t skipped_users)
-{
- size_t got_users = 0;
-
- while (getline (lineptr, n, infh) != -1)
- {
- char *saveptr;
- char *origline;
- const char *user, *type, *passwd, *secret;
- int r;
- unsigned digits, totpstepsize;
-
- origline = strdup (*lineptr);
-
- type = strtok_r (*lineptr, whitespace, &saveptr);
- if (type == NULL)
- goto skip_line;
-
- /* Read token type */
- if (parse_type (type, &digits, &totpstepsize) != 0)
- goto skip_line;
-
- /* Read username */
- user = strtok_r (NULL, whitespace, &saveptr);
- if (user == NULL || strcmp (user, username) != 0
- || got_users++ != skipped_users)
- goto skip_line;
-
- passwd = strtok_r (NULL, whitespace, &saveptr);
- if (passwd == NULL)
- passwd = "-";
-
- secret = strtok_r (NULL, whitespace, &saveptr);
- if (secret == NULL)
- secret = "-";
-
- r = fprintf (outfh, "%s\t%s\t%s\t%s\t%llu\t%s\t%s\n",
- type, username, passwd, secret,
- (unsigned long long) new_moving_factor, otp, timestamp);
- free (origline);
- if (r <= 0)
- return OATH_PRINTF_ERROR;
- continue;
-
- skip_line:
- r = fprintf (outfh, "%s", origline);
- free (origline);
- if (r <= 0)
- return OATH_PRINTF_ERROR;
- continue;
- }
+ // token type algorithm is TOTP
+ // but the current line doesn't contain a "log"
+ // it's the first OTP the user supply
- return OATH_OK;
-}
-
-static int
-update_usersfile (const char *usersfile,
- const char *username,
- const char *otp,
- FILE * infh,
- char **lineptr,
- size_t * n, char *timestamp, uint64_t new_moving_factor,
- size_t skipped_users)
-{
- FILE *outfh, *lockfh;
- int rc;
- char *newfilename, *lockfile;
-
- /* Rewind input file. */
- {
- int pos;
-
- pos = fseeko (infh, 0L, SEEK_SET);
- if (pos == -1)
- return OATH_FILE_SEEK_ERROR;
- clearerr (infh);
- }
-
- /* Open lockfile. */
- {
- int l;
+ // check if the suppied OTP is valid
+ rc = oath_totp_validate (secret, secret_length,
+ time (NULL), totpstepsize, 0, window, otp);
+ }
- l = asprintf (&lockfile, "%s.lock", usersfile);
- if (lockfile == NULL || ((size_t) l) != strlen (usersfile) + 5)
- return OATH_PRINTF_ERROR;
+ if (rc == OATH_INVALID_OTP)
+ {
+ // the supplied otp doesn't match the current line
- lockfh = fopen (lockfile, "w");
- if (!lockfh)
- {
- free (lockfile);
- return OATH_FILE_CREATE_ERROR;
- }
- }
+ // we continue because we can have multiple secret key for the
+ // same pair
+ rc = OATH_OK;
+ continue;
+ }
- /* Lock the lockfile. */
- {
- struct flock l;
+ if (rc < 0)
+ // there were an error other than OATH_INVALID_OTP
+ break;
- memset (&l, 0, sizeof (l));
- l.l_whence = SEEK_SET;
- l.l_start = 0;
- l.l_len = 0;
- l.l_type = F_WRLCK;
+ // OTP is valide
- while ((rc = fcntl (fileno (lockfh), F_SETLKW, &l)) < 0 && errno == EINTR)
- continue;
- if (rc == -1)
- {
- fclose (lockfh);
- free (lockfile);
- return OATH_FILE_LOCK_ERROR;
- }
- }
+ // compute the current timestamp
+ char timestamp_buffer[TIME_BUFFER_SIZE];
+ rc = compute_timestamp (timestamp_buffer);
- /* Open the "new" file. */
- {
- int l;
+ if (rc != OATH_OK)
+ // timestamp can't be generated
+ break;
- l = asprintf (&newfilename, "%s.new", usersfile);
- if (newfilename == NULL || ((size_t) l) != strlen (usersfile) + 4)
- {
- fclose (lockfh);
- free (lockfile);
- return OATH_PRINTF_ERROR;
- }
+ // compute the new moving factor
+ unsigned long long new_moving_factor = start_moving_factor + rc;
- outfh = fopen (newfilename, "w");
- if (!outfh)
- {
- free (newfilename);
- fclose (lockfh);
- free (lockfile);
- return OATH_FILE_CREATE_ERROR;
- }
- }
+ // record the new "log" in new_log_buffer
+ rc =
+ snprintf (new_log_buffer, BUFFER_SIZE, "\t%llu\t%s\t%s\n",
+ new_moving_factor, otp, timestamp_buffer);
- /* Create the new usersfile content. */
- rc = update_usersfile2 (username, otp, infh, outfh, lineptr, n,
- timestamp, new_moving_factor, skipped_users);
+ if (rc < 0)
+ {
+ rc = OATH_PRINTF_ERROR;
+ break;
+ }
- /* On success, flush the buffers. */
- if (rc == OATH_OK && fflush (outfh) != 0)
- rc = OATH_FILE_FLUSH_ERROR;
+ // save the file position of the start and end
+ // of the old "log"
+ *old_log_end = ftello (usersfile_fd);
+ *old_log_start = *old_log_end - old_log_size;
- /* On success, sync the disks. */
- if (rc == OATH_OK && fsync (fileno (outfh)) != 0)
- rc = OATH_FILE_SYNC_ERROR;
+ if (*old_log_end == -1)
+ {
+ rc = OATH_FILE_TELL_ERROR;
+ break;
+ }
- /* Close the file regardless of success. */
- if (fclose (outfh) != 0)
- rc = OATH_FILE_CLOSE_ERROR;
+ free (line_buffer);
+ return OATH_OK;
+ }
- /* On success, overwrite the usersfile with the new copy. */
- if (rc == OATH_OK && rename (newfilename, usersfile) != 0)
- rc = OATH_FILE_RENAME_ERROR;
+ free (line_buffer);
- /* Something has failed, don't leave garbage lying around. */
if (rc != OATH_OK)
- unlink (newfilename);
+ return rc;
- free (newfilename);
+ // the usersfile was parse entirely without error
+ // but no matching OTP have been found
- /* Complete, close the lockfile */
- if (fclose (lockfh) != 0)
- rc = OATH_FILE_CLOSE_ERROR;
- if (unlink (lockfile) != 0)
- rc = OATH_FILE_UNLINK_ERROR;
- free (lockfile);
+ if (matching_user_and_passwd_line)
+ // there were line(s) matchine username and password
+ return OATH_INVALID_OTP;
+ else if (matching_user_line)
+ // there were line(s) matchine username (but not password)
+ return OATH_BAD_PASSWORD;
- return rc;
+ // there were no line matchine the username
+ return OATH_UNKNOWN_USER;
}
/**
@@ -420,7 +455,7 @@ update_usersfile (const char *usersfile,
* @otp: string with one-time password to authenticate
* @window: how many past/future OTPs to search
* @passwd: string with password, or NULL to disable password checking
- * @last_otp: output variable holding last successful authentication
+ * @last_otp_timestamp: output variable holding last successful authentication timestamp, or NULL to not record this value
*
* Authenticate user named @username with the one-time password @otp
* and (optional) password @passwd. Credentials are read (and
@@ -434,7 +469,7 @@ update_usersfile (const char *usersfile,
* Returns: On successful validation, %OATH_OK is returned. If the
* supplied @otp is the same as the last successfully authenticated
* one-time password, %OATH_REPLAYED_OTP is returned and the
- * timestamp of the last authentication is returned in @last_otp.
+ * timestamp of the last authentication is returned in @last_otp_timestamp.
* If the one-time password is not found in the indicated search
* window, %OATH_INVALID_OTP is returned. Otherwise, an error code
* is returned.
@@ -444,52 +479,128 @@ oath_authenticate_usersfile (const char *usersfile,
const char *username,
const char *otp,
size_t window,
- const char *passwd, time_t * last_otp)
+ const char *passwd, time_t * last_otp_timestamp)
{
- FILE *infh;
- char *line = NULL;
- size_t n = 0;
- uint64_t new_moving_factor;
- int rc;
- size_t skipped_users;
-
- infh = fopen (usersfile, "r");
- if (!infh)
- return OATH_NO_SUCH_FILE;
-
- rc = parse_usersfile (username, otp, window, passwd, last_otp,
- infh, &line, &n, &new_moving_factor, &skipped_users);
-
- if (rc == OATH_OK)
- {
- char timestamp[30];
- size_t max = sizeof (timestamp);
- struct tm now;
- time_t t;
- size_t l;
- mode_t old_umask;
-
- if (time (&t) == (time_t) - 1)
- return OATH_TIME_ERROR;
-
- if (localtime_r (&t, &now) == NULL)
- return OATH_TIME_ERROR;
-
- l = strftime (timestamp, max, TIME_FORMAT_STRING, &now);
- if (l != 20)
- return OATH_TIME_ERROR;
-
- old_umask = umask (~(S_IRUSR | S_IWUSR));
-
- rc = update_usersfile (usersfile, username, otp, infh,
- &line, &n, timestamp, new_moving_factor,
- skipped_users);
-
- umask (old_umask);
- }
-
- free (line);
- fclose (infh);
+ int rc = OATH_OK;
+ // temporary variable to check various syscall output for error
+ int syscall_output = 0;
+
+ // open usersfile
+ FILE *usersfile_fd;
+ usersfile_fd = fopen (usersfile, "r+");
+ IF_ERROR_GOTO (usersfile_fd == NULL, OATH_FILE_OPEN_ERROR, end);
+
+ { // this context was created to avoid goto crosses variable declaration
+
+ // put a read lock on usersfile
+ struct flock lock;
+ memset (&lock, 0, sizeof (lock));
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_type = F_RDLCK;
+ syscall_output = fcntl (fileno (usersfile_fd), F_SETLKW, &lock);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_LOCK_ERROR, close_end);
+
+ // parse usersfile and check if the supplied otp is valide
+ off_t old_log_start, old_log_end;
+ char new_log_buffer[BUFFER_SIZE];
+ rc =
+ parse_usersfile (username, otp, window, passwd, usersfile_fd,
+ last_otp_timestamp, &old_log_start, &old_log_end,
+ new_log_buffer);
+ if (rc != OATH_OK)
+ // supplied otp is not valide or
+ // the were error during usersfile the parsing
+ goto close_end;
+
+ // the otp is valide
+ // now we have to record the new "log" in usersfile
+
+ // put a write lock on usersfile
+ lock.l_type = F_WRLCK;
+ syscall_output = fcntl (fileno (usersfile_fd), F_SETLKW, &lock);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_LOCK_ERROR, close_end);
+
+ size_t new_log_len = strlen (new_log_buffer);
+ if ((size_t) (old_log_end - old_log_start) == new_log_len)
+ {
+ // the new and old "log" have the same size
+ // we write directly the new log in the usersfile inplace
+ // of the old "log"
+
+ // go the the start possition of the old "log"
+ syscall_output = fseeko (usersfile_fd, old_log_start, SEEK_SET);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_SEEK_ERROR, close_end);
+
+ // write the new "log"
+ fwrite (new_log_buffer, sizeof (char), strlen (new_log_buffer),
+ usersfile_fd);
+ IF_ERROR_GOTO (ferror (usersfile_fd), OATH_FILE_WRITE_ERROR,
+ close_end);
+ }
+ else
+ {
+ // the new and old "log" doesn't have the same size
+ // we will load in memory the end of the usersfile from the end of the old "log" to the end of the file
+ // write the new "log" from the starting point of the old "log"
+ // and append the end of the usersfile previously loaded in memory
+
+ // compute the amont of memory we will need
+ syscall_output = fseeko (usersfile_fd, 0, SEEK_END);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_SEEK_ERROR, close_end);
+
+ off_t usersfile_buffer_size = ftello (usersfile_fd) - old_log_end;
+ IF_ERROR_GOTO (usersfile_buffer_size < 0, OATH_FILE_TELL_ERROR,
+ close_end);
+
+ // allocate the dynamic memory
+ char *usersfile_buffer =
+ malloc (usersfile_buffer_size * sizeof (char));
+ IF_ERROR_GOTO (usersfile_buffer == NULL, OATH_MALLOC_ERROR,
+ close_end);
+
+ // load usersfile from the end of the old "log" to the end of the file
+ syscall_output = fseeko (usersfile_fd, old_log_end, SEEK_SET);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_SEEK_ERROR,
+ free_close_end);
+
+ syscall_output =
+ fread (usersfile_buffer, sizeof (char), usersfile_buffer_size,
+ usersfile_fd);
+ IF_ERROR_GOTO (syscall_output != usersfile_buffer_size,
+ OATH_FILE_READ_ERROR, free_close_end);
+
+ // write the new log in the usersfile from the start of the old "log"
+ syscall_output = fseeko (usersfile_fd, old_log_start, SEEK_SET);
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_SEEK_ERROR,
+ free_close_end);
+
+ fwrite (new_log_buffer, sizeof (char), strlen (new_log_buffer),
+ usersfile_fd);
+ IF_ERROR_GOTO (ferror (usersfile_fd), OATH_FILE_WRITE_ERROR,
+ free_close_end);
+
+ // write the end of the usersfile previously loaded in memory
+ fwrite (usersfile_buffer, sizeof (char), usersfile_buffer_size,
+ usersfile_fd);
+ IF_ERROR_GOTO (ferror (usersfile_fd), OATH_FILE_WRITE_ERROR,
+ free_close_end);
+
+ // in case the old log was longer than the new one we truncate the end of the file
+ syscall_output =
+ ftruncate (fileno (usersfile_fd), ftello (usersfile_fd));
+ IF_ERROR_GOTO (syscall_output == -1, OATH_FILE_TRUNCATE_ERROR,
+ free_close_end);
+
+ free_close_end:
+ free (usersfile_buffer);
+ }
+ }
+close_end:
+ // close the usersfile and remove the lock
+ fclose (usersfile_fd);
+end:
return rc;
}
diff --git a/pam_oath/README b/pam_oath/README
index 5fc534b..639cd73 100644
--- a/pam_oath/README
+++ b/pam_oath/README
@@ -34,7 +34,7 @@ file:
---------
# cat>/etc/users.oath
HOTP root - 00
-# chmod go-rw /etc/users.oath
+# chmod 600 /etc/users.oath
#
---------
--
2.2.1