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 }