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