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