(root)/
Linux-PAM-1.5.3/
modules/
pam_sepermit/
pam_sepermit.c
       1  /******************************************************************************
       2   * A module for Linux-PAM that allows/denies access based on SELinux state.
       3   *
       4   * Copyright (c) 2007, 2008, 2009 Red Hat, Inc.
       5   * Originally written by Tomas Mraz <tmraz@redhat.com>
       6   * Contributions by Dan Walsh <dwalsh@redhat.com>
       7   *
       8   * Redistribution and use in source and binary forms, with or without
       9   * modification, are permitted provided that the following conditions
      10   * are met:
      11   * 1. Redistributions of source code must retain the above copyright
      12   *    notice, and the entire permission notice in its entirety,
      13   *    including the disclaimer of warranties.
      14   * 2. Redistributions in binary form must reproduce the above copyright
      15   *    notice, this list of conditions and the following disclaimer in the
      16   *    documentation and/or other materials provided with the distribution.
      17   * 3. The name of the author may not be used to endorse or promote
      18   *    products derived from this software without specific prior
      19   *    written permission.
      20   *
      21   * ALTERNATIVELY, this product may be distributed under the terms of
      22   * the GNU Public License, in which case the provisions of the GPL are
      23   * required INSTEAD OF the above restrictions.  (This clause is
      24   * necessary due to a potential bad interaction between the GPL and
      25   * the restrictions contained in a BSD-style copyright.)
      26   *
      27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
      28   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
      29   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      30   * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
      31   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
      32   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      33   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      34   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
      35   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
      36   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
      37   * OF THE POSSIBILITY OF SUCH DAMAGE.
      38   */
      39  
      40  #include "config.h"
      41  
      42  #include <errno.h>
      43  #include <pwd.h>
      44  #include <stdio.h>
      45  #include <stdlib.h>
      46  #include <string.h>
      47  #include <syslog.h>
      48  #include <ctype.h>
      49  #include <signal.h>
      50  #include <limits.h>
      51  #include <sys/types.h>
      52  #include <sys/stat.h>
      53  #include <fcntl.h>
      54  #include <unistd.h>
      55  #include <dirent.h>
      56  
      57  #include <security/pam_modules.h>
      58  #include <security/_pam_macros.h>
      59  #include <security/pam_modutil.h>
      60  #include <security/pam_ext.h>
      61  
      62  #include <selinux/selinux.h>
      63  
      64  #include "pam_inline.h"
      65  
      66  #define SEPERMIT_CONF_FILE	(SCONFIGDIR "/sepermit.conf")
      67  #ifdef VENDOR_SCONFIGDIR
      68  # define SEPERMIT_VENDOR_CONF_FILE	(VENDOR_SCONFIGDIR "/sepermit.conf");
      69  #endif
      70  #define MODULE "pam_sepermit"
      71  #define OPT_DELIM ":"
      72  
      73  struct lockfd {
      74  	uid_t uid;
      75  	int fd;
      76          int debug;
      77  };
      78  
      79  #define PROC_BASE "/proc"
      80  #define MAX_NAMES (int)(sizeof(unsigned long)*8)
      81  
      82  static int
      83  match_process_uid(pid_t pid, uid_t uid)
      84  {
      85  	char buf[128];
      86  	uid_t puid;
      87  	FILE *f;
      88  	int re = 0;
      89  
      90  	snprintf (buf, sizeof buf, PROC_BASE "/%d/status", pid);
      91  	if (!(f = fopen (buf, "r")))
      92  		return 0;
      93  
      94  	while (fgets(buf, sizeof buf, f)) {
      95  		if (sscanf (buf, "Uid:\t%d", &puid)) {
      96  			re = uid == puid;
      97  			break;
      98  		}
      99  	}
     100  	fclose(f);
     101  	return re;
     102  }
     103  
     104  static int
     105  check_running (pam_handle_t *pamh, uid_t uid, int killall, int debug)
     106  {
     107  	DIR *dir;
     108  	struct dirent *de;
     109  	pid_t *pid_table, pid, self;
     110  	int i;
     111  	int pids, max_pids;
     112  	int running = 0;
     113  	self = getpid();
     114  	if (!(dir = opendir(PROC_BASE))) {
     115  		pam_syslog(pamh, LOG_ERR, "Failed to open proc directory file %s:", PROC_BASE);
     116  		return -1;
     117  	}
     118  	max_pids = 256;
     119  	pid_table = malloc(max_pids * sizeof (pid_t));
     120  	if (!pid_table) {
     121  		(void)closedir(dir);
     122  		pam_syslog(pamh, LOG_CRIT, "Memory allocation error");
     123  		return -1;
     124  	}
     125  	pids = 0;
     126  	while ((de = readdir (dir)) != NULL) {
     127  		if (!(pid = (pid_t)atoi(de->d_name)) || pid == self)
     128  			continue;
     129  
     130  		if (pids == max_pids) {
     131  			pid_t *npt;
     132  
     133  			if (!(npt = realloc(pid_table, 2*pids*sizeof(pid_t)))) {
     134  				free(pid_table);
     135  				(void)closedir(dir);
     136  				pam_syslog(pamh, LOG_CRIT, "Memory allocation error");
     137  				return -1;
     138  			}
     139  			pid_table = npt;
     140  			max_pids *= 2;
     141  		}
     142  		pid_table[pids++] = pid;
     143  	}
     144  
     145  	(void)closedir(dir);
     146  
     147  	for (i = 0; i < pids; i++) {
     148  		pid_t id;
     149  
     150  		if (match_process_uid(pid_table[i], uid) == 0)
     151  			continue;
     152  		id = pid_table[i];
     153  
     154  		if (killall) {
     155  			if (debug)
     156  				pam_syslog(pamh, LOG_NOTICE, "Attempting to kill %d", id);
     157  			kill(id, SIGKILL);
     158  		}
     159  		running++;
     160  	}
     161  
     162  	free(pid_table);
     163  	return running;
     164  }
     165  
     166  /*
     167   * This function reads the loginuid from the /proc system. It returns
     168   * (uid_t)-1 on failure.
     169   */
     170  static uid_t get_loginuid(pam_handle_t *pamh)
     171  {
     172  	int fd, count;
     173  	char loginuid[24];
     174  	char *eptr;
     175  	uid_t rv = (uid_t)-1;
     176  
     177  	fd = open("/proc/self/loginuid", O_NOFOLLOW|O_RDONLY);
     178  	if (fd < 0) {
     179  		if (errno != ENOENT) {
     180  			pam_syslog(pamh, LOG_ERR,
     181  				   "Cannot open /proc/self/loginuid: %m");
     182  		}
     183  		return rv;
     184  	}
     185  	if ((count = pam_modutil_read(fd, loginuid, sizeof(loginuid)-1)) < 1) {
     186  		close(fd);
     187  		return rv;
     188  	}
     189  	loginuid[count] = '\0';
     190  	close(fd);
     191  
     192  	errno = 0;
     193  	rv = strtoul(loginuid, &eptr, 10);
     194  	if (errno != 0 || eptr == loginuid)
     195  		rv = (uid_t) -1;
     196  
     197  	return rv;
     198  }
     199  
     200  static void
     201  sepermit_unlock(pam_handle_t *pamh, void *plockfd, int error_status UNUSED)
     202  {
     203  	struct lockfd *lockfd = plockfd;
     204  	struct flock fl;
     205  
     206  	memset(&fl, 0, sizeof(fl));
     207  	fl.l_type = F_UNLCK;
     208  	fl.l_whence = SEEK_SET;
     209  
     210  	if (lockfd->debug)
     211  		pam_syslog(pamh, LOG_ERR, "Unlocking fd: %d uid: %d", lockfd->fd, lockfd->uid);
     212  
     213  	/* Don't kill uid==0 */
     214  	if (lockfd->uid)
     215  		/* This is a DOS but it prevents an app from forking to prevent killing */
     216  		while(check_running(pamh, lockfd->uid, 1, lockfd->debug) > 0)
     217  			continue;
     218  
     219  	(void)fcntl(lockfd->fd, F_SETLK, &fl);
     220  	(void)close(lockfd->fd);
     221  	free(lockfd);
     222  }
     223  
     224  static int
     225  sepermit_lock(pam_handle_t *pamh, const char *user, int debug)
     226  {
     227  	char buf[PATH_MAX];
     228  	struct flock fl;
     229  
     230  	memset(&fl, 0, sizeof(fl));
     231  	fl.l_type = F_WRLCK;
     232  	fl.l_whence = SEEK_SET;
     233  
     234  	struct passwd *pw = pam_modutil_getpwnam( pamh, user );
     235  	if (!pw) {
     236  		pam_syslog(pamh, LOG_NOTICE, "Unable to find uid for user %s",
     237  			   user);
     238  		return -1;
     239  	}
     240  	if (check_running(pamh, pw->pw_uid, 0, debug) > 0)  {
     241  		pam_syslog(pamh, LOG_ERR, "User %s processes are running. Exclusive login not allowed", user);
     242  		return -1;
     243  	}
     244  
     245  	snprintf(buf, sizeof(buf), "%s/%d.lock", SEPERMIT_LOCKDIR, pw->pw_uid);
     246  	int fd = open(buf, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
     247  	if (fd < 0) {
     248  		pam_syslog(pamh, LOG_ERR, "Unable to open lock file %s/%d.lock", SEPERMIT_LOCKDIR, pw->pw_uid);
     249  		return -1;
     250  	}
     251  
     252  	/* Need to close on exec */
     253  	fcntl(fd, F_SETFD, FD_CLOEXEC);
     254  
     255  	if (fcntl(fd, F_SETLK, &fl) == -1) {
     256  		pam_syslog(pamh, LOG_ERR, "User %s with exclusive login already logged in", user);
     257  		close(fd);
     258  		return -1;
     259  	}
     260  	struct lockfd *lockfd=calloc(1, sizeof(struct lockfd));
     261  	if (!lockfd) {
     262  		close(fd);
     263  		pam_syslog(pamh, LOG_CRIT, "Memory allocation error");
     264  		return -1;
     265  	}
     266  	lockfd->uid = pw->pw_uid;
     267  	lockfd->debug = debug;
     268  	lockfd->fd=fd;
     269          pam_set_data(pamh, MODULE, lockfd, sepermit_unlock);
     270  	return 0;
     271  }
     272  
     273  /* return 0 when matched, -1 when unmatched, pam error otherwise */
     274  static int
     275  sepermit_match(pam_handle_t *pamh, const char *cfgfile, const char *user,
     276  	       const char *seuser, int debug, int *sense)
     277  {
     278  	FILE *f;
     279  	char *line = NULL;
     280  	char *start;
     281  	size_t len = 0;
     282  	int matched = 0;
     283  	int exclusive = 0;
     284  	int ignore = 0;
     285  
     286  	f = fopen(cfgfile, "r");
     287  
     288  	if (!f) {
     289  		pam_syslog(pamh, LOG_ERR, "Failed to open config file %s: %m", cfgfile);
     290  		return PAM_SERVICE_ERR;
     291  	}
     292  
     293  	while (!matched && getline(&line, &len, f) != -1) {
     294  		size_t n;
     295  		char *sptr;
     296  		char *opt;
     297  
     298  		if (line[0] == '#')
     299  			continue;
     300  
     301  		start = line;
     302  		while (isspace(*start))
     303  			++start;
     304  		n = strlen(start);
     305  		while (n > 0 && isspace(start[n-1])) {
     306  			--n;
     307  		}
     308  		if (n == 0)
     309  			continue;
     310  
     311  		start[n] = '\0';
     312  		start = strtok_r(start, OPT_DELIM, &sptr);
     313  
     314  		switch (start[0]) {
     315  			case '@':
     316  				++start;
     317  				if (debug)
     318  					pam_syslog(pamh, LOG_NOTICE, "Matching user %s against group %s", user, start);
     319  				if (pam_modutil_user_in_group_nam_nam(pamh, user, start)) {
     320  					matched = 1;
     321  				}
     322  				break;
     323  			case '%':
     324  				if (seuser == NULL)
     325  					break;
     326  				++start;
     327  				if (debug)
     328  					pam_syslog(pamh, LOG_NOTICE, "Matching seuser %s against seuser %s", seuser, start);
     329  				if (strcmp(seuser, start) == 0) {
     330  					matched = 1;
     331  				}
     332  				break;
     333  			default:
     334  				if (debug)
     335  					pam_syslog(pamh, LOG_NOTICE, "Matching user %s against user %s", user, start);
     336  				if (strcmp(user, start) == 0) {
     337  					matched = 1;
     338  				}
     339  		}
     340  		if (matched)
     341  			while ((opt=strtok_r(NULL, OPT_DELIM, &sptr)) != NULL) {
     342  				if (strcmp(opt, "exclusive") == 0)
     343  					exclusive = 1;
     344  				else if (strcmp(opt, "ignore") == 0)
     345  					ignore = 1;
     346  				else if (debug) {
     347  					pam_syslog(pamh, LOG_NOTICE, "Unknown user option: %s", opt);
     348  				}
     349  			}
     350  	}
     351  
     352  	free(line);
     353  	fclose(f);
     354  	if (matched) {
     355  		if (*sense == PAM_SUCCESS) {
     356  			if (ignore)
     357  				*sense = PAM_IGNORE;
     358  			if (geteuid() == 0 && exclusive && get_loginuid(pamh) == (uid_t)-1)
     359  				if (sepermit_lock(pamh, user, debug) < 0)
     360  					*sense = PAM_AUTH_ERR;
     361  		}
     362  		return 0;
     363  	}
     364  	else
     365  		return -1;
     366  }
     367  
     368  int
     369  pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
     370  		    int argc, const char **argv)
     371  {
     372  	int i;
     373  	int rv;
     374  	int debug = 0;
     375  	int sense = PAM_AUTH_ERR;
     376  	const char *user = NULL;
     377  	char *seuser = NULL;
     378  	char *level = NULL;
     379  	const char *cfgfile = NULL;
     380  
     381  	/* Parse arguments. */
     382  	for (i = 0; i < argc; i++) {
     383  		const char *str;
     384  
     385  		if (strcmp(argv[i], "debug") == 0) {
     386  			debug = 1;
     387  		} else if ((str = pam_str_skip_prefix(argv[i], "conf=")) != NULL) {
     388  			cfgfile = str;
     389  		} else {
     390  			pam_syslog(pamh, LOG_ERR, "unknown option: %s", argv[i]);
     391  		}
     392  	}
     393  
     394  	if (cfgfile == NULL) {
     395  #ifdef SEPERMIT_VENDOR_CONF_FILE
     396  		struct stat buffer;
     397  
     398  		cfgfile = SEPERMIT_CONF_FILE;
     399  		if (stat(cfgfile, &buffer) != 0 && errno == ENOENT)
     400  			cfgfile = SEPERMIT_VENDOR_CONF_FILE;
     401  #else
     402  		cfgfile = SEPERMIT_CONF_FILE;
     403  #endif
     404  	}
     405  
     406  	if (debug)
     407  		pam_syslog(pamh, LOG_NOTICE, "Parsing config file: %s", cfgfile);
     408  
     409  	if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || *user == '\0') {
     410  		pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
     411  		return PAM_USER_UNKNOWN;
     412  	}
     413  
     414  	if (is_selinux_enabled() > 0) {
     415  		if (security_getenforce() == 1) {
     416  			if (debug)
     417  				pam_syslog(pamh, LOG_NOTICE, "Enforcing mode, access will be allowed on match");
     418  			sense = PAM_SUCCESS;
     419  		}
     420  	}
     421  
     422  	if (getseuserbyname(user, &seuser, &level) != 0) {
     423  		seuser = NULL;
     424  		level = NULL;
     425  		pam_syslog(pamh, LOG_ERR, "getseuserbyname failed: %m");
     426  	}
     427  
     428  	if (debug && sense != PAM_SUCCESS)
     429  		pam_syslog(pamh, LOG_NOTICE, "Access will not be allowed on match");
     430  
     431  	rv = sepermit_match(pamh, cfgfile, user, seuser, debug, &sense);
     432  
     433  	if (debug)
     434  		pam_syslog(pamh, LOG_NOTICE, "sepermit_match returned: %d", rv);
     435  
     436  	free(seuser);
     437  	free(level);
     438  
     439  	switch (rv) {
     440  		case -1:
     441  			return PAM_IGNORE;
     442  		case 0:
     443  			return sense;
     444  	}
     445  
     446  	return rv;
     447  }
     448  
     449  int
     450  pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
     451                  int argc UNUSED, const char **argv UNUSED)
     452  {
     453  	return PAM_IGNORE;
     454  }
     455  
     456  int
     457  pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
     458  		     int argc, const char **argv)
     459  {
     460  	return pam_sm_authenticate(pamh, flags, argc, argv);
     461  }