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