xref: /aosp_15_r20/external/libcap/contrib/sucap/su.c (revision 2810ac1b38eead2603277920c78344c84ddf3aff)
1 /*
2  * Originally based on an implementation of `su' by
3  *
4  *     Peter Orbaek  <[email protected]>
5  *
6  * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/
7  *
8  * Rewritten for Linux-PAM by Andrew G. Morgan <[email protected]>
9  * Modified by Andrey V. Savochkin <[email protected]>
10  * Modified for use with libcap by Andrew G. Morgan <[email protected]>
11  */
12 
13 /* #define PAM_DEBUG */
14 
15 #include <sys/prctl.h>
16 
17 /* non-root user of convenience to block signals */
18 #define TEMP_UID                  1
19 
20 #ifndef PAM_APP_NAME
21 #define PAM_APP_NAME              "su"
22 #endif /* ndef PAM_APP_NAME */
23 
24 #define DEFAULT_HOME              "/"
25 #define DEFAULT_SHELL             "/bin/bash"
26 #define SLEEP_TO_KILL_CHILDREN    3  /* seconds to wait after SIGTERM before
27 					SIGKILL */
28 #define SU_FAIL_DELAY     2000000    /* usec on authentication failure */
29 
30 #define RHOST_UNKNOWN_NAME        ""     /* perhaps "[from.where?]" */
31 #define DEVICE_FILE_PREFIX        "/dev/"
32 #define WTMP_LOCK_TIMEOUT         3      /* in seconds */
33 
34 #ifndef UT_IDSIZE
35 #define UT_IDSIZE 4            /* XXX - this is sizeof(struct utmp.ut_id) */
36 #endif
37 
38 #include <stdlib.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <unistd.h>
44 #include <pwd.h>
45 #include <grp.h>
46 #include <string.h>
47 #include <syslog.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <termios.h>
51 #include <sys/wait.h>
52 #include <utmp.h>
53 #include <ctype.h>
54 #include <stdarg.h>
55 #include <netdb.h>
56 #include <unistd.h>
57 
58 #include <security/pam_appl.h>
59 #include <security/pam_misc.h>
60 #include <sys/capability.h>
61 
62 #include <security/_pam_macros.h>
63 
64 /* -------------------------------------------- */
65 /* ------ declarations ------------------------ */
66 /* -------------------------------------------- */
67 
68 extern char **environ;
69 static pam_handle_t *pamh = NULL;
70 
71 static int wait_for_child_caught=0;
72 static int need_job_control=0;
73 static int is_terminal = 0;
74 static struct termios stored_mode;        /* initial terminal mode settings */
75 static uid_t terminal_uid = (uid_t) -1;
76 static uid_t invoked_uid = (uid_t) -1;
77 
78 /* -------------------------------------------- */
79 /* ------ some local (static) functions ------- */
80 /* -------------------------------------------- */
81 
82 /*
83  * We will attempt to transcribe the following env variables
84  * independent of whether we keep the whole environment. Others will
85  * be set elsewhere: either in modules; or after the identity of the
86  * user is known.
87  */
88 
89 static const char *posix_env[] = {
90     "LANG",
91     "LC_COLLATE",
92     "LC_CTYPE",
93     "LC_MONETARY",
94     "LC_NUMERIC",
95     "TZ",
96     NULL
97 };
98 
99 /*
100  * make_environment transcribes a selection of environment variables
101  * from the invoking user.
102  */
make_environment(int keep_env)103 static int make_environment(int keep_env)
104 {
105     const char *tmpe;
106     int i;
107     int retval;
108 
109     if (keep_env) {
110 	/* preserve the original environment */
111 	return pam_misc_paste_env(pamh, (const char * const *)environ);
112     }
113 
114     /* we always transcribe some variables anyway */
115     tmpe = getenv("TERM");
116     if (tmpe == NULL) {
117 	tmpe = "dumb";
118     }
119     retval = pam_misc_setenv(pamh, "TERM", tmpe, 0);
120     if (retval == PAM_SUCCESS) {
121 	retval = pam_misc_setenv(pamh, "PATH", "/bin:/usr/bin", 0);
122     }
123     if (retval != PAM_SUCCESS) {
124 	tmpe = NULL;
125 	D(("error setting environment variables"));
126 	return retval;
127     }
128 
129     /* also propagate the POSIX specific ones */
130     for (i=0; retval == PAM_SUCCESS && posix_env[i]; ++i) {
131 	tmpe = getenv(posix_env[i]);
132 	if (tmpe != NULL) {
133 	    retval = pam_misc_setenv(pamh, posix_env[i], tmpe, 0);
134 	}
135     }
136     tmpe = NULL;
137 
138     return retval;
139 }
140 
141 /*
142  * checkfds ensures that stdout and stderr filedescriptors are
143  * defined. If all else fails, it directs them to /dev/null.
144  */
checkfds(void)145 static void checkfds(void)
146 {
147     struct stat st;
148     int fd;
149 
150     if (fstat(1, &st) == -1) {
151         fd = open("/dev/null", O_WRONLY);
152         if (fd == -1) goto badfds;
153         if (fd != 1) {
154             if (dup2(fd, 1) == -1) goto badfds;
155             if (close(fd) == -1) goto badfds;
156         }
157     }
158     if (fstat(2, &st) == -1) {
159         fd = open("/dev/null", O_WRONLY);
160         if (fd == -1) goto badfds;
161         if (fd != 2) {
162             if (dup2(fd, 2) == -1) goto badfds;
163             if (close(fd) == -1) goto badfds;
164         }
165     }
166 
167     return;
168 
169 badfds:
170     perror("bad filedes");
171     exit(1);
172 }
173 
174 /*
175  * store_terminal_modes captures the current state of the input
176  * terminal. Calling this at the start of the program, we ensure we
177  * can restore these default settings when su exits.
178  */
store_terminal_modes(void)179 static void store_terminal_modes(void)
180 {
181     if (isatty(STDIN_FILENO)) {
182 	is_terminal = 1;
183 	if (tcgetattr(STDIN_FILENO, &stored_mode) != 0) {
184 	    fprintf(stderr, PAM_APP_NAME ": couldn't copy terminal mode");
185 	    exit(1);
186 	}
187 	return;
188     }
189     fprintf(stderr, PAM_APP_NAME ": must be run from a terminal\n");
190     exit(1);
191 }
192 
193 /*
194  * restore_terminal_modes resets the terminal to the state it was in
195  * when the program started.
196  *
197  * Returns:
198  *   0     ok
199  *   1     error
200  */
restore_terminal_modes(void)201 static int restore_terminal_modes(void)
202 {
203     if (is_terminal && tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode) != 0) {
204 	fprintf(stderr, PAM_APP_NAME ": cannot restore terminal mode: %s\n",
205 		strerror(errno));
206 	return 1;
207     } else {
208 	return 0;
209     }
210 }
211 
212 /* ------ unexpected signals ------------------ */
213 
214 struct sigaction old_int_act, old_quit_act, old_tstp_act, old_pipe_act;
215 
216 /*
217  * disable_terminal_signals attempts to make the process resistant to
218  * being stopped - it helps ensure that the PAM stack can complete
219  * session and auth failure logging etc.
220  */
disable_terminal_signals(void)221 static void disable_terminal_signals(void)
222 {
223     /*
224      * Protect the process from dangerous terminal signals.
225      * The protection is implemented via sigaction() because
226      * the signals are sent regardless of the process' uid.
227      */
228     struct sigaction act;
229 
230     act.sa_handler = SIG_IGN;  /* ignore the signal */
231     sigemptyset(&act.sa_mask); /* no signal blocking on handler
232 				  call needed */
233     act.sa_flags = SA_RESTART; /* do not reset after first signal
234 				  arriving, restart interrupted
235 				  system calls if possible */
236     sigaction(SIGINT, &act, &old_int_act);
237     sigaction(SIGQUIT, &act, &old_quit_act);
238     /*
239      * Ignore SIGTSTP signals. Why? attacker could otherwise stop
240      * a process and a. kill it, or b. wait for the system to
241      * shutdown - either way, nothing appears in syslogs.
242      */
243     sigaction(SIGTSTP, &act, &old_tstp_act);
244     /*
245      * Ignore SIGPIPE. The parent `su' process may print something
246      * on stderr. Killing of the process would be undesired.
247      */
248     sigaction(SIGPIPE, &act, &old_pipe_act);
249 }
250 
enable_terminal_signals(void)251 static void enable_terminal_signals(void)
252 {
253     sigaction(SIGINT, &old_int_act, NULL);
254     sigaction(SIGQUIT, &old_quit_act, NULL);
255     sigaction(SIGTSTP, &old_tstp_act, NULL);
256     sigaction(SIGPIPE, &old_pipe_act, NULL);
257 }
258 
259 /* ------ terminal ownership ------------------ */
260 
261 /*
262  * change_terminal_owner changes the ownership of STDIN if needed.
263  * Returns:
264  *   0     ok,
265  *  -1     fatal error (continuing is impossible),
266  *   1     non-fatal error.
267  * In the case of an error "err_descr" is set to the error message
268  * and "callname" to the name of the failed call.
269  */
change_terminal_owner(uid_t uid,int is_login,const char ** callname,const char ** err_descr)270 static int change_terminal_owner(uid_t uid, int is_login,
271 				 const char **callname, const char **err_descr)
272 {
273     /* determine who owns the terminal line */
274     if (is_terminal && is_login) {
275 	struct stat stat_buf;
276 	cap_t current, working;
277 	int status;
278 	cap_value_t cchown = CAP_CHOWN;
279 
280 	if (fstat(STDIN_FILENO, &stat_buf) != 0) {
281             *callname = "fstat to STDIN";
282 	    *err_descr = strerror(errno);
283 	    return -1;
284 	}
285 
286 	current = cap_get_proc();
287 	working = cap_dup(current);
288 	cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
289 	status = cap_set_proc(working);
290 	cap_free(working);
291 
292 	if (status != 0) {
293 	    *callname = "capset CHOWN";
294 	} else if ((status = fchown(STDIN_FILENO, uid, -1)) != 0) {
295 	    *callname = "fchown of STDIN";
296 	} else {
297 	    cap_set_proc(current);
298 	}
299 	cap_free(current);
300 
301 	if (status != 0) {
302 	    *err_descr = strerror(errno);
303 	    return 1;
304 	}
305 
306 	terminal_uid = stat_buf.st_uid;
307     }
308     return 0;
309 }
310 
311 /*
312  * restore_terminal_owner changes the terminal owner back to the value
313  * it had when su was started.
314  */
restore_terminal_owner(void)315 static void restore_terminal_owner(void)
316 {
317     if (terminal_uid != (uid_t) -1) {
318 	cap_t current, working;
319 	int status;
320 	cap_value_t cchown = CAP_CHOWN;
321 
322 	current = cap_get_proc();
323 	working = cap_dup(current);
324 	cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
325 	status = cap_set_proc(working);
326 	cap_free(working);
327 
328 	if (status == 0) {
329 	    status = fchown(STDIN_FILENO, terminal_uid, -1);
330 	    cap_set_proc(current);
331 	}
332 	cap_free(current);
333 
334         if (status != 0) {
335             openlog(PAM_APP_NAME, LOG_CONS|LOG_PERROR|LOG_PID, LOG_AUTHPRIV);
336 	    syslog(LOG_ALERT, "Terminal owner hasn\'t been restored: %s",
337 		   strerror(errno));
338 	    closelog();
339         }
340         terminal_uid = (uid_t) -1;
341     }
342 }
343 
344 /*
345  * make_process_unkillable changes the uid of the process. TEMP_UID is
346  * used for this temporary state.
347  *
348  * Returns:
349  *   0     ok,
350  *  -1     fatal error (continue of the work is impossible),
351  *   1     non-fatal error.
352  * In the case of an error "err_descr" is set to the error message
353  * and "callname" to the name of the failed call.
354  */
make_process_unkillable(const char ** callname,const char ** err_descr)355 static int make_process_unkillable(const char **callname,
356 				   const char **err_descr)
357 {
358     invoked_uid = getuid();
359     if (invoked_uid == TEMP_UID) {
360 	/* no change needed */
361 	return 0;
362     }
363 
364     if (cap_setuid(TEMP_UID) != 0) {
365         *callname = "setuid";
366 	*err_descr = strerror(errno);
367 	return -1;
368     }
369     return 0;
370 }
371 
372 /*
373  * make_process_killable restores the invoking uid to the current
374  * process.
375  */
make_process_killable(void)376 static void make_process_killable(void)
377 {
378     (void) cap_setuid(invoked_uid);
379 }
380 
381 /* ------ command line parser ----------------- */
382 
usage(int exit_val)383 static void usage(int exit_val)
384 {
385     fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n");
386     exit(exit_val);
387 }
388 
389 /*
390  * parse_command_line extracts the options from the command line
391  * arguments.
392  */
parse_command_line(int argc,char * argv[],int * is_login,const char ** user,const char ** command)393 static void parse_command_line(int argc, char *argv[], int *is_login,
394 			       const char **user, const char **command)
395 {
396     int username_present, command_present;
397 
398     *is_login = 0;
399     *user = NULL;
400     *command = NULL;
401     username_present = command_present = 0;
402 
403     while ( --argc > 0 ) {
404 	const char *token;
405 
406 	token = *++argv;
407 	if (*token == '-') {
408 	    switch (*++token) {
409 	    case '\0':             /* su as a login shell for the user */
410 		if (*is_login)
411 		    usage(1);
412 		*is_login = 1;
413 		break;
414 	    case 'c':
415 		if (command_present) {
416 		    usage(1);
417 		} else {               /* indicate we are running commands */
418 		    if (*++token != '\0') {
419 			command_present = 1;
420 			*command = token;
421 		    } else if (--argc > 0) {
422 			command_present = 1;
423 			*command = *++argv;
424 		    } else
425 			usage(1);
426 		}
427 		break;
428 	    case 'h':
429 		usage(0);
430 	    default:
431 		usage(1);
432 	    }
433 	} else {                       /* must be username */
434 	    if (username_present) {
435 		usage(1);
436 	    }
437 	    username_present = 1;
438 	    *user = *argv;
439 	}
440     }
441 
442     if (!username_present) {
443 	fprintf(stderr, PAM_APP_NAME ": requires a username\n");
444 	usage(1);
445     }
446 }
447 
448 /*
449  * This following contains code that waits for a child process to die.
450  * It also chooses to intercept a couple of signals that it will
451  * kindly pass on a SIGTERM to the child ;^). Waiting again for the
452  * child to exit. If the child resists dying, it will SIGKILL it!
453  */
454 
wait_for_child_catch_sig(int ignore)455 static void wait_for_child_catch_sig(int ignore)
456 {
457     wait_for_child_caught = 1;
458 }
459 
prepare_for_job_control(int need_it)460 static void prepare_for_job_control(int need_it)
461 {
462     sigset_t ourset;
463 
464     (void) sigfillset(&ourset);
465     if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) {
466 	fprintf(stderr,"[trouble blocking signals]\n");
467 	wait_for_child_caught = 1;
468 	return;
469     }
470     need_job_control = need_it;
471 }
472 
wait_for_child(pid_t child)473 static int wait_for_child(pid_t child)
474 {
475     int retval, status, exit_code;
476     sigset_t ourset;
477 
478     exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */
479     if (child == -1) {
480 	return exit_code;
481     }
482 
483     /*
484      * set up signal handling
485      */
486 
487     if (!wait_for_child_caught) {
488 	struct sigaction action, defaction;
489 
490 	action.sa_handler = wait_for_child_catch_sig;
491 	sigemptyset(&action.sa_mask);
492 	action.sa_flags = 0;
493 
494 	defaction.sa_handler = SIG_DFL;
495 	sigemptyset(&defaction.sa_mask);
496 	defaction.sa_flags = 0;
497 
498 	sigemptyset(&ourset);
499 
500 	if (   sigaddset(&ourset, SIGTERM)
501 	    || sigaction(SIGTERM, &action, NULL)
502 	    || sigaddset(&ourset, SIGHUP)
503 	    || sigaction(SIGHUP, &action, NULL)
504 	    || sigaddset(&ourset, SIGALRM)          /* required by sleep(3) */
505             || (need_job_control && sigaddset(&ourset, SIGTSTP))
506             || (need_job_control && sigaction(SIGTSTP, &defaction, NULL))
507             || (need_job_control && sigaddset(&ourset, SIGTTIN))
508             || (need_job_control && sigaction(SIGTTIN, &defaction, NULL))
509             || (need_job_control && sigaddset(&ourset, SIGTTOU))
510             || (need_job_control && sigaction(SIGTTOU, &defaction, NULL))
511 	    || (need_job_control && sigaddset(&ourset, SIGCONT))
512             || (need_job_control && sigaction(SIGCONT, &defaction, NULL))
513 	    || sigprocmask(SIG_UNBLOCK, &ourset, NULL)
514 	    ) {
515 	    fprintf(stderr,"[trouble setting signal intercept]\n");
516 	    wait_for_child_caught = 1;
517 	}
518 
519 	/* application should be ready for receiving a SIGTERM/HUP now */
520     }
521 
522     /*
523      * This code waits for the process to actually die. If it stops,
524      * then the parent attempts to mimic the behavior of the
525      * child.. There is a slight bug in the code when the 'su'd user
526      * attempts to restart the child independently of the parent --
527      * the child dies.
528      */
529     while (!wait_for_child_caught) {
530         /* parent waits for child */
531 	if ((retval = waitpid(child, &status, 0)) <= 0) {
532             if (errno == EINTR) {
533                 continue;             /* recovering from a 'fg' */
534 	    }
535             fprintf(stderr, "[error waiting child: %s]\n", strerror(errno));
536             /*
537              * Break the loop keeping exit_code undefined.
538              * Do we have a chance for a successful wait() call
539              * after kill()? (SAW)
540              */
541             wait_for_child_caught = 1;
542             break;
543         } else {
544 	    /* the child is terminated via exit() or a fatal signal */
545 	    if (WIFEXITED(status)) {
546 		exit_code = WEXITSTATUS(status);
547 	    } else {
548 		exit_code = 1;
549 	    }
550 	    break;
551 	}
552     }
553 
554     if (wait_for_child_caught) {
555 	fprintf(stderr,"\nKilling shell...");
556 	kill(child, SIGTERM);
557     }
558 
559     /*
560      * do we need to wait for the child to catch up?
561      */
562     if (wait_for_child_caught) {
563 	sleep(SLEEP_TO_KILL_CHILDREN);
564 	kill(child, SIGKILL);
565 	fprintf(stderr, "killed\n");
566     }
567 
568     /*
569      * collect the zombie the shell was killed by ourself
570      */
571     if (exit_code == -1) {
572 	do {
573 	    retval = waitpid(child, &status, 0);
574 	} while (retval == -1 && errno == EINTR);
575 	if (retval == -1) {
576 	    fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n",
577 		    strerror(errno));
578 	}
579 	if (WIFEXITED(status)) {
580 	    exit_code = WEXITSTATUS(status);
581 	} else {
582 	    exit_code = 1;
583 	}
584     }
585 
586     return exit_code;
587 }
588 
589 
590 /*
591  * Next some code that parses the spawned shell command line.
592  */
593 
build_shell_args(const char * pw_shell,int login,const char * command)594 static const char * const *build_shell_args(const char *pw_shell, int login,
595 					    const char *command)
596 {
597     int use_default = 1;  /* flag to signal we should use the default shell */
598     const char **args=NULL;             /* array of PATH+ARGS+NULL pointers */
599 
600     D(("called."));
601     if (login) {
602         command = NULL;                 /* command always ignored for login */
603     }
604 
605     if (pw_shell && *pw_shell != '\0') {
606         char *line;
607         const char *tmp, *tmpb=NULL;
608         int arg_no=0,i;
609 
610         /* first find the number of arguments */
611         D(("non-null shell"));
612         for (tmp=pw_shell; *tmp; ++arg_no) {
613 
614             /* skip leading spaces */
615             while (isspace(*tmp))
616                 ++tmp;
617 
618             if (tmpb == NULL)               /* mark beginning token */
619                 tmpb = tmp;
620             if (*tmp == '\0')               /* end of line with no token */
621                 break;
622 
623             /* skip token */
624             while (*tmp && !isspace(*tmp))
625                 ++tmp;
626         }
627 
628         /*
629          * We disallow shells:
630          *    - without a full specified path;
631          *    - when we are not logging in and the #args != 1
632          *                                         (unlikely a simple shell)
633          */
634 
635         D(("shell so far = %s, arg_no = %d", tmpb, arg_no));
636         if (tmpb != NULL && tmpb[0] == '/'    /* something (full path) */
637             && ( login || arg_no == 1 )       /* login, or single arg shells */
638             ) {
639 
640             use_default = 0;                  /* we will use this shell */
641             D(("committed to using user's shell"));
642             if (command) {
643                 arg_no += 2;                  /* will append "-c" "command" */
644             }
645 
646             /* allocate an array of pointers long enough */
647 
648             D(("building array of size %d", 2+arg_no));
649             args = (const char **) calloc(2+arg_no, sizeof(const char *));
650             if (args == NULL)
651                 return NULL;
652             /* get a string long enough for all the arguments */
653 
654             D(("an array of size %d chars", 2+strlen(tmpb)
655                                    + ( command ? 4:0 )));
656             line = (char *) malloc(2+strlen(tmpb)
657                                    + ( command ? 4:0 ));
658             if (line == NULL) {
659                 free(args);
660                 return NULL;
661             }
662 
663             /* fill array - tmpb points to start of first non-space char */
664 
665             line[0] = '-';
666             strcpy(line+1, tmpb);
667 
668             /* append " -c" to line? */
669             if (command) {
670                 strcat(line, " -c");
671             }
672 
673             D(("complete command: %s [+] %s", line, command));
674 
675             tmp = strtok(line, " \t");
676             D(("command path=%s", line+1));
677             args[0] = line+1;
678 
679             if (login) {               /* standard procedure for login shell */
680                 D(("argv[0]=%s", line));
681                 args[i=1] = line;
682             } else {                 /* not a login shell -- for use with su */
683                 D(("argv[0]=%s", line+1));
684                 args[i=1] = line+1;
685             }
686 
687             while ((tmp = strtok(NULL, " \t"))) {
688                 D(("adding argument %d: %s",i,tmp));
689                 args[++i] = tmp;
690             }
691             if (command) {
692                 D(("appending command [%s]", command));
693                 args[++i] = command;
694             }
695             D(("terminating args with NULL"));
696             args[++i] = NULL;
697             D(("list completed."));
698         }
699     }
700 
701     /* should we use the default shell instead of specific one? */
702 
703     if (use_default && !login) {
704         int last_arg;
705 
706         D(("selecting default shell"));
707         last_arg = command ? 5:3;
708 
709         args = (const char **) calloc(last_arg--, sizeof(const char *));
710         if (args == NULL) {
711             return NULL;
712         }
713         args[1] = DEFAULT_SHELL;      /* mapped to argv[0] (NOT login shell) */
714         args[0] = args[1];            /* path to program */
715         if (command) {
716             args[2] = "-c";           /* should perform command and exit */
717             args[3] = command;        /* the desired command */
718         }
719         args[last_arg] = NULL;        /* terminate list of args */
720     }
721 
722     D(("returning arg list"));
723     return (const char * const *) args;
724 }
725 
726 
727 /* ------ abnormal termination ---------------- */
728 
exit_now(int exit_code,const char * format,...)729 static void exit_now(int exit_code, const char *format, ...)
730 {
731     va_list args;
732 
733     va_start(args, format);
734     vfprintf(stderr, format, args);
735     va_end(args);
736 
737     if (pamh != NULL)
738 	pam_end(pamh, exit_code ? PAM_ABORT:PAM_SUCCESS);
739 
740     /* USER's shell may have completely broken terminal settings
741        restore the sane(?) initial conditions */
742     restore_terminal_modes();
743 
744     exit(exit_code);
745 }
746 
747 /* ------ PAM setup --------------------------- */
748 
749 static struct pam_conv conv = {
750     misc_conv,                   /* defined in <pam_misc/libmisc.h> */
751     NULL
752 };
753 
do_pam_init(const char * user,int is_login)754 static void do_pam_init(const char *user, int is_login)
755 {
756     int retval;
757 
758     retval = pam_start(PAM_APP_NAME, user, &conv, &pamh);
759     if (retval != PAM_SUCCESS) {
760 	/*
761 	 * From my point of view failing of pam_start() means that
762 	 * pamh isn't a valid handler. Without a handler
763 	 * we couldn't call pam_strerror :-(   1998/03/29 (SAW)
764 	 */
765 	fprintf(stderr, PAM_APP_NAME ": pam_start failed with code %d\n",
766 		retval);
767 	exit(1);
768     }
769 
770     /*
771      * Fill in some blanks
772      */
773 
774     retval = make_environment(!is_login);
775     D(("made_environment returned: %s", pam_strerror(pamh, retval)));
776 
777     if (retval == PAM_SUCCESS && is_terminal) {
778 	const char *terminal = ttyname(STDIN_FILENO);
779 	if (terminal) {
780 	    retval = pam_set_item(pamh, PAM_TTY, (const void *)terminal);
781 	} else {
782 	    retval = PAM_PERM_DENIED;                /* how did we get here? */
783 	}
784 	terminal = NULL;
785     }
786 
787     if (retval == PAM_SUCCESS && is_terminal) {
788 	const char *ruser = getlogin();      /* Who is running this program? */
789 	if (ruser) {
790 	    retval = pam_set_item(pamh, PAM_RUSER, (const void *)ruser);
791 	} else {
792 	    retval = PAM_PERM_DENIED;             /* must be known to system */
793 	}
794 	ruser = NULL;
795     }
796 
797     if (retval == PAM_SUCCESS) {
798 	retval = pam_set_item(pamh, PAM_RHOST, (const void *)"localhost");
799     }
800 
801     if (retval != PAM_SUCCESS) {
802 	exit_now(1, PAM_APP_NAME ": problem establishing environment\n");
803     }
804 
805     /* have to pause on failure. At least this long (doubles..) */
806     retval = pam_fail_delay(pamh, SU_FAIL_DELAY);
807     if (retval != PAM_SUCCESS) {
808 	exit_now(1, PAM_APP_NAME ": problem initializing failure delay\n");
809     }
810 }
811 
812 /*
813  * authenticate_user arranges for the PAM authentication stack to run.
814  */
authenticate_user(cap_t all,int * retval,const char ** place,const char ** err_descr)815 static int authenticate_user(cap_t all, int *retval, const char **place,
816 			     const char **err_descr)
817 {
818     *place = "pre-auth cap_set_proc";
819     if (cap_set_proc(all)) {
820 	D(("failed to raise all capabilities"));
821 	*err_descr = "cap_set_proc() failed";
822 	*retval = PAM_SUCCESS;
823 	return 1;
824     }
825 
826     D(("attempt to authenticate user"));
827     *place = "pam_authenticate";
828     *retval = pam_authenticate(pamh, 0);
829     return (*retval != PAM_SUCCESS);
830 }
831 
832 /*
833  * user_accounting confirms an authenticated user is permitted service.
834  */
user_accounting(cap_t all,int * retval,const char ** place,const char ** err_descr)835 static int user_accounting(cap_t all, int *retval, const char **place,
836 			   const char **err_descr) {
837     *place = "user_accounting";
838     if (cap_set_proc(all)) {
839 	D(("failed to raise all capabilities"));
840 	*err_descr = "cap_set_proc() failed";
841 	return 1;
842     }
843     *place = "pam_acct_mgmt";
844     *retval = pam_acct_mgmt(pamh, 0);
845     return (*retval != PAM_SUCCESS);
846 }
847 
848 /*
849  * Find entry for this terminal (if there is one).
850  * Utmp file should have been opened and rewinded for the call.
851  *
852  * XXX: the search should be more or less compatible with libc one.
853  * The caller expects that pututline with the same arguments
854  * will replace the found entry.
855  */
find_utmp_entry(const char * ut_line,const char * ut_id)856 static const struct utmp *find_utmp_entry(const char *ut_line,
857 					  const char *ut_id)
858 {
859     struct utmp *u_tmp_p;
860 
861     while ((u_tmp_p = getutent()) != NULL)
862 	if ((u_tmp_p->ut_type == INIT_PROCESS ||
863              u_tmp_p->ut_type == LOGIN_PROCESS ||
864              u_tmp_p->ut_type == USER_PROCESS ||
865              u_tmp_p->ut_type == DEAD_PROCESS) &&
866             !strncmp(u_tmp_p->ut_id, ut_id, UT_IDSIZE) &&
867             !strncmp(u_tmp_p->ut_line, ut_line, UT_LINESIZE))
868                 break;
869 
870     return u_tmp_p;
871 }
872 
873 /*
874  * Identify the terminal name and the abbreviation we will use.
875  */
set_terminal_name(const char * terminal,char * ut_line,char * ut_id)876 static void set_terminal_name(const char *terminal, char *ut_line, char *ut_id)
877 {
878     memset(ut_line, 0, UT_LINESIZE);
879     memset(ut_id, 0, UT_IDSIZE);
880 
881     /* set the terminal entry */
882     if ( *terminal == '/' ) {     /* now deal with filenames */
883 	int o1, o2;
884 
885 	o1 = strncmp(DEVICE_FILE_PREFIX, terminal, 5) ? 0 : 5;
886 	if (!strncmp("/dev/tty", terminal, 8)) {
887 	    o2 = 8;
888 	} else {
889 	    o2 = strlen(terminal) - sizeof(UT_IDSIZE);
890 	    if (o2 < 0)
891 		o2 = 0;
892 	}
893 
894 	strncpy(ut_line, terminal + o1, UT_LINESIZE);
895 	strncpy(ut_id, terminal + o2, UT_IDSIZE);
896     } else if (strchr(terminal, ':')) {  /* deal with X-based session */
897 	const char *suffix;
898 
899 	suffix = strrchr(terminal,':');
900 	strncpy(ut_line, terminal, UT_LINESIZE);
901 	strncpy(ut_id, suffix, UT_IDSIZE);
902     } else {	                         /* finally deal with weird terminals */
903 	strncpy(ut_line, terminal, UT_LINESIZE);
904 	ut_id[0] = '?';
905 	strncpy(ut_id + 1, terminal, UT_IDSIZE - 1);
906     }
907 }
908 
909 /*
910  * Append an entry to wtmp. See utmp_open_session for the return convention.
911  * Be careful: the function uses alarm().
912  */
913 
914 #define WWTMP_STATE_BEGINNING     0
915 #define WWTMP_STATE_FILE_OPENED   1
916 #define WWTMP_STATE_SIGACTION_SET 2
917 #define WWTMP_STATE_LOCK_TAKEN    3
918 
write_wtmp(struct utmp * u_tmp_p,const char ** callname,const char ** err_descr)919 static int write_wtmp(struct utmp *u_tmp_p, const char **callname,
920 		      const char **err_descr)
921 {
922     int w_tmp_fd;
923     struct flock w_lock;
924     struct sigaction act1, act2;
925     int state;
926     int retval;
927 
928     state = WWTMP_STATE_BEGINNING;
929     retval = 1;
930 
931     do {
932         D(("writing to wtmp"));
933         w_tmp_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY);
934         if (w_tmp_fd == -1) {
935             *callname = "wtmp open";
936             *err_descr = strerror(errno);
937             break;
938         }
939         state = WWTMP_STATE_FILE_OPENED;
940 
941         /* prepare for blocking operation... */
942         act1.sa_handler = SIG_DFL;
943         sigemptyset(&act1.sa_mask);
944         act1.sa_flags = 0;
945         if (sigaction(SIGALRM, &act1, &act2) == -1) {
946             *callname = "sigaction";
947             *err_descr = strerror(errno);
948             break;
949         }
950         alarm(WTMP_LOCK_TIMEOUT);
951         state = WWTMP_STATE_SIGACTION_SET;
952 
953         /* now we try to lock this file-rcord exclusively; non-blocking */
954         memset(&w_lock, 0, sizeof(w_lock));
955         w_lock.l_type = F_WRLCK;
956         w_lock.l_whence = SEEK_END;
957         if (fcntl(w_tmp_fd, F_SETLK, &w_lock) < 0) {
958             D(("locking %s failed.", _PATH_WTMP));
959             *callname = "fcntl(F_SETLK)";
960             *err_descr = strerror(errno);
961             break;
962         }
963         alarm(0);
964         sigaction(SIGALRM, &act2, NULL);
965         state = WWTMP_STATE_LOCK_TAKEN;
966 
967         if (write(w_tmp_fd, u_tmp_p, sizeof(struct utmp)) != -1) {
968             retval = 0;
969 	}
970     } while(0); /* it's not a loop! */
971 
972     if (state >= WWTMP_STATE_LOCK_TAKEN) {
973         w_lock.l_type = F_UNLCK;               /* unlock wtmp file */
974         fcntl(w_tmp_fd, F_SETLK, &w_lock);
975     }else if (state >= WWTMP_STATE_SIGACTION_SET) {
976         alarm(0);
977         sigaction(SIGALRM, &act2, NULL);
978     }
979 
980     if (state >= WWTMP_STATE_FILE_OPENED) {
981         close(w_tmp_fd);                       /* close wtmp file */
982         D(("wtmp written"));
983     }
984 
985     return retval;
986 }
987 
988 /*
989  * XXX - if this gets turned into a module, make this a
990  * pam_data item. You should put the pid in the name so we can
991  * "probably" nest calls more safely...
992  */
993 struct utmp *login_stored_utmp=NULL;
994 
995 /*
996  * Returns:
997  *   0     ok,
998  *   1     non-fatal error
999  *  -1     fatal error
1000  *  callname and err_descr will be set
1001  * Be careful: the function indirectly uses alarm().
1002  */
utmp_do_open_session(const char * user,const char * terminal,const char * rhost,pid_t pid,const char ** place,const char ** err_descr)1003 static int utmp_do_open_session(const char *user, const char *terminal,
1004 				const char *rhost, pid_t pid,
1005 				const char **place, const char **err_descr)
1006 {
1007     struct utmp u_tmp;
1008     const struct utmp *u_tmp_p;
1009     char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
1010     int retval;
1011 
1012     set_terminal_name(terminal, ut_line, ut_id);
1013 
1014     utmpname(_PATH_UTMP);
1015     setutent();                                           /* rewind file */
1016     u_tmp_p = find_utmp_entry(ut_line, ut_id);
1017 
1018     /* reset new entry */
1019     memset(&u_tmp, 0, sizeof(u_tmp));                     /* reset new entry */
1020     if (u_tmp_p == NULL) {
1021 	D(("[NEW utmp]"));
1022     } else {
1023 	D(("[OLD utmp]"));
1024 
1025 	/*
1026 	 * here, we make a record of the former entry. If the
1027 	 * utmp_close_session code is attached to the same process,
1028 	 * the wtmp will be replaced, otherwise we leave init to pick
1029 	 * up the pieces.
1030 	 */
1031 	if (login_stored_utmp == NULL) {
1032 	    login_stored_utmp = malloc(sizeof(struct utmp));
1033             if (login_stored_utmp == NULL) {
1034                 *place = "malloc";
1035                 *err_descr = "fail";
1036                 endutent();
1037                 return -1;
1038             }
1039 	}
1040         memcpy(login_stored_utmp, u_tmp_p, sizeof(struct utmp));
1041     }
1042 
1043     /* we adjust the entry to reflect the current session */
1044     {
1045 	strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
1046 	memset(ut_line, 0, UT_LINESIZE);
1047 	strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
1048 	memset(ut_id, 0, UT_IDSIZE);
1049 	strncpy(u_tmp.ut_user, user
1050 		, sizeof(u_tmp.ut_user));
1051 	strncpy(u_tmp.ut_host, rhost ? rhost : RHOST_UNKNOWN_NAME
1052 		, sizeof(u_tmp.ut_host));
1053 
1054 	/* try to fill the host address entry */
1055 	if (rhost != NULL) {
1056 	    struct hostent *hptr;
1057 
1058 	    /* XXX: it isn't good to do DNS lookup here...  1998/05/29  SAW */
1059             hptr = gethostbyname(rhost);
1060 	    if (hptr != NULL && hptr->h_addr_list) {
1061 		memcpy(&u_tmp.ut_addr, hptr->h_addr_list[0]
1062 		       , sizeof(u_tmp.ut_addr));
1063 	    }
1064 	}
1065 
1066 	/* we fill in the remaining info */
1067 	u_tmp.ut_type = USER_PROCESS;          /* a user process starting */
1068 	u_tmp.ut_pid = pid;                    /* session identifier */
1069 	u_tmp.ut_time = time(NULL);
1070     }
1071 
1072     setutent();                                /* rewind file (replace old) */
1073     pututline(&u_tmp);                         /* write it to utmp */
1074     endutent();                                /* close the file */
1075 
1076     retval = write_wtmp(&u_tmp, place, err_descr); /* write to wtmp file */
1077     memset(&u_tmp, 0, sizeof(u_tmp));          /* reset entry */
1078 
1079     return retval;
1080 }
1081 
utmp_do_close_session(const char * terminal,const char ** place,const char ** err_descr)1082 static int utmp_do_close_session(const char *terminal,
1083 				 const char **place, const char **err_descr)
1084 {
1085     struct utmp u_tmp;
1086     const struct utmp *u_tmp_p;
1087     char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
1088 
1089     set_terminal_name(terminal, ut_line, ut_id);
1090 
1091     utmpname(_PATH_UTMP);
1092     setutent();                                              /* rewind file */
1093 
1094     /*
1095      * if there was a stored entry, return it to the utmp file, else
1096      * if there is a session to close, we close that
1097      */
1098     if (login_stored_utmp) {
1099 	pututline(login_stored_utmp);
1100 
1101 	memcpy(&u_tmp, login_stored_utmp, sizeof(u_tmp));
1102 	u_tmp.ut_time = time(NULL);            /* a new time to restart */
1103 
1104         write_wtmp(&u_tmp, place, err_descr);
1105 
1106 	memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */
1107 	free(login_stored_utmp);
1108     } else {
1109         u_tmp_p = find_utmp_entry(ut_line, ut_id);
1110         if (u_tmp_p != NULL) {
1111             memset(&u_tmp, 0, sizeof(u_tmp));
1112             strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
1113             strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
1114             memset(&u_tmp.ut_user, 0, sizeof(u_tmp.ut_user));
1115             memset(&u_tmp.ut_host, 0, sizeof(u_tmp.ut_host));
1116             u_tmp.ut_addr = 0;
1117             u_tmp.ut_type = DEAD_PROCESS;      /* `old' login process */
1118             u_tmp.ut_pid = 0;
1119             u_tmp.ut_time = time(NULL);
1120             setutent();                        /* rewind file (replace old) */
1121             pututline(&u_tmp);                 /* mark as dead */
1122 
1123             write_wtmp(&u_tmp, place, err_descr);
1124         }
1125     }
1126 
1127     /* clean up */
1128     memset(ut_line, 0, UT_LINESIZE);
1129     memset(ut_id, 0, UT_IDSIZE);
1130 
1131     endutent();                                /* close utmp file */
1132     memset(&u_tmp, 0, sizeof(u_tmp));          /* reset entry */
1133 
1134     return 0;
1135 }
1136 
1137 /*
1138  * Returns:
1139  *   0     ok,
1140  *   1     non-fatal error
1141  *  -1     fatal error
1142  * place and err_descr will be set
1143  * Be careful: the function indirectly uses alarm().
1144  */
utmp_open_session(pid_t pid,int * retval,const char ** place,const char ** err_descr)1145 static int utmp_open_session(pid_t pid, int *retval,
1146 			     const char **place, const char **err_descr)
1147 {
1148     const char *user, *terminal, *rhost;
1149 
1150     *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
1151     if (*retval != PAM_SUCCESS) {
1152         return -1;
1153     }
1154     *retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
1155     if (retval != PAM_SUCCESS) {
1156         return -1;
1157     }
1158     *retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
1159     if (retval != PAM_SUCCESS) {
1160         rhost = NULL;
1161     }
1162 
1163     return utmp_do_open_session(user, terminal, rhost, pid, place, err_descr);
1164 }
1165 
utmp_close_session(const char ** place,const char ** err_descr)1166 static int utmp_close_session(const char **place, const char **err_descr)
1167 {
1168     int retval;
1169     const char *terminal;
1170 
1171     retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
1172     if (retval != PAM_SUCCESS) {
1173         *place = "pam_get_item(PAM_TTY)";
1174         *err_descr = pam_strerror(pamh, retval);
1175         return -1;
1176     }
1177 
1178     return utmp_do_close_session(terminal, place, err_descr);
1179 }
1180 
1181 /*
1182  * set_credentials raises the process and PAM credentials.
1183  */
set_credentials(cap_t all,int login,const char ** user_p,uid_t * uid_p,const char ** pw_shell,int * retval,const char ** place,const char ** err_descr)1184 static int set_credentials(cap_t all, int login,
1185 			   const char **user_p, uid_t *uid_p,
1186 			   const char **pw_shell, int *retval,
1187 			   const char **place, const char **err_descr)
1188 {
1189     const char *user;
1190     char *shell;
1191     cap_value_t csetgid = CAP_SETGID;
1192     cap_t current;
1193     int status;
1194     struct passwd *pw;
1195     uid_t uid;
1196 
1197     D(("get user from pam"));
1198     *place = "set_credentials";
1199     *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
1200     if (*retval != PAM_SUCCESS || user == NULL || *user == '\0') {
1201 	D(("error identifying user from PAM."));
1202 	*retval = PAM_USER_UNKNOWN;
1203 	return 1;
1204     }
1205     *user_p = user;
1206 
1207     /*
1208      * Add the LOGNAME and HOME environment variables.
1209      */
1210 
1211     pw = getpwnam(user);
1212     if (pw == NULL || (user = x_strdup(pw->pw_name)) == NULL) {
1213 	D(("failed to identify user"));
1214 	*retval = PAM_USER_UNKNOWN;
1215 	return 1;
1216     }
1217 
1218     uid = pw->pw_uid;
1219     if (uid == 0) {
1220 	D(("user is superuser: %s", user));
1221 	*retval = PAM_CRED_ERR;
1222 	return 1;
1223     }
1224     *uid_p = uid;
1225 
1226     shell = x_strdup(pw->pw_shell);
1227     if (shell == NULL) {
1228 	D(("user %s has no shell", user));
1229 	*retval = PAM_CRED_ERR;
1230 	return 1;
1231     }
1232 
1233     if (login) {
1234 	/* set LOGNAME, HOME */
1235 	if (pam_misc_setenv(pamh, "LOGNAME", user, 0) != PAM_SUCCESS) {
1236 	    D(("failed to set LOGNAME"));
1237 	    *retval = PAM_CRED_ERR;
1238 	    return 1;
1239 	}
1240     }
1241 
1242     /* bash requires these be set to the target user values */
1243     if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
1244 	D(("failed to set HOME"));
1245 	*retval = PAM_CRED_ERR;
1246 	return 1;
1247     }
1248     if (pam_misc_setenv(pamh, "USER", user, 0) != PAM_SUCCESS) {
1249 	D(("failed to set USER"));
1250 	*retval = PAM_CRED_ERR;
1251 	return 1;
1252     }
1253 
1254     current = cap_get_proc();
1255     cap_set_flag(current, CAP_EFFECTIVE, 1, &csetgid, CAP_SET);
1256     status = cap_set_proc(current);
1257     cap_free(current);
1258     if (status != 0) {
1259 	*err_descr = "unable to raise CAP_SETGID";
1260 	return 1;
1261     }
1262 
1263     /* initialize groups */
1264     if (initgroups(pw->pw_name, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) {
1265 	D(("failed to setgid etc"));
1266 	*retval = PAM_PERM_DENIED;
1267 	return 1;
1268     }
1269     *pw_shell = shell;
1270 
1271     pw = NULL;                                                  /* be tidy */
1272 
1273     D(("desired uid=%d", uid));
1274 
1275     /* assume user's identity - but preserve the permitted set */
1276     if (cap_setuid(uid) != 0) {
1277 	D(("failed to setuid: %v", strerror(errno)));
1278 	*retval = PAM_PERM_DENIED;
1279 	return 1;
1280     }
1281 
1282     /*
1283      * Next, we call the PAM framework to add/enhance the credentials
1284      * of this user [it may change the user's home directory in the
1285      * pam_env, and add supplemental group memberships...].
1286      */
1287     D(("setting credentials"));
1288     if (cap_set_proc(all)) {
1289 	D(("failed to raise all capabilities"));
1290 	*retval = PAM_PERM_DENIED;
1291 	return 1;
1292     }
1293 
1294     D(("calling pam_setcred to establish credentials"));
1295     *retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
1296 
1297     return (*retval != PAM_SUCCESS);
1298 }
1299 
1300 /*
1301  * open_session invokes the open session PAM stack.
1302  */
open_session(cap_t all,int * retval,const char ** place,const char ** err_descr)1303 static int open_session(cap_t all, int *retval, const char **place,
1304 			const char **err_descr)
1305 {
1306     /* Open the su-session */
1307     *place = "pam_open_session";
1308     if (cap_set_proc(all)) {
1309 	D(("failed to raise t_caps capabilities"));
1310 	*err_descr = "capability setting failed";
1311 	return 1;
1312     }
1313     *retval = pam_open_session(pamh, 0);     /* Must take care to close */
1314     if (*retval != PAM_SUCCESS) {
1315 	return 1;
1316     }
1317     return 0;
1318 }
1319 
1320 /* ------ shell invoker ----------------------- */
1321 
launch_callback_fn(void * h)1322 static int launch_callback_fn(void *h)
1323 {
1324     pam_handle_t *my_pamh = h;
1325     int retval;
1326 
1327     D(("pam_end"));
1328     retval = pam_end(my_pamh, PAM_SUCCESS | PAM_DATA_SILENT);
1329     pamh = NULL;
1330     if (retval != PAM_SUCCESS) {
1331 	return -1;
1332     }
1333 
1334     /*
1335      * Restore a signal status: information if the signal is ignored
1336      * is inherited across exec() call.  (SAW)
1337      */
1338     enable_terminal_signals();
1339 
1340 #ifdef PAM_DEBUG
1341     cap_iab_t iab = cap_iab_get_proc();
1342     char *text = cap_iab_to_text(iab);
1343     D(("iab = %s", text));
1344     cap_free(text);
1345     cap_free(iab);
1346     cap_t cap = cap_get_proc();
1347     text = cap_to_text(cap, NULL);
1348     D(("cap = %s", text));
1349     cap_free(text);
1350     cap_free(cap);
1351 #endif
1352 
1353     D(("about to launch"));
1354     return 0;
1355 }
1356 
1357 /* Returns PAM_<STATUS>. */
perform_launch_and_cleanup(cap_t all,int is_login,const char * user,const char * shell,const char * command)1358 static int perform_launch_and_cleanup(cap_t all, int is_login, const char *user,
1359 				      const char *shell, const char *command)
1360 {
1361     int status;
1362     const char *home;
1363     const char * const * shell_args;
1364     char * const * shell_env;
1365     cap_launch_t launcher;
1366     pid_t child;
1367     cap_iab_t iab;
1368 
1369     /*
1370      * Break up the shell command into a command and arguments
1371      */
1372     shell_args = build_shell_args(shell, is_login, command);
1373     if (shell_args == NULL) {
1374 	D(("failed to compute shell arguments"));
1375 	return PAM_SYSTEM_ERR;
1376     }
1377 
1378     home = pam_getenv(pamh, "HOME");
1379     if ( !home || home[0] == '\0' ) {
1380 	fprintf(stderr, "setting home directory for %s to %s\n",
1381 		user, DEFAULT_HOME);
1382 	home = DEFAULT_HOME;
1383 	if (pam_misc_setenv(pamh, "HOME", home, 0) != PAM_SUCCESS) {
1384 	    D(("unable to set $HOME"));
1385 	    fprintf(stderr,
1386 		    "Warning: unable to set HOME environment variable\n");
1387 	}
1388     }
1389     if (is_login) {
1390 	if (chdir(home) && chdir(DEFAULT_HOME)) {
1391 	    D(("failed to change directory"));
1392 	    return PAM_SYSTEM_ERR;
1393 	}
1394     }
1395 
1396     shell_env = pam_getenvlist(pamh);
1397     if (shell_env == NULL) {
1398 	D(("failed to obtain environment for child"));
1399 	return PAM_SYSTEM_ERR;
1400     }
1401 
1402     iab = cap_iab_get_proc();
1403     if (iab == NULL) {
1404 	D(("failed to read IAB value of process"));
1405 	return PAM_SYSTEM_ERR;
1406     }
1407 
1408     launcher = cap_new_launcher(shell_args[0],
1409 				(const char * const *) &shell_args[1],
1410 				(const char * const *) shell_env);
1411     if (launcher == NULL) {
1412 	D(("failed to initialize launcher"));
1413 	return PAM_SYSTEM_ERR;
1414     }
1415     cap_launcher_callback(launcher, launch_callback_fn);
1416 
1417     child = cap_launch(launcher, pamh);
1418     cap_free(launcher);
1419 
1420     if (cap_set_proc(all) != 0) {
1421 	D(("failed to restore process capabilities"));
1422 	return PAM_SYSTEM_ERR;
1423     }
1424 
1425     /* job control is off for login sessions */
1426     prepare_for_job_control(!is_login && command != NULL);
1427 
1428     if (cap_setuid(TEMP_UID) != 0) {
1429 	fprintf(stderr, "[failed to change monitor UID=%d]\n", TEMP_UID);
1430     }
1431 
1432     /* wait for child to terminate */
1433     status = wait_for_child(child);
1434     if (status != 0) {
1435 	D(("shell returned %d", status));
1436     }
1437     return status;
1438 }
1439 
close_session(cap_t all)1440 static void close_session(cap_t all)
1441 {
1442     int retval;
1443 
1444     D(("session %p closing", pamh));
1445     if (cap_set_proc(all)) {
1446 	fprintf(stderr, "WARNING: could not raise all caps\n");
1447     }
1448     retval = pam_close_session(pamh, 0);
1449     if (retval != PAM_SUCCESS) {
1450 	fprintf(stderr, "WARNING: could not close session\n\t%s\n",
1451 		pam_strerror(pamh,retval));
1452     }
1453 }
1454 
1455 /* -------------------------------------------- */
1456 /* ------ the application itself -------------- */
1457 /* -------------------------------------------- */
1458 
main(int argc,char * argv[])1459 int main(int argc, char *argv[])
1460 {
1461     int retcode, is_login, status;
1462     int retval, final_retval; /* PAM_xxx return values */
1463     const char *command, *shell;
1464     uid_t uid;
1465     const char *place = NULL, *err_descr = NULL;
1466     cap_t all, t_caps;
1467     const char *user;
1468 
1469     all = cap_get_proc();
1470     cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED);
1471     cap_clear_flag(all, CAP_INHERITABLE);
1472 
1473     checkfds();
1474 
1475     /*
1476      * Check whether stdin is a terminal and store terminal modes for later.
1477      */
1478     store_terminal_modes();
1479 
1480     /* ---------- parse the argument list and --------- */
1481     /* ------ initialize the Linux-PAM interface ------ */
1482     {
1483 	parse_command_line(argc, argv, &is_login, &user, &command);
1484 	place = "do_pam_init";
1485 	do_pam_init(user, is_login);   /* call pam_start and set PAM items */
1486 	user = NULL;                   /* transient until PAM_USER defined */
1487     }
1488 
1489     /*
1490      * Turn off terminal signals - this is to be sure that su gets a
1491      * chance to call pam_end() and restore the terminal modes in
1492      * spite of the frustrated user pressing Ctrl-C.
1493      */
1494     disable_terminal_signals();
1495 
1496     /*
1497      * Random exits from here are strictly prohibited :-) (SAW) AGM
1498      * achieves this with goto's and a single exit at the end of main.
1499      */
1500     status = 1;                       /* fake exit status of a child */
1501     err_descr = NULL;                 /* errors haven't happened */
1502 
1503     if (make_process_unkillable(&place, &err_descr) != 0) {
1504 	goto su_exit;
1505     }
1506 
1507     if (authenticate_user(all, &retval, &place, &err_descr) != 0) {
1508 	goto auth_exit;
1509     }
1510 
1511     /*
1512      * The user is valid, but should they have access at this
1513      * time?
1514      */
1515     if (user_accounting(all, &retval, &place, &err_descr) != 0) {
1516 	goto auth_exit;
1517     }
1518 
1519     D(("su attempt is confirmed as authorized"));
1520 
1521     if (set_credentials(all, is_login, &user, &uid, &shell,
1522 			&retval, &place, &err_descr) != 0) {
1523 	D(("failed to set credentials"));
1524 	goto auth_exit;
1525     }
1526 
1527     /*
1528      * ... setup terminal, ...
1529      */
1530     retcode = change_terminal_owner(uid, is_login, &place, &err_descr);
1531     if (retcode > 0) {
1532 	fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
1533 	err_descr = NULL; /* forget about the problem */
1534     } else if (retcode < 0) {
1535 	D(("terminal owner to uid=%d change failed", uid));
1536 	goto auth_exit;
1537     }
1538 
1539     /*
1540      * Here the IAB value is fixed and may differ from all's
1541      * Inheritable value. So synthesize what we need to proceed in the
1542      * child, for now, in this current process.
1543      */
1544     place = "preserving inheritable parts";
1545     t_caps = cap_get_proc();
1546     if (t_caps == NULL) {
1547 	D(("failed to read capabilities"));
1548 	err_descr = "capability read failed";
1549 	goto delete_cred;
1550     }
1551     if (cap_fill(t_caps, CAP_EFFECTIVE, CAP_PERMITTED)) {
1552 	D(("failed to fill effective bits"));
1553 	err_descr = "capability fill failed";
1554 	goto delete_cred;
1555     }
1556 
1557     /*
1558      * ... make [uw]tmp entries.
1559      */
1560     if (is_login) {
1561 	/*
1562 	 * Note: we use the parent pid as a session identifier for
1563 	 * the logging.
1564 	 */
1565 	retcode = utmp_open_session(getpid(), &retval, &place, &err_descr);
1566 	if (retcode > 0) {
1567 	    fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
1568 	    err_descr = NULL; /* forget about this non-critical problem */
1569 	} else if (retcode < 0) {
1570 	    goto delete_cred;
1571 	}
1572     }
1573 
1574 #ifdef PAM_DEBUG
1575     cap_iab_t iab = cap_iab_get_proc();
1576     char *text = cap_iab_to_text(iab);
1577     D(("pre-session open iab = %s", text));
1578     cap_free(text);
1579     cap_free(iab);
1580 #endif
1581 
1582     if (open_session(t_caps, &retval, &place, &err_descr) != 0) {
1583 	goto utmp_closer;
1584     }
1585 
1586     status = perform_launch_and_cleanup(all, is_login, user, shell, command);
1587     close_session(all);
1588 
1589 utmp_closer:
1590     if (is_login) {
1591 	/* do [uw]tmp cleanup */
1592 	retcode = utmp_close_session(&place, &err_descr);
1593 	if (retcode) {
1594 	    fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
1595 	}
1596     }
1597 
1598 delete_cred:
1599     D(("delete credentials"));
1600     if (cap_set_proc(all)) {
1601 	D(("failed to raise all capabilities"));
1602     }
1603     retcode = pam_setcred(pamh, PAM_DELETE_CRED);
1604     if (retcode != PAM_SUCCESS) {
1605 	fprintf(stderr, "WARNING: could not delete credentials\n\t%s\n",
1606 		pam_strerror(pamh, retcode));
1607     }
1608 
1609     D(("return terminal to local control"));
1610     restore_terminal_owner();
1611 
1612 auth_exit:
1613     D(("for clean up we restore the launching user"));
1614     make_process_killable();
1615 
1616     D(("all done - closing down pam"));
1617     if (retval != PAM_SUCCESS) {      /* PAM has failed */
1618 	fprintf(stderr, PAM_APP_NAME ": %s\n", pam_strerror(pamh, retval));
1619 	final_retval = PAM_ABORT;
1620     } else if (err_descr != NULL) {   /* a system error has happened */
1621 	fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
1622 	final_retval = PAM_ABORT;
1623     } else {
1624 	final_retval = PAM_SUCCESS;
1625     }
1626     (void) pam_end(pamh, final_retval);
1627     pamh = NULL;
1628 
1629     if (restore_terminal_modes() != 0 && !status) {
1630 	status = 1;
1631     }
1632 
1633 su_exit:
1634     if (status != 0) {
1635 	perror(PAM_APP_NAME " failed");
1636     }
1637     exit(status);                 /* transparent exit */
1638 }
1639