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