]>
Commit | Line | Data |
---|---|---|
b1195aa0 KZ |
1 | /* |
2 | * su(1) for Linux. Run a shell with substitute user and group IDs. | |
3 | * | |
4 | * Copyright (C) 1992-2006 Free Software Foundation, Inc. | |
5 | * Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg | |
eb7d0ad0 | 6 | * Copyright (C) 2016-2017 Karel Zak <kzak@redhat.com> |
b1195aa0 KZ |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the Free | |
10 | * Software Foundation; either version 2, or (at your option) any later | |
11 | * version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | * more details. You should have received a copy of the GNU General Public | |
17 | * License along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | |
19 | * USA. | |
20 | * | |
21 | * | |
22 | * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>. | |
23 | */ | |
cf1a99da KZ |
24 | #include <stdio.h> |
25 | #include <getopt.h> | |
26 | #include <sys/types.h> | |
27 | #include <pwd.h> | |
28 | #include <grp.h> | |
29 | #include <security/pam_appl.h> | |
fe2c9909 | 30 | #ifdef HAVE_SECURITY_PAM_MISC_H |
b1195aa0 | 31 | # include <security/pam_misc.h> |
fe2c9909 | 32 | #elif defined(HAVE_SECURITY_OPENPAM_H) |
b1195aa0 | 33 | # include <security/openpam.h> |
fe2c9909 | 34 | #endif |
cf1a99da KZ |
35 | #include <signal.h> |
36 | #include <sys/wait.h> | |
37 | #include <syslog.h> | |
b4b919fe | 38 | #include <utmpx.h> |
cf1a99da | 39 | |
b923bdbe | 40 | #ifdef HAVE_PTY |
eb7d0ad0 KZ |
41 | # include <pty.h> |
42 | # include <poll.h> | |
43 | # include <sys/signalfd.h> | |
b923bdbe | 44 | # include "pty-session.h" |
eb7d0ad0 KZ |
45 | # define USE_PTY |
46 | #endif | |
47 | ||
cf1a99da KZ |
48 | #include "err.h" |
49 | ||
50 | #include <stdbool.h> | |
b1195aa0 | 51 | |
cf1a99da KZ |
52 | #include "c.h" |
53 | #include "xalloc.h" | |
54 | #include "nls.h" | |
55 | #include "pathnames.h" | |
56 | #include "env.h" | |
4e183497 | 57 | #include "closestream.h" |
75efef98 | 58 | #include "strv.h" |
c74a7af1 | 59 | #include "strutils.h" |
bbc5a5ea | 60 | #include "ttyutils.h" |
032d759a | 61 | #include "pwdutils.h" |
75efef98 | 62 | #include "optutils.h" |
cf1a99da | 63 | |
b1195aa0 KZ |
64 | #include "logindefs.h" |
65 | #include "su-common.h" | |
66 | ||
2260e493 KZ |
67 | #include "debug.h" |
68 | ||
69 | UL_DEBUG_DEFINE_MASK(su); | |
70 | UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES; | |
71 | ||
72 | #define SU_DEBUG_INIT (1 << 1) | |
73 | #define SU_DEBUG_PAM (1 << 2) | |
74 | #define SU_DEBUG_PARENT (1 << 3) | |
75 | #define SU_DEBUG_TTY (1 << 4) | |
76 | #define SU_DEBUG_LOG (1 << 5) | |
77 | #define SU_DEBUG_MISC (1 << 6) | |
78 | #define SU_DEBUG_SIG (1 << 7) | |
eb7d0ad0 | 79 | #define SU_DEBUG_PTY (1 << 8) |
2260e493 KZ |
80 | #define SU_DEBUG_ALL 0xFFFF |
81 | ||
82 | #define DBG(m, x) __UL_DBG(su, SU_DEBUG_, m, x) | |
83 | #define ON_DBG(m, x) __UL_DBG_CALL(su, SU_DEBUG_, m, x) | |
84 | ||
85 | ||
cf1a99da | 86 | /* name of the pam configuration files. separate configs for su and su - */ |
7ec6adb1 KZ |
87 | #define PAM_SRVNAME_SU "su" |
88 | #define PAM_SRVNAME_SU_L "su-l" | |
89 | ||
90 | #define PAM_SRVNAME_RUNUSER "runuser" | |
91 | #define PAM_SRVNAME_RUNUSER_L "runuser-l" | |
92 | ||
37410713 KZ |
93 | #define _PATH_LOGINDEFS_SU "/etc/default/su" |
94 | #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser" | |
cf1a99da KZ |
95 | |
96 | #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS) | |
97 | ||
cf1a99da KZ |
98 | /* The shell to run if none is given in the user's passwd entry. */ |
99 | #define DEFAULT_SHELL "/bin/sh" | |
100 | ||
101 | /* The user to become if none is specified. */ | |
102 | #define DEFAULT_USER "root" | |
103 | ||
104 | #ifndef HAVE_ENVIRON_DECL | |
105 | extern char **environ; | |
106 | #endif | |
107 | ||
665f36be KZ |
108 | enum { |
109 | SIGTERM_IDX = 0, | |
110 | SIGINT_IDX, | |
111 | SIGQUIT_IDX, | |
112 | ||
113 | SIGNALS_IDX_COUNT | |
114 | }; | |
115 | ||
832f5cd5 KZ |
116 | /* |
117 | * su/runuser control struct | |
118 | */ | |
119 | struct su_context { | |
120 | pam_handle_t *pamh; /* PAM handler */ | |
121 | struct pam_conv conv; /* PAM conversation */ | |
122 | ||
032d759a KZ |
123 | struct passwd *pwd; /* new user info */ |
124 | char *pwdbuf; /* pwd strings */ | |
125 | ||
302b7b65 KZ |
126 | const char *tty_name; /* tty_path without /dev prefix */ |
127 | const char *tty_number; /* end of the tty_path */ | |
128 | ||
94c6730b | 129 | char *new_user; /* wanted user */ |
fcf841f8 | 130 | char *old_user; /* original user */ |
94c6730b | 131 | |
44f36ad1 | 132 | pid_t child; /* fork() baby */ |
13ee2f4d | 133 | int childstatus; /* wait() status */ |
44f36ad1 | 134 | |
75efef98 KZ |
135 | char **env_whitelist_names; /* environment whitelist */ |
136 | char **env_whitelist_vals; | |
137 | ||
665f36be | 138 | struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */ |
eb7d0ad0 | 139 | #ifdef USE_PTY |
b923bdbe | 140 | struct ul_pty *pty; /* pseudo terminal handler (for --pty) */ |
eb7d0ad0 | 141 | #endif |
832f5cd5 | 142 | unsigned int runuser :1, /* flase=su, true=runuser */ |
94c6730b | 143 | runuser_uopt :1, /* runuser -u specified */ |
302b7b65 | 144 | isterm :1, /* is stdin terminal? */ |
832f5cd5 KZ |
145 | fast_startup :1, /* pass the `-f' option to the subshell. */ |
146 | simulate_login :1, /* simulate a login instead of just starting a shell. */ | |
147 | change_environment :1, /* change some environment vars to indicate the user su'd to.*/ | |
148 | same_session :1, /* don't call setsid() with a command. */ | |
149 | suppress_pam_info:1, /* don't print PAM info messages (Last login, etc.). */ | |
150 | pam_has_session :1, /* PAM session opened */ | |
151 | pam_has_cred :1, /* PAM cred established */ | |
b923bdbe | 152 | force_pty :1, /* create pseudo-terminal */ |
832f5cd5 KZ |
153 | restricted :1; /* false for root user */ |
154 | }; | |
cf1a99da | 155 | |
cf1a99da | 156 | |
cf1a99da | 157 | static sig_atomic_t volatile caught_signal = false; |
cf1a99da | 158 | |
b9a92282 KZ |
159 | /* Signal handler for parent process. */ |
160 | static void | |
161 | su_catch_sig(int sig) | |
162 | { | |
163 | caught_signal = sig; | |
164 | } | |
165 | ||
2260e493 KZ |
166 | static void su_init_debug(void) |
167 | { | |
a15dca2f | 168 | __UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG); |
2260e493 KZ |
169 | } |
170 | ||
302b7b65 KZ |
171 | static void init_tty(struct su_context *su) |
172 | { | |
173 | su->isterm = isatty(STDIN_FILENO) ? 1 : 0; | |
73afd3f8 | 174 | DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false")); |
302b7b65 KZ |
175 | if (su->isterm) |
176 | get_terminal_name(NULL, &su->tty_name, &su->tty_number); | |
177 | } | |
178 | ||
13ee2f4d KZ |
179 | /* |
180 | * Note, this function has to be possible call more than once. If the child is | |
181 | * already dead than it returns saved result from the previous call. | |
182 | */ | |
eb7d0ad0 KZ |
183 | static int wait_for_child(struct su_context *su) |
184 | { | |
bdd43357 | 185 | pid_t pid = (pid_t) -1; |
eb7d0ad0 KZ |
186 | int status = 0; |
187 | ||
13ee2f4d KZ |
188 | if (su->child == (pid_t) -1) |
189 | return su->childstatus; | |
190 | ||
eb7d0ad0 | 191 | if (su->child != (pid_t) -1) { |
e9fde3e9 KZ |
192 | /* |
193 | * The "su" parent process spends all time here in waitpid(), | |
194 | * but "su --pty" uses pty_proxy_master() and waitpid() is only | |
195 | * called to pick up child status or to react to SIGSTOP. | |
196 | */ | |
eb7d0ad0 KZ |
197 | DBG(SIG, ul_debug("waiting for child [%d]...", su->child)); |
198 | for (;;) { | |
199 | pid = waitpid(su->child, &status, WUNTRACED); | |
200 | ||
201 | if (pid != (pid_t) - 1 && WIFSTOPPED(status)) { | |
e9fde3e9 | 202 | DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session")); |
eb7d0ad0 KZ |
203 | kill(getpid(), SIGSTOP); |
204 | /* once we get here, we must have resumed */ | |
205 | kill(pid, SIGCONT); | |
e9fde3e9 KZ |
206 | DBG(SIG, ul_debug(" session resumed -- continue")); |
207 | #ifdef USE_PTY | |
208 | /* Let's go back to pty_proxy_master() */ | |
b923bdbe | 209 | if (su->force_pty && ul_pty_is_running(su->pty)) { |
e9fde3e9 KZ |
210 | DBG(SIG, ul_debug(" leaving on child SIGSTOP")); |
211 | return 0; | |
212 | } | |
213 | #endif | |
eb7d0ad0 KZ |
214 | } else |
215 | break; | |
216 | } | |
217 | } | |
218 | if (pid != (pid_t) -1) { | |
219 | if (WIFSIGNALED(status)) { | |
220 | fprintf(stderr, "%s%s\n", | |
221 | strsignal(WTERMSIG(status)), | |
222 | WCOREDUMP(status) ? _(" (core dumped)") | |
223 | : ""); | |
224 | status = WTERMSIG(status) + 128; | |
225 | } else | |
226 | status = WEXITSTATUS(status); | |
227 | ||
228 | DBG(SIG, ul_debug("child %d is dead", su->child)); | |
229 | su->child = (pid_t) -1; /* Don't use the PID anymore! */ | |
13ee2f4d | 230 | su->childstatus = status; |
b923bdbe KZ |
231 | #ifdef USE_PTY |
232 | /* inform pty suff that we have no child anymore */ | |
233 | if (su->force_pty) | |
234 | ul_pty_set_child(su->pty, (pid_t) -1); | |
235 | #endif | |
eb7d0ad0 KZ |
236 | } else if (caught_signal) |
237 | status = caught_signal + 128; | |
238 | else | |
239 | status = 1; | |
240 | ||
13ee2f4d | 241 | DBG(SIG, ul_debug("child status=%d", status)); |
eb7d0ad0 KZ |
242 | return status; |
243 | } | |
244 | ||
eb7d0ad0 | 245 | #ifdef USE_PTY |
bdd43357 KZ |
246 | static void wait_for_child_cb( |
247 | void *data, | |
248 | pid_t child __attribute__((__unused__))) | |
eb7d0ad0 | 249 | { |
b923bdbe | 250 | wait_for_child((struct su_context *) data); |
eb7d0ad0 | 251 | } |
b923bdbe | 252 | #endif |
eb7d0ad0 | 253 | |
cf1a99da KZ |
254 | /* Log the fact that someone has run su to the user given by PW; |
255 | if SUCCESSFUL is true, they gave the correct password, etc. */ | |
256 | ||
032d759a | 257 | static void log_syslog(struct su_context *su, bool successful) |
cf1a99da | 258 | { |
2260e493 KZ |
259 | DBG(LOG, ul_debug("syslog logging")); |
260 | ||
983652ab KZ |
261 | openlog(program_invocation_short_name, 0, LOG_AUTH); |
262 | syslog(LOG_NOTICE, "%s(to %s) %s on %s", | |
263 | successful ? "" : | |
832f5cd5 | 264 | su->runuser ? "FAILED RUNUSER " : "FAILED SU ", |
94c6730b | 265 | su->new_user, su->old_user ? : "", |
302b7b65 | 266 | su->tty_name ? : "none"); |
983652ab KZ |
267 | closelog(); |
268 | } | |
c74a7af1 KZ |
269 | |
270 | /* | |
271 | * Log failed login attempts in _PATH_BTMP if that exists. | |
272 | */ | |
032d759a | 273 | static void log_btmp(struct su_context *su) |
c74a7af1 | 274 | { |
b4b919fe | 275 | struct utmpx ut; |
c74a7af1 | 276 | struct timeval tv; |
c74a7af1 | 277 | |
2260e493 KZ |
278 | DBG(LOG, ul_debug("btmp logging")); |
279 | ||
c74a7af1 | 280 | memset(&ut, 0, sizeof(ut)); |
7f76bc8a | 281 | str2memcpy(ut.ut_user, |
032d759a | 282 | su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)", |
c74a7af1 KZ |
283 | sizeof(ut.ut_user)); |
284 | ||
302b7b65 | 285 | if (su->tty_number) |
7f76bc8a | 286 | str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id)); |
302b7b65 | 287 | if (su->tty_name) |
7f76bc8a | 288 | str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line)); |
c74a7af1 | 289 | |
c74a7af1 KZ |
290 | gettimeofday(&tv, NULL); |
291 | ut.ut_tv.tv_sec = tv.tv_sec; | |
292 | ut.ut_tv.tv_usec = tv.tv_usec; | |
c74a7af1 KZ |
293 | ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ |
294 | ut.ut_pid = getpid(); | |
295 | ||
b4b919fe | 296 | updwtmpx(_PATH_BTMP, &ut); |
c74a7af1 KZ |
297 | } |
298 | ||
b9a92282 KZ |
299 | static int supam_conv( int num_msg, |
300 | const struct pam_message **msg, | |
301 | struct pam_response **resp, | |
302 | void *data) | |
fb4edda7 | 303 | { |
b9a92282 | 304 | struct su_context *su = (struct su_context *) data; |
832f5cd5 KZ |
305 | |
306 | if (su->suppress_pam_info | |
b9a92282 KZ |
307 | && num_msg == 1 |
308 | && msg && msg[0]->msg_style == PAM_TEXT_INFO) | |
fb4edda7 | 309 | return PAM_SUCCESS; |
832f5cd5 | 310 | |
fe2c9909 | 311 | #ifdef HAVE_SECURITY_PAM_MISC_H |
b9a92282 | 312 | return misc_conv(num_msg, msg, resp, data); |
fe2c9909 | 313 | #elif defined(HAVE_SECURITY_OPENPAM_H) |
b9a92282 | 314 | return openpam_ttyconv(num_msg, msg, resp, data); |
fe2c9909 | 315 | #endif |
fb4edda7 KZ |
316 | } |
317 | ||
b9a92282 | 318 | static void supam_cleanup(struct su_context *su, int retcode) |
cf1a99da | 319 | { |
e402d137 | 320 | const int errsv = errno; |
cf1a99da | 321 | |
2260e493 KZ |
322 | DBG(PAM, ul_debug("cleanup")); |
323 | ||
832f5cd5 KZ |
324 | if (su->pam_has_session) |
325 | pam_close_session(su->pamh, 0); | |
832f5cd5 KZ |
326 | if (su->pam_has_cred) |
327 | pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT); | |
832f5cd5 | 328 | pam_end(su->pamh, retcode); |
e402d137 | 329 | errno = errsv; |
cf1a99da KZ |
330 | } |
331 | ||
cf1a99da | 332 | |
b9a92282 | 333 | static void supam_export_environment(struct su_context *su) |
cf1a99da | 334 | { |
2260e493 KZ |
335 | char **env; |
336 | ||
337 | DBG(PAM, ul_debug("init environ[]")); | |
338 | ||
983652ab | 339 | /* This is a copy but don't care to free as we exec later anyways. */ |
2260e493 | 340 | env = pam_getenvlist(su->pamh); |
b9a92282 | 341 | |
983652ab KZ |
342 | while (env && *env) { |
343 | if (putenv(*env) != 0) | |
b9a92282 | 344 | err(EXIT_FAILURE, _("failed to modify environment")); |
983652ab KZ |
345 | env++; |
346 | } | |
cf1a99da KZ |
347 | } |
348 | ||
032d759a | 349 | static void supam_authenticate(struct su_context *su) |
dc5bfb71 | 350 | { |
302b7b65 | 351 | const char *srvname = NULL; |
e402d137 | 352 | int rc; |
dc5bfb71 KZ |
353 | |
354 | srvname = su->runuser ? | |
355 | (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) : | |
356 | (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU); | |
357 | ||
2260e493 KZ |
358 | DBG(PAM, ul_debug("start [name: %s]", srvname)); |
359 | ||
e402d137 KZ |
360 | rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh); |
361 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
362 | goto done; |
363 | ||
302b7b65 | 364 | if (su->tty_name) { |
e402d137 KZ |
365 | rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name); |
366 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
367 | goto done; |
368 | } | |
94c6730b | 369 | if (su->old_user) { |
e402d137 KZ |
370 | rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user); |
371 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
372 | goto done; |
373 | } | |
dc5bfb71 KZ |
374 | if (su->runuser) { |
375 | /* | |
376 | * This is the only difference between runuser(1) and su(1). The command | |
377 | * runuser(1) does not required authentication, because user is root. | |
378 | */ | |
379 | if (su->restricted) | |
380 | errx(EXIT_FAILURE, _("may not be used by non-root users")); | |
381 | return; | |
382 | } | |
383 | ||
e402d137 KZ |
384 | rc = pam_authenticate(su->pamh, 0); |
385 | if (is_pam_failure(rc)) | |
dc5bfb71 KZ |
386 | goto done; |
387 | ||
e402d137 KZ |
388 | /* Check password expiration and offer option to change it. */ |
389 | rc = pam_acct_mgmt(su->pamh, 0); | |
390 | if (rc == PAM_NEW_AUTHTOK_REQD) | |
391 | rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
dc5bfb71 | 392 | done: |
e402d137 | 393 | log_syslog(su, !is_pam_failure(rc)); |
dc5bfb71 | 394 | |
e402d137 | 395 | if (is_pam_failure(rc)) { |
dc5bfb71 KZ |
396 | const char *msg; |
397 | ||
2260e493 | 398 | DBG(PAM, ul_debug("authentication failed")); |
032d759a | 399 | log_btmp(su); |
dc5bfb71 | 400 | |
e402d137 KZ |
401 | msg = pam_strerror(su->pamh, rc); |
402 | pam_end(su->pamh, rc); | |
dc5bfb71 | 403 | sleep(getlogindefs_num("FAIL_DELAY", 1)); |
99c1ebee | 404 | errx(EXIT_FAILURE, "%s", msg ? msg : _("authentication failed")); |
dc5bfb71 KZ |
405 | } |
406 | } | |
407 | ||
a4440cd1 | 408 | static void supam_open_session(struct su_context *su) |
cf1a99da | 409 | { |
2260e493 KZ |
410 | int rc; |
411 | ||
412 | DBG(PAM, ul_debug("opening session")); | |
983652ab | 413 | |
2260e493 | 414 | rc = pam_open_session(su->pamh, 0); |
e402d137 KZ |
415 | if (is_pam_failure(rc)) { |
416 | supam_cleanup(su, rc); | |
983652ab | 417 | errx(EXIT_FAILURE, _("cannot open session: %s"), |
e402d137 | 418 | pam_strerror(su->pamh, rc)); |
983652ab | 419 | } else |
832f5cd5 | 420 | su->pam_has_session = 1; |
a4440cd1 KZ |
421 | } |
422 | ||
305ef556 | 423 | static void parent_setup_signals(struct su_context *su) |
a4440cd1 | 424 | { |
a4440cd1 | 425 | sigset_t ourset; |
983652ab | 426 | |
5fc211d2 | 427 | /* |
8ce9c386 KZ |
428 | * Signals setup |
429 | * | |
5fc211d2 | 430 | * 1) block all signals |
5fc211d2 | 431 | */ |
b09e7ea8 | 432 | DBG(SIG, ul_debug("initialize signals")); |
b09e7ea8 | 433 | |
983652ab | 434 | sigfillset(&ourset); |
0214f438 | 435 | if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { |
983652ab KZ |
436 | warn(_("cannot block signals")); |
437 | caught_signal = true; | |
cf1a99da | 438 | } |
5fc211d2 | 439 | |
983652ab KZ |
440 | if (!caught_signal) { |
441 | struct sigaction action; | |
442 | action.sa_handler = su_catch_sig; | |
443 | sigemptyset(&action.sa_mask); | |
444 | action.sa_flags = 0; | |
8ce9c386 | 445 | |
983652ab | 446 | sigemptyset(&ourset); |
5fc211d2 | 447 | |
8ce9c386 | 448 | /* 2a) add wanted signals to the mask (for session) */ |
5fc211d2 KZ |
449 | if (!su->same_session |
450 | && (sigaddset(&ourset, SIGINT) | |
451 | || sigaddset(&ourset, SIGQUIT))) { | |
452 | ||
8ce9c386 | 453 | warn(_("cannot initialize signal mask for session")); |
5fc211d2 | 454 | caught_signal = true; |
983652ab | 455 | } |
8ce9c386 | 456 | /* 2b) add wanted generic signals to the mask */ |
5fc211d2 KZ |
457 | if (!caught_signal |
458 | && (sigaddset(&ourset, SIGTERM) | |
8ce9c386 | 459 | || sigaddset(&ourset, SIGALRM))) { |
5fc211d2 | 460 | |
8ce9c386 | 461 | warn(_("cannot initialize signal mask")); |
983652ab KZ |
462 | caught_signal = true; |
463 | } | |
8ce9c386 KZ |
464 | |
465 | /* 3a) set signal handlers (for session) */ | |
5fc211d2 KZ |
466 | if (!caught_signal |
467 | && !su->same_session | |
665f36be KZ |
468 | && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX]) |
469 | || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) { | |
5fc211d2 | 470 | |
8ce9c386 KZ |
471 | warn(_("cannot set signal handler for session")); |
472 | caught_signal = true; | |
473 | } | |
474 | ||
475 | /* 3b) set signal handlers */ | |
476 | if (!caught_signal | |
665f36be | 477 | && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) { |
8ce9c386 | 478 | |
983652ab KZ |
479 | warn(_("cannot set signal handler")); |
480 | caught_signal = true; | |
481 | } | |
8ce9c386 KZ |
482 | |
483 | /* 4) unblock wanted signals */ | |
484 | if (!caught_signal | |
485 | && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) { | |
486 | ||
487 | warn(_("cannot set signal mask")); | |
488 | caught_signal = true; | |
489 | } | |
983652ab | 490 | } |
305ef556 KZ |
491 | } |
492 | ||
493 | ||
494 | static void create_watching_parent(struct su_context *su) | |
495 | { | |
496 | int status; | |
497 | ||
498 | DBG(MISC, ul_debug("forking...")); | |
ae6e2537 | 499 | #ifdef USE_PTY |
b923bdbe KZ |
500 | if (su->force_pty) { |
501 | struct ul_pty_callbacks *cb; | |
502 | ||
b923bdbe KZ |
503 | /* set callbacks */ |
504 | ul_pty_set_callback_data(su->pty, (void *) su); | |
0214f438 | 505 | |
b923bdbe KZ |
506 | cb = ul_pty_get_callbacks(su->pty); |
507 | cb->child_wait = wait_for_child_cb; | |
508 | cb->child_sigstop = wait_for_child_cb; | |
509 | ||
510 | /* create pty */ | |
511 | if (ul_pty_setup(su->pty)) | |
512 | err(EXIT_FAILURE, _("failed to create pseudo-terminal")); | |
513 | } | |
ae6e2537 | 514 | #endif |
eb7d0ad0 KZ |
515 | fflush(stdout); /* ??? */ |
516 | ||
305ef556 KZ |
517 | switch ((int) (su->child = fork())) { |
518 | case -1: /* error */ | |
519 | supam_cleanup(su, PAM_ABORT); | |
ae6e2537 | 520 | #ifdef USE_PTY |
b923bdbe KZ |
521 | if (su->force_pty) |
522 | ul_pty_cleanup(su->pty); | |
ae6e2537 | 523 | #endif |
305ef556 KZ |
524 | err(EXIT_FAILURE, _("cannot create child process")); |
525 | break; | |
526 | ||
527 | case 0: /* child */ | |
528 | return; | |
529 | ||
530 | default: /* parent */ | |
531 | DBG(MISC, ul_debug("child [pid=%d]", (int) su->child)); | |
532 | break; | |
533 | } | |
534 | ||
f4b03edb KZ |
535 | /* free unnecessary stuff */ |
536 | free_getlogindefs_data(); | |
305ef556 KZ |
537 | |
538 | /* In the parent watch the child. */ | |
539 | ||
540 | /* su without pam support does not have a helper that keeps | |
541 | sitting on any directory so let's go to /. */ | |
542 | if (chdir("/") != 0) | |
543 | warn(_("cannot change directory to %s"), "/"); | |
ae6e2537 | 544 | #ifdef USE_PTY |
b923bdbe KZ |
545 | if (su->force_pty) { |
546 | ul_pty_set_child(su->pty, su->child); | |
547 | ||
548 | if (ul_pty_proxy_master(su->pty) != 0) | |
549 | caught_signal = true; | |
550 | ||
551 | /* ul_pty_proxy_master() keeps classic signal handler are out of game */ | |
552 | caught_signal = ul_pty_get_delivered_signal(su->pty); | |
553 | ||
554 | ul_pty_cleanup(su->pty); | |
555 | } else | |
ae6e2537 | 556 | #endif |
eb7d0ad0 | 557 | parent_setup_signals(su); |
5fc211d2 KZ |
558 | |
559 | /* | |
560 | * Wait for child | |
561 | */ | |
b09e7ea8 | 562 | if (!caught_signal) |
44f36ad1 | 563 | status = wait_for_child(su); |
b09e7ea8 | 564 | else |
983652ab KZ |
565 | status = 1; |
566 | ||
13ee2f4d KZ |
567 | DBG(SIG, ul_debug("final child status=%d", status)); |
568 | ||
44f36ad1 | 569 | if (caught_signal && su->child != (pid_t)-1) { |
983652ab | 570 | fprintf(stderr, _("\nSession terminated, killing shell...")); |
44f36ad1 | 571 | kill(su->child, SIGTERM); |
dffab154 | 572 | } |
8960f3ae | 573 | |
b9a92282 | 574 | supam_cleanup(su, PAM_SUCCESS); |
983652ab KZ |
575 | |
576 | if (caught_signal) { | |
44f36ad1 | 577 | if (su->child != (pid_t)-1) { |
6b283282 KZ |
578 | DBG(SIG, ul_debug("killing child")); |
579 | sleep(2); | |
44f36ad1 | 580 | kill(su->child, SIGKILL); |
6b283282 KZ |
581 | fprintf(stderr, _(" ...killed.\n")); |
582 | } | |
983652ab KZ |
583 | |
584 | /* Let's terminate itself with the received signal. | |
585 | * | |
586 | * It seems that shells use WIFSIGNALED() rather than our exit status | |
587 | * value to detect situations when is necessary to cleanup (reset) | |
588 | * terminal settings (kzak -- Jun 2013). | |
589 | */ | |
2260e493 | 590 | DBG(SIG, ul_debug("restore signals setting")); |
983652ab KZ |
591 | switch (caught_signal) { |
592 | case SIGTERM: | |
665f36be | 593 | sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL); |
983652ab KZ |
594 | break; |
595 | case SIGINT: | |
665f36be | 596 | sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL); |
983652ab KZ |
597 | break; |
598 | case SIGQUIT: | |
665f36be | 599 | sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL); |
983652ab KZ |
600 | break; |
601 | default: | |
602 | /* just in case that signal stuff initialization failed and | |
603 | * caught_signal = true */ | |
604 | caught_signal = SIGKILL; | |
605 | break; | |
606 | } | |
2260e493 | 607 | DBG(SIG, ul_debug("self-send %d signal", caught_signal)); |
983652ab KZ |
608 | kill(getpid(), caught_signal); |
609 | } | |
2260e493 KZ |
610 | |
611 | DBG(MISC, ul_debug("exiting [rc=%d]", status)); | |
983652ab | 612 | exit(status); |
cf1a99da KZ |
613 | } |
614 | ||
75efef98 KZ |
615 | /* Adds @name from the current environment to the whitelist. If @name is not |
616 | * set then nothing is added to the whitelist and returns 1. | |
617 | */ | |
618 | static int env_whitelist_add(struct su_context *su, const char *name) | |
619 | { | |
620 | const char *env = getenv(name); | |
621 | ||
622 | if (!env) | |
623 | return 1; | |
624 | if (strv_extend(&su->env_whitelist_names, name)) | |
625 | err_oom(); | |
626 | if (strv_extend(&su->env_whitelist_vals, env)) | |
627 | err_oom(); | |
628 | return 0; | |
629 | } | |
630 | ||
631 | static int env_whitelist_setenv(struct su_context *su, int overwrite) | |
632 | { | |
633 | char **one; | |
634 | size_t i = 0; | |
635 | int rc; | |
636 | ||
637 | STRV_FOREACH(one, su->env_whitelist_names) { | |
638 | rc = setenv(*one, su->env_whitelist_vals[i], overwrite); | |
639 | if (rc) | |
640 | return rc; | |
641 | i++; | |
642 | } | |
643 | ||
644 | return 0; | |
645 | } | |
646 | ||
647 | /* Creates (add to) whitelist from comma delimited string */ | |
648 | static int env_whitelist_from_string(struct su_context *su, const char *str) | |
649 | { | |
650 | char **all = strv_split(str, ","); | |
651 | char **one; | |
652 | ||
653 | if (!all) { | |
654 | if (errno == ENOMEM) | |
655 | err_oom(); | |
656 | return -EINVAL; | |
657 | } | |
658 | ||
659 | STRV_FOREACH(one, all) | |
660 | env_whitelist_add(su, *one); | |
661 | strv_free(all); | |
662 | return 0; | |
663 | } | |
664 | ||
13396b10 | 665 | static void setenv_path(const struct passwd *pw) |
cf1a99da | 666 | { |
e402d137 | 667 | int rc; |
13396b10 | 668 | |
2260e493 KZ |
669 | DBG(MISC, ul_debug("setting PATH")); |
670 | ||
983652ab | 671 | if (pw->pw_uid) |
e402d137 | 672 | rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH); |
cf1a99da | 673 | |
86f42e5a SB |
674 | else if ((rc = logindefs_setenv("PATH", "ENV_SUPATH", NULL)) != 0) |
675 | rc = logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT); | |
cf1a99da | 676 | |
e402d137 KZ |
677 | if (rc) |
678 | err(EXIT_FAILURE, _("failed to set the PATH environment variable")); | |
cf1a99da KZ |
679 | } |
680 | ||
13396b10 | 681 | static void modify_environment(struct su_context *su, const char *shell) |
cf1a99da | 682 | { |
032d759a KZ |
683 | const struct passwd *pw = su->pwd; |
684 | ||
2260e493 KZ |
685 | |
686 | DBG(MISC, ul_debug("modify environ[]")); | |
687 | ||
13396b10 | 688 | /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. |
75efef98 KZ |
689 | * |
690 | * Unset all other environment variables, but follow | |
691 | * --whitelist-environment if specified. | |
13396b10 | 692 | */ |
832f5cd5 | 693 | if (su->simulate_login) { |
75efef98 KZ |
694 | /* leave TERM unchanged */ |
695 | env_whitelist_add(su, "TERM"); | |
696 | ||
697 | /* Note that original su(1) has allocated environ[] by malloc | |
698 | * to the number of expected variables. This seems unnecessary | |
00749b05 | 699 | * optimization as libc later re-alloc(current_size+2) and for |
75efef98 KZ |
700 | * empty environ[] the curren_size is zero. It seems better to |
701 | * keep all logic around environment in glibc's hands. | |
702 | * --kzak [Aug 2018] | |
703 | */ | |
704 | #ifdef HAVE_CLEARENV | |
705 | clearenv(); | |
706 | #else | |
707 | environ = NULL; | |
708 | #endif | |
709 | /* always reset */ | |
983652ab KZ |
710 | if (shell) |
711 | xsetenv("SHELL", shell, 1); | |
75efef98 KZ |
712 | |
713 | setenv_path(pw); | |
714 | ||
715 | xsetenv("HOME", pw->pw_dir, 1); | |
983652ab KZ |
716 | xsetenv("USER", pw->pw_name, 1); |
717 | xsetenv("LOGNAME", pw->pw_name, 1); | |
75efef98 KZ |
718 | |
719 | /* apply all from whitelist, but no overwrite */ | |
720 | env_whitelist_setenv(su, 0); | |
13396b10 KZ |
721 | |
722 | /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME. | |
723 | */ | |
724 | } else if (su->change_environment) { | |
725 | xsetenv("HOME", pw->pw_dir, 1); | |
726 | if (shell) | |
727 | xsetenv("SHELL", shell, 1); | |
728 | ||
729 | if (getlogindefs_bool("ALWAYS_SET_PATH", 0)) | |
730 | setenv_path(pw); | |
731 | ||
732 | if (pw->pw_uid) { | |
733 | xsetenv("USER", pw->pw_name, 1); | |
734 | xsetenv("LOGNAME", pw->pw_name, 1); | |
983652ab KZ |
735 | } |
736 | } | |
737 | ||
b9a92282 | 738 | supam_export_environment(su); |
cf1a99da KZ |
739 | } |
740 | ||
93031585 | 741 | static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups) |
cf1a99da | 742 | { |
93031585 | 743 | int rc; |
983652ab | 744 | |
2260e493 KZ |
745 | DBG(MISC, ul_debug("initialize groups")); |
746 | ||
983652ab | 747 | errno = 0; |
93031585 KZ |
748 | if (ngroups) |
749 | rc = setgroups(ngroups, groups); | |
983652ab | 750 | else |
93031585 | 751 | rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid); |
983652ab | 752 | |
93031585 | 753 | if (rc == -1) { |
b9a92282 | 754 | supam_cleanup(su, PAM_ABORT); |
983652ab KZ |
755 | err(EXIT_FAILURE, _("cannot set groups")); |
756 | } | |
757 | endgrent(); | |
758 | ||
93031585 KZ |
759 | rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED); |
760 | if (is_pam_failure(rc)) | |
761 | errx(EXIT_FAILURE, _("failed to user credentials: %s"), | |
762 | pam_strerror(su->pamh, rc)); | |
763 | su->pam_has_cred = 1; | |
cf1a99da KZ |
764 | } |
765 | ||
93031585 | 766 | static void change_identity(const struct passwd *pw) |
cf1a99da | 767 | { |
2260e493 KZ |
768 | DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid)); |
769 | ||
983652ab KZ |
770 | if (setgid(pw->pw_gid)) |
771 | err(EXIT_FAILURE, _("cannot set group id")); | |
772 | if (setuid(pw->pw_uid)) | |
773 | err(EXIT_FAILURE, _("cannot set user id")); | |
cf1a99da KZ |
774 | } |
775 | ||
581ddd37 KZ |
776 | /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option. |
777 | * Pass ADDITIONAL_ARGS to the shell as more arguments; there are | |
778 | * N_ADDITIONAL_ARGS extra arguments. | |
779 | */ | |
780 | static void run_shell( | |
781 | struct su_context *su, | |
782 | char const *shell, char const *command, char **additional_args, | |
783 | size_t n_additional_args) | |
cf1a99da | 784 | { |
581ddd37 | 785 | size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1; |
feab5687 | 786 | const char **args = xcalloc(n_args, sizeof *args); |
983652ab KZ |
787 | size_t argno = 1; |
788 | ||
242708de KZ |
789 | DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]", |
790 | shell, command, | |
791 | su->simulate_login ? " login" : "", | |
792 | su->fast_startup ? " fast-start" : "")); | |
2260e493 | 793 | |
832f5cd5 | 794 | if (su->simulate_login) { |
983652ab KZ |
795 | char *arg0; |
796 | char *shell_basename; | |
797 | ||
798 | shell_basename = basename(shell); | |
799 | arg0 = xmalloc(strlen(shell_basename) + 2); | |
800 | arg0[0] = '-'; | |
801 | strcpy(arg0 + 1, shell_basename); | |
802 | args[0] = arg0; | |
803 | } else | |
804 | args[0] = basename(shell); | |
581ddd37 | 805 | |
832f5cd5 | 806 | if (su->fast_startup) |
983652ab KZ |
807 | args[argno++] = "-f"; |
808 | if (command) { | |
809 | args[argno++] = "-c"; | |
810 | args[argno++] = command; | |
811 | } | |
581ddd37 | 812 | |
983652ab KZ |
813 | memcpy(args + argno, additional_args, n_additional_args * sizeof *args); |
814 | args[argno + n_additional_args] = NULL; | |
815 | execv(shell, (char **)args); | |
3c29b695 | 816 | errexec(shell); |
cf1a99da KZ |
817 | } |
818 | ||
819 | /* Return true if SHELL is a restricted shell (one not returned by | |
581ddd37 KZ |
820 | * getusershell), else false, meaning it is a standard shell. |
821 | */ | |
822 | static bool is_restricted_shell(const char *shell) | |
cf1a99da | 823 | { |
983652ab KZ |
824 | char *line; |
825 | ||
826 | setusershell(); | |
827 | while ((line = getusershell()) != NULL) { | |
828 | if (*line != '#' && !strcmp(line, shell)) { | |
829 | endusershell(); | |
830 | return false; | |
831 | } | |
cf1a99da | 832 | } |
983652ab | 833 | endusershell(); |
242708de | 834 | |
e19db044 | 835 | DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell)); |
983652ab | 836 | return true; |
cf1a99da KZ |
837 | } |
838 | ||
42be9bda | 839 | static void usage_common(void) |
cf1a99da | 840 | { |
75efef98 KZ |
841 | fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout); |
842 | fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout); | |
843 | fputs(USAGE_SEPARATOR, stdout); | |
844 | ||
42be9bda KZ |
845 | fputs(_(" -g, --group <group> specify the primary group\n"), stdout); |
846 | fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout); | |
847 | fputs(USAGE_SEPARATOR, stdout); | |
983652ab | 848 | |
42be9bda KZ |
849 | fputs(_(" -, -l, --login make the shell a login shell\n"), stdout); |
850 | fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout); | |
851 | fputs(_(" --session-command <command> pass a single command to the shell with -c\n" | |
852 | " and do not create a new session\n"), stdout); | |
853 | fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout); | |
854 | fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout); | |
927ded6b | 855 | fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout); |
42be9bda KZ |
856 | |
857 | fputs(USAGE_SEPARATOR, stdout); | |
858 | printf(USAGE_HELP_OPTIONS(33)); | |
42be9bda | 859 | } |
983652ab | 860 | |
e192de65 | 861 | static void usage_runuser(void) |
42be9bda KZ |
862 | { |
863 | fputs(USAGE_HEADER, stdout); | |
864 | fprintf(stdout, | |
865 | _(" %1$s [options] -u <user> [[--] <command>]\n" | |
866 | " %1$s [options] [-] [<user> [<argument>...]]\n"), | |
867 | program_invocation_short_name); | |
983652ab KZ |
868 | |
869 | fputs(USAGE_SEPARATOR, stdout); | |
42be9bda KZ |
870 | fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n" |
871 | "not given, fall back to su(1)-compatible semantics and execute standard shell.\n" | |
872 | "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout); | |
873 | ||
874 | fputs(USAGE_OPTIONS, stdout); | |
875 | fputs(_(" -u, --user <user> username\n"), stdout); | |
876 | usage_common(); | |
877 | fputs(USAGE_SEPARATOR, stdout); | |
878 | ||
879 | fprintf(stdout, USAGE_MAN_TAIL("runuser(1)")); | |
42be9bda KZ |
880 | } |
881 | ||
e192de65 | 882 | static void usage_su(void) |
42be9bda KZ |
883 | { |
884 | fputs(USAGE_HEADER, stdout); | |
885 | fprintf(stdout, | |
886 | _(" %s [options] [-] [<user> [<argument>...]]\n"), | |
887 | program_invocation_short_name); | |
888 | ||
889 | fputs(USAGE_SEPARATOR, stdout); | |
890 | fputs(_("Change the effective user ID and group ID to that of <user>.\n" | |
891 | "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout); | |
892 | ||
893 | fputs(USAGE_OPTIONS, stdout); | |
894 | usage_common(); | |
895 | ||
896 | fprintf(stdout, USAGE_MAN_TAIL("su(1)")); | |
42be9bda KZ |
897 | } |
898 | ||
e192de65 | 899 | static void __attribute__((__noreturn__)) usage(int mode) |
42be9bda KZ |
900 | { |
901 | if (mode == SU_MODE) | |
902 | usage_su(); | |
903 | else | |
904 | usage_runuser(); | |
e192de65 KZ |
905 | |
906 | exit(EXIT_SUCCESS); | |
cf1a99da KZ |
907 | } |
908 | ||
832f5cd5 | 909 | static void load_config(void *data) |
cf1a99da | 910 | { |
832f5cd5 | 911 | struct su_context *su = (struct su_context *) data; |
983652ab | 912 | |
2260e493 | 913 | DBG(MISC, ul_debug("loading logindefs")); |
983652ab | 914 | logindefs_load_file(_PATH_LOGINDEFS); |
15a191f6 | 915 | logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU); |
cf1a99da KZ |
916 | } |
917 | ||
918 | /* | |
919 | * Returns 1 if the current user is not root | |
920 | */ | |
13de9b21 | 921 | static int is_not_root(void) |
cf1a99da | 922 | { |
983652ab KZ |
923 | const uid_t ruid = getuid(); |
924 | const uid_t euid = geteuid(); | |
cf1a99da | 925 | |
983652ab KZ |
926 | /* if we're really root and aren't running setuid */ |
927 | return (uid_t) 0 == ruid && ruid == euid ? 0 : 1; | |
cf1a99da KZ |
928 | } |
929 | ||
e402d137 | 930 | static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups) |
c619d3d1 | 931 | { |
983652ab | 932 | struct group *gr; |
c619d3d1 | 933 | |
983652ab KZ |
934 | if (*ngroups >= NGROUPS_MAX) |
935 | errx(EXIT_FAILURE, | |
e402d137 KZ |
936 | P_("specifying more than %d supplemental group is not possible", |
937 | "specifying more than %d supplemental groups is not possible", | |
938 | NGROUPS_MAX - 1), NGROUPS_MAX - 1); | |
c619d3d1 | 939 | |
983652ab KZ |
940 | gr = getgrnam(name); |
941 | if (!gr) | |
942 | errx(EXIT_FAILURE, _("group %s does not exist"), name); | |
c619d3d1 | 943 | |
2260e493 KZ |
944 | DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid)); |
945 | ||
983652ab KZ |
946 | *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1)); |
947 | (*groups)[*ngroups] = gr->gr_gid; | |
948 | (*ngroups)++; | |
c619d3d1 | 949 | |
983652ab | 950 | return gr->gr_gid; |
c619d3d1 KZ |
951 | } |
952 | ||
e402d137 | 953 | int su_main(int argc, char **argv, int mode) |
cf1a99da | 954 | { |
832f5cd5 | 955 | struct su_context _su = { |
b9a92282 | 956 | .conv = { supam_conv, NULL }, |
832f5cd5 | 957 | .runuser = (mode == RUNUSER_MODE ? 1 : 0), |
94c6730b | 958 | .change_environment = 1, |
b923bdbe | 959 | .new_user = DEFAULT_USER |
832f5cd5 KZ |
960 | }, *su = &_su; |
961 | ||
983652ab | 962 | int optc; |
983652ab KZ |
963 | char *command = NULL; |
964 | int request_same_session = 0; | |
965 | char *shell = NULL; | |
983652ab KZ |
966 | |
967 | gid_t *groups = NULL; | |
968 | size_t ngroups = 0; | |
969 | bool use_supp = false; | |
970 | bool use_gid = false; | |
971 | gid_t gid = 0; | |
972 | ||
973 | static const struct option longopts[] = { | |
974 | {"command", required_argument, NULL, 'c'}, | |
975 | {"session-command", required_argument, NULL, 'C'}, | |
976 | {"fast", no_argument, NULL, 'f'}, | |
977 | {"login", no_argument, NULL, 'l'}, | |
978 | {"preserve-environment", no_argument, NULL, 'p'}, | |
04845ec7 | 979 | {"pty", no_argument, NULL, 'P'}, |
983652ab KZ |
980 | {"shell", required_argument, NULL, 's'}, |
981 | {"group", required_argument, NULL, 'g'}, | |
982 | {"supp-group", required_argument, NULL, 'G'}, | |
983 | {"user", required_argument, NULL, 'u'}, /* runuser only */ | |
75efef98 | 984 | {"whitelist-environment", required_argument, NULL, 'w'}, |
983652ab KZ |
985 | {"help", no_argument, 0, 'h'}, |
986 | {"version", no_argument, 0, 'V'}, | |
987 | {NULL, 0, NULL, 0} | |
988 | }; | |
75efef98 KZ |
989 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
990 | { 'm', 'w' }, /* preserve-environment, whitelist-environment */ | |
991 | { 'p', 'w' }, /* preserve-environment, whitelist-environment */ | |
992 | { 0 } | |
993 | }; | |
994 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
983652ab KZ |
995 | |
996 | setlocale(LC_ALL, ""); | |
997 | bindtextdomain(PACKAGE, LOCALEDIR); | |
998 | textdomain(PACKAGE); | |
2c308875 | 999 | close_stdout_atexit(); |
983652ab | 1000 | |
2260e493 | 1001 | su_init_debug(); |
832f5cd5 | 1002 | su->conv.appdata_ptr = (void *) su; |
983652ab KZ |
1003 | |
1004 | while ((optc = | |
75efef98 | 1005 | getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts, |
983652ab | 1006 | NULL)) != -1) { |
75efef98 KZ |
1007 | |
1008 | err_exclusive_options(optc, longopts, excl, excl_st); | |
1009 | ||
983652ab KZ |
1010 | switch (optc) { |
1011 | case 'c': | |
1012 | command = optarg; | |
1013 | break; | |
1014 | ||
1015 | case 'C': | |
1016 | command = optarg; | |
1017 | request_same_session = 1; | |
1018 | break; | |
1019 | ||
1020 | case 'f': | |
832f5cd5 | 1021 | su->fast_startup = true; |
983652ab KZ |
1022 | break; |
1023 | ||
1024 | case 'g': | |
1025 | use_gid = true; | |
1026 | gid = add_supp_group(optarg, &groups, &ngroups); | |
1027 | break; | |
1028 | ||
1029 | case 'G': | |
1030 | use_supp = true; | |
1031 | add_supp_group(optarg, &groups, &ngroups); | |
1032 | break; | |
1033 | ||
1034 | case 'l': | |
832f5cd5 | 1035 | su->simulate_login = true; |
983652ab KZ |
1036 | break; |
1037 | ||
1038 | case 'm': | |
1039 | case 'p': | |
832f5cd5 | 1040 | su->change_environment = false; |
983652ab KZ |
1041 | break; |
1042 | ||
75efef98 KZ |
1043 | case 'w': |
1044 | env_whitelist_from_string(su, optarg); | |
1045 | break; | |
1046 | ||
04845ec7 | 1047 | case 'P': |
eb7d0ad0 | 1048 | #ifdef USE_PTY |
b923bdbe | 1049 | su->force_pty = 1; |
eb7d0ad0 | 1050 | #else |
ae6e2537 | 1051 | errx(EXIT_FAILURE, _("--pty is not supported for your system")); |
eb7d0ad0 | 1052 | #endif |
04845ec7 KZ |
1053 | break; |
1054 | ||
983652ab KZ |
1055 | case 's': |
1056 | shell = optarg; | |
1057 | break; | |
1058 | ||
1059 | case 'u': | |
832f5cd5 | 1060 | if (!su->runuser) |
42be9bda | 1061 | errtryhelp(EXIT_FAILURE); |
94c6730b KZ |
1062 | su->runuser_uopt = 1; |
1063 | su->new_user = optarg; | |
983652ab KZ |
1064 | break; |
1065 | ||
1066 | case 'h': | |
42be9bda | 1067 | usage(mode); |
983652ab KZ |
1068 | |
1069 | case 'V': | |
2c308875 | 1070 | print_version(EXIT_SUCCESS); |
983652ab | 1071 | default: |
42be9bda | 1072 | errtryhelp(EXIT_FAILURE); |
983652ab KZ |
1073 | } |
1074 | } | |
cf1a99da | 1075 | |
13de9b21 | 1076 | su->restricted = is_not_root(); |
983652ab KZ |
1077 | |
1078 | if (optind < argc && !strcmp(argv[optind], "-")) { | |
832f5cd5 | 1079 | su->simulate_login = true; |
983652ab KZ |
1080 | ++optind; |
1081 | } | |
1082 | ||
832f5cd5 | 1083 | if (su->simulate_login && !su->change_environment) { |
983652ab KZ |
1084 | warnx(_ |
1085 | ("ignoring --preserve-environment, it's mutually exclusive with --login")); | |
832f5cd5 | 1086 | su->change_environment = true; |
983652ab KZ |
1087 | } |
1088 | ||
832f5cd5 | 1089 | switch (mode) { |
983652ab | 1090 | case RUNUSER_MODE: |
e192de65 KZ |
1091 | /* runuser -u <user> <command> |
1092 | * | |
1093 | * If -u <user> is not specified, then follow traditional su(1) behavior and | |
1094 | * fallthrough | |
1095 | */ | |
94c6730b KZ |
1096 | if (su->runuser_uopt) { |
1097 | if (shell || su->fast_startup || command || su->simulate_login) | |
983652ab | 1098 | errx(EXIT_FAILURE, |
94c6730b | 1099 | _("options --{shell,fast,command,session-command,login} and " |
983652ab | 1100 | "--user are mutually exclusive")); |
983652ab | 1101 | if (optind == argc) |
94c6730b | 1102 | errx(EXIT_FAILURE, _("no command was specified")); |
983652ab KZ |
1103 | break; |
1104 | } | |
e192de65 | 1105 | /* fallthrough */ |
983652ab KZ |
1106 | case SU_MODE: |
1107 | if (optind < argc) | |
94c6730b | 1108 | su->new_user = argv[optind++]; |
983652ab KZ |
1109 | break; |
1110 | } | |
1111 | ||
832f5cd5 | 1112 | if ((use_supp || use_gid) && su->restricted) |
983652ab KZ |
1113 | errx(EXIT_FAILURE, |
1114 | _("only root can specify alternative groups")); | |
1115 | ||
832f5cd5 | 1116 | logindefs_set_loader(load_config, (void *) su); |
302b7b65 | 1117 | init_tty(su); |
983652ab | 1118 | |
94c6730b | 1119 | su->pwd = xgetpwnam(su->new_user, &su->pwdbuf); |
032d759a KZ |
1120 | if (!su->pwd |
1121 | || !su->pwd->pw_passwd | |
1122 | || !su->pwd->pw_name || !*su->pwd->pw_name | |
1123 | || !su->pwd->pw_dir || !*su->pwd->pw_dir) | |
9a1a9822 JH |
1124 | errx(EXIT_FAILURE, |
1125 | _("user %s does not exist or the user entry does not " | |
1126 | "contain all the required fields"), su->new_user); | |
94c6730b KZ |
1127 | |
1128 | su->new_user = su->pwd->pw_name; | |
1129 | su->old_user = xgetlogin(); | |
983652ab | 1130 | |
032d759a KZ |
1131 | if (!su->pwd->pw_shell || !*su->pwd->pw_shell) |
1132 | su->pwd->pw_shell = DEFAULT_SHELL; | |
983652ab KZ |
1133 | |
1134 | if (use_supp && !use_gid) | |
032d759a | 1135 | su->pwd->pw_gid = groups[0]; |
983652ab | 1136 | else if (use_gid) |
032d759a | 1137 | su->pwd->pw_gid = gid; |
983652ab | 1138 | |
032d759a | 1139 | supam_authenticate(su); |
983652ab | 1140 | |
032d759a | 1141 | if (request_same_session || !command || !su->pwd->pw_uid) |
832f5cd5 | 1142 | su->same_session = 1; |
983652ab KZ |
1143 | |
1144 | /* initialize shell variable only if "-u <user>" not specified */ | |
94c6730b | 1145 | if (su->runuser_uopt) { |
983652ab KZ |
1146 | shell = NULL; |
1147 | } else { | |
832f5cd5 | 1148 | if (!shell && !su->change_environment) |
983652ab | 1149 | shell = getenv("SHELL"); |
581ddd37 KZ |
1150 | |
1151 | if (shell | |
1152 | && getuid() != 0 | |
1153 | && is_restricted_shell(su->pwd->pw_shell)) { | |
1154 | /* The user being su'd to has a nonstandard shell, and | |
1155 | * so is probably a uucp account or has restricted | |
1156 | * access. Don't compromise the account by allowing | |
1157 | * access with a standard shell. | |
1158 | */ | |
032d759a | 1159 | warnx(_("using restricted shell %s"), su->pwd->pw_shell); |
983652ab KZ |
1160 | shell = NULL; |
1161 | } | |
032d759a | 1162 | shell = xstrdup(shell ? shell : su->pwd->pw_shell); |
cf1a99da | 1163 | } |
cf1a99da | 1164 | |
032d759a | 1165 | init_groups(su, groups, ngroups); |
983652ab | 1166 | |
832f5cd5 KZ |
1167 | if (!su->simulate_login || command) |
1168 | su->suppress_pam_info = 1; /* don't print PAM info messages */ | |
983652ab | 1169 | |
a4440cd1 KZ |
1170 | supam_open_session(su); |
1171 | ||
b923bdbe KZ |
1172 | #ifdef USE_PTY |
1173 | if (su->force_pty) { | |
1174 | ON_DBG(PTY, ul_pty_init_debug(0xffff)); | |
1175 | ||
1176 | su->pty = ul_new_pty(su->isterm); | |
1177 | if (!su->pty) | |
1178 | err(EXIT_FAILURE, _("failed to allocate pty handler")); | |
1179 | } | |
1180 | #endif | |
832f5cd5 | 1181 | create_watching_parent(su); |
983652ab KZ |
1182 | /* Now we're in the child. */ |
1183 | ||
032d759a | 1184 | change_identity(su->pwd); |
b923bdbe KZ |
1185 | if (!su->same_session) { |
1186 | /* note that on --pty we call setsid() in ul_pty_init_slave() */ | |
927ded6b | 1187 | DBG(MISC, ul_debug("call setsid()")); |
983652ab | 1188 | setsid(); |
927ded6b | 1189 | } |
4365c810 | 1190 | #ifdef USE_PTY |
b923bdbe KZ |
1191 | if (su->force_pty) |
1192 | ul_pty_init_slave(su->pty); | |
4365c810 | 1193 | #endif |
983652ab KZ |
1194 | /* Set environment after pam_open_session, which may put KRB5CCNAME |
1195 | into the pam_env, etc. */ | |
1196 | ||
032d759a | 1197 | modify_environment(su, shell); |
983652ab | 1198 | |
032d759a KZ |
1199 | if (su->simulate_login && chdir(su->pwd->pw_dir) != 0) |
1200 | warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir); | |
983652ab KZ |
1201 | |
1202 | if (shell) | |
832f5cd5 KZ |
1203 | run_shell(su, shell, command, argv + optind, max(0, argc - optind)); |
1204 | ||
1205 | execvp(argv[optind], &argv[optind]); | |
1206 | err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]); | |
983652ab | 1207 | } |