xref: /openbsd/usr.bin/su/su.c (revision 881f6c5f)
1 /*	$OpenBSD: su.c,v 1.89 2022/12/22 19:53:23 kn Exp $	*/
2 
3 /*
4  * Copyright (c) 1988 The Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/time.h>
33 #include <sys/resource.h>
34 
35 #include <err.h>
36 #include <errno.h>
37 #include <grp.h>
38 #include <login_cap.h>
39 #include <paths.h>
40 #include <pwd.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <unistd.h>
46 #include <limits.h>
47 #include <utmp.h>
48 #include <stdarg.h>
49 #include <bsd_auth.h>
50 
51 char   *getloginname(void);
52 char   *ontty(void);
53 int	chshell(const char *);
54 int	verify_user(char *, struct passwd *, char *, login_cap_t *,
55 	    auth_session_t *);
56 void	usage(void);
57 void	auth_err(auth_session_t *, int, const char *, ...);
58 void	auth_errx(auth_session_t *, int, const char *, ...);
59 
60 int
main(int argc,char ** argv)61 main(int argc, char **argv)
62 {
63 	int asme = 0, asthem = 0, ch, fastlogin = 0, emlogin = 0, prio;
64 	int altshell = 0, homeless = 0;
65 	char *user, *shell = NULL, *avshell, *username, **np;
66 	char *class = NULL, *style = NULL, *p;
67 	enum { UNSET, YES, NO } iscsh = UNSET;
68 	char avshellbuf[PATH_MAX];
69 	extern char **environ;
70 	auth_session_t *as;
71 	struct passwd *pwd;
72 	login_cap_t *lc;
73 	uid_t ruid;
74 	u_int flags;
75 
76 	if (pledge("stdio unveil rpath getpw proc exec id", NULL) == -1)
77 		err(1, "pledge");
78 
79 	while ((ch = getopt(argc, argv, "a:c:fKLlms:-")) != -1)
80 		switch (ch) {
81 		case 'a':
82 			if (style)
83 				usage();
84 			style = optarg;
85 			break;
86 		case 'c':
87 			if (class)
88 				usage();
89 			class = optarg;
90 			break;
91 		case 'f':
92 			fastlogin = 1;
93 			break;
94 		case 'K':
95 			if (style)
96 				usage();
97 			style = "passwd";
98 			break;
99 		case 'L':
100 			emlogin = 1;
101 			break;
102 		case 'l':
103 		case '-':
104 			asme = 0;
105 			asthem = 1;
106 			break;
107 		case 'm':
108 			asme = 1;
109 			asthem = 0;
110 			break;
111 		case 's':
112 			altshell = 1;
113 			shell = optarg;
114 			break;
115 		default:
116 			usage();
117 		}
118 	argv += optind;
119 
120 	errno = 0;
121 	prio = getpriority(PRIO_PROCESS, 0);
122 	if (errno)
123 		prio = 0;
124 	setpriority(PRIO_PROCESS, 0, -2);
125 	openlog("su", LOG_CONS, 0);
126 
127 	if ((as = auth_open()) == NULL) {
128 		syslog(LOG_ERR, "auth_open: %m");
129 		err(1, "unable to initialize BSD authentication");
130 	}
131 	auth_setoption(as, "login", "yes");
132 
133 	/* get current login name and shell */
134 	ruid = getuid();
135 	username = getlogin();
136 
137 	if (ruid && class)
138 		auth_errx(as, 1, "only the superuser may specify a login class");
139 
140 	if (ruid && altshell)
141 		auth_errx(as, 1, "only the superuser may specify a login shell");
142 
143 	if (username != NULL)
144 		auth_setoption(as, "invokinguser", username);
145 
146 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
147 	    pwd->pw_uid != ruid)
148 		pwd = getpwuid(ruid);
149 	if (pwd == NULL)
150 		auth_errx(as, 1, "who are you?");
151 	if ((username = strdup(pwd->pw_name)) == NULL)
152 		auth_err(as, 1, NULL);
153 	if (asme && !altshell) {
154 		if (pwd->pw_shell && *pwd->pw_shell) {
155 			if ((shell = strdup(pwd->pw_shell)) == NULL)
156 				auth_err(as, 1, NULL);
157 		} else {
158 			shell = _PATH_BSHELL;
159 			iscsh = NO;
160 		}
161 	}
162 
163 	if (unveil(_PATH_LOGIN_CONF, "r") == -1)
164 		err(1, "unveil %s", _PATH_LOGIN_CONF);
165 	if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1)
166 		err(1, "unveil %s.db", _PATH_LOGIN_CONF);
167 	if (unveil(_PATH_LOGIN_CONF_D, "r") == -1)
168 		err(1, "unveil %s", _PATH_LOGIN_CONF_D);
169 	if (unveil(_PATH_AUTHPROGDIR, "x") == -1)
170 		err(1, "unveil %s", _PATH_AUTHPROGDIR);
171 	if (unveil(_PATH_SHELLS, "r") == -1)
172 		err(1, "unveil %s", _PATH_SHELLS);
173 	if (unveil(_PATH_DEVDB, "r") == -1)
174 		err(1, "unveil %s", _PATH_DEVDB);
175 	if (unveil(_PATH_NOLOGIN, "r") == -1)
176 		err(1, "unveil %s", _PATH_NOLOGIN);
177 
178 	for (;;) {
179 		char *pw_class = class;
180 
181 		/* get target user, default to root unless in -L mode */
182 		if (*argv) {
183 			user = *argv;
184 		} else if (emlogin) {
185 			if ((user = getloginname()) == NULL) {
186 				auth_close(as);
187 				exit(1);
188 			}
189 		} else {
190 			user = "root";
191 		}
192 		/* style may be specified as part of the username */
193 		if ((p = strchr(user, ':')) != NULL) {
194 			*p++ = '\0';
195 			style = p;	/* XXX overrides -a flag */
196 		}
197 
198 		/*
199 		 * Clean and setup our current authentication session.
200 		 * Note that options *are* not cleared.
201 		 */
202 		auth_clean(as);
203 		if (auth_setitem(as, AUTHV_INTERACTIVE, "True") != 0 ||
204 		    auth_setitem(as, AUTHV_NAME, user) != 0)
205 			auth_err(as, 1, NULL);
206 		if ((user = auth_getitem(as, AUTHV_NAME)) == NULL)
207 			auth_errx(as, 1, "internal error");
208 		if (auth_setpwd(as, NULL) || (pwd = auth_getpwd(as)) == NULL) {
209 			if (emlogin)
210 				pwd = NULL;
211 			else
212 				auth_errx(as, 1, "unknown login %s", user);
213 		}
214 
215 		/* If the user specified a login class, use it */
216 		if (pw_class == NULL && pwd != NULL)
217 			pw_class = pwd->pw_class;
218 		if ((lc = login_getclass(pw_class)) == NULL)
219 			auth_errx(as, 1, "no such login class: %s",
220 			    pw_class ? pw_class : LOGIN_DEFCLASS);
221 
222 		if ((ruid == 0 && !emlogin) ||
223 		    verify_user(username, pwd, style, lc, as) == 0)
224 			break;
225 		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s",
226 		    username, user, ontty());
227 		if (!emlogin) {
228 			fprintf(stderr, "Sorry\n");
229 			auth_close(as);
230 			exit(1);
231 		}
232 		fprintf(stderr, "Login incorrect\n");
233 	}
234 	if (pwd == NULL)
235 		auth_errx(as, 1, "internal error");
236 
237 	if (pledge("stdio unveil rpath getpw exec id", NULL) == -1)
238 		err(1, "pledge");
239 
240 	if (!altshell) {
241 		if (asme) {
242 			/* must be root to override non-std target shell */
243 			if (ruid && !chshell(pwd->pw_shell))
244 				auth_errx(as, 1, "permission denied (shell).");
245 		} else if (pwd->pw_shell && *pwd->pw_shell) {
246 			if ((shell = strdup(pwd->pw_shell)) == NULL)
247 				auth_err(as, 1, NULL);
248 			iscsh = UNSET;
249 		} else {
250 			shell = _PATH_BSHELL;
251 			iscsh = NO;
252 		}
253 	}
254 
255 	if (unveil(shell, "x") == -1)
256 		err(1, "unveil %s", shell);
257 	if (unveil(pwd->pw_dir, "r") == -1)
258 		err(1, "unveil %s", pwd->pw_dir);
259 
260 	if ((p = strrchr(shell, '/')))
261 		avshell = p+1;
262 	else
263 		avshell = shell;
264 
265 	/* if we're forking a csh, we want to slightly muck the args */
266 	if (iscsh == UNSET)
267 		iscsh = strcmp(avshell, "csh") ? NO : YES;
268 
269 	if (!asme) {
270 		if (asthem) {
271 			p = getenv("TERM");
272 			if ((environ = calloc(1, sizeof (char *))) == NULL)
273 				auth_errx(as, 1, "calloc");
274 			if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
275 				auth_err(as, 1, "unable to set user context");
276 			if (p && setenv("TERM", p, 1) == -1)
277 				auth_err(as, 1, "unable to set environment");
278 
279 			setegid(pwd->pw_gid);
280 			seteuid(pwd->pw_uid);
281 
282 			homeless = chdir(pwd->pw_dir);
283 			if (homeless == -1) {
284 				if (login_getcapbool(lc, "requirehome", 0)) {
285 					auth_err(as, 1, "%s", pwd->pw_dir);
286 				} else {
287 					if (unveil("/", "r") == -1)
288 						err(1, "unveil /");
289 					printf("No home directory %s!\n", pwd->pw_dir);
290 					printf("Logging in with home = \"/\".\n");
291 					if (chdir("/") == -1)
292 						auth_err(as, 1, "/");
293 				}
294 			}
295 			setegid(0);	/* XXX use a saved gid instead? */
296 			seteuid(0);
297 		} else if (pwd->pw_uid == 0) {
298 			if (setusercontext(lc,
299 			    pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK))
300 				auth_err(as, 1, "unable to set user context");
301 		}
302 		if (asthem || pwd->pw_uid) {
303 			if (setenv("LOGNAME", pwd->pw_name, 1) == -1 ||
304 			    setenv("USER", pwd->pw_name, 1) == -1)
305 				auth_err(as, 1, "unable to set environment");
306 		}
307 		if (setenv("HOME", homeless ? "/" : pwd->pw_dir, 1) == -1 ||
308 		    setenv("SHELL", shell, 1) == -1)
309 			auth_err(as, 1, "unable to set environment");
310 	} else if (altshell) {
311 		if (setenv("SHELL", shell, 1) == -1)
312 			auth_err(as, 1, "unable to set environment");
313 	}
314 	if (pledge("stdio rpath getpw exec id", NULL) == -1)
315 		err(1, "pledge");
316 
317 	np = *argv ? argv : argv - 1;
318 
319 	if (iscsh == YES) {
320 		if (fastlogin)
321 			*np-- = "-f";
322 		if (asme)
323 			*np-- = "-m";
324 
325 		if (asthem)
326 			avshellbuf[0] = '-';
327 		else {
328 			/* csh strips the first character... */
329 			avshellbuf[0] = '_';
330 		}
331 		strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
332 		avshell = avshellbuf;
333 	} else if (asthem && !fastlogin) {
334 		avshellbuf[0] = '-';
335 		strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
336 		avshell = avshellbuf;
337 	}
338 
339 	*np = avshell;
340 
341 	if (ruid != 0)
342 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
343 		    username, user, ontty());
344 
345 	setpriority(PRIO_PROCESS, 0, prio);
346 	if (emlogin) {
347 		flags = LOGIN_SETALL & ~LOGIN_SETPATH;
348 		/*
349 		 * Only call setlogin() if this process is a session leader.
350 		 * In practice, this means the login name will be set only if
351 		 * we are exec'd by a shell.  This is important because
352 		 * otherwise the parent shell's login name would change too.
353 		 */
354 		if (getsid(0) != getpid())
355 			flags &= ~LOGIN_SETLOGIN;
356 	} else {
357 		flags = LOGIN_SETRESOURCES|LOGIN_SETGROUP|LOGIN_SETUSER;
358 		if (!asme)
359 			flags |= LOGIN_SETRTABLE;
360 		if (asthem)
361 			flags |= LOGIN_SETENV|LOGIN_SETPRIORITY|LOGIN_SETUMASK;
362 	}
363 	if (setusercontext(lc, pwd, pwd->pw_uid, flags) != 0)
364 		auth_err(as, 1, "unable to set user context");
365 
366 	if (pledge("stdio rpath exec", NULL) == -1)
367 		err(1, "pledge");
368 
369 	if (pwd->pw_uid && auth_approval(as, lc, pwd->pw_name, "su") == 0)
370 		auth_errx(as, 1, "approval failure");
371 	auth_close(as);
372 
373 	execv(shell, np);
374 	err(1, "%s", shell);
375 }
376 
377 int
verify_user(char * from,struct passwd * pwd,char * style,login_cap_t * lc,auth_session_t * as)378 verify_user(char *from, struct passwd *pwd, char *style,
379     login_cap_t *lc, auth_session_t *as)
380 {
381 	struct group *gr;
382 	char **g, *cp;
383 	int authok;
384 
385 	/*
386 	 * If we are trying to become root and the default style
387 	 * is being used, don't bother to look it up (we might be
388 	 * be su'ing up to fix /etc/login.conf)
389 	 */
390 	if ((pwd == NULL || pwd->pw_uid != 0 || style == NULL ||
391 	    strcmp(style, LOGIN_DEFSTYLE) != 0) &&
392 	    (style = login_getstyle(lc, style, "auth-su")) == NULL)
393 		auth_errx(as, 1, "invalid authentication type");
394 
395 	/*
396 	 * Let the authentication program know whether they are
397 	 * in group wheel or not (if trying to become super user)
398 	 */
399 	if (pwd != NULL && pwd->pw_uid == 0 && (gr = getgrgid(0)) != NULL &&
400 	    gr->gr_mem != NULL && *(gr->gr_mem) != NULL) {
401 		for (g = gr->gr_mem; *g; ++g) {
402 			if (strcmp(from, *g) == 0) {
403 				auth_setoption(as, "wheel", "yes");
404 				break;
405 			}
406 		}
407 		if (!*g)
408 			auth_setoption(as, "wheel", "no");
409 	}
410 
411 	auth_verify(as, style, NULL, lc->lc_class, (char *)NULL);
412 	authok = auth_getstate(as);
413 	if ((authok & AUTH_ALLOW) == 0) {
414 		if ((cp = auth_getvalue(as, "errormsg")) != NULL)
415 			fprintf(stderr, "%s\n", cp);
416 		return(1);
417 	}
418 	return(0);
419 }
420 
421 int
chshell(const char * sh)422 chshell(const char *sh)
423 {
424 	char *cp;
425 	int found = 0;
426 
427 	setusershell();
428 	while ((cp = getusershell()) != NULL) {
429 		if (strcmp(cp, sh) == 0) {
430 			found = 1;
431 			break;
432 		}
433 	}
434 	endusershell();
435 	return (found);
436 }
437 
438 char *
ontty(void)439 ontty(void)
440 {
441 	static char buf[PATH_MAX + 4];
442 	char *p;
443 
444 	buf[0] = 0;
445 	if ((p = ttyname(STDERR_FILENO)))
446 		snprintf(buf, sizeof(buf), " on %s", p);
447 	return (buf);
448 }
449 
450 /*
451  * Allow for a '.' and 16 characters for any instance as well as
452  * space for a ':' and 16 characters defining the authentication type.
453  */
454 #define NBUFSIZ		(UT_NAMESIZE + 1 + 16 + 1 + 16)
455 
456 char *
getloginname(void)457 getloginname(void)
458 {
459 	static char nbuf[NBUFSIZ], *p;
460 	int ch;
461 
462 	for (;;) {
463 		printf("login: ");
464 		for (p = nbuf; (ch = getchar()) != '\n'; ) {
465 			if (ch == EOF)
466 				return (NULL);
467 			if (p < nbuf + (NBUFSIZ - 1))
468 				*p++ = ch;
469 		}
470 		if (p > nbuf) {
471 			if (nbuf[0] == '-') {
472 				fprintf(stderr,
473 				    "login names may not start with '-'.\n");
474 			} else {
475 				*p = '\0';
476 				break;
477 			}
478 		}
479 	}
480 	return (nbuf);
481 }
482 
483 void
usage(void)484 usage(void)
485 {
486 	extern char *__progname;
487 
488 	fprintf(stderr, "usage: %s [-fKLlm] [-a auth-type] [-c login-class] "
489 	    "[-s login-shell]\n"
490 	    "%-*s[login [shell-argument ...]]\n", __progname,
491 	    (int)strlen(__progname) + 8, "");
492 	exit(1);
493 }
494 
495 void
auth_err(auth_session_t * as,int eval,const char * fmt,...)496 auth_err(auth_session_t *as, int eval, const char *fmt, ...)
497 {
498 	va_list ap;
499 
500 	va_start(ap, fmt);
501 	vwarn(fmt, ap);
502 	va_end(ap);
503 	auth_close(as);
504 	exit(eval);
505 }
506 
507 void
auth_errx(auth_session_t * as,int eval,const char * fmt,...)508 auth_errx(auth_session_t *as, int eval, const char *fmt, ...)
509 {
510 	va_list ap;
511 
512 	va_start(ap, fmt);
513 	vwarnx(fmt, ap);
514 	va_end(ap);
515 	auth_close(as);
516 	exit(eval);
517 }
518