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