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