xref: /netbsd-src/usr.bin/su/su.c (revision fdecd6a253f999ae92b139670d9e15cc9df4497c)
1 /*	$NetBSD: su.c,v 1.18 1997/07/02 05:42:13 lukem Exp $	*/
2 
3 /*
4  * Copyright (c) 1988 The Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 char copyright[] =
38 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
39  All rights reserved.\n";
40 #endif /* not lint */
41 
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";*/
45 #else
46 static char rcsid[] = "$NetBSD: su.c,v 1.18 1997/07/02 05:42:13 lukem Exp $";
47 #endif
48 #endif /* not lint */
49 
50 #include <sys/param.h>
51 #include <sys/time.h>
52 #include <sys/resource.h>
53 #include <err.h>
54 #include <errno.h>
55 #include <grp.h>
56 #include <paths.h>
57 #include <pwd.h>
58 #include <stdio.h>
59 #include <skey.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <syslog.h>
63 #include <tzfile.h>
64 #include <unistd.h>
65 
66 #ifdef KERBEROS
67 #include <kerberosIV/des.h>
68 #include <kerberosIV/krb.h>
69 #include <netdb.h>
70 
71 #define	ARGSTR	"-Kflm"
72 
73 int use_kerberos = 1;
74 
75 static int kerberos __P((char *, char *, int));
76 static int koktologin __P((char *, char *, char *));
77 
78 #else
79 #define	ARGSTR	"-flm"
80 #endif
81 
82 #ifndef	SUGROUP
83 #define	SUGROUP	"wheel"
84 #endif
85 
86 
87 int main __P((int, char **));
88 
89 static int chshell __P((char *));
90 static char *ontty __P((void));
91 
92 
93 int
94 main(argc, argv)
95 	int argc;
96 	char **argv;
97 {
98 	extern char *__progname;
99 	extern char **environ;
100 	struct passwd *pwd;
101 	char *p;
102 	struct group *gr;
103 	struct timeval tp;
104 	uid_t ruid;
105 	int asme, ch, asthem, fastlogin, prio;
106 	enum { UNSET, YES, NO } iscsh = UNSET;
107 	char *user, *shell, *avshell, *username, *cleanenv[10], **np;
108 	char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
109 
110 	asme = asthem = fastlogin = 0;
111 	shell = NULL;
112 	while ((ch = getopt(argc, argv, ARGSTR)) != EOF)
113 		switch((char)ch) {
114 #ifdef KERBEROS
115 		case 'K':
116 			use_kerberos = 0;
117 			break;
118 #endif
119 		case 'f':
120 			fastlogin = 1;
121 			break;
122 		case '-':
123 		case 'l':
124 			asme = 0;
125 			asthem = 1;
126 			break;
127 		case 'm':
128 			asme = 1;
129 			asthem = 0;
130 			break;
131 		case '?':
132 		default:
133 			(void)fprintf(stderr,
134 			    "Usage: %s [%s] [login [shell arguments]]\n",
135 			    __progname, ARGSTR);
136 			exit(1);
137 		}
138 	argv += optind;
139 
140 	errno = 0;
141 	prio = getpriority(PRIO_PROCESS, 0);
142 	if (errno)
143 		prio = 0;
144 	(void)setpriority(PRIO_PROCESS, 0, -2);
145 	openlog("su", LOG_CONS, 0);
146 
147 	/* get current login name and shell */
148 	ruid = getuid();
149 	username = getlogin();
150 	if (username == NULL || (pwd = getpwnam(username)) == NULL ||
151 	    pwd->pw_uid != ruid)
152 		pwd = getpwuid(ruid);
153 	if (pwd == NULL) {
154 		errx(1, "who are you?");
155 	}
156 	username = strdup(pwd->pw_name);
157 	if (asme)
158 		if (pwd->pw_shell && *pwd->pw_shell)
159 			shell = strncpy(shellbuf, pwd->pw_shell,
160 			    sizeof(shellbuf) + 1);
161 		else {
162 			shell = _PATH_BSHELL;
163 			iscsh = NO;
164 		}
165 
166 	/* get target login information, default to root */
167 	user = *argv ? *argv : "root";
168 	np = *argv ? argv : argv-1;
169 
170 	if ((pwd = getpwnam(user)) == NULL) {
171 		errx(1, "unknown login %s", user);
172 	}
173 
174 	if (ruid) {
175 #ifdef KERBEROS
176 	    if (!use_kerberos || kerberos(username, user, pwd->pw_uid))
177 #endif
178 	    {
179 		/* Only allow those in group SUGROUP to su to root,
180 		   but only if that group has any members.
181 		   If SUGROUP has no members, allow anyone to su root */
182 		if (pwd->pw_uid == 0 &&
183 		    (gr = getgrnam(SUGROUP)) && *gr->gr_mem) {
184 			char **g;
185 
186 			for (g = gr->gr_mem; ; g++) {
187 				if (*g == NULL)
188 					errx(1,
189 	    "you are not listed in the correct secondary group (%s) to su %s.",
190 					    SUGROUP, user);
191 				if (strcmp(username, *g) == 0)
192 					break;
193 			}
194 		}
195 		/* if target requires a password, verify it */
196 		if (*pwd->pw_passwd) {
197 			p = getpass("Password:");
198 #ifdef SKEY
199 			if (strcasecmp(p, "s/key") == 0) {
200 				if (skey_haskey(user))
201 					errx(1, "Sorry, you have no s/key.");
202 				else {
203 					if (skey_authenticate(user)) {
204 						goto badlogin;
205 					}
206 				}
207 
208 			} else
209 #endif
210 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
211 badlogin:
212 				fprintf(stderr, "Sorry\n");
213 				syslog(LOG_AUTH|LOG_WARNING,
214 					"BAD SU %s to %s%s", username,
215 					user, ontty());
216 				exit(1);
217 			}
218 		}
219 	    }
220 	}
221 
222 	if (asme) {
223 		/* if asme and non-standard target shell, must be root */
224 		if (!chshell(pwd->pw_shell) && ruid)
225 			errx(1,"permission denied (shell).");
226 	} else if (pwd->pw_shell && *pwd->pw_shell) {
227 		shell = pwd->pw_shell;
228 		iscsh = UNSET;
229 	} else {
230 		shell = _PATH_BSHELL;
231 		iscsh = NO;
232 	}
233 
234 	if ((p = strrchr(shell, '/')) != NULL)
235 		avshell = p+1;
236 	else
237 		avshell = shell;
238 
239 	/* if we're forking a csh, we want to slightly muck the args */
240 	if (iscsh == UNSET)
241 		iscsh = strstr(avshell, "csh") ? YES : NO;
242 
243 	/* set permissions */
244 	if (setgid(pwd->pw_gid) < 0)
245 		err(1, "setgid");
246 	if (initgroups(user, pwd->pw_gid))
247 		errx(1, "initgroups failed");
248 	if (setuid(pwd->pw_uid) < 0)
249 		err(1, "setuid");
250 
251 	if (!asme) {
252 		if (asthem) {
253 			p = getenv("TERM");
254 			cleanenv[0] = NULL;
255 			environ = cleanenv;
256 			(void)setenv("PATH", _PATH_DEFPATH, 1);
257 			if (p)
258 				(void)setenv("TERM", p, 1);
259 			if (chdir(pwd->pw_dir) < 0)
260 				errx(1, "no directory");
261 		}
262 		if (asthem || pwd->pw_uid)
263 			(void)setenv("USER", pwd->pw_name, 1);
264 		(void)setenv("HOME", pwd->pw_dir, 1);
265 		(void)setenv("SHELL", shell, 1);
266 	}
267 
268 	if (iscsh == YES) {
269 		if (fastlogin)
270 			*np-- = "-f";
271 		if (asme)
272 			*np-- = "-m";
273 	}
274 
275 	if (asthem) {
276 		avshellbuf[0] = '-';
277 		(void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
278 		avshell = avshellbuf;
279 	} else if (iscsh == YES) {
280 		/* csh strips the first character... */
281 		avshellbuf[0] = '_';
282 		(void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
283 		avshell = avshellbuf;
284 	}
285 	*np = avshell;
286 
287 	if (pwd->pw_change || pwd->pw_expire)
288 		(void)gettimeofday(&tp, (struct timezone *)NULL);
289 	if (pwd->pw_change)
290 		if (tp.tv_sec >= pwd->pw_change) {
291 			(void)printf("%s -- %s's password has expired.\n",
292 				     (ruid ? "Sorry" : "Note"), user);
293 			if (ruid != 0)
294 				exit(1);
295 		} else if (pwd->pw_change - tp.tv_sec <
296 		    _PASSWORD_WARNDAYS * SECSPERDAY)
297 			(void)printf("Warning: %s's password expires on %s",
298 				     user, ctime(&pwd->pw_change));
299 	if (pwd->pw_expire)
300 		if (tp.tv_sec >= pwd->pw_expire) {
301 			(void)printf("%s -- %s's account has expired.\n",
302 				     (ruid ? "Sorry" : "Note"), user);
303 			if (ruid != 0)
304 				exit(1);
305 		} else if (pwd->pw_expire - tp.tv_sec <
306 		    _PASSWORD_WARNDAYS * SECSPERDAY)
307 			(void)printf("Warning: %s's account expires on %s",
308 				     user, ctime(&pwd->pw_expire));
309 
310 	if (ruid != 0)
311 		syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
312 		    username, user, ontty());
313 
314 	(void)setpriority(PRIO_PROCESS, 0, prio);
315 
316 	execv(shell, np);
317 	err(1, "%s", shell);
318 }
319 
320 static int
321 chshell(sh)
322 	char *sh;
323 {
324 	char *cp;
325 	char *getusershell();
326 
327 	while ((cp = getusershell()) != NULL)
328 		if (!strcmp(cp, sh))
329 			return (1);
330 	return (0);
331 }
332 
333 static char *
334 ontty()
335 {
336 	char *p, *ttyname();
337 	static char buf[MAXPATHLEN + 4];
338 
339 	buf[0] = 0;
340 	if ((p = ttyname(STDERR_FILENO)) != NULL)
341 		(void)snprintf(buf, sizeof buf, " on %s", p);
342 	return (buf);
343 }
344 
345 #ifdef KERBEROS
346 static int
347 kerberos(username, user, uid)
348 	char *username, *user;
349 	int uid;
350 {
351 	KTEXT_ST ticket;
352 	AUTH_DAT authdata;
353 	struct hostent *hp;
354 	register char *p;
355 	int kerno;
356 	u_long faddr;
357 	char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
358 	char hostname[MAXHOSTNAMELEN], savehost[MAXHOSTNAMELEN];
359 	char *ontty(), *krb_get_phost();
360 
361 	if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
362 		return (1);
363 	if (koktologin(username, lrealm, user) && !uid) {
364 		warnx("kerberos: not in %s's ACL.", user);
365 		return (1);
366 	}
367 	(void)(void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
368 	    user, getuid());
369 
370 	(void)setenv("KRBTKFILE", krbtkfile, 1);
371 	(void)krb_set_tkt_string(krbtkfile);
372 	/*
373 	 * Set real as well as effective ID to 0 for the moment,
374 	 * to make the kerberos library do the right thing.
375 	 */
376 	if (setuid(0) < 0) {
377 		warn("setuid");
378 		return (1);
379 	}
380 
381 	/*
382 	 * Little trick here -- if we are su'ing to root,
383 	 * we need to get a ticket for "xxx.root", where xxx represents
384 	 * the name of the person su'ing.  Otherwise (non-root case),
385 	 * we need to get a ticket for "yyy.", where yyy represents
386 	 * the name of the person being su'd to, and the instance is null
387 	 *
388 	 * We should have a way to set the ticket lifetime,
389 	 * with a system default for root.
390 	 */
391 	kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
392 		(uid == 0 ? "root" : ""), lrealm,
393 		"krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
394 
395 	if (kerno != KSUCCESS) {
396 		if (kerno == KDC_PR_UNKNOWN) {
397 			warnx("kerberos: principal unknown: %s.%s@%s",
398 				(uid == 0 ? username : user),
399 				(uid == 0 ? "root" : ""), lrealm);
400 			return (1);
401 		}
402 		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
403 		syslog(LOG_NOTICE|LOG_AUTH,
404 		    "BAD Kerberos SU: %s to %s%s: %s",
405 		    username, user, ontty(), krb_err_txt[kerno]);
406 		return (1);
407 	}
408 
409 	if (chown(krbtkfile, uid, -1) < 0) {
410 		warn("chown");
411 		(void)unlink(krbtkfile);
412 		return (1);
413 	}
414 
415 	(void)setpriority(PRIO_PROCESS, 0, -2);
416 
417 	if (gethostname(hostname, sizeof(hostname)) == -1) {
418 		warn("gethostname");
419 		dest_tkt();
420 		return (1);
421 	}
422 
423 	(void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
424 	savehost[sizeof(savehost) - 1] = '\0';
425 
426 	kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
427 
428 	if (kerno == KDC_PR_UNKNOWN) {
429 		warnx("Warning: TGT not verified.");
430 		syslog(LOG_NOTICE|LOG_AUTH,
431 		    "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
432 		    username, user, ontty(), krb_err_txt[kerno],
433 		    "rcmd", savehost);
434 	} else if (kerno != KSUCCESS) {
435 		warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
436 		syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
437 		    username, user, ontty(), krb_err_txt[kerno]);
438 		dest_tkt();
439 		return (1);
440 	} else {
441 		if (!(hp = gethostbyname(hostname))) {
442 			warnx("can't get addr of %s", hostname);
443 			dest_tkt();
444 			return (1);
445 		}
446 		memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
447 
448 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
449 		    &authdata, "")) != KSUCCESS) {
450 			warnx("kerberos: unable to verify rcmd ticket: %s\n",
451 			    krb_err_txt[kerno]);
452 			syslog(LOG_NOTICE|LOG_AUTH,
453 			    "failed su: %s to %s%s: %s", username,
454 			     user, ontty(), krb_err_txt[kerno]);
455 			dest_tkt();
456 			return (1);
457 		}
458 	}
459 	return (0);
460 }
461 
462 static int
463 koktologin(name, realm, toname)
464 	char *name, *realm, *toname;
465 {
466 	register AUTH_DAT *kdata;
467 	AUTH_DAT kdata_st;
468 
469 	kdata = &kdata_st;
470 	memset((char *)kdata, 0, sizeof(*kdata));
471 	(void)strncpy(kdata->pname, name, sizeof(kdata->pname) - 1);
472 	(void)strncpy(kdata->pinst,
473 	    ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof(kdata->pinst) - 1);
474 	(void)strncpy(kdata->prealm, realm, sizeof(kdata->prealm) - 1);
475 	return (kuserok(kdata, toname));
476 }
477 #endif
478