xref: /freebsd/usr.bin/su/su.c (revision 8e6b01171e30297084bb0b4457c4183c2746aacc)
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 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 static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
42 #endif /* not lint */
43 
44 #include <sys/param.h>
45 #include <sys/time.h>
46 #include <sys/resource.h>
47 
48 #include <err.h>
49 #include <errno.h>
50 #include <grp.h>
51 #include <paths.h>
52 #include <pwd.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <syslog.h>
57 #include <unistd.h>
58 
59 #ifdef	SKEY
60 #include <skey.h>
61 #endif
62 
63 #ifdef KERBEROS
64 #include <kerberosIV/des.h>
65 #include <kerberosIV/krb.h>
66 #include <netdb.h>
67 
68 #define	ARGSTR	"-Kflm"
69 
70 int use_kerberos = 1;
71 #else
72 #define	ARGSTR	"-flm"
73 #endif
74 
75 char   *ontty __P((void));
76 int	chshell __P((char *));
77 
78 int
79 main(argc, argv)
80 	int argc;
81 	char **argv;
82 {
83 	extern char **environ;
84 	struct passwd *pwd;
85 #ifdef WHEELSU
86 	char *targetpass;
87 	int iswheelsu;
88 #endif /* WHEELSU */
89 	char *p, **g, *user, *shell, *username, *cleanenv[20], **nargv, **np;
90 	struct group *gr;
91 	uid_t ruid;
92 	int asme, ch, asthem, fastlogin, prio, i;
93 	enum { UNSET, YES, NO } iscsh = UNSET;
94 	char shellbuf[MAXPATHLEN];
95 
96 #ifdef WHEELSU
97 	iswheelsu =
98 #endif /* WHEELSU */
99 	asme = asthem = fastlogin = 0;
100 	user = "root";
101 	while(optind < argc)
102 	    if((ch = getopt(argc, argv, ARGSTR)) != EOF)
103 		switch((char)ch) {
104 #ifdef KERBEROS
105 		case 'K':
106 			use_kerberos = 0;
107 			break;
108 #endif
109 		case 'f':
110 			fastlogin = 1;
111 			break;
112 		case '-':
113 		case 'l':
114 			asme = 0;
115 			asthem = 1;
116 			break;
117 		case 'm':
118 			asme = 1;
119 			asthem = 0;
120 			break;
121 		case '?':
122 		default:
123 			(void)fprintf(stderr, "usage: su [%s] [login]\n",
124 				      ARGSTR);
125 			exit(1);
126 		      }
127 	    else
128 	    {
129 		user = argv[optind++];
130 		break;
131 	    }
132 
133 	if((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
134 	    errx(1, "malloc failure");
135 	}
136 
137 	nargv[argc + 3] = NULL;
138 	for (i = argc; i >= optind; i--)
139 	    nargv[i + 3] = argv[i];
140 	np = &nargv[i + 3];
141 
142 	argv += optind;
143 
144 	errno = 0;
145 	prio = getpriority(PRIO_PROCESS, 0);
146 	if (errno)
147 		prio = 0;
148 	(void)setpriority(PRIO_PROCESS, 0, -2);
149 	openlog("su", LOG_CONS, 0);
150 
151 	/* get current login name and shell */
152 	ruid = getuid();
153 	username = getlogin();
154 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
155 	    pwd->pw_uid != ruid)
156 		pwd = getpwuid(ruid);
157 	if (pwd == NULL)
158 		errx(1, "who are you?");
159 	username = strdup(pwd->pw_name);
160 	if (username == NULL)
161 		err(1, NULL);
162 	if (asme)
163 		if (pwd->pw_shell && *pwd->pw_shell)
164 			shell = strcpy(shellbuf,  pwd->pw_shell);
165 		else {
166 			shell = _PATH_BSHELL;
167 			iscsh = NO;
168 		}
169 
170 	/* get target login information, default to root */
171 	if ((pwd = getpwnam(user)) == NULL) {
172 		errx(1, "unknown login: %s", user);
173 	}
174 
175 #ifdef WHEELSU
176 	targetpass = strdup(pwd->pw_passwd);
177 #endif /* WHEELSU */
178 
179 	if (ruid) {
180 #ifdef KERBEROS
181 	    if (!use_kerberos || kerberos(username, user, pwd->pw_uid))
182 #endif
183 	    {
184 		/* only allow those in group zero to su to root. */
185 		if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)))
186 			for (g = gr->gr_mem;; ++g) {
187 				if (!*g)
188 					errx(1,
189 			    "you are not in the correct group to su %s.",
190 					    user);
191 				if (strcmp(username, *g) == 0) {
192 #ifdef WHEELSU
193 					iswheelsu = 1;
194 #endif /* WHEELSU */
195 					break;
196 				}
197 			}
198 		/* if target requires a password, verify it */
199 		if (*pwd->pw_passwd) {
200 #ifdef	SKEY
201 #ifdef WHEELSU
202 			if (iswheelsu) {
203 				pwd = getpwnam(username);
204 			}
205 #endif /* WHEELSU */
206 			p = skey_getpass("Password:", pwd, 1);
207 			if (!(!strcmp(pwd->pw_passwd,
208 				      skey_crypt(p, pwd->pw_passwd, pwd, 1))
209 #ifdef WHEELSU
210 			      || (iswheelsu && !strcmp(targetpass,
211 						       crypt(p,
212 							     targetpass)))
213 #endif /* WHEELSU */
214 			      )) {
215 #else
216 			p = getpass("Password:");
217 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
218 #endif
219 				fprintf(stderr, "Sorry\n");
220 				syslog(LOG_AUTH|LOG_WARNING,
221 					"BAD SU %s to %s%s", username,
222 					user, ontty());
223 				exit(1);
224 			}
225 #ifdef WHEELSU
226 			if (iswheelsu) {
227 				pwd = getpwnam(user);
228 			}
229 #endif /* WHEELSU */
230 		}
231 		if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
232 			fprintf(stderr, "Sorry - account expired\n");
233 			syslog(LOG_AUTH|LOG_WARNING,
234 				"BAD SU %s to %s%s", username,
235 				user, ontty());
236 			exit(1);
237 		}
238 	    }
239 	}
240 
241 	if (asme) {
242 		/* if asme and non-standard target shell, must be root */
243 		if (!chshell(pwd->pw_shell) && ruid)
244 			errx(1, "permission denied (shell).");
245 	} else if (pwd->pw_shell && *pwd->pw_shell) {
246 		shell = pwd->pw_shell;
247 		iscsh = UNSET;
248 	} else {
249 		shell = _PATH_BSHELL;
250 		iscsh = NO;
251 	}
252 
253 	/* if we're forking a csh, we want to slightly muck the args */
254 	if (iscsh == UNSET) {
255 		if (p = strrchr(shell, '/'))
256 			++p;
257 		else
258 			p = shell;
259 		if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
260 		    iscsh = strcmp(p, "tcsh") ? NO : YES;
261 	}
262 
263 	/* set permissions */
264 	if (setgid(pwd->pw_gid) < 0)
265 		err(1, "setgid");
266 	if (initgroups(user, pwd->pw_gid))
267 		errx(1, "initgroups failed");
268 	if (setuid(pwd->pw_uid) < 0)
269 		err(1, "setuid");
270 
271 	if (!asme) {
272 		if (asthem) {
273 			p = getenv("TERM");
274 			cleanenv[0] = NULL;
275 			environ = cleanenv;
276 			(void)setenv("PATH", _PATH_DEFPATH, 1);
277 			(void)setenv("TERM", p, 1);
278 			if (chdir(pwd->pw_dir) < 0)
279 				errx(1, "no directory");
280 		}
281 		if (asthem || pwd->pw_uid)
282 			(void)setenv("USER", pwd->pw_name, 1);
283 		(void)setenv("HOME", pwd->pw_dir, 1);
284 		(void)setenv("SHELL", shell, 1);
285 	}
286 
287 	if (iscsh == YES) {
288 		if (fastlogin)
289 			*np-- = "-f";
290 		if (asme)
291 			*np-- = "-m";
292 	}
293 
294 	/* csh strips the first character... */
295 	*np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
296 
297 	if (ruid != 0)
298 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
299 		    username, user, ontty());
300 
301 	(void)setpriority(PRIO_PROCESS, 0, prio);
302 
303 	execv(shell, np);
304 	err(1, "%s", shell);
305 }
306 
307 int
308 chshell(sh)
309 	char *sh;
310 {
311 	char *cp;
312 
313 	while ((cp = getusershell()) != NULL)
314 		if (strcmp(cp, sh) == 0)
315 			return (1);
316 	return (0);
317 }
318 
319 char *
320 ontty()
321 {
322 	char *p;
323 	static char buf[MAXPATHLEN + 4];
324 
325 	buf[0] = 0;
326 	if (p = ttyname(STDERR_FILENO))
327 		snprintf(buf, sizeof(buf), " on %s", p);
328 	return (buf);
329 }
330 
331 #ifdef KERBEROS
332 kerberos(username, user, uid)
333 	char *username, *user;
334 	int uid;
335 {
336 	extern char *krb_err_txt[];
337 	KTEXT_ST ticket;
338 	AUTH_DAT authdata;
339 	struct hostent *hp;
340 	char *p;
341 	int kerno;
342 	u_long faddr;
343 	struct sockaddr_in local_addr;
344 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
345 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
346 	char *krb_get_phost();
347 
348 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
349 		return (1);
350 	if (koktologin(username, lrealm, user) && !uid) {
351 		warnx("kerberos: not in %s's ACL.", user);
352 		return (1);
353 	}
354 	(void)sprintf(krbtkfile, "%s_%s_%d", TKT_ROOT, user, getuid());
355 
356 	(void)setenv("KRBTKFILE", krbtkfile, 1);
357 	(void)krb_set_tkt_string(krbtkfile);
358 	/*
359 	 * Set real as well as effective ID to 0 for the moment,
360 	 * to make the kerberos library do the right thing.
361 	 */
362 	if (setuid(0) < 0) {
363 		warn("setuid");
364 		return (1);
365 	}
366 
367 	/*
368 	 * Little trick here -- if we are su'ing to root,
369 	 * we need to get a ticket for "xxx.root", where xxx represents
370 	 * the name of the person su'ing.  Otherwise (non-root case),
371 	 * we need to get a ticket for "yyy.", where yyy represents
372 	 * the name of the person being su'd to, and the instance is null
373 	 *
374 	 * We should have a way to set the ticket lifetime,
375 	 * with a system default for root.
376 	 */
377 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
378 		(uid == 0 ? "root" : ""), lrealm,
379 	    	"krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
380 
381 	if (kerno != KSUCCESS) {
382 		if (kerno == KDC_PR_UNKNOWN) {
383 			warnx("kerberos: principal unknown: %s.%s@%s",
384 				(uid == 0 ? username : user),
385 				(uid == 0 ? "root" : ""), lrealm);
386 			return (1);
387 		}
388 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
389 		syslog(LOG_NOTICE|LOG_AUTH,
390 		    "BAD Kerberos SU: %s to %s%s: %s",
391 		    username, user, ontty(), krb_err_txt[kerno]);
392 		return (1);
393 	}
394 
395 	if (chown(krbtkfile, uid, -1) < 0) {
396 		warn("chown");
397 		(void)unlink(krbtkfile);
398 		return (1);
399 	}
400 
401 	(void)setpriority(PRIO_PROCESS, 0, -2);
402 
403 	if (gethostname(hostname, sizeof(hostname)) == -1) {
404 		warn("gethostname");
405 		dest_tkt();
406 		return (1);
407 	}
408 
409 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
410 	savehost[sizeof(savehost) - 1] = '\0';
411 
412 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
413 
414 	if (kerno == KDC_PR_UNKNOWN) {
415 		warnx("Warning: TGT not verified.");
416 		syslog(LOG_NOTICE|LOG_AUTH,
417 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
418 		    username, user, ontty(), krb_err_txt[kerno],
419 		    "rcmd", savehost);
420 	} else if (kerno != KSUCCESS) {
421 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
422 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
423 		    username, user, ontty(), krb_err_txt[kerno]);
424 		dest_tkt();
425 		return (1);
426 	} else {
427 		if ((kerno = krb_get_local_addr(&local_addr)) != KSUCCESS) {
428 			warnx("Unable to get our local address: %s",
429 			      krb_err_txt[kerno]);
430 			dest_tkt();
431 			return (1);
432 		}
433 		faddr = local_addr.sin_addr.s_addr;
434 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
435 		    &authdata, "")) != KSUCCESS) {
436 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
437 			    krb_err_txt[kerno]);
438 			syslog(LOG_NOTICE|LOG_AUTH,
439 			    "failed su: %s to %s%s: %s", username,
440 			     user, ontty(), krb_err_txt[kerno]);
441 			dest_tkt();
442 			return (1);
443 		}
444 	}
445 	return (0);
446 }
447 
448 koktologin(name, realm, toname)
449 	char *name, *realm, *toname;
450 {
451 	AUTH_DAT *kdata;
452 	AUTH_DAT kdata_st;
453 
454 	kdata = &kdata_st;
455 	memset((char *)kdata, 0, sizeof(*kdata));
456 	(void)strcpy(kdata->pname, name);
457 	(void)strcpy(kdata->pinst,
458 	    ((strcmp(toname, "root") == 0) ? "root" : ""));
459 	(void)strcpy(kdata->prealm, realm);
460 	return (kuserok(kdata, toname));
461 }
462 #endif
463