xref: /freebsd/usr.bin/su/su.c (revision 1d66272a85cde1c8a69c58f4b5dd649babd6eca6)
1 /*
2  * Copyright (c) 1988, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1988, 1993, 1994\n\
37 	The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39 
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
43 #endif
44 static const char rcsid[] =
45   "$FreeBSD$";
46 #endif /* not lint */
47 
48 #include <sys/param.h>
49 #include <sys/time.h>
50 #include <sys/resource.h>
51 
52 #include <err.h>
53 #include <errno.h>
54 #include <grp.h>
55 #include <paths.h>
56 #include <pwd.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <syslog.h>
61 #include <unistd.h>
62 #include <libutil.h>
63 
64 #ifdef LOGIN_CAP
65 #include <login_cap.h>
66 #endif
67 
68 #ifdef	SKEY
69 #include <skey.h>
70 #endif
71 
72 #ifdef KERBEROS
73 #include <openssl/des.h>
74 #include <krb.h>
75 #include <netdb.h>
76 
77 #ifdef LOGIN_CAP
78 #define	ARGSTR	"-Kflmc:"
79 #else
80 #define	ARGSTR	"-Kflm"
81 #endif
82 
83 static int kerberos(char *username, char *user, int uid, char *pword);
84 static int koktologin(char *name, char *toname);
85 
86 int use_kerberos = 1;
87 #else /* !KERBEROS */
88 #ifdef LOGIN_CAP
89 #define	ARGSTR	"-flmc:"
90 #else
91 #define	ARGSTR	"-flm"
92 #endif
93 #endif /* KERBEROS */
94 
95 char   *ontty __P((void));
96 int	chshell __P((char *));
97 static void usage __P((void));
98 
99 int
100 main(argc, argv)
101 	int argc;
102 	char **argv;
103 {
104 	extern char **environ;
105 	struct passwd *pwd;
106 #ifdef WHEELSU
107 	char *targetpass;
108 	int iswheelsu;
109 #endif /* WHEELSU */
110 	char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
111 	struct group *gr;
112 	uid_t ruid;
113 	gid_t gid;
114 	int asme, ch, asthem, fastlogin, prio, i;
115 	enum { UNSET, YES, NO } iscsh = UNSET;
116 #ifdef LOGIN_CAP
117 	login_cap_t *lc;
118 	char *class=NULL;
119 	int setwhat;
120 #endif
121 #ifdef KERBEROS
122 	char *k;
123 #endif
124 	char shellbuf[MAXPATHLEN];
125 
126 #ifdef WHEELSU
127 	iswheelsu =
128 #endif /* WHEELSU */
129 	asme = asthem = fastlogin = 0;
130 	user = "root";
131 	while((ch = getopt(argc, argv, ARGSTR)) != -1)
132 		switch((char)ch) {
133 #ifdef KERBEROS
134 		case 'K':
135 			use_kerberos = 0;
136 			break;
137 #endif
138 		case 'f':
139 			fastlogin = 1;
140 			break;
141 		case '-':
142 		case 'l':
143 			asme = 0;
144 			asthem = 1;
145 			break;
146 		case 'm':
147 			asme = 1;
148 			asthem = 0;
149 			break;
150 #ifdef LOGIN_CAP
151 		case 'c':
152 			class = optarg;
153 			break;
154 #endif
155 		case '?':
156 		default:
157 			usage();
158 		}
159 
160 	if (optind < argc)
161 		user = argv[optind++];
162 
163 	if (strlen(user) > MAXLOGNAME - 1) {
164 		(void)fprintf(stderr, "su: username too long.\n");
165 		exit(1);
166 	}
167 
168 	if (user == NULL)
169 		usage();
170 
171 	if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
172 	    errx(1, "malloc failure");
173 	}
174 
175 	nargv[argc + 3] = NULL;
176 	for (i = argc; i >= optind; i--)
177 	    nargv[i + 3] = argv[i];
178 	np = &nargv[i + 3];
179 
180 	argv += optind;
181 
182 #ifdef KERBEROS
183 	k = auth_getval("auth_list");
184 	if (k && !strstr(k, "kerberos"))
185 	    use_kerberos = 0;
186 #endif
187 	errno = 0;
188 	prio = getpriority(PRIO_PROCESS, 0);
189 	if (errno)
190 		prio = 0;
191 	(void)setpriority(PRIO_PROCESS, 0, -2);
192 	openlog("su", LOG_CONS, 0);
193 
194 	/* get current login name and shell */
195 	ruid = getuid();
196 	username = getlogin();
197 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
198 	    pwd->pw_uid != ruid)
199 		pwd = getpwuid(ruid);
200 	if (pwd == NULL)
201 		errx(1, "who are you?");
202 	username = strdup(pwd->pw_name);
203 	gid = pwd->pw_gid;
204 	if (username == NULL)
205 		err(1, NULL);
206 	if (asme) {
207 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
208 			/* copy: pwd memory is recycled */
209 			shell = strncpy(shellbuf,  pwd->pw_shell, sizeof shellbuf);
210 			shellbuf[sizeof shellbuf - 1] = '\0';
211 		} else {
212 			shell = _PATH_BSHELL;
213 			iscsh = NO;
214 		}
215 	}
216 
217 	/* get target login information, default to root */
218 	if ((pwd = getpwnam(user)) == NULL) {
219 		errx(1, "unknown login: %s", user);
220 	}
221 #ifdef LOGIN_CAP
222 	if (class==NULL) {
223 		lc = login_getpwclass(pwd);
224 	} else {
225 		if (ruid)
226 			errx(1, "only root may use -c");
227 		lc = login_getclass(class);
228 		if (lc == NULL)
229 			errx(1, "unknown class: %s", class);
230 	}
231 #endif
232 
233 #ifdef WHEELSU
234 	targetpass = strdup(pwd->pw_passwd);
235 #endif /* WHEELSU */
236 
237 	if (ruid) {
238 #ifdef KERBEROS
239 		if (use_kerberos && koktologin(username, user)
240 		    && !pwd->pw_uid) {
241 			warnx("kerberos: not in %s's ACL.", user);
242 			use_kerberos = 0;
243 		}
244 #endif
245 		{
246 			/*
247 			 * Only allow those with pw_gid==0 or those listed in
248 			 * group zero to su to root.  If group zero entry is
249 			 * missing or empty, then allow anyone to su to root.
250 			 * iswheelsu will only be set if the user is EXPLICITLY
251 			 * listed in group zero.
252 			 */
253 			if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
254 			    gr->gr_mem && *(gr->gr_mem))
255 				for (g = gr->gr_mem;; ++g) {
256 					if (!*g) {
257 						if (gid == 0)
258 							break;
259 						else
260 							errx(1, "you are not in the correct group to su %s.", user);
261 					}
262 					if (strcmp(username, *g) == 0) {
263 #ifdef WHEELSU
264 						iswheelsu = 1;
265 #endif /* WHEELSU */
266 						break;
267 					}
268 				}
269 		}
270 		/* if target requires a password, verify it */
271 		if (*pwd->pw_passwd) {
272 #ifdef	SKEY
273 #ifdef WHEELSU
274 			if (iswheelsu) {
275 				pwd = getpwnam(username);
276 			}
277 #endif /* WHEELSU */
278 			p = skey_getpass("Password:", pwd, 1);
279 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
280 #ifdef WHEELSU
281 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
282 #endif /* WHEELSU */
283 			      )) {
284 #else
285 			p = getpass("Password:");
286 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
287 #endif
288 #ifdef KERBEROS
289 	    			if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
290 #endif
291 					{
292 					fprintf(stderr, "Sorry\n");
293 					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
294 					exit(1);
295 				}
296 			}
297 #ifdef WHEELSU
298 			if (iswheelsu) {
299 				pwd = getpwnam(user);
300 			}
301 #endif /* WHEELSU */
302 		}
303 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
304 			fprintf(stderr, "Sorry - account expired\n");
305 			syslog(LOG_AUTH|LOG_WARNING,
306 				"BAD SU %s to %s%s", username,
307 				user, ontty());
308 			exit(1);
309 		}
310 	}
311 
312 	if (asme) {
313 		/* if asme and non-standard target shell, must be root */
314 		if (!chshell(pwd->pw_shell) && ruid)
315 			errx(1, "permission denied (shell).");
316 	} else if (pwd->pw_shell && *pwd->pw_shell) {
317 		shell = pwd->pw_shell;
318 		iscsh = UNSET;
319 	} else {
320 		shell = _PATH_BSHELL;
321 		iscsh = NO;
322 	}
323 
324 	/* if we're forking a csh, we want to slightly muck the args */
325 	if (iscsh == UNSET) {
326 		p = strrchr(shell, '/');
327 		if (p)
328 			++p;
329 		else
330 			p = shell;
331 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
332 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
333 	}
334 
335 	(void)setpriority(PRIO_PROCESS, 0, prio);
336 
337 #ifdef LOGIN_CAP
338 	/*
339 	 * Set all user context except for:
340 	 *   Environmental variables
341 	 *   Umask
342 	 *   Login records (wtmp, etc)
343 	 *   Path
344 	 */
345 	setwhat =  LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
346 	    LOGIN_SETLOGIN | LOGIN_SETPATH);
347 
348 	/*
349 	 * Don't touch resource/priority settings if -m has been
350 	 * used or -l and -c hasn't, and we're not su'ing to root.
351 	 */
352         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
353 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
354 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
355 		err(1, "setusercontext");
356 #else
357 	/* set permissions */
358 	if (setgid(pwd->pw_gid) < 0)
359 		err(1, "setgid");
360 	if (initgroups(user, pwd->pw_gid))
361 		errx(1, "initgroups failed");
362 	if (setuid(pwd->pw_uid) < 0)
363 		err(1, "setuid");
364 #endif
365 
366 	if (!asme) {
367 		if (asthem) {
368 			p = getenv("TERM");
369 #ifdef KERBEROS
370 			k = getenv("KRBTKFILE");
371 #endif
372 			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
373 				errx(1, "calloc");
374 			cleanenv[0] = NULL;
375 			environ = cleanenv;
376 #ifdef LOGIN_CAP
377 			/* set the su'd user's environment & umask */
378 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
379 #else
380 			(void)setenv("PATH", _PATH_DEFPATH, 1);
381 #endif
382 			if (p)
383 				(void)setenv("TERM", p, 1);
384 #ifdef KERBEROS
385 			if (k)
386 				(void)setenv("KRBTKFILE", k, 1);
387 #endif
388 			if (chdir(pwd->pw_dir) < 0)
389 				errx(1, "no directory");
390 		}
391 		if (asthem || pwd->pw_uid)
392 			(void)setenv("USER", pwd->pw_name, 1);
393 		(void)setenv("HOME", pwd->pw_dir, 1);
394 		(void)setenv("SHELL", shell, 1);
395 	}
396 	if (iscsh == YES) {
397 		if (fastlogin)
398 			*np-- = "-f";
399 		if (asme)
400 			*np-- = "-m";
401 	}
402 
403 	/* csh strips the first character... */
404 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
405 
406 	if (ruid != 0)
407 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
408 		    username, user, ontty());
409 
410 	login_close(lc);
411 
412 	execv(shell, np);
413 	err(1, "%s", shell);
414 }
415 
416 static void
417 usage()
418 {
419 	(void)fprintf(stderr, "usage: su [%s] [login [args]]\n", ARGSTR);
420 	exit(1);
421 }
422 
423 int
424 chshell(sh)
425 	char *sh;
426 {
427 	int  r = 0;
428 	char *cp;
429 
430 	setusershell();
431 	while (!r && (cp = getusershell()) != NULL)
432 		r = strcmp(cp, sh) == 0;
433 	endusershell();
434 	return r;
435 }
436 
437 char *
438 ontty()
439 {
440 	char *p;
441 	static char buf[MAXPATHLEN + 4];
442 
443 	buf[0] = 0;
444 	p = ttyname(STDERR_FILENO);
445 	if (p)
446 		snprintf(buf, sizeof(buf), " on %s", p);
447 	return (buf);
448 }
449 
450 #ifdef KERBEROS
451 int
452 kerberos(username, user, uid, pword)
453 	char *username, *user;
454 	int uid;
455 	char *pword;
456 {
457 	KTEXT_ST ticket;
458 	AUTH_DAT authdata;
459 	int kerno;
460 	u_long faddr;
461 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
462 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
463 	char *krb_get_phost();
464 	struct hostent *hp;
465 
466 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
467 		return (1);
468 	(void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
469 	    (unsigned long)getuid());
470 
471 	(void)setenv("KRBTKFILE", krbtkfile, 1);
472 	(void)krb_set_tkt_string(krbtkfile);
473 	/*
474 	 * Set real as well as effective ID to 0 for the moment,
475 	 * to make the kerberos library do the right thing.
476 	 */
477 	if (setuid(0) < 0) {
478 		warn("setuid");
479 		return (1);
480 	}
481 
482 	/*
483 	 * Little trick here -- if we are su'ing to root,
484 	 * we need to get a ticket for "xxx.root", where xxx represents
485 	 * the name of the person su'ing.  Otherwise (non-root case),
486 	 * we need to get a ticket for "yyy.", where yyy represents
487 	 * the name of the person being su'd to, and the instance is null
488 	 *
489 	 * We should have a way to set the ticket lifetime,
490 	 * with a system default for root.
491 	 */
492 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
493 		(uid == 0 ? "root" : ""), lrealm,
494 	    	"krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
495 
496 	if (kerno != KSUCCESS) {
497 		if (kerno == KDC_PR_UNKNOWN) {
498 			warnx("kerberos: principal unknown: %s.%s@%s",
499 				(uid == 0 ? username : user),
500 				(uid == 0 ? "root" : ""), lrealm);
501 			return (1);
502 		}
503 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
504 		syslog(LOG_NOTICE|LOG_AUTH,
505 		    "BAD Kerberos SU: %s to %s%s: %s",
506 		    username, user, ontty(), krb_err_txt[kerno]);
507 		return (1);
508 	}
509 
510 	if (chown(krbtkfile, uid, -1) < 0) {
511 		warn("chown");
512 		(void)unlink(krbtkfile);
513 		return (1);
514 	}
515 
516 	(void)setpriority(PRIO_PROCESS, 0, -2);
517 
518 	if (gethostname(hostname, sizeof(hostname)) == -1) {
519 		warn("gethostname");
520 		dest_tkt();
521 		return (1);
522 	}
523 
524 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
525 	savehost[sizeof(savehost) - 1] = '\0';
526 
527 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
528 
529 	if (kerno == KDC_PR_UNKNOWN) {
530 		warnx("Warning: TGT not verified.");
531 		syslog(LOG_NOTICE|LOG_AUTH,
532 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
533 		    username, user, ontty(), krb_err_txt[kerno],
534 		    "rcmd", savehost);
535 	} else if (kerno != KSUCCESS) {
536 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
537 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
538 		    username, user, ontty(), krb_err_txt[kerno]);
539 		dest_tkt();
540 		return (1);
541 	} else {
542 		if (!(hp = gethostbyname(hostname))) {
543 			warnx("can't get addr of %s", hostname);
544 			dest_tkt();
545 			return (1);
546 		}
547 		memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
548 
549 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
550 		    &authdata, "")) != KSUCCESS) {
551 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
552 			    krb_err_txt[kerno]);
553 			syslog(LOG_NOTICE|LOG_AUTH,
554 			    "failed su: %s to %s%s: %s", username,
555 			     user, ontty(), krb_err_txt[kerno]);
556 			dest_tkt();
557 			return (1);
558 		}
559 	}
560 	return (0);
561 }
562 
563 int
564 koktologin(name, toname)
565 	char *name, *toname;
566 {
567 	AUTH_DAT *kdata;
568 	AUTH_DAT kdata_st;
569 	char realm[REALM_SZ];
570 
571 	if (krb_get_lrealm(realm, 1) != KSUCCESS)
572 		return (1);
573 	kdata = &kdata_st;
574 	memset((char *)kdata, 0, sizeof(*kdata));
575 	(void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
576 	(void)strncpy(kdata->pinst,
577 	    ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
578 	(void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
579 	return (kuserok(kdata, toname));
580 }
581 #endif
582