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