(root)/
Linux-PAM-1.5.3/
modules/
pam_faillock/
pam_faillock.c
       1  /*
       2   * Copyright (c) 2010, 2017, 2019 Tomas Mraz <tmraz@redhat.com>
       3   * Copyright (c) 2010, 2017, 2019 Red Hat, Inc.
       4   *
       5   * Redistribution and use in source and binary forms, with or without
       6   * modification, are permitted provided that the following conditions
       7   * are met:
       8   * 1. Redistributions of source code must retain the above copyright
       9   *    notice, and the entire permission notice in its entirety,
      10   *    including the disclaimer of warranties.
      11   * 2. Redistributions in binary form must reproduce the above copyright
      12   *    notice, this list of conditions and the following disclaimer in the
      13   *    documentation and/or other materials provided with the distribution.
      14   * 3. The name of the author may not be used to endorse or promote
      15   *    products derived from this software without specific prior
      16   *    written permission.
      17   *
      18   * ALTERNATIVELY, this product may be distributed under the terms of
      19   * the GNU Public License, in which case the provisions of the GPL are
      20   * required INSTEAD OF the above restrictions.  (This clause is
      21   * necessary due to a potential bad interaction between the GPL and
      22   * the restrictions contained in a BSD-style copyright.)
      23   *
      24   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      25   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      26   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      27   * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
      28   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      29   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      30   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      31   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      32   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      33   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      34   * OF THE POSSIBILITY OF SUCH DAMAGE.
      35   */
      36  
      37  #include "config.h"
      38  #include <stdio.h>
      39  #include <string.h>
      40  #include <unistd.h>
      41  #include <stdlib.h>
      42  #include <errno.h>
      43  #include <time.h>
      44  #include <pwd.h>
      45  #include <syslog.h>
      46  #include <ctype.h>
      47  
      48  #ifdef HAVE_LIBAUDIT
      49  #include <libaudit.h>
      50  #endif
      51  
      52  #include <security/pam_modules.h>
      53  #include <security/pam_modutil.h>
      54  #include <security/pam_ext.h>
      55  
      56  #include "pam_inline.h"
      57  #include "faillock.h"
      58  #include "faillock_config.h"
      59  
      60  #define FAILLOCK_ACTION_PREAUTH  0
      61  #define FAILLOCK_ACTION_AUTHSUCC 1
      62  #define FAILLOCK_ACTION_AUTHFAIL 2
      63  
      64  static int
      65  args_parse(pam_handle_t *pamh, int argc, const char **argv,
      66  		int flags, struct options *opts)
      67  {
      68  	int i;
      69  	int config_arg_index = -1;
      70  	int rv;
      71  	const char *conf = NULL;
      72  
      73  	memset(opts, 0, sizeof(*opts));
      74  
      75  	opts->deny = 3;
      76  	opts->fail_interval = 900;
      77  	opts->unlock_time = 600;
      78  	opts->root_unlock_time = MAX_TIME_INTERVAL+1;
      79  
      80  	for (i = 0; i < argc; ++i) {
      81  		const char *str = pam_str_skip_prefix(argv[i], "conf=");
      82  
      83  		if (str != NULL) {
      84  			conf = str;
      85  			config_arg_index = i;
      86  		}
      87  	}
      88  
      89  	if ((rv = read_config_file(pamh, opts, conf)) != PAM_SUCCESS) {
      90  		pam_syslog(pamh, LOG_ERR,
      91  					"Configuration file missing or broken");
      92  		return rv;
      93  	}
      94  
      95  	for (i = 0; i < argc; ++i) {
      96  		if (i == config_arg_index) {
      97  			continue;
      98  		}
      99  		else if (strcmp(argv[i], "preauth") == 0) {
     100  			opts->action = FAILLOCK_ACTION_PREAUTH;
     101  		}
     102  		else if (strcmp(argv[i], "authfail") == 0) {
     103  			opts->action = FAILLOCK_ACTION_AUTHFAIL;
     104  		}
     105  		else if (strcmp(argv[i], "authsucc") == 0) {
     106  			opts->action = FAILLOCK_ACTION_AUTHSUCC;
     107  		}
     108  		else {
     109  			char buf[FAILLOCK_CONF_MAX_LINELEN + 1];
     110  			char *val;
     111  
     112  			strncpy(buf, argv[i], sizeof(buf) - 1);
     113  			buf[sizeof(buf) - 1] = '\0';
     114  
     115  			val = strchr(buf, '=');
     116  			if (val != NULL) {
     117  				*val = '\0';
     118  				++val;
     119  			}
     120  			else {
     121  				val = buf + sizeof(buf) - 1;
     122  			}
     123  			set_conf_opt(pamh, opts, buf, val);
     124  		}
     125  	}
     126  
     127  	if (opts->root_unlock_time == MAX_TIME_INTERVAL+1)
     128  		opts->root_unlock_time = opts->unlock_time;
     129  	if (flags & PAM_SILENT)
     130  		opts->flags |= FAILLOCK_FLAG_SILENT;
     131  
     132  	if (opts->fatal_error)
     133  		return PAM_BUF_ERR;
     134  	return PAM_SUCCESS;
     135  }
     136  
     137  static int
     138  check_local_user (pam_handle_t *pamh, const char *user)
     139  {
     140  	return pam_modutil_check_user_in_passwd(pamh, user, NULL) == PAM_SUCCESS;
     141  }
     142  
     143  static int
     144  get_pam_user(pam_handle_t *pamh, struct options *opts)
     145  {
     146  	const char *user;
     147  	int rv;
     148  	struct passwd *pwd;
     149  
     150  	if ((rv=pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) {
     151  		return rv == PAM_CONV_AGAIN ? PAM_INCOMPLETE : rv;
     152  	}
     153  
     154  	if (*user == '\0') {
     155  		return PAM_IGNORE;
     156  	}
     157  
     158  	if ((pwd=pam_modutil_getpwnam(pamh, user)) == NULL) {
     159  		if (opts->flags & FAILLOCK_FLAG_AUDIT) {
     160  			pam_syslog(pamh, LOG_NOTICE, "User unknown: %s", user);
     161  		}
     162  		else {
     163  			pam_syslog(pamh, LOG_NOTICE, "User unknown");
     164  		}
     165  		return PAM_IGNORE;
     166  	}
     167  	opts->user = user;
     168  	opts->uid = pwd->pw_uid;
     169  
     170  	if (pwd->pw_uid == 0) {
     171  		opts->is_admin = 1;
     172  		return PAM_SUCCESS;
     173  	}
     174  
     175  	if (opts->admin_group && *opts->admin_group) {
     176  		opts->is_admin = pam_modutil_user_in_group_uid_nam(pamh,
     177  			pwd->pw_uid, opts->admin_group);
     178  	}
     179  
     180  	return PAM_SUCCESS;
     181  }
     182  
     183  static int
     184  check_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies, int *fd)
     185  {
     186  	int tfd;
     187  	unsigned int i;
     188  	uint64_t latest_time;
     189  	int failures;
     190  	const char *dir = get_tally_dir(opts);
     191  
     192  	opts->now = time(NULL);
     193  
     194  	tfd = open_tally(dir, opts->user, opts->uid, 0);
     195  
     196  	*fd = tfd;
     197  
     198  	if (tfd == -1) {
     199  		if (errno == EACCES || errno == ENOENT) {
     200  			return PAM_SUCCESS;
     201  		}
     202  		pam_syslog(pamh, LOG_ERR, "Error opening the tally file for %s: %m", opts->user);
     203  		return PAM_SYSTEM_ERR;
     204  	}
     205  
     206  	if (read_tally(tfd, tallies) != 0) {
     207  		pam_syslog(pamh, LOG_ERR, "Error reading the tally file for %s: %m", opts->user);
     208  		return PAM_SYSTEM_ERR;
     209  	}
     210  
     211  	if (opts->is_admin && !(opts->flags & FAILLOCK_FLAG_DENY_ROOT)) {
     212  		return PAM_SUCCESS;
     213  	}
     214  
     215  	latest_time = 0;
     216  	for (i = 0; i < tallies->count; i++) {
     217  		if ((tallies->records[i].status & TALLY_STATUS_VALID) &&
     218  			tallies->records[i].time > latest_time)
     219  			latest_time = tallies->records[i].time;
     220  	}
     221  
     222  	opts->latest_time = latest_time;
     223  
     224  	failures = 0;
     225  	for (i = 0; i < tallies->count; i++) {
     226  		if ((tallies->records[i].status & TALLY_STATUS_VALID) &&
     227  			latest_time - tallies->records[i].time < opts->fail_interval) {
     228  			++failures;
     229  		}
     230  	}
     231  
     232  	opts->failures = failures;
     233  
     234  	if (opts->deny && failures >= opts->deny) {
     235  		if ((!opts->is_admin && opts->unlock_time && latest_time + opts->unlock_time < opts->now) ||
     236  			(opts->is_admin && opts->root_unlock_time && latest_time + opts->root_unlock_time < opts->now)) {
     237  #ifdef HAVE_LIBAUDIT
     238  			if (opts->action != FAILLOCK_ACTION_PREAUTH) { /* do not audit in preauth */
     239  				char buf[64];
     240  				int audit_fd;
     241  				const void *rhost = NULL, *tty = NULL;
     242  
     243  				audit_fd = audit_open();
     244  				/* If there is an error & audit support is in the kernel report error */
     245  				if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
     246  					errno == EAFNOSUPPORT))
     247  					return PAM_SYSTEM_ERR;
     248  
     249  				(void)pam_get_item(pamh, PAM_TTY, &tty);
     250  				(void)pam_get_item(pamh, PAM_RHOST, &rhost);
     251  				snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid);
     252  				audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_UNLOCK_TIMED, buf,
     253  					rhost, NULL, tty, 1);
     254  			}
     255  #endif
     256  			opts->flags |= FAILLOCK_FLAG_UNLOCKED;
     257  			return PAM_SUCCESS;
     258  		}
     259  		return PAM_AUTH_ERR;
     260  	}
     261  	return PAM_SUCCESS;
     262  }
     263  
     264  static void
     265  reset_tally(pam_handle_t *pamh, struct options *opts, int *fd)
     266  {
     267  	int rv;
     268  	const char *dir = get_tally_dir(opts);
     269  
     270  	if (*fd == -1) {
     271  		*fd = open_tally(dir, opts->user, opts->uid, 1);
     272  	}
     273  	else {
     274  		while ((rv=ftruncate(*fd, 0)) == -1 && errno == EINTR);
     275  		if (rv == -1) {
     276  			pam_syslog(pamh, LOG_ERR, "Error clearing the tally file for %s: %m", opts->user);
     277  		}
     278  	}
     279  }
     280  
     281  static int
     282  write_tally(pam_handle_t *pamh, struct options *opts, struct tally_data *tallies, int *fd)
     283  {
     284  	struct tally *records;
     285  	unsigned int i;
     286  	int failures;
     287  	unsigned int oldest;
     288  	uint64_t oldtime;
     289  	const void *source = NULL;
     290  	const char *dir = get_tally_dir(opts);
     291  
     292  	if (*fd == -1) {
     293  		*fd = open_tally(dir, opts->user, opts->uid, 1);
     294  	}
     295  	if (*fd == -1) {
     296  		if (errno == EACCES) {
     297  			return PAM_SUCCESS;
     298  		}
     299  		pam_syslog(pamh, LOG_ERR, "Error opening the tally file for %s: %m", opts->user);
     300  		return PAM_SYSTEM_ERR;
     301  	}
     302  
     303  	oldtime = 0;
     304  	oldest = 0;
     305  	failures = 0;
     306  
     307  	for (i = 0; i < tallies->count; ++i) {
     308  		if (oldtime == 0 || tallies->records[i].time < oldtime) {
     309  			oldtime = tallies->records[i].time;
     310  			oldest = i;
     311  		}
     312  		if (opts->flags & FAILLOCK_FLAG_UNLOCKED ||
     313  			opts->now - tallies->records[i].time >= opts->fail_interval ) {
     314  			tallies->records[i].status &= ~TALLY_STATUS_VALID;
     315  		} else {
     316  			++failures;
     317  		}
     318  	}
     319  
     320  	if (oldest >= tallies->count || (tallies->records[oldest].status & TALLY_STATUS_VALID)) {
     321  		oldest = tallies->count;
     322  
     323  		if ((records=realloc(tallies->records, (oldest+1) * sizeof (*tallies->records))) == NULL) {
     324  			pam_syslog(pamh, LOG_CRIT, "Error allocating memory for tally records: %m");
     325  			return PAM_BUF_ERR;
     326  		}
     327  
     328  		++tallies->count;
     329  		tallies->records = records;
     330  	}
     331  
     332  	memset(&tallies->records[oldest], 0, sizeof (*tallies->records));
     333  
     334  	tallies->records[oldest].status = TALLY_STATUS_VALID;
     335  	if (pam_get_item(pamh, PAM_RHOST, &source) != PAM_SUCCESS || source == NULL) {
     336  		if (pam_get_item(pamh, PAM_TTY, &source) != PAM_SUCCESS || source == NULL) {
     337  			if (pam_get_item(pamh, PAM_SERVICE, &source) != PAM_SUCCESS || source == NULL) {
     338  				source = "";
     339  			}
     340  		}
     341  		else {
     342  			tallies->records[oldest].status |= TALLY_STATUS_TTY;
     343  		}
     344  	}
     345  	else {
     346  		tallies->records[oldest].status |= TALLY_STATUS_RHOST;
     347  	}
     348  
     349  	strncpy(tallies->records[oldest].source, source, sizeof(tallies->records[oldest].source));
     350  	/* source does not have to be null terminated */
     351  
     352  	tallies->records[oldest].time = opts->now;
     353  
     354  	++failures;
     355  
     356  	if (opts->deny && failures == opts->deny) {
     357  #ifdef HAVE_LIBAUDIT
     358  		char buf[64];
     359  		int audit_fd;
     360  
     361  		audit_fd = audit_open();
     362  		/* If there is an error & audit support is in the kernel report error */
     363  		if ((audit_fd < 0) && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
     364  			errno == EAFNOSUPPORT))
     365  			return PAM_SYSTEM_ERR;
     366  
     367  		snprintf(buf, sizeof(buf), "pam_faillock uid=%u ", opts->uid);
     368  		audit_log_user_message(audit_fd, AUDIT_ANOM_LOGIN_FAILURES, buf,
     369  			NULL, NULL, NULL, 1);
     370  
     371  		if (!opts->is_admin || (opts->flags & FAILLOCK_FLAG_DENY_ROOT)) {
     372  			audit_log_user_message(audit_fd, AUDIT_RESP_ACCT_LOCK, buf,
     373  				NULL, NULL, NULL, 1);
     374  		}
     375  		close(audit_fd);
     376  #endif
     377  		if (!(opts->flags & FAILLOCK_FLAG_NO_LOG_INFO) &&
     378  		    ((opts->flags & FAILLOCK_FLAG_DENY_ROOT) || (opts->uid != 0))) {
     379  			pam_syslog(pamh, LOG_INFO,
     380  				   "Consecutive login failures for user %s account temporarily locked",
     381  				   opts->user);
     382  		}
     383  	}
     384  
     385  	if (update_tally(*fd, tallies) == 0)
     386  		return PAM_SUCCESS;
     387  
     388  	return PAM_SYSTEM_ERR;
     389  }
     390  
     391  static void
     392  faillock_message(pam_handle_t *pamh, struct options *opts)
     393  {
     394  	int64_t left;
     395  
     396  	if (!(opts->flags & FAILLOCK_FLAG_SILENT)) {
     397  		if (opts->is_admin) {
     398  			left = opts->latest_time + opts->root_unlock_time - opts->now;
     399  		}
     400  		else {
     401  			left = opts->latest_time + opts->unlock_time - opts->now;
     402  		}
     403  
     404  		pam_info(pamh, _("The account is locked due to %u failed logins."),
     405  			(unsigned int)opts->failures);
     406  		if (left > 0) {
     407  			left = (left + 59)/60; /* minutes */
     408  
     409  #if defined HAVE_DNGETTEXT && defined ENABLE_NLS
     410  			pam_info(
     411  				pamh,
     412  				dngettext(PACKAGE,
     413  					"(%d minute left to unlock)",
     414  					"(%d minutes left to unlock)",
     415  					(int)left),
     416  				(int)left);
     417  #else
     418  			if (left == 1)
     419  				pam_info(pamh, _("(%d minute left to unlock)"), (int)left);
     420  			else
     421  				/* TRANSLATORS: only used if dngettext is not supported. */
     422  				pam_info(pamh, _("(%d minutes left to unlock)"), (int)left);
     423  #endif
     424  		}
     425  	}
     426  }
     427  
     428  static void
     429  tally_cleanup(struct tally_data *tallies, int fd)
     430  {
     431  	if (fd != -1) {
     432  		close(fd);
     433  	}
     434  
     435  	free(tallies->records);
     436  }
     437  
     438  static void
     439  opts_cleanup(struct options *opts)
     440  {
     441  	free(opts->dir);
     442  	free(opts->admin_group);
     443  }
     444  
     445  /*---------------------------------------------------------------------*/
     446  
     447  int
     448  pam_sm_authenticate(pam_handle_t *pamh, int flags,
     449  		    int argc, const char **argv)
     450  {
     451  	struct options opts;
     452  	int rv, fd = -1;
     453  	struct tally_data tallies;
     454  
     455  	memset(&tallies, 0, sizeof(tallies));
     456  
     457  	rv = args_parse(pamh, argc, argv, flags, &opts);
     458  	if (rv != PAM_SUCCESS)
     459  		goto err;
     460  
     461  	if (!(opts.flags & FAILLOCK_FLAG_NO_DELAY)) {
     462  		pam_fail_delay(pamh, 2000000);	/* 2 sec delay on failure */
     463  	}
     464  
     465  	if ((rv=get_pam_user(pamh, &opts)) != PAM_SUCCESS) {
     466  		goto err;
     467  	}
     468  
     469  	if (!(opts.flags & FAILLOCK_FLAG_LOCAL_ONLY) ||
     470  		check_local_user (pamh, opts.user) != 0) {
     471  		switch (opts.action) {
     472  			case FAILLOCK_ACTION_PREAUTH:
     473  				rv = check_tally(pamh, &opts, &tallies, &fd);
     474  				if (rv == PAM_AUTH_ERR && !(opts.flags & FAILLOCK_FLAG_SILENT)) {
     475  					faillock_message(pamh, &opts);
     476  				}
     477  				break;
     478  
     479  			case FAILLOCK_ACTION_AUTHSUCC:
     480  				rv = check_tally(pamh, &opts, &tallies, &fd);
     481  				if (rv == PAM_SUCCESS) {
     482  					reset_tally(pamh, &opts, &fd);
     483  				}
     484  				break;
     485  
     486  			case FAILLOCK_ACTION_AUTHFAIL:
     487  				rv = check_tally(pamh, &opts, &tallies, &fd);
     488  				if (rv == PAM_SUCCESS) {
     489  					rv = PAM_IGNORE; /* this return value should be ignored */
     490  					write_tally(pamh, &opts, &tallies, &fd);
     491  				}
     492  				break;
     493  		}
     494  	}
     495  
     496  	tally_cleanup(&tallies, fd);
     497  
     498  err:
     499  	opts_cleanup(&opts);
     500  
     501  	return rv;
     502  }
     503  
     504  /*---------------------------------------------------------------------*/
     505  
     506  int
     507  pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
     508  	       int argc UNUSED, const char **argv UNUSED)
     509  {
     510  	return PAM_SUCCESS;
     511  }
     512  
     513  /*---------------------------------------------------------------------*/
     514  
     515  int
     516  pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
     517  		 int argc, const char **argv)
     518  {
     519  	struct options opts;
     520  	int rv, fd = -1;
     521  	struct tally_data tallies;
     522  
     523  	memset(&tallies, 0, sizeof(tallies));
     524  
     525  	rv = args_parse(pamh, argc, argv, flags, &opts);
     526  
     527  	if (rv != PAM_SUCCESS)
     528  		goto err;
     529  
     530  	opts.action = FAILLOCK_ACTION_AUTHSUCC;
     531  
     532  	if ((rv=get_pam_user(pamh, &opts)) != PAM_SUCCESS) {
     533  		goto err;
     534  	}
     535  
     536  	if (!(opts.flags & FAILLOCK_FLAG_LOCAL_ONLY) ||
     537  		check_local_user (pamh, opts.user) != 0) {
     538  		check_tally(pamh, &opts, &tallies, &fd); /* for auditing */
     539  		reset_tally(pamh, &opts, &fd);
     540  	}
     541  
     542  	tally_cleanup(&tallies, fd);
     543  
     544  err:
     545  	opts_cleanup(&opts);
     546  
     547  	return rv;
     548  }
     549  
     550  /*-----------------------------------------------------------------------*/