/****************************************************************************** * A module for Linux-PAM that allows/denies acces based on SELinux state. * * Copyright (c) 2007, 2008, 2009 Red Hat, Inc. * Originally written by Tomas Mraz * Contributions by Dan Walsh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAM_SM_AUTH #define PAM_SM_ACCOUNT #include #include #include #include #include #define MODULE "pam_sepermit" #define OPT_DELIM ":" struct lockfd { uid_t uid; int fd; int debug; }; #define PROC_BASE "/proc" #define MAX_NAMES (int)(sizeof(unsigned long)*8) static int match_process_uid(pid_t pid, uid_t uid) { char buf[128]; uid_t puid; FILE *f; int re = 0; snprintf (buf, sizeof buf, PROC_BASE "/%d/status", pid); if (!(f = fopen (buf, "r"))) return 0; while (fgets(buf, sizeof buf, f)) { if (sscanf (buf, "Uid:\t%d", &puid)) { re = uid == puid; break; } } fclose(f); return re; } static int check_running (pam_handle_t *pamh, uid_t uid, int killall, int debug) { DIR *dir; struct dirent *de; pid_t *pid_table, pid, self; int i; int pids, max_pids; int running = 0; self = getpid(); if (!(dir = opendir(PROC_BASE))) { pam_syslog(pamh, LOG_ERR, "Failed to open proc directory file %s:", PROC_BASE); return -1; } max_pids = 256; pid_table = malloc(max_pids * sizeof (pid_t)); if (!pid_table) { (void)closedir(dir); pam_syslog(pamh, LOG_CRIT, "Memory allocation error"); return -1; } pids = 0; while ((de = readdir (dir)) != NULL) { if (!(pid = (pid_t)atoi(de->d_name)) || pid == self) continue; if (pids == max_pids) { pid_t *npt; if (!(npt = realloc(pid_table, 2*pids*sizeof(pid_t)))) { free(pid_table); (void)closedir(dir); pam_syslog(pamh, LOG_CRIT, "Memory allocation error"); return -1; } pid_table = npt; max_pids *= 2; } pid_table[pids++] = pid; } (void)closedir(dir); for (i = 0; i < pids; i++) { pid_t id; if (match_process_uid(pid_table[i], uid) == 0) continue; id = pid_table[i]; if (killall) { if (debug) pam_syslog(pamh, LOG_NOTICE, "Attempting to kill %d", id); kill(id, SIGKILL); } running++; } free(pid_table); return running; } /* * This function reads the loginuid from the /proc system. It returns * (uid_t)-1 on failure. */ static uid_t get_loginuid(pam_handle_t *pamh) { int fd, count; char loginuid[24]; char *eptr; uid_t rv = (uid_t)-1; fd = open("/proc/self/loginuid", O_NOFOLLOW|O_RDONLY); if (fd < 0) { if (errno != ENOENT) { pam_syslog(pamh, LOG_ERR, "Cannot open /proc/self/loginuid: %m"); } return rv; } if ((count = pam_modutil_read(fd, loginuid, sizeof(loginuid)-1)) < 1) { close(fd); return rv; } loginuid[count] = '\0'; close(fd); errno = 0; rv = strtoul(loginuid, &eptr, 10); if (errno != 0 || eptr == loginuid) rv = (uid_t) -1; return rv; } static void sepermit_unlock(pam_handle_t *pamh, void *plockfd, int error_status UNUSED) { struct lockfd *lockfd = plockfd; struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; if (lockfd->debug) pam_syslog(pamh, LOG_ERR, "Unlocking fd: %d uid: %d", lockfd->fd, lockfd->uid); /* Don't kill uid==0 */ if (lockfd->uid) /* This is a DOS but it prevents an app from forking to prevent killing */ while(check_running(pamh, lockfd->uid, 1, lockfd->debug) > 0) continue; (void)fcntl(lockfd->fd, F_SETLK, &fl); (void)close(lockfd->fd); free(lockfd); } static int sepermit_lock(pam_handle_t *pamh, const char *user, int debug) { char buf[PATH_MAX]; struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; struct passwd *pw = pam_modutil_getpwnam( pamh, user ); if (!pw) { pam_syslog(pamh, LOG_ERR, "Unable to find uid for user %s", user); return -1; } if (check_running(pamh, pw->pw_uid, 0, debug) > 0) { pam_syslog(pamh, LOG_ERR, "User %s processes are running. Exclusive login not allowed", user); return -1; } snprintf(buf, sizeof(buf), "%s/%d.lock", SEPERMIT_LOCKDIR, pw->pw_uid); int fd = open(buf, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd < 0) { pam_syslog(pamh, LOG_ERR, "Unable to open lock file %s/%d.lock", SEPERMIT_LOCKDIR, pw->pw_uid); return -1; } /* Need to close on exec */ fcntl(fd, F_SETFD, FD_CLOEXEC); if (fcntl(fd, F_SETLK, &fl) == -1) { pam_syslog(pamh, LOG_ERR, "User %s with exclusive login already logged in", user); close(fd); return -1; } struct lockfd *lockfd=calloc(1, sizeof(struct lockfd)); if (!lockfd) { close(fd); pam_syslog(pamh, LOG_CRIT, "Memory allocation error"); return -1; } lockfd->uid = pw->pw_uid; lockfd->debug = debug; lockfd->fd=fd; pam_set_data(pamh, MODULE, lockfd, sepermit_unlock); return 0; } /* return 0 when matched, -1 when unmatched, pam error otherwise */ static int sepermit_match(pam_handle_t *pamh, const char *cfgfile, const char *user, const char *seuser, int debug, int *sense) { FILE *f; char *line = NULL; char *start; size_t len = 0; int matched = 0; int exclusive = 0; int ignore = 0; f = fopen(cfgfile, "r"); if (!f) { pam_syslog(pamh, LOG_ERR, "Failed to open config file %s: %m", cfgfile); return PAM_SERVICE_ERR; } while (!matched && getline(&line, &len, f) != -1) { size_t n; char *sptr; char *opt; if (line[0] == '#') continue; start = line; while (isspace(*start)) ++start; n = strlen(start); while (n > 0 && isspace(start[n-1])) { --n; } if (n == 0) continue; start[n] = '\0'; start = strtok_r(start, OPT_DELIM, &sptr); switch (start[0]) { case '@': ++start; if (debug) pam_syslog(pamh, LOG_NOTICE, "Matching user %s against group %s", user, start); if (pam_modutil_user_in_group_nam_nam(pamh, user, start)) { matched = 1; } break; case '%': if (seuser == NULL) break; ++start; if (debug) pam_syslog(pamh, LOG_NOTICE, "Matching seuser %s against seuser %s", seuser, start); if (strcmp(seuser, start) == 0) { matched = 1; } break; default: if (debug) pam_syslog(pamh, LOG_NOTICE, "Matching user %s against user %s", user, start); if (strcmp(user, start) == 0) { matched = 1; } } if (matched) while ((opt=strtok_r(NULL, OPT_DELIM, &sptr)) != NULL) { if (strcmp(opt, "exclusive") == 0) exclusive = 1; else if (strcmp(opt, "ignore") == 0) ignore = 1; else if (debug) { pam_syslog(pamh, LOG_NOTICE, "Unknown user option: %s", opt); } } } free(line); fclose(f); if (matched) { if (*sense == PAM_SUCCESS) { if (ignore) *sense = PAM_IGNORE; if (geteuid() == 0 && exclusive && get_loginuid(pamh) == -1) if (sepermit_lock(pamh, user, debug) < 0) *sense = PAM_AUTH_ERR; } return 0; } else return -1; } int pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { int i; int rv; int debug = 0; int sense = PAM_AUTH_ERR; const char *user = NULL; char *seuser = NULL; char *level = NULL; const char *cfgfile = SEPERMIT_CONF_FILE; /* Parse arguments. */ for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) { debug = 1; } if (strcmp(argv[i], "conf=") == 0) { cfgfile = argv[i] + 5; } } if (debug) pam_syslog(pamh, LOG_NOTICE, "Parsing config file: %s", cfgfile); if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL || *user == '\0') { pam_syslog(pamh, LOG_ERR, "Cannot determine the user's name"); return PAM_USER_UNKNOWN; } if (is_selinux_enabled() > 0) { if (security_getenforce() == 1) { if (debug) pam_syslog(pamh, LOG_NOTICE, "Enforcing mode, access will be allowed on match"); sense = PAM_SUCCESS; } } if (getseuserbyname(user, &seuser, &level) != 0) { seuser = NULL; level = NULL; pam_syslog(pamh, LOG_ERR, "getseuserbyname failed: %m"); } if (debug && sense != PAM_SUCCESS) pam_syslog(pamh, LOG_NOTICE, "Access will not be allowed on match"); rv = sepermit_match(pamh, cfgfile, user, seuser, debug, &sense); if (debug) pam_syslog(pamh, LOG_NOTICE, "sepermit_match returned: %d", rv); free(seuser); free(level); switch (rv) { case -1: return PAM_IGNORE; case 0: return sense; } return rv; } int pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { return PAM_IGNORE; } int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { return pam_sm_authenticate(pamh, flags, argc, argv); }