xref: /freebsd/usr.bin/su/su.c (revision 11afcc8f9f96d657b8e6f7547c02c1957331fc96)
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 	"$Id: su.c,v 1.26 1998/05/25 03:34:52 steve Exp $";
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 
63 #ifdef LOGIN_CAP
64 #include <login_cap.h>
65 #ifdef LOGIN_CAP_AUTH
66 #undef SKEY
67 #undef KERBEROS
68 #endif
69 #endif
70 
71 #ifdef	SKEY
72 #include <skey.h>
73 #endif
74 
75 #ifdef KERBEROS
76 #include <des.h>
77 #include <krb.h>
78 #include <netdb.h>
79 
80 #ifdef LOGIN_CAP
81 #define	ARGSTR	"-Kflmc:"
82 #else
83 #define	ARGSTR	"-Kflm"
84 #endif
85 
86 static int kerberos(char *username, char *user, int uid, char *pword);
87 static int koktologin(char *name, char *toname);
88 
89 int use_kerberos = 1;
90 #else /* !KERBEROS */
91 #ifdef LOGIN_CAP
92 #define	ARGSTR	"-flmc:"
93 #else
94 #define	ARGSTR	"-flm"
95 #endif
96 #endif /* KERBEROS */
97 
98 char   *ontty __P((void));
99 int	chshell __P((char *));
100 static void usage __P((void));
101 
102 int
103 main(argc, argv)
104 	int argc;
105 	char **argv;
106 {
107 	extern char **environ;
108 	struct passwd *pwd;
109 #ifdef WHEELSU
110 	char *targetpass;
111 	int iswheelsu;
112 #endif /* WHEELSU */
113 	char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
114 	struct group *gr;
115 	uid_t ruid;
116 	gid_t gid;
117 	int asme, ch, asthem, fastlogin, prio, i;
118 	enum { UNSET, YES, NO } iscsh = UNSET;
119 #ifdef LOGIN_CAP
120 	login_cap_t *lc;
121 	char *class=NULL;
122 	int setwhat;
123 #ifdef LOGIN_CAP_AUTH
124 	char *style, *approvep, *auth_method = NULL;
125 #endif
126 #endif
127 #ifdef KERBEROS
128 	char *k;
129 #endif
130 	char shellbuf[MAXPATHLEN];
131 
132 #ifdef WHEELSU
133 	iswheelsu =
134 #endif /* WHEELSU */
135 	asme = asthem = fastlogin = 0;
136 	user = "root";
137 	while(optind < argc)
138 	    if((ch = getopt(argc, argv, ARGSTR)) != -1)
139 		switch((char)ch) {
140 #ifdef KERBEROS
141 		case 'K':
142 			use_kerberos = 0;
143 			break;
144 #endif
145 		case 'f':
146 			fastlogin = 1;
147 			break;
148 		case '-':
149 		case 'l':
150 			asme = 0;
151 			asthem = 1;
152 			break;
153 		case 'm':
154 			asme = 1;
155 			asthem = 0;
156 			break;
157 #ifdef LOGIN_CAP
158 		case 'c':
159 			class = optarg;
160 			break;
161 #endif
162 		case '?':
163 		default:
164 			usage();
165 		}
166 	    else
167 	    {
168 		user = argv[optind++];
169 		break;
170 	    }
171 
172 	if (strlen(user) > MAXLOGNAME - 1) {
173 		(void)fprintf(stderr, "su: username too long.\n");
174 		exit(1);
175 	}
176 
177 	if (user == NULL)
178 		usage();
179 
180 	if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
181 	    errx(1, "malloc failure");
182 	}
183 
184 	nargv[argc + 3] = NULL;
185 	for (i = argc; i >= optind; i--)
186 	    nargv[i + 3] = argv[i];
187 	np = &nargv[i + 3];
188 
189 	argv += optind;
190 
191 	errno = 0;
192 	prio = getpriority(PRIO_PROCESS, 0);
193 	if (errno)
194 		prio = 0;
195 	(void)setpriority(PRIO_PROCESS, 0, -2);
196 	openlog("su", LOG_CONS, 0);
197 
198 	/* get current login name and shell */
199 	ruid = getuid();
200 	username = getlogin();
201 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
202 	    pwd->pw_uid != ruid)
203 		pwd = getpwuid(ruid);
204 	if (pwd == NULL)
205 		errx(1, "who are you?");
206 	username = strdup(pwd->pw_name);
207 	gid = pwd->pw_gid;
208 	if (username == NULL)
209 		err(1, NULL);
210 	if (asme) {
211 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
212 			/* copy: pwd memory is recycled */
213 			shell = strncpy(shellbuf,  pwd->pw_shell, sizeof shellbuf);
214 			shellbuf[sizeof shellbuf - 1] = '\0';
215 		} else {
216 			shell = _PATH_BSHELL;
217 			iscsh = NO;
218 		}
219 	}
220 
221 #ifdef LOGIN_CAP_AUTH
222 	if (auth_method = strchr(user, ':')) {
223 		*auth_method = '\0';
224 		auth_method++;
225 		if (*auth_method == '\0')
226 			auth_method = NULL;
227 	}
228 #endif /* !LOGIN_CAP_AUTH */
229 
230 	/* get target login information, default to root */
231 	if ((pwd = getpwnam(user)) == NULL) {
232 		errx(1, "unknown login: %s", user);
233 	}
234 #ifdef LOGIN_CAP
235 	if (class==NULL) {
236 		lc = login_getpwclass(pwd);
237 	} else {
238 		if (ruid)
239 			errx(1, "only root may use -c");
240 		lc = login_getclass(class);
241 		if (lc == NULL)
242 			errx(1, "unknown class: %s", class);
243 	}
244 #endif
245 
246 #ifdef WHEELSU
247 	targetpass = strdup(pwd->pw_passwd);
248 #endif /* WHEELSU */
249 
250 	if (ruid) {
251 #ifdef KERBEROS
252 		if (use_kerberos && koktologin(username, user)
253 		    && !pwd->pw_uid) {
254 			warnx("kerberos: not in %s's ACL.", user);
255 			use_kerberos = 0;
256 		}
257 #endif
258 		{
259 			/*
260 			 * Only allow those with pw_gid==0 or those listed in
261 			 * group zero to su to root.  If group zero entry is
262 			 * missing or empty, then allow anyone to su to root.
263 			 * iswheelsu will only be set if the user is EXPLICITLY
264 			 * listed in group zero.
265 			 */
266 			if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
267 			    gr->gr_mem && *(gr->gr_mem))
268 				for (g = gr->gr_mem;; ++g) {
269 					if (!*g)
270 						if (gid == 0)
271 							break;
272 						else
273 							errx(1, "you are not in the correct group to su %s.", user);
274 					if (strcmp(username, *g) == 0) {
275 #ifdef WHEELSU
276 						iswheelsu = 1;
277 #endif /* WHEELSU */
278 						break;
279 					}
280 				}
281 		}
282 		/* if target requires a password, verify it */
283 		if (*pwd->pw_passwd) {
284 #ifdef LOGIN_CAP_AUTH
285 		/*
286 		 * This hands off authorisation to an authorisation program,
287 		 * depending on the styles available for the "auth-su",
288 		 * authorisation styles.
289 		 */
290 		if ((style = login_getstyle(lc, auth_method, "su")) == NULL)
291 			errx(1, "auth method available for su.\n");
292 		if (authenticate(user, lc ? lc->lc_class : "default", style, "su") != 0) {
293 #ifdef WHEELSU
294 			if (!iswheelsu || authenticate(username, lc ? lc->lc_class : "default", style, "su") != 0) {
295 #endif /* WHEELSU */
296 			{
297 			fprintf(stderr, "Sorry\n");
298 			syslog(LOG_AUTH|LOG_WARNING,"BAD SU %s to %s%s", username, user, ontty());
299 			exit(1);
300 			}
301 		}
302 
303 		/*
304 		 * If authentication succeeds, run any approval
305 		 * program, if applicable for this class.
306 		 */
307 		approvep = login_getcapstr(lc, "approve", NULL, NULL);
308 		if (approvep==NULL || auth_script(approvep, approvep, username, lc->lc_class, 0) == 0) {
309 			int     r = auth_scan(AUTH_OKAY);
310 			/* See what the authorise program says */
311 			if (!(r & AUTH_ROOTOKAY) && pwd->pw_uid == 0) {
312 				fprintf(stderr, "Sorry\n");
313 				syslog(LOG_AUTH|LOG_WARNING,"UNAPPROVED ROOT SU %s%s", user, ontty());
314 				exit(1);
315 			}
316 		}
317 #else /* !LOGIN_CAP_AUTH */
318 #ifdef	SKEY
319 #ifdef WHEELSU
320 			if (iswheelsu) {
321 				pwd = getpwnam(username);
322 			}
323 #endif /* WHEELSU */
324 			p = skey_getpass("Password:", pwd, 1);
325 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
326 #ifdef WHEELSU
327 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
328 #endif /* WHEELSU */
329 			      )) {
330 #else
331 			p = getpass("Password:");
332 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
333 #endif
334 #ifdef KERBEROS
335 	    			if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
336 #endif
337 					{
338 					fprintf(stderr, "Sorry\n");
339 					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
340 					exit(1);
341 				}
342 			}
343 #ifdef WHEELSU
344 			if (iswheelsu) {
345 				pwd = getpwnam(user);
346 			}
347 #endif /* WHEELSU */
348 #endif /* LOGIN_CAP_AUTH */
349 		}
350 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
351 			fprintf(stderr, "Sorry - account expired\n");
352 			syslog(LOG_AUTH|LOG_WARNING,
353 				"BAD SU %s to %s%s", username,
354 				user, ontty());
355 			exit(1);
356 		}
357 	}
358 
359 	if (asme) {
360 		/* if asme and non-standard target shell, must be root */
361 		if (!chshell(pwd->pw_shell) && ruid)
362 			errx(1, "permission denied (shell).");
363 	} else if (pwd->pw_shell && *pwd->pw_shell) {
364 		shell = pwd->pw_shell;
365 		iscsh = UNSET;
366 	} else {
367 		shell = _PATH_BSHELL;
368 		iscsh = NO;
369 	}
370 
371 	/* if we're forking a csh, we want to slightly muck the args */
372 	if (iscsh == UNSET) {
373 		p = strrchr(shell, '/');
374 		if (p)
375 			++p;
376 		else
377 			p = shell;
378 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
379 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
380 	}
381 
382 	(void)setpriority(PRIO_PROCESS, 0, prio);
383 
384 #ifdef LOGIN_CAP
385 	/* Set everything now except the environment & umask */
386 	setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
387 	/*
388 	 * Don't touch resource/priority settings if -m has been
389 	 * used or -l and -c hasn't, and we're not su'ing to root.
390 	 */
391         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
392 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
393 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
394 		err(1, "setusercontext");
395 #else
396 	/* set permissions */
397 	if (setgid(pwd->pw_gid) < 0)
398 		err(1, "setgid");
399 	if (initgroups(user, pwd->pw_gid))
400 		errx(1, "initgroups failed");
401 	if (setuid(pwd->pw_uid) < 0)
402 		err(1, "setuid");
403 #endif
404 
405 	if (!asme) {
406 		if (asthem) {
407 			p = getenv("TERM");
408 #ifdef KERBEROS
409 			k = getenv("KRBTKFILE");
410 #endif
411 			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
412 				errx(1, "calloc");
413 			cleanenv[0] = NULL;
414 			environ = cleanenv;
415 #ifdef LOGIN_CAP
416 			/* set the su'd user's environment & umask */
417 			setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
418 #else
419 			(void)setenv("PATH", _PATH_DEFPATH, 1);
420 #endif
421 			if (p)
422 				(void)setenv("TERM", p, 1);
423 #ifdef KERBEROS
424 			if (k)
425 				(void)setenv("KRBTKFILE", k, 1);
426 #endif
427 			if (chdir(pwd->pw_dir) < 0)
428 				errx(1, "no directory");
429 		}
430 		if (asthem || pwd->pw_uid)
431 			(void)setenv("USER", pwd->pw_name, 1);
432 		(void)setenv("HOME", pwd->pw_dir, 1);
433 		(void)setenv("SHELL", shell, 1);
434 	}
435 	if (iscsh == YES) {
436 		if (fastlogin)
437 			*np-- = "-f";
438 		if (asme)
439 			*np-- = "-m";
440 	}
441 
442 	/* csh strips the first character... */
443 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
444 
445 	if (ruid != 0)
446 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
447 		    username, user, ontty());
448 
449 	login_close(lc);
450 
451 	execv(shell, np);
452 	err(1, "%s", shell);
453 }
454 
455 static void
456 usage()
457 {
458 	(void)fprintf(stderr, "usage: su [%s] [login [args]]\n", ARGSTR);
459 	exit(1);
460 }
461 
462 int
463 chshell(sh)
464 	char *sh;
465 {
466 	int  r = 0;
467 	char *cp;
468 
469 	setusershell();
470 	while (!r && (cp = getusershell()) != NULL)
471 		r = strcmp(cp, sh) == 0;
472 	endusershell();
473 	return r;
474 }
475 
476 char *
477 ontty()
478 {
479 	char *p;
480 	static char buf[MAXPATHLEN + 4];
481 
482 	buf[0] = 0;
483 	p = ttyname(STDERR_FILENO);
484 	if (p)
485 		snprintf(buf, sizeof(buf), " on %s", p);
486 	return (buf);
487 }
488 
489 #ifdef KERBEROS
490 int
491 kerberos(username, user, uid, pword)
492 	char *username, *user;
493 	int uid;
494 	char *pword;
495 {
496 	KTEXT_ST ticket;
497 	AUTH_DAT authdata;
498 	int kerno;
499 	u_long faddr;
500 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
501 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
502 	char *krb_get_phost();
503 	struct hostent *hp;
504 
505 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
506 		return (1);
507 	(void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
508 	    (unsigned long)getuid());
509 
510 	(void)setenv("KRBTKFILE", krbtkfile, 1);
511 	(void)krb_set_tkt_string(krbtkfile);
512 	/*
513 	 * Set real as well as effective ID to 0 for the moment,
514 	 * to make the kerberos library do the right thing.
515 	 */
516 	if (setuid(0) < 0) {
517 		warn("setuid");
518 		return (1);
519 	}
520 
521 	/*
522 	 * Little trick here -- if we are su'ing to root,
523 	 * we need to get a ticket for "xxx.root", where xxx represents
524 	 * the name of the person su'ing.  Otherwise (non-root case),
525 	 * we need to get a ticket for "yyy.", where yyy represents
526 	 * the name of the person being su'd to, and the instance is null
527 	 *
528 	 * We should have a way to set the ticket lifetime,
529 	 * with a system default for root.
530 	 */
531 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
532 		(uid == 0 ? "root" : ""), lrealm,
533 	    	"krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
534 
535 	if (kerno != KSUCCESS) {
536 		if (kerno == KDC_PR_UNKNOWN) {
537 			warnx("kerberos: principal unknown: %s.%s@%s",
538 				(uid == 0 ? username : user),
539 				(uid == 0 ? "root" : ""), lrealm);
540 			return (1);
541 		}
542 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
543 		syslog(LOG_NOTICE|LOG_AUTH,
544 		    "BAD Kerberos SU: %s to %s%s: %s",
545 		    username, user, ontty(), krb_err_txt[kerno]);
546 		return (1);
547 	}
548 
549 	if (chown(krbtkfile, uid, -1) < 0) {
550 		warn("chown");
551 		(void)unlink(krbtkfile);
552 		return (1);
553 	}
554 
555 	(void)setpriority(PRIO_PROCESS, 0, -2);
556 
557 	if (gethostname(hostname, sizeof(hostname)) == -1) {
558 		warn("gethostname");
559 		dest_tkt();
560 		return (1);
561 	}
562 
563 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
564 	savehost[sizeof(savehost) - 1] = '\0';
565 
566 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
567 
568 	if (kerno == KDC_PR_UNKNOWN) {
569 		warnx("Warning: TGT not verified.");
570 		syslog(LOG_NOTICE|LOG_AUTH,
571 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
572 		    username, user, ontty(), krb_err_txt[kerno],
573 		    "rcmd", savehost);
574 	} else if (kerno != KSUCCESS) {
575 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
576 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
577 		    username, user, ontty(), krb_err_txt[kerno]);
578 		dest_tkt();
579 		return (1);
580 	} else {
581 		if (!(hp = gethostbyname(hostname))) {
582 			warnx("can't get addr of %s", hostname);
583 			dest_tkt();
584 			return (1);
585 		}
586 		memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
587 
588 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
589 		    &authdata, "")) != KSUCCESS) {
590 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
591 			    krb_err_txt[kerno]);
592 			syslog(LOG_NOTICE|LOG_AUTH,
593 			    "failed su: %s to %s%s: %s", username,
594 			     user, ontty(), krb_err_txt[kerno]);
595 			dest_tkt();
596 			return (1);
597 		}
598 	}
599 	return (0);
600 }
601 
602 int
603 koktologin(name, toname)
604 	char *name, *toname;
605 {
606 	AUTH_DAT *kdata;
607 	AUTH_DAT kdata_st;
608 	char realm[REALM_SZ];
609 
610 	if (krb_get_lrealm(realm, 1) != KSUCCESS)
611 		return (1);
612 	kdata = &kdata_st;
613 	memset((char *)kdata, 0, sizeof(*kdata));
614 	(void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
615 	(void)strncpy(kdata->pinst,
616 	    ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
617 	(void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
618 	return (kuserok(kdata, toname));
619 }
620 #endif
621