diff options
Diffstat (limited to 'libmisc/failure.c')
-rw-r--r-- | libmisc/failure.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/libmisc/failure.c b/libmisc/failure.c new file mode 100644 index 0000000..1aab299 --- /dev/null +++ b/libmisc/failure.c @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh + * SPDX-FileCopyrightText: 1996 - 1998, Marek Michałkiewicz + * SPDX-FileCopyrightText: 2002 - 2005, Tomasz Kłoczko + * SPDX-FileCopyrightText: 2008 - 2010, Nicolas François + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <config.h> + +#ident "$Id$" + +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include "defines.h" +#include "faillog.h" +#include "getdef.h" +#include "failure.h" +#define YEAR (365L*DAY) +/* + * failure - make failure entry + * + * failure() creates a new (struct faillog) entry or updates an + * existing one with the current failed login information. + */ +void failure (uid_t uid, const char *tty, struct faillog *fl) +{ + int fd; + off_t offset_uid = (off_t) (sizeof *fl) * uid; + + /* + * Don't do anything if failure logging isn't set up. + */ + + if (access (FAILLOG_FILE, F_OK) != 0) { + return; + } + + fd = open (FAILLOG_FILE, O_RDWR); + if (fd < 0) { + SYSLOG ((LOG_WARN, + "Can't write faillog entry for UID %lu in %s.", + (unsigned long) uid, FAILLOG_FILE)); + return; + } + + /* + * The file is indexed by UID value meaning that shared UID's + * share failure log records. That's OK since they really + * share just about everything else ... + */ + + if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid) + || (read (fd, (char *) fl, sizeof *fl) != (ssize_t) sizeof *fl)) { + /* This is not necessarily a failure. The file is + * initially zero length. + * + * If lseek() or read() failed for any other reason, this + * might reset the counter. But the new failure will be + * logged. + */ + memzero (fl, sizeof *fl); + } + + /* + * Update the record. We increment the failure count to log the + * latest failure. The only concern here is overflow, and we'll + * check for that. The line name and time of day are both + * updated as well. + */ + + if (fl->fail_cnt + 1 > 0) { + fl->fail_cnt++; + } + + strncpy (fl->fail_line, tty, sizeof (fl->fail_line) - 1); + (void) time (&fl->fail_time); + + /* + * Seek back to the correct position in the file and write the + * record out. Ideally we should lock the file in case the same + * account is being logged simultaneously. But the risk doesn't + * seem that great. + */ + + if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid) + || (write (fd, (char *) fl, sizeof *fl) != (ssize_t) sizeof *fl) + || (close (fd) != 0)) { + SYSLOG ((LOG_WARN, + "Can't write faillog entry for UID %lu in %s.", + (unsigned long) uid, FAILLOG_FILE)); + (void) close (fd); + } +} + +static bool too_many_failures (const struct faillog *fl) +{ + time_t now; + + if ((0 == fl->fail_max) || (fl->fail_cnt < fl->fail_max)) { + return false; + } + + if (0 == fl->fail_locktime) { + return true; /* locked until reset manually */ + } + + (void) time (&now); + if ((fl->fail_time + fl->fail_locktime) < now) { + return false; /* enough time since last failure */ + } + + return true; +} + +/* + * failcheck - check for failures > allowable + * + * failcheck() is called AFTER the password has been validated. If the + * account has been "attacked" with too many login failures, failcheck() + * returns 0 to indicate that the login should be denied even though + * the password is valid. + * + * failed indicates if the login failed AFTER the password has been + * validated. + */ + +int failcheck (uid_t uid, struct faillog *fl, bool failed) +{ + int fd; + struct faillog fail; + off_t offset_uid = (off_t) (sizeof *fl) * uid; + + /* + * Suppress the check if the log file isn't there. + */ + + if (access (FAILLOG_FILE, F_OK) != 0) { + return 1; + } + + fd = open (FAILLOG_FILE, failed?O_RDONLY:O_RDWR); + if (fd < 0) { + SYSLOG ((LOG_WARN, + "Can't open the faillog file (%s) to check UID %lu. " + "User access authorized.", + FAILLOG_FILE, (unsigned long) uid)); + return 1; + } + + /* + * Get the record from the file and determine if the user has + * exceeded the failure limit. If "max" is zero, any number + * of failures are permitted. Only when "max" is non-zero and + * "cnt" is greater than or equal to "max" is the account + * considered to be locked. + * + * If read fails, there is no record for this user yet (the + * file is initially zero length and extended by writes), so + * no need to reset the count. + */ + + if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid) + || (read (fd, (char *) fl, sizeof *fl) != (ssize_t) sizeof *fl)) { + (void) close (fd); + return 1; + } + + if (too_many_failures (fl)) { + (void) close (fd); + return 0; + } + + /* + * The record is updated if this is not a failure. The count will + * be reset to zero, but the rest of the information will be left + * in the record in case someone wants to see where the failed + * login originated. + */ + + if (!failed) { + fail = *fl; + fail.fail_cnt = 0; + + if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid) + || (write (fd, (const void *) &fail, sizeof fail) != (ssize_t) sizeof fail) + || (close (fd) != 0)) { + SYSLOG ((LOG_WARN, + "Can't reset faillog entry for UID %lu in %s.", + (unsigned long) uid, FAILLOG_FILE)); + (void) close (fd); + } + } else { + (void) close (fd); + } + + return 1; +} + +/* + * failprint - print line of failure information + * + * failprint takes a (struct faillog) entry and formats it into a + * message which is displayed at login time. + */ + +void failprint (const struct faillog *fail) +{ + struct tm *tp; + char lasttimeb[256]; + char *lasttime = lasttimeb; + time_t NOW; + + if (0 == fail->fail_cnt) { + return; + } + + tp = localtime (&(fail->fail_time)); + (void) time (&NOW); + + /* + * Print all information we have. + */ + (void) strftime (lasttimeb, sizeof lasttimeb, "%c", tp); + + /*@-formatconst@*/ + (void) printf (ngettext ("%d failure since last login.\n" + "Last was %s on %s.\n", + "%d failures since last login.\n" + "Last was %s on %s.\n", + (unsigned long) fail->fail_cnt), + fail->fail_cnt, lasttime, fail->fail_line); + /*@=formatconst@*/ +} + +/* + * failtmp - update the cumulative failure log + * + * failtmp updates the (struct utmp) formatted failure log which + * maintains a record of all login failures. + */ + +void failtmp (const char *username, +#ifdef USE_UTMPX + const struct utmpx *failent +#else /* !USE_UTMPX */ + const struct utmp *failent +#endif /* !USE_UTMPX */ + ) +{ + const char *ftmp; + int fd; + + /* + * Get the name of the failure file. If no file has been defined + * in login.defs, don't do this. + */ + + ftmp = getdef_str ("FTMP_FILE"); + if (NULL == ftmp) { + return; + } + + /* + * Open the file for append. It must already exist for this + * feature to be used. + */ + + if (access (ftmp, F_OK) != 0) { + return; + } + + fd = open (ftmp, O_WRONLY | O_APPEND); + if (-1 == fd) { + SYSLOG ((LOG_WARN, + "Can't append failure of user %s to %s.", + username, ftmp)); + return; + } + + /* + * Append the new failure record and close the log file. + */ + + if ( (write (fd, (const void *) failent, sizeof *failent) != (ssize_t) sizeof *failent) + || (close (fd) != 0)) { + SYSLOG ((LOG_WARN, + "Can't append failure of user %s to %s.", + username, ftmp)); + (void) close (fd); + } +} + |