xref: /freebsd/usr.bin/su/su.c (revision 3e0f6b97b257a96f7275e4442204263e44b16686)
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.15 1997/01/13 06:39:19 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 				for (g = gr->gr_mem;; ++g) {
228 					if (!*g)
229 						errx(1,
230 			    "you are not in the correct group to su %s.",
231 						    user);
232 					if (strcmp(username, *g) == 0) {
233 #ifdef WHEELSU
234 						iswheelsu = 1;
235 #endif /* WHEELSU */
236 						break;
237 					}
238 				}
239 		}
240 		/* if target requires a password, verify it */
241 		if (*pwd->pw_passwd) {
242 #ifdef LOGIN_CAP_AUTH
243 		/*
244 		 * This hands off authorisation to an authorisation program,
245 		 * depending on the styles available for the "auth-su",
246 		 * authorisation styles.
247 		 */
248 		if ((style = login_getstyle(lc, auth_method, "su")) == NULL)
249 			errx(1, "auth method available for su.\n");
250 		if (authenticate(user, lc ? lc->lc_class : "default", style, "su") != 0) {
251 #ifdef WHEELSU
252 			if (!iswheelsu || authenticate(username, lc ? lc->lc_class : "default", style, "su") != 0) {
253 #endif /* WHEELSU */
254 			{
255 			fprintf(stderr, "Sorry\n");
256 			syslog(LOG_AUTH|LOG_WARNING,"BAD SU %s to %s%s", username, user, ontty());
257 			exit(1);
258 			}
259 		}
260 
261 		/*
262 		 * If authentication succeeds, run any approval
263 		 * program, if applicable for this class.
264 		 */
265 		approvep = login_getcapstr(lc, "approve", NULL, NULL);
266 		if (approvep==NULL || auth_script(approvep, approvep, username, lc->lc_class, 0) == 0) {
267 			int     r = auth_scan(AUTH_OKAY);
268 			/* See what the authorise program says */
269 			if (!(r & AUTH_ROOTOKAY) && pwd->pw_uid == 0) {
270 				fprintf(stderr, "Sorry\n");
271 				syslog(LOG_AUTH|LOG_WARNING,"UNAPPROVED ROOT SU %s%s", user, ontty());
272 				exit(1);
273 			}
274 		}
275 #else /* !LOGIN_CAP_AUTH */
276 #ifdef	SKEY
277 #ifdef WHEELSU
278 			if (iswheelsu) {
279 				pwd = getpwnam(username);
280 			}
281 #endif /* WHEELSU */
282 			p = skey_getpass("Password:", pwd, 1);
283 			if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
284 #ifdef WHEELSU
285 			      || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
286 #endif /* WHEELSU */
287 			      )) {
288 #else
289 			p = getpass("Password:");
290 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
291 #endif
292 #ifdef KERBEROS
293 	    			if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
294 #endif
295 					{
296 					fprintf(stderr, "Sorry\n");
297 					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
298 					exit(1);
299 				}
300 			}
301 #ifdef WHEELSU
302 			if (iswheelsu) {
303 				pwd = getpwnam(user);
304 			}
305 #endif /* WHEELSU */
306 #endif /* LOGIN_CAP_AUTH */
307 		}
308 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
309 			fprintf(stderr, "Sorry - account expired\n");
310 			syslog(LOG_AUTH|LOG_WARNING,
311 				"BAD SU %s to %s%s", username,
312 				user, ontty());
313 			exit(1);
314 		}
315 	}
316 
317 	if (asme) {
318 		/* if asme and non-standard target shell, must be root */
319 		if (!chshell(pwd->pw_shell) && ruid)
320 			errx(1, "permission denied (shell).");
321 	} else if (pwd->pw_shell && *pwd->pw_shell) {
322 		shell = pwd->pw_shell;
323 		iscsh = UNSET;
324 	} else {
325 		shell = _PATH_BSHELL;
326 		iscsh = NO;
327 	}
328 
329 	/* if we're forking a csh, we want to slightly muck the args */
330 	if (iscsh == UNSET) {
331 		p = strrchr(shell, '/');
332 		if (p)
333 			++p;
334 		else
335 			p = shell;
336 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
337 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
338 	}
339 
340 	(void)setpriority(PRIO_PROCESS, 0, prio);
341 
342 #ifdef LOGIN_CAP
343 	/* Set everything now except the environment & umask */
344 	setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
345 	/*
346 	 * Don't touch resource/priority settings if -m has been
347 	 * used or -l hasn't, and we're not su'ing to root.
348 	 */
349         if ((asme || !asthem) && pwd->pw_uid)
350 		setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
351 	if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
352 		err(1, "setusercontext");
353 #else
354 	/* set permissions */
355 	if (setgid(pwd->pw_gid) < 0)
356 		err(1, "setgid");
357 	if (initgroups(user, pwd->pw_gid))
358 		errx(1, "initgroups failed");
359 	if (setuid(pwd->pw_uid) < 0)
360 		err(1, "setuid");
361 #endif
362 
363 	if (!asme) {
364 		if (asthem) {
365 			p = getenv("TERM");
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 			if (chdir(pwd->pw_dir) < 0)
377 				errx(1, "no directory");
378 		}
379 		if (asthem || pwd->pw_uid)
380 			(void)setenv("USER", pwd->pw_name, 1);
381 		(void)setenv("HOME", pwd->pw_dir, 1);
382 		(void)setenv("SHELL", shell, 1);
383 	}
384 	if (iscsh == YES) {
385 		if (fastlogin)
386 			*np-- = "-f";
387 		if (asme)
388 			*np-- = "-m";
389 	}
390 
391 	/* csh strips the first character... */
392 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
393 
394 	if (ruid != 0)
395 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
396 		    username, user, ontty());
397 
398 	login_close(lc);
399 
400 	execv(shell, np);
401 	err(1, "%s", shell);
402 }
403 
404 int
405 chshell(sh)
406 	char *sh;
407 {
408 	int  r = 0;
409 	char *cp;
410 
411 	setusershell();
412 	while (!r && (cp = getusershell()) != NULL)
413 		r = strcmp(cp, sh) == 0;
414 	endusershell();
415 	return r;
416 }
417 
418 char *
419 ontty()
420 {
421 	char *p;
422 	static char buf[MAXPATHLEN + 4];
423 
424 	buf[0] = 0;
425 	p = ttyname(STDERR_FILENO);
426 	if (p)
427 		snprintf(buf, sizeof(buf), " on %s", p);
428 	return (buf);
429 }
430 
431 #ifdef KERBEROS
432 int
433 kerberos(username, user, uid, pword)
434 	char *username, *user;
435 	int uid;
436 	char *pword;
437 {
438 	extern char *krb_err_txt[];
439 	KTEXT_ST ticket;
440 	AUTH_DAT authdata;
441 	int kerno;
442 	u_long faddr;
443 	struct sockaddr_in local_addr;
444 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
445 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
446 	char *krb_get_phost();
447 
448 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
449 		return (1);
450 	(void)sprintf(krbtkfile, "%s_%s_%lu", TKT_ROOT, user,
451 	    (unsigned long)getuid());
452 
453 	(void)setenv("KRBTKFILE", krbtkfile, 1);
454 	(void)krb_set_tkt_string(krbtkfile);
455 	/*
456 	 * Set real as well as effective ID to 0 for the moment,
457 	 * to make the kerberos library do the right thing.
458 	 */
459 	if (setuid(0) < 0) {
460 		warn("setuid");
461 		return (1);
462 	}
463 
464 	/*
465 	 * Little trick here -- if we are su'ing to root,
466 	 * we need to get a ticket for "xxx.root", where xxx represents
467 	 * the name of the person su'ing.  Otherwise (non-root case),
468 	 * we need to get a ticket for "yyy.", where yyy represents
469 	 * the name of the person being su'd to, and the instance is null
470 	 *
471 	 * We should have a way to set the ticket lifetime,
472 	 * with a system default for root.
473 	 */
474 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
475 		(uid == 0 ? "root" : ""), lrealm,
476 	    	"krbtgt", lrealm, DEFAULT_TKT_LIFE, pword);
477 
478 	if (kerno != KSUCCESS) {
479 		if (kerno == KDC_PR_UNKNOWN) {
480 			warnx("kerberos: principal unknown: %s.%s@%s",
481 				(uid == 0 ? username : user),
482 				(uid == 0 ? "root" : ""), lrealm);
483 			return (1);
484 		}
485 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
486 		syslog(LOG_NOTICE|LOG_AUTH,
487 		    "BAD Kerberos SU: %s to %s%s: %s",
488 		    username, user, ontty(), krb_err_txt[kerno]);
489 		return (1);
490 	}
491 
492 	if (chown(krbtkfile, uid, -1) < 0) {
493 		warn("chown");
494 		(void)unlink(krbtkfile);
495 		return (1);
496 	}
497 
498 	(void)setpriority(PRIO_PROCESS, 0, -2);
499 
500 	if (gethostname(hostname, sizeof(hostname)) == -1) {
501 		warn("gethostname");
502 		dest_tkt();
503 		return (1);
504 	}
505 
506 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
507 	savehost[sizeof(savehost) - 1] = '\0';
508 
509 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
510 
511 	if (kerno == KDC_PR_UNKNOWN) {
512 		warnx("Warning: TGT not verified.");
513 		syslog(LOG_NOTICE|LOG_AUTH,
514 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
515 		    username, user, ontty(), krb_err_txt[kerno],
516 		    "rcmd", savehost);
517 	} else if (kerno != KSUCCESS) {
518 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
519 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
520 		    username, user, ontty(), krb_err_txt[kerno]);
521 		dest_tkt();
522 		return (1);
523 	} else {
524 		if ((kerno = krb_get_local_addr(&local_addr)) != KSUCCESS) {
525 			warnx("Unable to get our local address: %s",
526 			      krb_err_txt[kerno]);
527 			dest_tkt();
528 			return (1);
529 		}
530 		faddr = local_addr.sin_addr.s_addr;
531 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
532 		    &authdata, "")) != KSUCCESS) {
533 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
534 			    krb_err_txt[kerno]);
535 			syslog(LOG_NOTICE|LOG_AUTH,
536 			    "failed su: %s to %s%s: %s", username,
537 			     user, ontty(), krb_err_txt[kerno]);
538 			dest_tkt();
539 			return (1);
540 		}
541 	}
542 	return (0);
543 }
544 
545 int
546 koktologin(name, toname)
547 	char *name, *toname;
548 {
549 	AUTH_DAT *kdata;
550 	AUTH_DAT kdata_st;
551 	char realm[REALM_SZ];
552 
553 	if (krb_get_lrealm(realm, 1) != KSUCCESS)
554 		return (1);
555 	kdata = &kdata_st;
556 	memset((char *)kdata, 0, sizeof(*kdata));
557 	(void)strncpy(kdata->pname, name, sizeof kdata->pname - 1);
558 	(void)strncpy(kdata->pinst,
559 	    ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof kdata->pinst - 1);
560 	(void)strncpy(kdata->prealm, realm, sizeof kdata->prealm - 1);
561 	return (kuserok(kdata, toname));
562 }
563 #endif
564