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