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