xref: /freebsd/usr.bin/su/su.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
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 	/* Set everything now except the environment & umask */
339 	setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
340 	/*
341 	 * Don't touch resource/priority settings if -m has been
342 	 * used or -l and -c hasn't, and we're not su'ing to root.
343 	 */
344         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
345 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
346 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
347 		err(1, "setusercontext");
348 #else
349 	/* set permissions */
350 	if (setgid(pwd->pw_gid) < 0)
351 		err(1, "setgid");
352 	if (initgroups(user, pwd->pw_gid))
353 		errx(1, "initgroups failed");
354 	if (setuid(pwd->pw_uid) < 0)
355 		err(1, "setuid");
356 #endif
357 
358 	if (!asme) {
359 		if (asthem) {
360 			p = getenv("TERM");
361 #ifdef KERBEROS
362 			k = getenv("KRBTKFILE");
363 #endif
364 			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
365 				errx(1, "calloc");
366 			cleanenv[0] = NULL;
367 			environ = cleanenv;
368 #ifdef LOGIN_CAP
369 			/* set the su'd user's environment & umask */
370 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
371 #else
372 			(void)setenv("PATH", _PATH_DEFPATH, 1);
373 #endif
374 			if (p)
375 				(void)setenv("TERM", p, 1);
376 #ifdef KERBEROS
377 			if (k)
378 				(void)setenv("KRBTKFILE", k, 1);
379 #endif
380 			if (chdir(pwd->pw_dir) < 0)
381 				errx(1, "no directory");
382 		}
383 		if (asthem || pwd->pw_uid)
384 			(void)setenv("USER", pwd->pw_name, 1);
385 		(void)setenv("HOME", pwd->pw_dir, 1);
386 		(void)setenv("SHELL", shell, 1);
387 	}
388 	if (iscsh == YES) {
389 		if (fastlogin)
390 			*np-- = "-f";
391 		if (asme)
392 			*np-- = "-m";
393 	}
394 
395 	/* csh strips the first character... */
396 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
397 
398 	if (ruid != 0)
399 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
400 		    username, user, ontty());
401 
402 	login_close(lc);
403 
404 	execv(shell, np);
405 	err(1, "%s", shell);
406 }
407 
408 static void
409 usage()
410 {
411 	(void)fprintf(stderr, "usage: su [%s] [login [args]]\n", ARGSTR);
412 	exit(1);
413 }
414 
415 int
416 chshell(sh)
417 	char *sh;
418 {
419 	int  r = 0;
420 	char *cp;
421 
422 	setusershell();
423 	while (!r && (cp = getusershell()) != NULL)
424 		r = strcmp(cp, sh) == 0;
425 	endusershell();
426 	return r;
427 }
428 
429 char *
430 ontty()
431 {
432 	char *p;
433 	static char buf[MAXPATHLEN + 4];
434 
435 	buf[0] = 0;
436 	p = ttyname(STDERR_FILENO);
437 	if (p)
438 		snprintf(buf, sizeof(buf), " on %s", p);
439 	return (buf);
440 }
441 
442 #ifdef KERBEROS
443 int
444 kerberos(username, user, uid, pword)
445 	char *username, *user;
446 	int uid;
447 	char *pword;
448 {
449 	KTEXT_ST ticket;
450 	AUTH_DAT authdata;
451 	int kerno;
452 	u_long faddr;
453 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
454 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
455 	char *krb_get_phost();
456 	struct hostent *hp;
457 
458 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
459 		return (1);
460 	(void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
461 	    (unsigned long)getuid());
462 
463 	(void)setenv("KRBTKFILE", krbtkfile, 1);
464 	(void)krb_set_tkt_string(krbtkfile);
465 	/*
466 	 * Set real as well as effective ID to 0 for the moment,
467 	 * to make the kerberos library do the right thing.
468 	 */
469 	if (setuid(0) < 0) {
470 		warn("setuid");
471 		return (1);
472 	}
473 
474 	/*
475 	 * Little trick here -- if we are su'ing to root,
476 	 * we need to get a ticket for "xxx.root", where xxx represents
477 	 * the name of the person su'ing.  Otherwise (non-root case),
478 	 * we need to get a ticket for "yyy.", where yyy represents
479 	 * the name of the person being su'd to, and the instance is null
480 	 *
481 	 * We should have a way to set the ticket lifetime,
482 	 * with a system default for root.
483 	 */
484 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
485 		(uid == 0 ? "root" : ""), lrealm,
486 	    	"krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
487 
488 	if (kerno != KSUCCESS) {
489 		if (kerno == KDC_PR_UNKNOWN) {
490 			warnx("kerberos: principal unknown: %s.%s@%s",
491 				(uid == 0 ? username : user),
492 				(uid == 0 ? "root" : ""), lrealm);
493 			return (1);
494 		}
495 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
496 		syslog(LOG_NOTICE|LOG_AUTH,
497 		    "BAD Kerberos SU: %s to %s%s: %s",
498 		    username, user, ontty(), krb_err_txt[kerno]);
499 		return (1);
500 	}
501 
502 	if (chown(krbtkfile, uid, -1) < 0) {
503 		warn("chown");
504 		(void)unlink(krbtkfile);
505 		return (1);
506 	}
507 
508 	(void)setpriority(PRIO_PROCESS, 0, -2);
509 
510 	if (gethostname(hostname, sizeof(hostname)) == -1) {
511 		warn("gethostname");
512 		dest_tkt();
513 		return (1);
514 	}
515 
516 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
517 	savehost[sizeof(savehost) - 1] = '\0';
518 
519 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
520 
521 	if (kerno == KDC_PR_UNKNOWN) {
522 		warnx("Warning: TGT not verified.");
523 		syslog(LOG_NOTICE|LOG_AUTH,
524 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
525 		    username, user, ontty(), krb_err_txt[kerno],
526 		    "rcmd", savehost);
527 	} else if (kerno != KSUCCESS) {
528 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
529 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
530 		    username, user, ontty(), krb_err_txt[kerno]);
531 		dest_tkt();
532 		return (1);
533 	} else {
534 		if (!(hp = gethostbyname(hostname))) {
535 			warnx("can't get addr of %s", hostname);
536 			dest_tkt();
537 			return (1);
538 		}
539 		memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
540 
541 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
542 		    &authdata, "")) != KSUCCESS) {
543 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
544 			    krb_err_txt[kerno]);
545 			syslog(LOG_NOTICE|LOG_AUTH,
546 			    "failed su: %s to %s%s: %s", username,
547 			     user, ontty(), krb_err_txt[kerno]);
548 			dest_tkt();
549 			return (1);
550 		}
551 	}
552 	return (0);
553 }
554 
555 int
556 koktologin(name, toname)
557 	char *name, *toname;
558 {
559 	AUTH_DAT *kdata;
560 	AUTH_DAT kdata_st;
561 	char realm[REALM_SZ];
562 
563 	if (krb_get_lrealm(realm, 1) != KSUCCESS)
564 		return (1);
565 	kdata = &kdata_st;
566 	memset((char *)kdata, 0, sizeof(*kdata));
567 	(void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
568 	(void)strncpy(kdata->pinst,
569 	    ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
570 	(void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
571 	return (kuserok(kdata, toname));
572 }
573 #endif
574