1 /*
2 * pam_filter module
3 *
4 * written by Andrew Morgan <morgan@transmeta.com> with much help from
5 * Richard Stevens' UNIX Network Programming book.
6 */
7
8 #include "config.h"
9
10 #include <stdlib.h>
11 #include <syslog.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <string.h>
15
16 #include <stdio.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <sys/time.h>
20 #include <sys/file.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <sys/ioctl.h>
24 #include <termios.h>
25
26 #include <signal.h>
27
28 #include <security/pam_modules.h>
29 #include <security/pam_ext.h>
30 #include "pam_filter.h"
31
32 /* ------ some tokens used for convenience throughout this file ------- */
33
34 #define FILTER_DEBUG 01
35 #define FILTER_RUN1 02
36 #define FILTER_RUN2 04
37 #define NEW_TERM 010
38 #define NON_TERM 020
39
40 /* -------------------------------------------------------------------- */
41
42 /* log errors */
43
44 #include <stdarg.h>
45
46 #define DEV_PTMX "/dev/ptmx"
47
48 static int
49 master (void)
50 {
51 int fd;
52
53 if ((fd = open(DEV_PTMX, O_RDWR)) >= 0) {
54 return fd;
55 }
56
57 return -1;
58 }
59
60 static int process_args(pam_handle_t *pamh
61 , int argc, const char **argv, const char *type
62 , char ***evp, const char **filtername)
63 {
64 int ctrl=0;
65
66 while (argc-- > 0) {
67 if (strcmp("debug",*argv) == 0) {
68 ctrl |= FILTER_DEBUG;
69 } else if (strcmp("new_term",*argv) == 0) {
70 ctrl |= NEW_TERM;
71 } else if (strcmp("non_term",*argv) == 0) {
72 ctrl |= NON_TERM;
73 } else if (strcmp("run1",*argv) == 0) {
74 ctrl |= FILTER_RUN1;
75 if (argc <= 0) {
76 pam_syslog(pamh, LOG_ERR, "no run filter supplied");
77 } else
78 break;
79 } else if (strcmp("run2",*argv) == 0) {
80 ctrl |= FILTER_RUN2;
81 if (argc <= 0) {
82 pam_syslog(pamh, LOG_ERR, "no run filter supplied");
83 } else
84 break;
85 } else {
86 pam_syslog(pamh, LOG_ERR, "unrecognized option: %s", *argv);
87 }
88 ++argv; /* step along list */
89 }
90
91 if (argc < 0) {
92 /* there was no reference to a filter */
93 *filtername = NULL;
94 *evp = NULL;
95 } else {
96 char **levp;
97 const char *user = NULL;
98 const void *tmp;
99 int i,size, retval;
100
101 *filtername = *++argv;
102 if (ctrl & FILTER_DEBUG) {
103 pam_syslog(pamh, LOG_DEBUG, "will run filter %s", *filtername);
104 }
105
106 levp = (char **) malloc(5*sizeof(char *));
107 if (levp == NULL) {
108 pam_syslog(pamh, LOG_CRIT, "no memory for environment of filter");
109 return -1;
110 }
111
112 /* the "ARGS" variable */
113
114 #define ARGS_NAME "ARGS="
115 #define ARGS_OFFSET (sizeof(ARGS_NAME) - 1)
116
117 size = sizeof(ARGS_NAME);
118
119 for (i=0; i<argc; ++i) {
120 size += strlen(argv[i]) + (i != 0);
121 }
122
123 levp[0] = malloc(size);
124 if (levp[0] == NULL) {
125 pam_syslog(pamh, LOG_CRIT, "no memory for filter arguments");
126 free(levp);
127 return -1;
128 }
129
130 strcpy(levp[0], ARGS_NAME);
131 size = ARGS_OFFSET;
132 for (i=0; i<argc; ++i) {
133 if (i)
134 levp[0][size++] = ' ';
135 strcpy(levp[0]+size, argv[i]);
136 size += strlen(argv[i]);
137 }
138
139 /* the "SERVICE" variable */
140
141 #define SERVICE_NAME "SERVICE="
142 #define SERVICE_OFFSET (sizeof(SERVICE_NAME) - 1)
143
144 retval = pam_get_item(pamh, PAM_SERVICE, &tmp);
145 if (retval != PAM_SUCCESS || tmp == NULL) {
146 pam_syslog(pamh, LOG_CRIT, "service name not found");
147 if (levp) {
148 free(levp[0]);
149 free(levp);
150 }
151 return -1;
152 }
153 size = SERVICE_OFFSET+strlen(tmp);
154
155 levp[1] = (char *) malloc(size+1);
156 if (levp[1] == NULL) {
157 pam_syslog(pamh, LOG_CRIT, "no memory for service name");
158 if (levp) {
159 free(levp[0]);
160 free(levp);
161 }
162 return -1;
163 }
164
165 strcpy(levp[1], SERVICE_NAME);
166 strcpy(levp[1]+SERVICE_OFFSET, tmp);
167 levp[1][size] = '\0'; /* <NUL> terminate */
168
169 /* the "USER" variable */
170
171 #define USER_NAME "USER="
172 #define USER_OFFSET (sizeof(USER_NAME) - 1)
173
174 if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
175 user = "<unknown>";
176 }
177 size = USER_OFFSET+strlen(user);
178
179 levp[2] = (char *) malloc(size+1);
180 if (levp[2] == NULL) {
181 pam_syslog(pamh, LOG_CRIT, "no memory for user's name");
182 if (levp) {
183 free(levp[1]);
184 free(levp[0]);
185 free(levp);
186 }
187 return -1;
188 }
189
190 strcpy(levp[2], USER_NAME);
191 strcpy(levp[2]+USER_OFFSET, user);
192 levp[2][size] = '\0'; /* <NUL> terminate */
193
194 /* the "USER" variable */
195
196 #define TYPE_NAME "TYPE="
197 #define TYPE_OFFSET (sizeof(TYPE_NAME) - 1)
198
199 size = TYPE_OFFSET+strlen(type);
200
201 levp[3] = (char *) malloc(size+1);
202 if (levp[3] == NULL) {
203 pam_syslog(pamh, LOG_CRIT, "no memory for type");
204 if (levp) {
205 free(levp[2]);
206 free(levp[1]);
207 free(levp[0]);
208 free(levp);
209 }
210 return -1;
211 }
212
213 strcpy(levp[3], TYPE_NAME);
214 strcpy(levp[3]+TYPE_OFFSET, type);
215 levp[3][size] = '\0'; /* <NUL> terminate */
216
217 levp[4] = NULL; /* end list */
218
219 *evp = levp;
220 }
221
222 if ((ctrl & FILTER_DEBUG) && *filtername) {
223 char **e;
224
225 pam_syslog(pamh, LOG_DEBUG, "filter[%s]: %s", type, *filtername);
226 pam_syslog(pamh, LOG_DEBUG, "environment:");
227 for (e=*evp; e && *e; ++e) {
228 pam_syslog(pamh, LOG_DEBUG, " %s", *e);
229 }
230 }
231
232 return ctrl;
233 }
234
235 static void free_evp(char *evp[])
236 {
237 int i;
238
239 if (evp)
240 for (i=0; i<4; ++i) {
241 if (evp[i])
242 free(evp[i]);
243 }
244 free(evp);
245 }
246
247 static int
248 set_filter (pam_handle_t *pamh, int flags UNUSED, int ctrl,
249 char * const evp[], const char *filtername)
250 {
251 int status=-1;
252 char* terminal = NULL;
253 struct termios stored_mode; /* initial terminal mode settings */
254 int fd[2], child=0, child2=0, aterminal;
255
256 if (filtername == NULL || *filtername != '/') {
257 pam_syslog(pamh, LOG_ERR,
258 "filtername not permitted; full pathname required");
259 return PAM_ABORT;
260 }
261
262 if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
263 aterminal = 0;
264 } else {
265 aterminal = 1;
266 }
267
268 if (aterminal) {
269
270 /* open the master pseudo terminal */
271
272 fd[0] = master();
273 if (fd[0] < 0) {
274 pam_syslog(pamh, LOG_CRIT, "no master terminal");
275 return PAM_AUTH_ERR;
276 }
277
278 /* set terminal into raw mode.. remember old mode so that we can
279 revert to it after the child has quit. */
280
281 /* this is termios terminal handling... */
282
283 if ( tcgetattr(STDIN_FILENO, &stored_mode) < 0 ) {
284 pam_syslog(pamh, LOG_CRIT, "couldn't copy terminal mode: %m");
285 /* in trouble, so close down */
286 close(fd[0]);
287 return PAM_ABORT;
288 } else {
289 struct termios t_mode = stored_mode;
290
291 t_mode.c_iflag = 0; /* no input control */
292 t_mode.c_oflag &= ~OPOST; /* no output post processing */
293
294 /* no signals, canonical input, echoing, upper/lower output */
295 #ifdef XCASE
296 t_mode.c_lflag &= ~(XCASE);
297 #endif
298 t_mode.c_lflag &= ~(ISIG|ICANON|ECHO);
299 t_mode.c_cflag &= ~(CSIZE|PARENB); /* no parity */
300 t_mode.c_cflag |= CS8; /* 8 bit chars */
301
302 t_mode.c_cc[VMIN] = 1; /* number of chars to satisfy a read */
303 t_mode.c_cc[VTIME] = 0; /* 0/10th second for chars */
304
305 if ( tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_mode) < 0 ) {
306 pam_syslog(pamh, LOG_ERR,
307 "couldn't put terminal in RAW mode: %m");
308 close(fd[0]);
309 return PAM_ABORT;
310 }
311
312 /*
313 * NOTE: Unlike the stream socket case here the child
314 * opens the slave terminal as fd[1] *after* the fork...
315 */
316 }
317 } else {
318
319 /*
320 * not a terminal line so just open a stream socket fd[0-1]
321 * both set...
322 */
323
324 if ( socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0 ) {
325 pam_syslog(pamh, LOG_ERR, "couldn't open a stream pipe: %m");
326 return PAM_ABORT;
327 }
328 }
329
330 /* start child process */
331
332 if ( (child = fork()) < 0 ) {
333
334 pam_syslog(pamh, LOG_ERR, "first fork failed: %m");
335 if (aterminal) {
336 (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode);
337 close(fd[0]);
338 } else {
339 /* Socket pair */
340 close(fd[0]);
341 close(fd[1]);
342 }
343
344 return PAM_AUTH_ERR;
345 }
346
347 if ( child == 0 ) { /* child process *is* application */
348
349 if (aterminal) {
350
351 /* close the controlling tty */
352
353 #if defined(__hpux) && defined(O_NOCTTY)
354 int t = open("/dev/tty", O_RDWR|O_NOCTTY);
355 #else
356 int t = open("/dev/tty",O_RDWR);
357 if (t >= 0) {
358 (void) ioctl(t, TIOCNOTTY, NULL);
359 close(t);
360 }
361 #endif /* defined(__hpux) && defined(O_NOCTTY) */
362
363 /* make this process it's own process leader */
364 if (setsid() == -1) {
365 pam_syslog(pamh, LOG_ERR,
366 "child cannot become new session: %m");
367 return PAM_ABORT;
368 }
369
370 /* grant slave terminal */
371 if (grantpt (fd[0]) < 0) {
372 pam_syslog(pamh, LOG_ERR, "Cannot grant access to slave terminal");
373 return PAM_ABORT;
374 }
375
376 /* unlock slave terminal */
377 if (unlockpt (fd[0]) < 0) {
378 pam_syslog(pamh, LOG_ERR, "Cannot unlock slave terminal");
379 return PAM_ABORT;
380 }
381
382 /* find slave's name */
383 terminal = ptsname(fd[0]); /* returned value should not be freed */
384
385 if (terminal == NULL) {
386 pam_syslog(pamh, LOG_ERR,
387 "Cannot get the name of the slave terminal: %m");
388 return PAM_ABORT;
389 }
390
391 fd[1] = open(terminal, O_RDWR);
392 close(fd[0]); /* process is the child -- uses line fd[1] */
393
394 if (fd[1] < 0) {
395 pam_syslog(pamh, LOG_ERR,
396 "cannot open slave terminal: %s: %m", terminal);
397 return PAM_ABORT;
398 }
399
400 /* initialize the child's terminal to be the way the
401 parent's was before we set it into RAW mode */
402
403 if ( tcsetattr(fd[1], TCSANOW, &stored_mode) < 0 ) {
404 pam_syslog(pamh, LOG_ERR,
405 "cannot set slave terminal mode: %s: %m", terminal);
406 close(fd[1]);
407 return PAM_ABORT;
408 }
409 } else {
410
411 /* nothing to do for a simple stream socket */
412
413 }
414
415 /* re-assign the stdin/out to fd[1] <- (talks to filter). */
416
417 if ( dup2(fd[1],STDIN_FILENO) != STDIN_FILENO ||
418 dup2(fd[1],STDOUT_FILENO) != STDOUT_FILENO ||
419 dup2(fd[1],STDERR_FILENO) != STDERR_FILENO ) {
420 pam_syslog(pamh, LOG_ERR,
421 "unable to re-assign STDIN/OUT/ERR: %m");
422 close(fd[1]);
423 return PAM_ABORT;
424 }
425
426 /* make sure that file descriptors survive 'exec's */
427
428 if ( fcntl(STDIN_FILENO, F_SETFD, 0) ||
429 fcntl(STDOUT_FILENO,F_SETFD, 0) ||
430 fcntl(STDERR_FILENO,F_SETFD, 0) ) {
431 pam_syslog(pamh, LOG_ERR,
432 "unable to re-assign STDIN/OUT/ERR: %m");
433 return PAM_ABORT;
434 }
435
436 /* now the user input is read from the parent/filter: forget fd */
437
438 close(fd[1]);
439
440 /* the current process is now apparently working with filtered
441 stdio/stdout/stderr --- success! */
442
443 return PAM_SUCCESS;
444 }
445
446 /* Clear out passwords... there is a security problem here in
447 * that this process never executes pam_end. Consequently, any
448 * other sensitive data in this process is *not* explicitly
449 * overwritten, before the process terminates */
450
451 (void) pam_set_item(pamh, PAM_AUTHTOK, NULL);
452 (void) pam_set_item(pamh, PAM_OLDAUTHTOK, NULL);
453
454 /* fork a copy of process to run the actual filter executable */
455
456 if ( (child2 = fork()) < 0 ) {
457
458 pam_syslog(pamh, LOG_ERR, "filter fork failed: %m");
459 child2 = 0;
460
461 } else if ( child2 == 0 ) { /* exec the child filter */
462
463 if ( dup2(fd[0],APPIN_FILENO) != APPIN_FILENO ||
464 dup2(fd[0],APPOUT_FILENO) != APPOUT_FILENO ||
465 dup2(fd[0],APPERR_FILENO) != APPERR_FILENO ) {
466 pam_syslog(pamh, LOG_ERR,
467 "unable to re-assign APPIN/OUT/ERR: %m");
468 close(fd[0]);
469 _exit(1);
470 }
471
472 /* make sure that file descriptors survive 'exec's */
473
474 if ( fcntl(APPIN_FILENO, F_SETFD, 0) == -1 ||
475 fcntl(APPOUT_FILENO,F_SETFD, 0) == -1 ||
476 fcntl(APPERR_FILENO,F_SETFD, 0) == -1 ) {
477 pam_syslog(pamh, LOG_ERR,
478 "unable to retain APPIN/OUT/ERR: %m");
479 close(APPIN_FILENO);
480 close(APPOUT_FILENO);
481 close(APPERR_FILENO);
482 _exit(1);
483 }
484
485 /* now the user input is read from the parent through filter */
486
487 execle(filtername, "<pam_filter>", NULL, evp);
488
489 /* getting to here is an error */
490
491 pam_syslog(pamh, LOG_ERR, "filter: %s: %m", filtername);
492 _exit(1);
493
494 } else { /* wait for either of the two children to exit */
495
496 while (child && child2) { /* loop if there are two children */
497 int lstatus=0;
498 int chid;
499
500 chid = wait(&lstatus);
501 if (chid == child) {
502
503 if (WIFEXITED(lstatus)) { /* exited ? */
504 status = WEXITSTATUS(lstatus);
505 } else if (WIFSIGNALED(lstatus)) { /* killed ? */
506 status = -1;
507 } else
508 continue; /* just stopped etc.. */
509 child = 0; /* the child has exited */
510
511 } else if (chid == child2) {
512 /*
513 * if the filter has exited. Let the child die
514 * naturally below
515 */
516 if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus))
517 child2 = 0;
518 } else {
519
520 pam_syslog(pamh, LOG_ERR,
521 "programming error <chid=%d,lstatus=%x> "
522 "in file %s at line %d",
523 chid, lstatus, __FILE__, __LINE__);
524 child = child2 = 0;
525 status = -1;
526
527 }
528 }
529 }
530
531 close(fd[0]);
532
533 /* if there is something running, wait for it to exit */
534
535 while (child || child2) {
536 int lstatus=0;
537 int chid;
538
539 chid = wait(&lstatus);
540
541 if (child && chid == child) {
542
543 if (WIFEXITED(lstatus)) { /* exited ? */
544 status = WEXITSTATUS(lstatus);
545 } else if (WIFSIGNALED(lstatus)) { /* killed ? */
546 status = -1;
547 } else
548 continue; /* just stopped etc.. */
549 child = 0; /* the child has exited */
550
551 } else if (child2 && chid == child2) {
552
553 if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus))
554 child2 = 0;
555
556 } else {
557
558 pam_syslog(pamh, LOG_ERR,
559 "programming error <chid=%d,lstatus=%x> "
560 "in file %s at line %d",
561 chid, lstatus, __FILE__, __LINE__);
562 child = child2 = 0;
563 status = -1;
564
565 }
566 }
567
568 if (aterminal) {
569 /* reset to initial terminal mode */
570 (void) tcsetattr(STDIN_FILENO, TCSANOW, &stored_mode);
571 }
572
573 if (ctrl & FILTER_DEBUG) {
574 pam_syslog(pamh, LOG_DEBUG, "parent process exited"); /* clock off */
575 }
576
577 /* quit the parent process, returning the child's exit status */
578
579 exit(status);
580 return status; /* never reached, to make gcc happy */
581 }
582
583 static int set_the_terminal(pam_handle_t *pamh)
584 {
585 const void *tty;
586
587 if (pam_get_item(pamh, PAM_TTY, &tty) != PAM_SUCCESS
588 || tty == NULL) {
589 tty = ttyname(STDIN_FILENO);
590 if (tty == NULL) {
591 pam_syslog(pamh, LOG_ERR, "couldn't get the tty name");
592 return PAM_ABORT;
593 }
594 if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
595 pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
596 return PAM_ABORT;
597 }
598 }
599 return PAM_SUCCESS;
600 }
601
602 static int need_a_filter(pam_handle_t *pamh
603 , int flags, int argc, const char **argv
604 , const char *name, int which_run)
605 {
606 int ctrl;
607 char **evp;
608 const char *filterfile;
609 int retval;
610
611 ctrl = process_args(pamh, argc, argv, name, &evp, &filterfile);
612 if (ctrl == -1) {
613 return PAM_AUTHINFO_UNAVAIL;
614 }
615
616 /* set the tty to the old or the new one? */
617
618 if (!(ctrl & NON_TERM) && !(ctrl & NEW_TERM)) {
619 retval = set_the_terminal(pamh);
620 if (retval != PAM_SUCCESS) {
621 pam_syslog(pamh, LOG_ERR, "tried and failed to set PAM_TTY");
622 }
623 } else {
624 retval = PAM_SUCCESS; /* nothing to do which is always a success */
625 }
626
627 if (retval == PAM_SUCCESS && (ctrl & which_run)) {
628 retval = set_filter(pamh, flags, ctrl, evp, filterfile);
629 }
630
631 if (retval == PAM_SUCCESS
632 && !(ctrl & NON_TERM) && (ctrl & NEW_TERM)) {
633 retval = set_the_terminal(pamh);
634 if (retval != PAM_SUCCESS) {
635 pam_syslog(pamh, LOG_ERR,
636 "tried and failed to set new terminal as PAM_TTY");
637 }
638 }
639
640 free_evp(evp);
641
642 if (ctrl & FILTER_DEBUG) {
643 pam_syslog(pamh, LOG_DEBUG, "filter/%s, returning %d", name, retval);
644 pam_syslog(pamh, LOG_DEBUG, "[%s]", pam_strerror(pamh, retval));
645 }
646
647 return retval;
648 }
649
650 /* ----------------- public functions ---------------- */
651
652 /*
653 * here are the advertised access points ...
654 */
655
656 /* ------------------ authentication ----------------- */
657
658 int pam_sm_authenticate(pam_handle_t *pamh,
659 int flags, int argc, const char **argv)
660 {
661 return need_a_filter(pamh, flags, argc, argv
662 , "authenticate", FILTER_RUN1);
663 }
664
665 int pam_sm_setcred(pam_handle_t *pamh, int flags,
666 int argc, const char **argv)
667 {
668 return need_a_filter(pamh, flags, argc, argv, "setcred", FILTER_RUN2);
669 }
670
671 /* --------------- account management ---------------- */
672
673 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
674 const char **argv)
675 {
676 return need_a_filter(pamh, flags, argc, argv
677 , "setcred", FILTER_RUN1|FILTER_RUN2 );
678 }
679
680 /* --------------- session management ---------------- */
681
682 int pam_sm_open_session(pam_handle_t *pamh, int flags,
683 int argc, const char **argv)
684 {
685 return need_a_filter(pamh, flags, argc, argv
686 , "open_session", FILTER_RUN1);
687 }
688
689 int pam_sm_close_session(pam_handle_t *pamh, int flags,
690 int argc, const char **argv)
691 {
692 return need_a_filter(pamh, flags, argc, argv
693 , "close_session", FILTER_RUN2);
694 }
695
696 /* --------- updating authentication tokens --------- */
697
698
699 int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
700 int argc, const char **argv)
701 {
702 int runN;
703
704 if (flags & PAM_PRELIM_CHECK)
705 runN = FILTER_RUN1;
706 else if (flags & PAM_UPDATE_AUTHTOK)
707 runN = FILTER_RUN2;
708 else {
709 pam_syslog(pamh, LOG_ERR, "unknown flags for chauthtok (0x%X)", flags);
710 return PAM_TRY_AGAIN;
711 }
712
713 return need_a_filter(pamh, flags, argc, argv, "chauthtok", runN);
714 }