1 /*
2 * su - become-super user or another user
3 *
4 * Gunnar Ritter, Freiburg i. Br., Germany, May 2001.
5 */
6 /*
7 * Copyright (c) 2003 Gunnar Ritter
8 *
9 * This software is provided 'as-is', without any express or implied
10 * warranty. In no event will the authors be held liable for any damages
11 * arising from the use of this software.
12 *
13 * Permission is granted to anyone to use this software for any purpose,
14 * including commercial applications, and to alter it and redistribute
15 * it freely, subject to the following restrictions:
16 *
17 * 1. The origin of this software must not be misrepresented; you must not
18 * claim that you wrote the original software. If you use this software
19 * in a product, an acknowledgment in the product documentation would be
20 * appreciated but is not required.
21 *
22 * 2. Altered source versions must be plainly marked as such, and must not be
23 * misrepresented as being the original software.
24 *
25 * 3. This notice may not be removed or altered from any source distribution.
26 */
27
28 #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4
29 #define USED __attribute__ ((used))
30 #elif defined __GNUC__
31 #define USED __attribute__ ((unused))
32 #else
33 #define USED
34 #endif
35 static const char sccsid[] USED = "@(#)su.sl 1.25 (gritter) 2/21/06";
36
37 #include "config.h"
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <strings.h>
46 #include <stdlib.h>
47 #include <errno.h>
48 #include <libgen.h>
49 #include <time.h>
50 #include <signal.h>
51 #include <pwd.h>
52 #include <grp.h>
53 #include <termios.h>
54 #include <limits.h>
55 #include <syslog.h>
56 #ifdef __APPLE__
57 #include <pam/pam_appl.h>
58 #elif PAM
59 #include <security/pam_appl.h>
60 #else /* !PAM */
61 #ifdef SHADOW_PWD
62 #include <shadow.h>
63 #endif /* SHADOW_PWD */
64 #define PAM_MAX_RESP_SIZE 512
65 #endif /* !PAM */
66
67 enum logtype {
68 LT_NONE,
69 LT_FAIL,
70 LT_ALL
71 };
72
73 extern char **environ;
74
75 static int dash; /* create login environment */
76 static int dofork; /* fork() before executing shell */
77 static enum logtype dosyslog = LT_NONE; /* log to syslog */
78 static int sleeptime = 4; /* sleep time if failed */
79 static char *user; /* desired login name */
80 static char *olduser; /* old login name */
81 static char *progname; /* argv[0] to main() */
82 static char *path; /* new $PATH */
83 static char *dir; /* new home directory */
84 static char *shell; /* new shell */
85 static char *sulog; /* logging file */
86 static char *console; /* console to log to (optional) */
87 static struct passwd *pwd; /* passwd entry for new user */
88 static void (*oldint)(int); /* old SIGINT handler */
89 static void (*oldquit)(int); /* old SIGQUIT handler */
90
91 static const char defaultfile[] = SUDFL;
92 static const char PATH[] = "PATH=/usr/local/bin:/bin:/usr/bin:";
93 static const char SUPATH[] =
94 "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:/bin:/usr/bin";
95
96 /*
97 * Memory allocation with check.
98 */
99 static void *
smalloc(size_t nbytes)100 smalloc(size_t nbytes)
101 {
102 void *p;
103
104 if ((p = malloc(nbytes)) == NULL) {
105 write(2, "Out of memory\n", 14);
106 exit(077);
107 }
108 return p;
109 }
110
111 /*
112 * Adjust environment.
113 */
114 static void
doenv(void)115 doenv(void)
116 {
117
118 if (dash) {
119 char *cp;
120 char *term, *oldterm;
121 char *display, *olddisplay;
122
123 if ((oldterm = getenv("TERM")) != NULL) {
124 term = smalloc(strlen(oldterm) + 6);
125 sprintf(term, "TERM=%s", oldterm);
126 } else
127 term = NULL;
128 if ((olddisplay = getenv("DISPLAY")) != NULL) {
129 display = smalloc(strlen(olddisplay) + 9);
130 sprintf(display, "DISPLAY=%s", olddisplay);
131 } else
132 display = NULL;
133 environ = NULL;
134 cp = smalloc(strlen(dir) + 6);
135 sprintf(cp, "HOME=%s", dir);
136 putenv(cp);
137 cp = smalloc(strlen(user) + 9);
138 sprintf(cp, "LOGNAME=%s", user);
139 putenv(cp);
140 cp = smalloc(strlen(shell) + 7);
141 sprintf(cp, "SHELL=%s", shell);
142 putenv(cp);
143 if (term)
144 putenv(term);
145 if (display)
146 putenv(display);
147 }
148 putenv(path);
149 if (pwd->pw_uid == 0)
150 putenv("PS1=# ");
151 }
152
153 /*
154 * Things to do in child process.
155 */
156 static void
child(char ** args)157 child(char **args)
158 {
159 if (initgroups(user, pwd->pw_gid) < 0 || setgid(pwd->pw_gid) < 0
160 || setuid(pwd->pw_uid) < 0) {
161 fprintf(stderr, "%s: Invalid ID\n", progname);
162 dofork ? _exit(1) : exit(1);
163 }
164 if (dash) {
165 args[0] = smalloc(strlen(basename(shell)) + 2);
166 args[0][0] = '-';
167 strcpy(&args[0][1], basename(shell));
168 if (dir == NULL || chdir(dir) < 0) {
169 fprintf(stderr, "%s: No directory! Using home=/\n",
170 progname);
171 dir = "/";
172 chdir(dir);
173 }
174 } else
175 args[0] = basename(shell);
176 doenv();
177 signal(SIGINT, oldint);
178 signal(SIGQUIT, oldquit);
179 execv(shell, args);
180 fprintf(stderr, "%s: No shell\n", progname);
181 dofork ? _exit(3) : exit(3);
182 }
183
184 /*
185 * Spawn the child process and wait for it.
186 */
187 static int
dospawn(char ** args)188 dospawn(char **args)
189 {
190 int status;
191 pid_t pid;
192
193 switch (pid = fork()) {
194 case 0:
195 child(args);
196 /*NOTREACHED*/
197 case -1:
198 fprintf(stderr, "%s: fork() failed, try again later\n",
199 progname);
200 return 1;
201 }
202 while (wait(&status) != pid);
203 return status;
204 }
205
206 /*
207 * Write log entries.
208 */
209 static void
dolog(int sign)210 dolog(int sign)
211 {
212 FILE *fp;
213 char *tty;
214 const char format[] = "SU %02u/%02u %02u:%02u %c %s %s-%s\n";
215 struct tm *tm;
216 time_t t;
217
218 time(&t);
219 tm = localtime(&t);
220 if ((tty = ttyname(0)) == NULL)
221 tty = "/dev/???";
222 if (dosyslog == LT_ALL || (dosyslog == LT_FAIL && sign == '-')) {
223 openlog("su", LOG_PID, LOG_AUTH);
224 if (sign == '-')
225 syslog(LOG_CRIT | LOG_AUTH,
226 "'su %s' failed for %s on %s",
227 user, olduser, tty);
228 else if (dosyslog != LT_FAIL)
229 syslog((pwd->pw_uid ? LOG_INFO : LOG_NOTICE) | LOG_AUTH,
230 "'su %s' succeeded for %s on %s",
231 user, olduser, tty);
232 closelog();
233 }
234 if (strncmp(tty, "/dev/", 5) == 0)
235 tty += 5;
236 if (sulog && (fp = fopen(sulog, "a+")) != NULL) {
237 fprintf(fp, format, tm->tm_mon + 1, tm->tm_mday,
238 tm->tm_hour, tm->tm_min,
239 sign, tty,
240 olduser, user);
241 fclose(fp);
242 }
243 if (console && (fp = fopen(console, "a+")) != NULL) {
244 fprintf(fp, format, tm->tm_mon + 1, tm->tm_mday,
245 tm->tm_hour, tm->tm_min,
246 sign, tty,
247 olduser, user);
248 fclose(fp);
249 }
250 }
251
252 /*
253 * Parse defaults file.
254 */
255 static void
defaults(void)256 defaults(void)
257 {
258 FILE *fp;
259
260 if ((fp = fopen(defaultfile, "r")) != NULL) {
261 char buf[LINE_MAX];
262 char *cp;
263
264 while (fgets(buf, sizeof buf, fp) != NULL) {
265 if (buf[0] == '\0' || buf[0] == '\n' || buf[0] == '#')
266 continue;
267 if ((cp = strchr(buf, '\n')) != NULL)
268 *cp = '\0';
269 if (strncmp(buf, pwd->pw_uid ? "PATH=" : "SUPATH=",
270 pwd->pw_uid ? 5 : 7) == 0) {
271 path = smalloc(strlen(buf) + 1);
272 strcpy(path, pwd->pw_uid ? buf : &buf[2]);
273 } else if (strncmp(buf, "SULOG=", 6) == 0) {
274 sulog = smalloc(strlen(buf) - 5);
275 strcpy(sulog, &buf[6]);
276 } else if (strncmp(buf, "CONSOLE=", 8) == 0) {
277 console = smalloc(strlen(buf) - 7);
278 strcpy(console, &buf[8]);
279 } else if (strncmp(buf, "SLEEPTIME=", 10) == 0) {
280 sleeptime = atoi(&buf[10]);
281 if (sleeptime < 0 || sleeptime > 5)
282 sleeptime = 4;
283 } else if (strncmp(buf, "SYSLOG=", 7) == 0) {
284 if (strcasecmp(&buf[7], "yes") == 0)
285 dosyslog = LT_ALL;
286 else if (strcasecmp(&buf[7], "fail") == 0)
287 dosyslog = LT_FAIL;
288 } else if (strncmp(buf, "DOFORK=", 7) == 0) {
289 if (strcasecmp(&buf[7], "yes") == 0)
290 dofork = 1;
291 }
292 }
293 fclose(fp);
294 }
295 if (path == NULL)
296 path = (char *)(pwd->pw_uid ? PATH : SUPATH);
297 }
298
299 /*
300 * Ask for passwords.
301 */
302 static char *
ask(const char * msg,int echo)303 ask(const char *msg, int echo)
304 {
305 struct termios tio;
306 char *rsp;
307 tcflag_t lflag;
308 int fd, i = 0;
309 char c;
310 size_t sz;
311
312 if ((fd = open("/dev/tty", O_RDWR)) < 0)
313 fd = 0;
314 if (tcgetattr(fd, &tio) < 0) {
315 if (fd)
316 close(fd);
317 return NULL;
318 }
319 lflag = tio.c_lflag;
320 if (echo == 0) {
321 tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
322 if (tcsetattr(fd, TCSAFLUSH, &tio) < 0) {
323 if (fd)
324 close(fd);
325 return NULL;
326 }
327 }
328 sz = strlen(msg);
329 if (sz > 2 && msg[sz-1] == ' ' && msg[sz-2] == ':')
330 sz--;
331 fwrite(msg, sizeof *msg, sz, stderr);
332 rsp = smalloc(PAM_MAX_RESP_SIZE + 1);
333 while (read(fd, &c, 1) == 1 && c != '\n')
334 if (i < PAM_MAX_RESP_SIZE)
335 rsp[i++] = c;
336 rsp[i] = '\0';
337 fputc('\n', stderr);
338 if (echo == 0) {
339 tio.c_lflag = lflag;
340 tcsetattr(fd, TCSADRAIN, &tio);
341 }
342 if (fd)
343 close(fd);
344 return rsp;
345 }
346
347 #ifdef PAM
348 /*
349 * PAM conversation.
350 */
351 static int
doconv(int num_msg,const struct pam_message ** msg,struct pam_response ** rsp,void * appdata)352 doconv(int num_msg, const struct pam_message **msg,
353 struct pam_response **rsp, void *appdata) {
354 char *cp;
355 int i;
356
357 if (num_msg <= 0)
358 return PAM_CONV_ERR;
359 for (i = 0; i < num_msg; i++) {
360 switch (msg[i]->msg_style) {
361 case PAM_PROMPT_ECHO_ON:
362 case PAM_PROMPT_ECHO_OFF:
363 if ((cp = ask(msg[i]->msg, msg[i]->msg_style
364 == PAM_PROMPT_ECHO_ON)) == NULL)
365 return PAM_CONV_ERR;
366 rsp[i] = smalloc(sizeof *rsp[i]);
367 rsp[i]->resp = cp;
368 rsp[i]->resp_retcode = 0;
369 break;
370 case PAM_ERROR_MSG:
371 fprintf(stderr, "%s\n", msg[i]->msg);
372 break;
373 case PAM_TEXT_INFO:
374 printf("%s\n", msg[i]->msg);
375 break;
376 }
377 }
378 return PAM_SUCCESS;
379 }
380
381 static struct pam_conv conv = {
382 doconv,
383 NULL
384 };
385 #else /* !PAM */
386 /*
387 * Check if the passed string is possibly a valid password, either a
388 * traditional or a MD5 one.
389 */
390 #ifdef SHADOW_PWD
391 static int
cantbevalid(const char * ncrypt)392 cantbevalid(const char *ncrypt)
393 {
394 if (ncrypt == NULL || strlen(ncrypt) < 13)
395 return 1;
396 while (*ncrypt)
397 if (strchr(
398 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./$",
399 *ncrypt) == NULL)
400 return 1;
401 return 0;
402 }
403 #endif /* SHADOW_PWD */
404
405 /*
406 * Traditional password file authentication.
407 */
408 static int
authenticate(const char * name,const char * ncrypt)409 authenticate(const char *name, const char *ncrypt)
410 {
411 char *password;
412 int val;
413
414 #ifdef SHADOW_PWD
415 if (cantbevalid(ncrypt)) {
416 struct spwd *sp;
417 time_t now;
418
419 if ((sp = getspnam(name)) == NULL)
420 return 0;
421 time(&now);
422 if (sp->sp_expire > 0 && now >= sp->sp_expire)
423 return 0;
424 if (sp->sp_lstchg > 0 && sp->sp_max >= 0 && sp->sp_inact >= 0 &&
425 now >= sp->sp_lstchg + sp->sp_max + sp->sp_inact)
426 return 0;
427 ncrypt = sp->sp_pwdp;
428 }
429 #endif /* SHADOW_PWD */
430 if (*ncrypt == '\0')
431 return 1;
432 if ((password = ask("Password:", 0)) == NULL)
433 return 0;
434 val = strcmp(ncrypt, crypt(password, ncrypt)) == 0;
435 while (*password)
436 *password++ = '\0';
437 return val;
438 }
439 #endif /* !PAM */
440
441 int
main(int argc,char ** argv)442 main(int argc, char **argv)
443 {
444 #ifdef PAM
445 pam_handle_t *pamh = NULL;
446 int ret;
447 #endif /* PAM */
448 int status = 0;
449 uid_t myuid;
450
451 progname = basename(argv[0]);
452 oldint = signal(SIGINT, SIG_IGN);
453 oldquit = signal(SIGQUIT, SIG_IGN);
454 if (argc > 1 && argv[1][0] == '-') {
455 dash++;
456 argc--, argv++;
457 }
458 if (argc > 1) {
459 user = argv[1];
460 argc--, argv++;
461 } else
462 user = "root";
463 myuid = getuid();
464 if ((pwd = getpwuid(myuid)) == NULL) {
465 fprintf(stderr, "%s: you do not exist\n", progname);
466 exit(1);
467 }
468 olduser = smalloc(strlen(pwd->pw_name) + 1);
469 strcpy(olduser, pwd->pw_name);
470 if ((pwd = getpwnam(user)) == NULL) {
471 fprintf(stderr, "%s: Unknown id: %s\n", progname, user);
472 exit(1);
473 }
474 if (pwd->pw_dir != NULL && *pwd->pw_dir != '\0') {
475 dir = smalloc(strlen(pwd->pw_dir) + 1);
476 strcpy(dir, pwd->pw_dir);
477 }
478 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
479 shell = smalloc(strlen(pwd->pw_shell) + 1);
480 strcpy(shell, pwd->pw_shell);
481 } else
482 shell = "/bin/sh";
483 defaults();
484 #ifdef PAM
485 ret = pam_start("su", user, &conv, &pamh);
486 if (ret == PAM_SUCCESS)
487 ret = pam_authenticate(pamh, 0);
488 if (ret == PAM_SUCCESS)
489 ret = pam_acct_mgmt(pamh, 0);
490 if (ret == PAM_SUCCESS) {
491 #else /* PAM */
492 if (myuid == 0 || authenticate(user, pwd->pw_passwd) != 0) {
493 #endif /* !PAM */
494 dolog('+');
495 if (dofork) {
496 status = dospawn(argv);
497 } else {
498 #ifdef PAM
499 pam_end(pamh, ret);
500 #endif /* PAM */
501 child(argv);
502 /*NOTREACHED*/
503 }
504 } else {
505 dolog('-');
506 if (sleeptime)
507 sleep(sleeptime);
508 fprintf(stderr, "%s: Sorry\n", progname);
509 status = 1;
510 }
511 #ifdef PAM
512 pam_end(pamh, ret);
513 #endif /* PAM */
514 return status;
515 }
516