1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 /*
12  * Copyright 2014 Nexenta Systems, Inc.
13  */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <strings.h>
18 #include <fcntl.h>
19 #include <security/pam_appl.h>
20 #include <security/pam_modules.h>
21 #include <security/pam_impl.h>
22 #include <sys/param.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <syslog.h>
26 #include <unistd.h>
27 #include <libgen.h>
28 #include <errno.h>
29 
30 #define	TIMESTAMP_DIR		"/var/run/tty_timestamps"
31 #define	TIMESTAMP_TIMEOUT	5 /* default timeout */
32 #define	ROOT_UID		0 /* root uid */
33 #define	ROOT_GID		0 /* root gid */
34 
35 struct user_info {
36 	dev_t dev;		/* ID of device tty resides on */
37 	dev_t rdev;		/* tty device ID */
38 	ino_t ino;		/* tty inode number */
39 	uid_t uid;		/* user's uid */
40 	pid_t ppid;		/* parent pid */
41 	pid_t sid;		/* session ID associated with tty/ppid */
42 	timestruc_t ts;		/* time of tty last status change */
43 };
44 
45 int debug = 0;
46 
47 int
validate_basic(pam_handle_t * pamh,char * user_tty,char * timestampfile)48 validate_basic(
49 	pam_handle_t		*pamh,
50 	char			*user_tty,
51 	char 			*timestampfile)
52 {
53 	char			*user;
54 	char			*auser;
55 	char			*ttyn;
56 
57 	/* get user, auser and users's tty */
58 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
59 	(void) pam_get_item(pamh, PAM_AUSER, (void **)&auser);
60 	(void) pam_get_item(pamh, PAM_TTY, (void **)&ttyn);
61 
62 	if (user == NULL || *user == '\0') {
63 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
64 		"PAM_USER NULL or empty");
65 		return (PAM_IGNORE);
66 	}
67 
68 	if (auser == NULL || *auser == '\0') {
69 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
70 		"PAM_AUSER NULL or empty");
71 		return (PAM_IGNORE);
72 	}
73 
74 	if (ttyn == NULL || *ttyn == '\0') {
75 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
76 		"PAM_TTY NULL or empty");
77 		return (PAM_IGNORE);
78 	}
79 
80 	if (debug)
81 		syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
82 		"user = %s, auser = %s, tty = %s", user, auser, ttyn);
83 
84 	(void) strlcpy(user_tty, ttyn, MAXPATHLEN);
85 
86 	if (strchr(ttyn, '/') == NULL || strncmp(ttyn, "/dev/", 5) == 0) {
87 		ttyn = strrchr(ttyn, '/') + 1;
88 	} else {
89 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
90 		"invalid tty: %s", ttyn);
91 		return (PAM_IGNORE);
92 	}
93 
94 	/* format timestamp file name */
95 	(void) snprintf(timestampfile, MAXPATHLEN, "%s/%s/%s:%s", TIMESTAMP_DIR,
96 	    auser, ttyn, user);
97 
98 	return (PAM_SUCCESS);
99 }
100 
101 int
validate_dir(const char * dir)102 validate_dir(const char *dir)
103 {
104 	struct		stat sb;
105 
106 	/*
107 	 * check that the directory exist and has
108 	 * right owner and permissions.
109 	 */
110 	if (lstat(dir, &sb) < 0) {
111 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
112 		    "directory %s does not exist", dir);
113 		return (PAM_IGNORE);
114 	}
115 
116 	if (!S_ISDIR(sb.st_mode)) {
117 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
118 		    "%s is not a directory", dir);
119 		return (PAM_IGNORE);
120 	}
121 
122 	if (S_ISLNK(sb.st_mode)) {
123 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
124 		    "%s is a symbolic link", dir);
125 		return (PAM_IGNORE);
126 	}
127 
128 	if (sb.st_uid != 0 || sb.st_gid != 0) {
129 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
130 		    "%s is not owned by root", dir);
131 		return (PAM_IGNORE);
132 	}
133 
134 	if (sb.st_mode & (S_IWGRP | S_IWOTH | S_IROTH)) {
135 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
136 		    "%s has wrong permissions", dir);
137 		return (PAM_IGNORE);
138 	}
139 
140 	return (PAM_SUCCESS);
141 }
142 
143 int
create_dir(char * dir)144 create_dir(char *dir)
145 {
146 	/*
147 	 * create directory if it doesn't exist and attempt to set
148 	 * the owner to root.
149 	 */
150 	if (mkdir(dir, S_IRWXU) < 0) {
151 		if (errno != EEXIST) {
152 			syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
153 			    "can't create directory %s", dir);
154 			return (PAM_IGNORE);
155 		}
156 	} else if (lchown(dir, ROOT_UID, ROOT_GID) < 0) {
157 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
158 		    "can't set permissions on directory %s", dir);
159 		return (PAM_IGNORE);
160 	}
161 	return (PAM_SUCCESS);
162 }
163 
164 /*
165  * pam_sm_authenticate
166  *
167  * Read authentication from user, using cached successful authentication
168  * attempts.
169  *
170  * returns PAM_SUCCESS on success, otherwise always returns PAM_IGNORE:
171  * while this module has "sufficient" control value, in case of any failure
172  * user will be authenticated with the pam_unix_auth module.
173  * options -
174  *	debug
175  *	timeout=	timeout in min, default is 5
176  */
177 /*ARGSUSED*/
178 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)179 pam_sm_authenticate(
180 	pam_handle_t		*pamh,
181 	int 			flags,
182 	int			argc,
183 	const char		**argv)
184 {
185 	struct			user_info info;
186 	struct			stat sb, tty;
187 	time_t			timeout = 0;
188 	long			tmp = 0;
189 	int			result = PAM_IGNORE;
190 	int			i;
191 	int			fd = -1;
192 	char			*p;
193 	char			user_tty[MAXPATHLEN];
194 	char			timestampdir[MAXPATHLEN];
195 	char			timestampfile[MAXPATHLEN];
196 	char			*sudir;
197 
198 	timeout = TIMESTAMP_TIMEOUT;
199 
200 	/* check options passed to this module */
201 	for (i = 0; i < argc; i++) {
202 		if (strcmp(argv[i], "debug") == 0) {
203 			debug = 1;
204 		} else if (strncmp(argv[i], "timeout=", 8) == 0) {
205 			tmp = strtol(argv[i] + 8, &p, 0);
206 			if ((p != NULL) && (*p == '\0') && tmp > 0) {
207 				timeout = tmp;
208 			}
209 		}
210 	}
211 
212 	if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
213 		return (result);
214 
215 	sudir = TIMESTAMP_DIR;
216 	if (validate_dir(sudir) != PAM_SUCCESS)
217 		return (result);
218 
219 	(void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
220 
221 	if (validate_dir(dirname(timestampdir)) != PAM_SUCCESS)
222 		return (result);
223 
224 	/*
225 	 * check that timestamp file is exist and has right owner
226 	 * and permissions.
227 	 */
228 	if (lstat(timestampfile, &sb) == 0 && sb.st_size != 0) {
229 		if (!S_ISREG(sb.st_mode)) {
230 			(void) unlink(timestampfile);
231 			syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
232 			    "timestamp file %s is not a regular file",
233 			    timestampfile);
234 			return (result);
235 		}
236 
237 		if (sb.st_uid != 0 || sb.st_gid != 0) {
238 			(void) unlink(timestampfile);
239 			syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
240 			    "timestamp file %s is not owned by root",
241 			    timestampfile);
242 			return (result);
243 		}
244 
245 		if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
246 			(void) unlink(timestampfile);
247 			syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
248 			    "timestamp file %s is a symbolic link",
249 			    timestampfile);
250 			return (result);
251 		}
252 
253 		if (sb.st_mode & (S_IRWXG | S_IRWXO)) {
254 			(void) unlink(timestampfile);
255 			syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
256 			    "timestamp file %s has wrong permissions",
257 			    timestampfile);
258 			return (result);
259 		}
260 	} else {
261 		if (debug)
262 			syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
263 			    "timestamp file %s does not exist: %m",
264 			    timestampfile);
265 		return (result);
266 	}
267 
268 
269 	if (stat(user_tty, &tty) < 0) {
270 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
271 		    "can't stat tty: %m");
272 		return (result);
273 	}
274 
275 	if ((fd = open(timestampfile, O_RDONLY)) < 0) {
276 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
277 		    "can't open timestamp file %s for reading: %m",
278 		    timestampfile);
279 		return (result);
280 	}
281 
282 	if (read(fd, &info, sizeof (info)) != sizeof (info)) {
283 		(void) close(fd);
284 		(void) unlink(timestampfile);
285 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
286 		    "timestamp file '%s' is corrupt: %m", timestampfile);
287 		return (result);
288 	}
289 
290 	if (info.dev != tty.st_dev || info.ino != tty.st_ino ||
291 	    info.rdev != tty.st_rdev || info.sid != getsid(getpid()) ||
292 	    info.uid != getuid() || info.ts.tv_sec != tty.st_ctim.tv_sec ||
293 	    info.ts.tv_nsec != tty.st_ctim.tv_nsec) {
294 		(void) close(fd);
295 		(void) unlink(timestampfile);
296 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
297 		    "the content of the timestamp file '%s' is not valid",
298 		    timestampfile);
299 		return (result);
300 	}
301 
302 	if (time((time_t *)0) - sb.st_mtime > 60 * timeout) {
303 		(void) unlink(timestampfile);
304 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
305 		    "timestamp file '%s' has expired, disallowing access",
306 		    timestampfile);
307 		return (result);
308 	} else {
309 		if (debug)
310 			syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
311 			    "timestamp file %s is not expired, "
312 			    "allowing access ", timestampfile);
313 		result = PAM_SUCCESS;
314 	}
315 
316 	return (result);
317 }
318 
319 /*
320  * pam_sm_setcred
321  *
322  * Creates timestamp directory and writes
323  * timestamp file if it doesn't exist.
324  *
325  * returns PAM_SUCCESS on success, otherwise PAM_IGNORE
326  */
327 /*ARGSUSED*/
328 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)329 pam_sm_setcred(
330 	pam_handle_t		*pamh,
331 	int			flags,
332 	int			argc,
333 	const char		**argv)
334 {
335 	struct			stat sb;
336 	struct			stat tty;
337 	struct			user_info info;
338 	int			result = PAM_IGNORE;
339 	int			fd = -1;
340 	char			user_tty[MAXPATHLEN];
341 	char			timestampdir[MAXPATHLEN];
342 	char			timestampfile[MAXPATHLEN];
343 
344 	/* validate flags */
345 	if (flags && !(flags & PAM_ESTABLISH_CRED) &&
346 	    !(flags & PAM_REINITIALIZE_CRED) &&
347 	    !(flags & PAM_REFRESH_CRED) &&
348 	    !(flags & PAM_DELETE_CRED) &&
349 	    !(flags & PAM_SILENT)) {
350 		syslog(LOG_ERR, "pam_timestamp: illegal flag %d", flags);
351 		return (result);
352 	}
353 
354 	if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
355 		return (result);
356 
357 	/*
358 	 * user doesn't need to authenticate for PAM_DELETE_CRED
359 	 */
360 	if (flags & PAM_DELETE_CRED) {
361 		(void) unlink(timestampfile);
362 		return (result);
363 	}
364 
365 	/* if the timestamp file exist, there is nothing to do */
366 	if (lstat(timestampfile, &sb) == 0) {
367 		if (debug)
368 			syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
369 			    "timestamp file %s is not expired", timestampfile);
370 		return (result);
371 	}
372 
373 	if (create_dir(TIMESTAMP_DIR) != PAM_SUCCESS)
374 		return (result);
375 
376 	(void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
377 
378 	if (create_dir(dirname(timestampdir)) != PAM_SUCCESS)
379 		return (result);
380 
381 	if (stat(user_tty, &tty) < 0) {
382 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
383 		    "can't stat tty: %m");
384 		return (result);
385 	}
386 
387 	info.dev = tty.st_dev;
388 	info.ino = tty.st_ino;
389 	info.rdev = tty.st_rdev;
390 	info.sid = getsid(getpid());
391 	info.uid = getuid();
392 	info.ts = tty.st_ctim;
393 
394 	if ((fd = open(timestampfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
395 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
396 		    "can't open timestamp file %s for writing: %m",
397 		    timestampfile);
398 		return (result);
399 	} else if (fchown(fd, ROOT_UID, ROOT_GID) != 0) {
400 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
401 		    "can't set permissions on timestamp file %s: %m",
402 		    timestampfile);
403 		(void) close(fd);
404 		return (result);
405 	}
406 
407 	if (write(fd, &info, sizeof (info)) != sizeof (info)) {
408 		(void) close(fd);
409 		syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
410 		    "can't write timestamp file %s: %m", timestampfile);
411 		return (result);
412 	}
413 	(void) close(fd);
414 
415 	return (PAM_SUCCESS);
416 }
417