]>
Commit | Line | Data |
---|---|---|
6987acf5 MT |
1 | diff -urNp coreutils-8.16-orig/configure.ac coreutils-8.16/configure.ac |
2 | --- coreutils-8.16-orig/configure.ac 2012-03-24 19:22:13.000000000 +0100 | |
3 | +++ coreutils-8.16/configure.ac 2012-03-26 17:59:07.900139497 +0200 | |
4 | @@ -185,6 +185,20 @@ fi | |
1555d43c SS |
5 | |
6 | AC_FUNC_FORK | |
7 | ||
8 | +AC_ARG_ENABLE(pam, AS_HELP_STRING([--disable-pam], | |
9 | + [Disable PAM support in su (default=auto)]), , [enable_pam=yes]) | |
10 | +if test "x$enable_pam" != xno; then | |
11 | + AC_CHECK_LIB([pam], [pam_start], [enable_pam=yes], [enable_pam=no]) | |
12 | + AC_CHECK_LIB([pam_misc], [misc_conv], [:], [enable_pam=no]) | |
13 | + if test "x$enable_pam" != xno; then | |
14 | + AC_DEFINE(USE_PAM, 1, [Define if you want to use PAM]) | |
15 | + PAM_LIBS="-lpam -lpam_misc" | |
16 | + AC_SUBST(PAM_LIBS) | |
17 | + fi | |
18 | +fi | |
19 | +AC_MSG_CHECKING([whether to enable PAM support in su]) | |
20 | +AC_MSG_RESULT([$enable_pam]) | |
21 | + | |
22 | optional_bin_progs= | |
23 | AC_CHECK_FUNCS([chroot], | |
24 | gl_ADD_PROG([optional_bin_progs], [chroot])) | |
6987acf5 MT |
25 | diff -urNp coreutils-8.16-orig/doc/coreutils.texi coreutils-8.16/doc/coreutils.texi |
26 | --- coreutils-8.16-orig/doc/coreutils.texi 2012-03-26 17:58:27.624763998 +0200 | |
27 | +++ coreutils-8.16/doc/coreutils.texi 2012-03-26 17:59:07.907138599 +0200 | |
28 | @@ -15804,7 +15804,9 @@ the exit status of @var{command} otherwi | |
29 | ||
30 | @command{su} allows one user to temporarily become another user. It runs a | |
31 | command (often an interactive shell) with the real and effective user | |
32 | -ID, group ID, and supplemental groups of a given @var{user}. Synopsis: | |
33 | +ID, group ID, and supplemental groups of a given @var{user}. When the -l | |
34 | +option is given, the su-l PAM file is used instead of the default su PAM file. | |
35 | +Synopsis: | |
36 | ||
37 | @example | |
38 | su [@var{option}]@dots{} [@var{user} [@var{arg}]@dots{}] | |
39 | @@ -15883,7 +15885,8 @@ environment variables except @env{TERM}, | |
40 | (which are set, even for the super-user, as described above), and set | |
41 | @env{PATH} to a compiled-in default value. Change to @var{user}'s home | |
42 | directory. Prepend @samp{-} to the shell's name, intended to make it | |
43 | -read its login startup file(s). | |
44 | +read its login startup file(s). When this option is given, /etc/pam.d/su-l | |
45 | +PAM file is used instead of the default one. | |
46 | ||
47 | @item -m | |
48 | @itemx -p | |
49 | diff -urNp coreutils-8.16-orig/src/Makefile.am coreutils-8.16/src/Makefile.am | |
50 | --- coreutils-8.16-orig/src/Makefile.am 2012-03-24 19:22:13.000000000 +0100 | |
51 | +++ coreutils-8.16/src/Makefile.am 2012-03-26 17:59:07.928142551 +0200 | |
52 | @@ -357,8 +357,8 @@ factor_LDADD += $(LIB_GMP) | |
1555d43c SS |
53 | # for getloadavg |
54 | uptime_LDADD += $(GETLOADAVG_LIBS) | |
55 | ||
56 | -# for crypt | |
57 | -su_LDADD += $(LIB_CRYPT) | |
58 | +# for crypt and pam | |
59 | +su_LDADD += $(LIB_CRYPT) $(PAM_LIBS) | |
60 | ||
61 | # for various ACL functions | |
62 | copy_LDADD += $(LIB_ACL) | |
6987acf5 MT |
63 | diff -urNp coreutils-8.16-orig/src/su.c coreutils-8.16/src/su.c |
64 | --- coreutils-8.16-orig/src/su.c 2012-03-26 17:58:27.629764055 +0200 | |
65 | +++ coreutils-8.16/src/su.c 2012-03-26 17:59:07.931138998 +0200 | |
1555d43c SS |
66 | @@ -37,6 +37,16 @@ |
67 | restricts who can su to UID 0 accounts. RMS considers that to | |
68 | be fascist. | |
69 | ||
70 | +#ifdef USE_PAM | |
71 | + | |
72 | + Actually, with PAM, su has nothing to do with whether or not a | |
73 | + wheel group is enforced by su. RMS tries to restrict your access | |
74 | + to a su which implements the wheel group, but PAM considers that | |
75 | + to be fascist, and gives the user/sysadmin the opportunity to | |
76 | + enforce a wheel group by proper editing of /etc/pam.d/su | |
77 | + | |
78 | +#endif | |
79 | + | |
80 | Compile-time options: | |
81 | -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog. | |
82 | -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog. | |
83 | @@ -52,6 +62,13 @@ | |
84 | #include <sys/types.h> | |
85 | #include <pwd.h> | |
86 | #include <grp.h> | |
87 | +#ifdef USE_PAM | |
88 | +#include <security/pam_appl.h> | |
89 | +#include <security/pam_misc.h> | |
90 | +#include <signal.h> | |
91 | +#include <sys/wait.h> | |
92 | +#include <sys/fsuid.h> | |
93 | +#endif | |
94 | ||
95 | #include "system.h" | |
96 | #include "getpass.h" | |
6987acf5 | 97 | @@ -120,7 +137,9 @@ |
1555d43c SS |
98 | /* The user to become if none is specified. */ |
99 | #define DEFAULT_USER "root" | |
100 | ||
101 | +#ifndef USE_PAM | |
102 | char *crypt (char const *key, char const *salt); | |
103 | +#endif | |
104 | ||
105 | static void run_shell (char const *, char const *, char **, size_t) | |
106 | ATTRIBUTE_NORETURN; | |
6987acf5 | 107 | @@ -134,6 +153,11 @@ static bool simulate_login; |
1555d43c SS |
108 | /* If true, change some environment vars to indicate the user su'd to. */ |
109 | static bool change_environment; | |
110 | ||
111 | +#ifdef USE_PAM | |
112 | +static bool _pam_session_opened; | |
113 | +static bool _pam_cred_established; | |
114 | +#endif | |
115 | + | |
116 | static struct option const longopts[] = | |
117 | { | |
118 | {"command", required_argument, NULL, 'c'}, | |
6987acf5 | 119 | @@ -212,7 +236,174 @@ log_su (struct passwd const *pw, bool su |
1555d43c SS |
120 | } |
121 | #endif | |
122 | ||
123 | +#ifdef USE_PAM | |
124 | +#define PAM_SERVICE_NAME PROGRAM_NAME | |
125 | +#define PAM_SERVICE_NAME_L PROGRAM_NAME "-l" | |
126 | +static sig_atomic_t volatile caught_signal = false; | |
127 | +static pam_handle_t *pamh = NULL; | |
128 | +static int retval; | |
129 | +static struct pam_conv conv = | |
130 | +{ | |
131 | + misc_conv, | |
132 | + NULL | |
133 | +}; | |
134 | + | |
135 | +#define PAM_BAIL_P(a) \ | |
136 | + if (retval) \ | |
137 | + { \ | |
138 | + pam_end (pamh, retval); \ | |
139 | + a; \ | |
140 | + } | |
141 | + | |
142 | +static void | |
143 | +cleanup_pam (int retcode) | |
144 | +{ | |
145 | + if (_pam_session_opened) | |
146 | + pam_close_session (pamh, 0); | |
147 | + | |
148 | + if (_pam_cred_established) | |
149 | + pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT); | |
150 | + | |
151 | + pam_end(pamh, retcode); | |
152 | +} | |
153 | + | |
154 | +/* Signal handler for parent process. */ | |
155 | +static void | |
156 | +su_catch_sig (int sig) | |
157 | +{ | |
158 | + caught_signal = true; | |
159 | +} | |
160 | + | |
161 | +/* Export env variables declared by PAM modules. */ | |
162 | +static void | |
163 | +export_pamenv (void) | |
164 | +{ | |
165 | + char **env; | |
166 | + | |
167 | + /* This is a copy but don't care to free as we exec later anyways. */ | |
168 | + env = pam_getenvlist (pamh); | |
169 | + while (env && *env) | |
170 | + { | |
171 | + if (putenv (*env) != 0) | |
172 | + xalloc_die (); | |
173 | + env++; | |
174 | + } | |
175 | +} | |
176 | + | |
177 | +static void | |
178 | +create_watching_parent (void) | |
179 | +{ | |
180 | + pid_t child; | |
6987acf5 | 181 | + sigset_t ourset, blockset; |
1555d43c SS |
182 | + int status = 0; |
183 | + | |
184 | + retval = pam_open_session (pamh, 0); | |
185 | + if (retval != PAM_SUCCESS) | |
186 | + { | |
187 | + cleanup_pam (retval); | |
188 | + error (EXIT_FAILURE, 0, _("cannot not open session: %s"), | |
189 | + pam_strerror (pamh, retval)); | |
190 | + } | |
191 | + else | |
192 | + _pam_session_opened = 1; | |
193 | + | |
194 | + child = fork (); | |
195 | + if (child == (pid_t) -1) | |
196 | + { | |
197 | + cleanup_pam (PAM_ABORT); | |
198 | + error (EXIT_FAILURE, errno, _("cannot create child process")); | |
199 | + } | |
200 | + | |
201 | + /* the child proceeds to run the shell */ | |
202 | + if (child == 0) | |
203 | + return; | |
204 | + | |
205 | + /* In the parent watch the child. */ | |
206 | + | |
207 | + /* su without pam support does not have a helper that keeps | |
208 | + sitting on any directory so let's go to /. */ | |
209 | + if (chdir ("/") != 0) | |
210 | + error (0, errno, _("warning: cannot change directory to %s"), "/"); | |
211 | + | |
212 | + sigfillset (&ourset); | |
213 | + if (sigprocmask (SIG_BLOCK, &ourset, NULL)) | |
214 | + { | |
215 | + error (0, errno, _("cannot block signals")); | |
216 | + caught_signal = true; | |
217 | + } | |
218 | + if (!caught_signal) | |
219 | + { | |
220 | + struct sigaction action; | |
221 | + action.sa_handler = su_catch_sig; | |
222 | + sigemptyset (&action.sa_mask); | |
223 | + action.sa_flags = 0; | |
224 | + sigemptyset (&ourset); | |
225 | + if (sigaddset (&ourset, SIGTERM) | |
226 | + || sigaddset (&ourset, SIGALRM) | |
227 | + || sigaction (SIGTERM, &action, NULL) | |
228 | + || sigprocmask (SIG_UNBLOCK, &ourset, NULL)) | |
229 | + { | |
230 | + error (0, errno, _("cannot set signal handler")); | |
231 | + caught_signal = true; | |
232 | + } | |
233 | + } | |
234 | + if (!caught_signal) | |
235 | + { | |
236 | + pid_t pid; | |
237 | + for (;;) | |
238 | + { | |
239 | + pid = waitpid (child, &status, WUNTRACED); | |
240 | + | |
241 | + if (pid != (pid_t)-1 && WIFSTOPPED (status)) | |
242 | + { | |
6987acf5 | 243 | + /* tcsh sends SIGTSTP to the process group, and so is already pending */ |
1555d43c | 244 | + kill (getpid (), SIGSTOP); |
6987acf5 MT |
245 | + if (WSTOPSIG(status) != SIGSTOP) { |
246 | + sigemptyset(&blockset); | |
247 | + if (sigaddset(&blockset, WSTOPSIG(status)) || | |
248 | + sigprocmask(SIG_UNBLOCK, &blockset, &ourset) || | |
249 | + sigprocmask(SIG_SETMASK, &ourset, NULL)) | |
250 | + { | |
251 | + error (0, errno, _("cannot set signal handler")); | |
252 | + } | |
253 | + } | |
1555d43c SS |
254 | + /* once we get here, we must have resumed */ |
255 | + kill (pid, SIGCONT); | |
256 | + } | |
257 | + else | |
258 | + break; | |
259 | + } | |
260 | + if (pid != (pid_t)-1) | |
261 | + if (WIFSIGNALED (status)) | |
262 | + status = WTERMSIG (status) + 128; | |
263 | + else | |
264 | + status = WEXITSTATUS (status); | |
265 | + else | |
266 | + status = 1; | |
267 | + } | |
268 | + else | |
269 | + status = 1; | |
270 | + | |
271 | + if (caught_signal) | |
272 | + { | |
273 | + fprintf (stderr, _("\nSession terminated, killing shell...")); | |
274 | + kill (child, SIGTERM); | |
275 | + } | |
276 | + | |
277 | + cleanup_pam (PAM_SUCCESS); | |
278 | + | |
279 | + if (caught_signal) | |
280 | + { | |
281 | + sleep (2); | |
282 | + kill (child, SIGKILL); | |
283 | + fprintf (stderr, _(" ...killed.\n")); | |
284 | + } | |
285 | + exit (status); | |
286 | +} | |
287 | +#endif | |
288 | + | |
289 | /* Ask the user for a password. | |
290 | + If PAM is in use, let PAM ask for the password if necessary. | |
291 | Return true if the user gives the correct password for entry PW, | |
292 | false if not. Return true without asking for a password if run by UID 0 | |
293 | or if PW has an empty password. */ | |
6987acf5 | 294 | @@ -220,10 +411,52 @@ log_su (struct passwd const *pw, bool su |
1555d43c SS |
295 | static bool |
296 | correct_password (const struct passwd *pw) | |
297 | { | |
298 | +#ifdef USE_PAM | |
299 | + const struct passwd *lpw; | |
300 | + const char *cp; | |
301 | + | |
302 | + retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME, | |
303 | + pw->pw_name, &conv, &pamh); | |
304 | + PAM_BAIL_P (return false); | |
305 | + | |
306 | + if (isatty (0) && (cp = ttyname (0)) != NULL) | |
307 | + { | |
308 | + const char *tty; | |
309 | + | |
310 | + if (strncmp (cp, "/dev/", 5) == 0) | |
311 | + tty = cp + 5; | |
312 | + else | |
313 | + tty = cp; | |
314 | + retval = pam_set_item (pamh, PAM_TTY, tty); | |
315 | + PAM_BAIL_P (return false); | |
316 | + } | |
317 | +#if 0 /* Manpage discourages use of getlogin. */ | |
318 | + cp = getlogin (); | |
319 | + if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ())) | |
320 | +#endif | |
321 | + lpw = getpwuid (getuid ()); | |
322 | + if (lpw && lpw->pw_name) | |
323 | + { | |
324 | + retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name); | |
325 | + PAM_BAIL_P (return false); | |
326 | + } | |
327 | + retval = pam_authenticate (pamh, 0); | |
328 | + PAM_BAIL_P (return false); | |
329 | + retval = pam_acct_mgmt (pamh, 0); | |
330 | + if (retval == PAM_NEW_AUTHTOK_REQD) | |
331 | + { | |
332 | + /* Password has expired. Offer option to change it. */ | |
333 | + retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
334 | + PAM_BAIL_P (return false); | |
335 | + } | |
336 | + PAM_BAIL_P (return false); | |
337 | + /* Must be authenticated if this point was reached. */ | |
338 | + return true; | |
339 | +#else /* !USE_PAM */ | |
340 | char *unencrypted, *encrypted, *correct; | |
341 | #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP | |
342 | /* Shadow passwd stuff for SVR3 and maybe other systems. */ | |
343 | - struct spwd *sp = getspnam (pw->pw_name); | |
344 | + const struct spwd *sp = getspnam (pw->pw_name); | |
345 | ||
346 | endspent (); | |
347 | if (sp) | |
6987acf5 | 348 | @@ -244,6 +477,7 @@ correct_password (const struct passwd *p |
1555d43c SS |
349 | encrypted = crypt (unencrypted, correct); |
350 | memset (unencrypted, 0, strlen (unencrypted)); | |
351 | return STREQ (encrypted, correct); | |
352 | +#endif /* !USE_PAM */ | |
353 | } | |
354 | ||
6987acf5 MT |
355 | /* Update 'environ' for the new shell based on PW, with SHELL being |
356 | @@ -286,19 +520,41 @@ modify_environment (const struct passwd | |
1555d43c SS |
357 | } |
358 | } | |
359 | } | |
360 | + | |
361 | +#ifdef USE_PAM | |
362 | + export_pamenv (); | |
363 | +#endif | |
364 | } | |
365 | ||
366 | /* Become the user and group(s) specified by PW. */ | |
367 | ||
368 | static void | |
369 | -change_identity (const struct passwd *pw) | |
370 | +init_groups (const struct passwd *pw) | |
371 | { | |
372 | #ifdef HAVE_INITGROUPS | |
373 | errno = 0; | |
374 | if (initgroups (pw->pw_name, pw->pw_gid) == -1) | |
375 | - error (EXIT_CANCELED, errno, _("cannot set groups")); | |
376 | + { | |
377 | +#ifdef USE_PAM | |
378 | + cleanup_pam (PAM_ABORT); | |
379 | +#endif | |
380 | + error (EXIT_FAILURE, errno, _("cannot set groups")); | |
381 | + } | |
382 | endgrent (); | |
383 | #endif | |
384 | + | |
385 | +#ifdef USE_PAM | |
386 | + retval = pam_setcred (pamh, PAM_ESTABLISH_CRED); | |
387 | + if (retval != PAM_SUCCESS) | |
388 | + error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval)); | |
389 | + else | |
390 | + _pam_cred_established = 1; | |
391 | +#endif | |
392 | +} | |
393 | + | |
394 | +static void | |
395 | +change_identity (const struct passwd *pw) | |
396 | +{ | |
397 | if (setgid (pw->pw_gid)) | |
398 | error (EXIT_CANCELED, errno, _("cannot set group id")); | |
399 | if (setuid (pw->pw_uid)) | |
6987acf5 | 400 | @@ -511,9 +767,21 @@ main (int argc, char **argv) |
1555d43c SS |
401 | shell = NULL; |
402 | } | |
403 | shell = xstrdup (shell ? shell : pw->pw_shell); | |
404 | - modify_environment (pw, shell); | |
405 | + | |
406 | + init_groups (pw); | |
407 | + | |
408 | +#ifdef USE_PAM | |
409 | + create_watching_parent (); | |
410 | + /* Now we're in the child. */ | |
411 | +#endif | |
412 | ||
413 | change_identity (pw); | |
414 | + | |
415 | + /* Set environment after pam_open_session, which may put KRB5CCNAME | |
416 | + into the pam_env, etc. */ | |
417 | + | |
418 | + modify_environment (pw, shell); | |
419 | + | |
420 | if (simulate_login && chdir (pw->pw_dir) != 0) | |
421 | error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir); | |
422 |