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
51char   *getloginname(void);
52char   *ontty(void);
53int	chshell(const char *);
54int	verify_user(char *, struct passwd *, char *, login_cap_t *,
55	    auth_session_t *);
56void	usage(void);
57void	auth_err(auth_session_t *, int, const char *, ...);
58void	auth_errx(auth_session_t *, int, const char *, ...);
59
60int
61main(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
377int
378verify_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
421int
422chshell(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
438char *
439ontty(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
456char *
457getloginname(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
483void
484usage(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
495void
496auth_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
507void
508auth_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