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