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