xref: /openbsd/usr.bin/su/su.c (revision a6445c1d)
1 /*	$OpenBSD: su.c,v 1.65 2011/01/11 10:07:56 robert 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/param.h>
33 #include <sys/time.h>
34 #include <sys/resource.h>
35 
36 #include <err.h>
37 #include <errno.h>
38 #include <grp.h>
39 #include <login_cap.h>
40 #include <paths.h>
41 #include <pwd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <syslog.h>
46 #include <unistd.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
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[MAXPATHLEN];
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 	while ((ch = getopt(argc, argv, "a:c:fKLlms:-")) != -1)
77 		switch (ch) {
78 		case 'a':
79 			if (style)
80 				usage();
81 			style = optarg;
82 			break;
83 		case 'c':
84 			if (class)
85 				usage();
86 			class = optarg;
87 			break;
88 		case 'f':
89 			fastlogin = 1;
90 			break;
91 		case 'K':
92 			if (style)
93 				usage();
94 			style = "passwd";
95 			break;
96 		case 'L':
97 			emlogin = 1;
98 			break;
99 		case 'l':
100 		case '-':
101 			asme = 0;
102 			asthem = 1;
103 			break;
104 		case 'm':
105 			asme = 1;
106 			asthem = 0;
107 			break;
108 		case 's':
109 			altshell = 1;
110 			shell = optarg;
111 			break;
112 		default:
113 			usage();
114 		}
115 	argv += optind;
116 
117 	errno = 0;
118 	prio = getpriority(PRIO_PROCESS, 0);
119 	if (errno)
120 		prio = 0;
121 	(void)setpriority(PRIO_PROCESS, 0, -2);
122 	openlog("su", LOG_CONS, 0);
123 
124 	if ((as = auth_open()) == NULL) {
125 		syslog(LOG_ERR, "auth_open: %m");
126 		err(1, "unable to initialize BSD authentication");
127 	}
128 	auth_setoption(as, "login", "yes");
129 
130 	/* get current login name and shell */
131 	ruid = getuid();
132 	username = getlogin();
133 
134 	if (ruid && class)
135 		auth_errx(as, 1, "only the superuser may specify a login class");
136 
137 	if (ruid && altshell)
138 		auth_errx(as, 1, "only the superuser may specify a login shell");
139 
140 	if (username != NULL)
141 		auth_setoption(as, "invokinguser", username);
142 
143 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
144 	    pwd->pw_uid != ruid)
145 		pwd = getpwuid(ruid);
146 	if (pwd == NULL)
147 		auth_errx(as, 1, "who are you?");
148 	if ((username = strdup(pwd->pw_name)) == NULL)
149 		auth_errx(as, 1, "can't allocate memory");
150 	if (asme && !altshell) {
151 		if (pwd->pw_shell && *pwd->pw_shell) {
152 			if ((shell = strdup(pwd->pw_shell)) == NULL)
153 				auth_errx(as, 1, "can't allocate memory");
154 		} else {
155 			shell = _PATH_BSHELL;
156 			iscsh = NO;
157 		}
158 	}
159 
160 	for (;;) {
161 		/* get target user, default to root unless in -L mode */
162 		if (*argv) {
163 			user = *argv;
164 		} else if (emlogin) {
165 			if ((user = getloginname()) == NULL) {
166 				auth_close(as);
167 				exit(1);
168 			}
169 		} else {
170 			user = "root";
171 		}
172 		/* style may be specified as part of the username */
173 		if ((p = strchr(user, ':')) != NULL) {
174 			*p++ = '\0';
175 			style = p;	/* XXX overrides -a flag */
176 		}
177 
178 		/*
179 		 * Clean and setup our current authentication session.
180 		 * Note that options *are* not cleared.
181 		 */
182 		auth_clean(as);
183 		if (auth_setitem(as, AUTHV_INTERACTIVE, "True") != 0 ||
184 		    auth_setitem(as, AUTHV_NAME, user) != 0)
185 			auth_errx(as, 1, "can't allocate memory");
186 		if ((user = auth_getitem(as, AUTHV_NAME)) == NULL)
187 			auth_errx(as, 1, "internal error");
188 		if (auth_setpwd(as, NULL) || (pwd = auth_getpwd(as)) == NULL) {
189 			if (emlogin)
190 				pwd = NULL;
191 			else
192 				auth_errx(as, 1, "unknown login %s", user);
193 		}
194 
195 		/* If the user specified a login class, use it */
196 		if (!class && pwd && pwd->pw_class && pwd->pw_class[0] != '\0')
197 			class = strdup(pwd->pw_class);
198 		if ((lc = login_getclass(class)) == NULL)
199 			auth_errx(as, 1, "no such login class: %s",
200 			    class ? class : LOGIN_DEFCLASS);
201 
202 		if ((ruid == 0 && !emlogin) ||
203 		    verify_user(username, pwd, style, lc, as) == 0)
204 			break;
205 		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s",
206 		    username, user, ontty());
207 		if (!emlogin) {
208 			fprintf(stderr, "Sorry\n");
209 			auth_close(as);
210 			exit(1);
211 		}
212 		fprintf(stderr, "Login incorrect\n");
213 	}
214 
215 	if (!altshell) {
216 		if (asme) {
217 			/* if asme and non-std target shell, must be root */
218 			if (ruid && !chshell(shell))
219 				auth_errx(as, 1, "permission denied (shell).");
220 		} else if (pwd->pw_shell && *pwd->pw_shell) {
221 			if ((shell = strdup(pwd->pw_shell)) == NULL)
222 				auth_errx(as, 1, "can't allocate memory");
223 			iscsh = UNSET;
224 		} else {
225 			shell = _PATH_BSHELL;
226 			iscsh = NO;
227 		}
228 	}
229 
230 	if ((p = strrchr(shell, '/')))
231 		avshell = p+1;
232 	else
233 		avshell = shell;
234 
235 	/* if we're forking a csh, we want to slightly muck the args */
236 	if (iscsh == UNSET)
237 		iscsh = strcmp(avshell, "csh") ? NO : YES;
238 
239 	if (!asme) {
240 		if (asthem) {
241 			p = getenv("TERM");
242 			if ((environ = calloc(1, sizeof (char *))) == NULL)
243 				auth_errx(as, 1, "calloc");
244 			if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
245 				auth_err(as, 1, "unable to set user context");
246 			if (p && setenv("TERM", p, 1) == -1)
247 				auth_err(as, 1, "unable to set environment");
248 
249 			setegid(pwd->pw_gid);
250 			seteuid(pwd->pw_uid);
251 
252 			homeless = chdir(pwd->pw_dir);
253 			if (homeless) {
254 				if (login_getcapbool(lc, "requirehome", 0)) {
255 					auth_err(as, 1, "%s", pwd->pw_dir);
256 				} else {
257 					(void)printf("No home directory %s!\n", pwd->pw_dir);
258 					(void)printf("Logging in with home = \"/\".\n");
259 					if (chdir("/") < 0)
260 						auth_err(as, 1, "/");
261 				}
262 			}
263 			setegid(0);	/* XXX use a saved gid instead? */
264 			seteuid(0);
265 		} else if (pwd->pw_uid == 0) {
266 			if (setusercontext(lc,
267 			    pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK))
268 				auth_err(as, 1, "unable to set user context");
269 		}
270 		if (asthem || pwd->pw_uid) {
271 			if (setenv("LOGNAME", pwd->pw_name, 1) == -1 ||
272 			    setenv("USER", pwd->pw_name, 1) == -1)
273 				auth_err(as, 1, "unable to set environment");
274 		}
275 		if (setenv("HOME", homeless ? "/" : pwd->pw_dir, 1) == -1 ||
276 		    setenv("SHELL", shell, 1) == -1)
277 			auth_err(as, 1, "unable to set environment");
278 	} else if (altshell) {
279 		if (setenv("SHELL", shell, 1) == -1)
280 			auth_err(as, 1, "unable to set environment");
281 	}
282 
283 	np = *argv ? argv : argv - 1;
284 	if (iscsh == YES) {
285 		if (fastlogin)
286 			*np-- = "-f";
287 		if (asme)
288 			*np-- = "-m";
289 	}
290 
291 	if (asthem) {
292 		avshellbuf[0] = '-';
293 		strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
294 		avshell = avshellbuf;
295 	} else if (iscsh == YES) {
296 		/* csh strips the first character... */
297 		avshellbuf[0] = '_';
298 		strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
299 		avshell = avshellbuf;
300 	}
301 
302 	*np = avshell;
303 
304 	if (ruid != 0)
305 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
306 		    username, user, ontty());
307 
308 	(void)setpriority(PRIO_PROCESS, 0, prio);
309 	if (emlogin) {
310 		flags = LOGIN_SETALL & ~LOGIN_SETPATH;
311 		/*
312 		 * Only call setlogin() if this process is a session leader.
313 		 * In practice, this means the login name will be set only if
314 		 * we are exec'd by a shell.  This is important because
315 		 * otherwise the parent shell's login name would change too.
316 		 */
317 		if (getsid(0) != getpid())
318 			flags &= ~LOGIN_SETLOGIN;
319 	} else {
320 		flags = LOGIN_SETRESOURCES|LOGIN_SETGROUP|LOGIN_SETUSER;
321 		if (asthem)
322 			flags |= LOGIN_SETENV|LOGIN_SETPRIORITY|LOGIN_SETUMASK;
323 	}
324 	if (setusercontext(lc, pwd, pwd->pw_uid, flags) != 0)
325 		auth_err(as, 1, "unable to set user context");
326 	if (pwd->pw_uid && auth_approval(as, lc, pwd->pw_name, "su") <= 0)
327 		auth_err(as, 1, "approval failure");
328 	auth_close(as);
329 
330 	execv(shell, np);
331 	err(1, "%s", shell);
332 }
333 
334 int
335 verify_user(char *from, struct passwd *pwd, char *style,
336     login_cap_t *lc, auth_session_t *as)
337 {
338 	struct group *gr;
339 	char **g, *cp;
340 	int authok;
341 
342 	/*
343 	 * If we are trying to become root and the default style
344 	 * is being used, don't bother to look it up (we might be
345 	 * be su'ing up to fix /etc/login.conf)
346 	 */
347 	if ((pwd == NULL || pwd->pw_uid != 0 || style == NULL ||
348 	    strcmp(style, LOGIN_DEFSTYLE) != 0) &&
349 	    (style = login_getstyle(lc, style, "auth-su")) == NULL)
350 		auth_errx(as, 1, "invalid authentication type");
351 
352 	/*
353 	 * Let the authentication program know whether they are
354 	 * in group wheel or not (if trying to become super user)
355 	 */
356 	if (pwd != NULL && pwd->pw_uid == 0 && (gr = getgrgid(0)) != NULL &&
357 	    gr->gr_mem != NULL && *(gr->gr_mem) != NULL) {
358 		for (g = gr->gr_mem; *g; ++g) {
359 			if (strcmp(from, *g) == 0) {
360 				auth_setoption(as, "wheel", "yes");
361 				break;
362 			}
363 		}
364 		if (!*g)
365 			auth_setoption(as, "wheel", "no");
366 	}
367 
368 	auth_verify(as, style, NULL, lc->lc_class, (char *)NULL);
369 	authok = auth_getstate(as);
370 	if ((authok & AUTH_ALLOW) == 0) {
371 		if ((cp = auth_getvalue(as, "errormsg")) != NULL)
372 			fprintf(stderr, "%s\n", cp);
373 		return(1);
374 	}
375 	return(0);
376 }
377 
378 int
379 chshell(const char *sh)
380 {
381 	char *cp;
382 	int found = 0;
383 
384 	setusershell();
385 	while ((cp = getusershell()) != NULL) {
386 		if (strcmp(cp, sh) == 0) {
387 			found = 1;
388 			break;
389 		}
390 	}
391 	endusershell();
392 	return (found);
393 }
394 
395 char *
396 ontty(void)
397 {
398 	static char buf[MAXPATHLEN + 4];
399 	char *p;
400 
401 	buf[0] = 0;
402 	if ((p = ttyname(STDERR_FILENO)))
403 		snprintf(buf, sizeof(buf), " on %s", p);
404 	return (buf);
405 }
406 
407 /*
408  * Allow for a '.' and 16 characters for any instance as well as
409  * space for a ':' and 16 characters defining the authentication type.
410  */
411 #define NBUFSIZ		(UT_NAMESIZE + 1 + 16 + 1 + 16)
412 
413 char *
414 getloginname(void)
415 {
416 	static char nbuf[NBUFSIZ], *p;
417 	int ch;
418 
419 	for (;;) {
420 		(void)printf("login: ");
421 		for (p = nbuf; (ch = getchar()) != '\n'; ) {
422 			if (ch == EOF)
423 				return (NULL);
424 			if (p < nbuf + (NBUFSIZ - 1))
425 				*p++ = ch;
426 		}
427 		if (p > nbuf) {
428 			if (nbuf[0] == '-') {
429 				(void)fprintf(stderr,
430 				    "login names may not start with '-'.\n");
431 			} else {
432 				*p = '\0';
433 				break;
434 			}
435 		}
436 	}
437 	return (nbuf);
438 }
439 
440 void
441 usage(void)
442 {
443 	extern char *__progname;
444 
445 	fprintf(stderr, "usage: %s [-fKLlm] [-a auth-type] [-c login-class] "
446 	    "[-s login-shell]\n"
447 	    "%-*s[login [shell arguments]]\n", __progname,
448 	    (int)strlen(__progname) + 8, "");
449 	exit(1);
450 }
451 
452 void
453 auth_err(auth_session_t *as, int eval, const char *fmt, ...)
454 {
455 	va_list ap;
456 
457 	va_start(ap, fmt);
458 	vwarn(fmt, ap);
459 	va_end(ap);
460 	auth_close(as);
461 	exit(eval);
462 }
463 
464 void
465 auth_errx(auth_session_t *as, int eval, const char *fmt, ...)
466 {
467 	va_list ap;
468 
469 	va_start(ap, fmt);
470 	vwarnx(fmt, ap);
471 	va_end(ap);
472 	auth_close(as);
473 	exit(eval);
474 }
475