summaryrefslogtreecommitdiffstats
path: root/libmisc/failure.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc/failure.c')
-rw-r--r--libmisc/failure.c295
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);
+ }
+}
+