]> git.saurik.com Git - apple/shell_cmds.git/blob - su/su.c
750d02b3f941925aa31dfb37cecb18e5417db544
[apple/shell_cmds.git] / su / su.c
1 /* $NetBSD: su.c,v 1.26 1998/08/25 20:59:40 ross 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 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT(
39 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
40 All rights reserved.\n");
41 #endif /* not lint */
42
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/
46 #else
47 __RCSID("$NetBSD: su.c,v 1.26 1998/08/25 20:59:40 ross Exp $");
48 #endif
49 #endif /* not lint */
50
51 #include <sys/param.h>
52 #include <sys/time.h>
53 #include <sys/resource.h>
54 #include <err.h>
55 #include <errno.h>
56 #include <grp.h>
57 #include <paths.h>
58 #include <pwd.h>
59 #include <stdio.h>
60 #ifdef SKEY
61 #include <skey.h>
62 #endif
63 #include <stdlib.h>
64 #include <string.h>
65 #include <syslog.h>
66 #include <time.h>
67 #include <tzfile.h>
68 #include <unistd.h>
69
70 #ifdef KERBEROS
71 #include <kerberosIV/des.h>
72 #include <kerberosIV/krb.h>
73 #include <netdb.h>
74
75 #define ARGSTR "-Kflm"
76
77 int use_kerberos = 1;
78
79 static int kerberos __P((char *, char *, int));
80 static int koktologin __P((char *, char *, char *));
81
82 #else
83 #define ARGSTR "-flm"
84 #endif
85
86 #ifndef SUGROUP
87 #define SUGROUP "wheel"
88 #endif
89
90
91 int main __P((int, char **));
92
93 static int chshell __P((const char *));
94 static char *ontty __P((void));
95
96
97 int
98 main(argc, argv)
99 int argc;
100 char **argv;
101 {
102 extern char *__progname;
103 extern char **environ;
104 struct passwd *pwd;
105 char *p;
106 struct timeval tp;
107 uid_t ruid;
108 gid_t usergid;
109 int asme, ch, asthem, fastlogin, prio;
110 enum { UNSET, YES, NO } iscsh = UNSET;
111 char *user, *shell, *avshell, *username, *cleanenv[10], **np;
112 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
113
114 asme = asthem = fastlogin = 0;
115 shell = NULL;
116 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
117 switch((char)ch) {
118 #ifdef KERBEROS
119 case 'K':
120 use_kerberos = 0;
121 break;
122 #endif
123 case 'f':
124 fastlogin = 1;
125 break;
126 case '-':
127 case 'l':
128 asme = 0;
129 asthem = 1;
130 break;
131 case 'm':
132 asme = 1;
133 asthem = 0;
134 break;
135 case '?':
136 default:
137 (void)fprintf(stderr,
138 "Usage: %s [%s] [login [shell arguments]]\n",
139 __progname, ARGSTR);
140 exit(1);
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 usergid = pwd->pw_gid;
161 if (username == NULL)
162 err(1, "strdup");
163
164 if (asme) {
165 if (pwd->pw_shell && *pwd->pw_shell) {
166 shell = strncpy(shellbuf, pwd->pw_shell,
167 sizeof(shellbuf) - 1);
168 shellbuf[sizeof(shellbuf) - 1] = '\0';
169 } else {
170 shell = _PATH_BSHELL;
171 iscsh = NO;
172 }
173 }
174 /* get target login information, default to root */
175 user = *argv ? *argv : "root";
176 np = *argv ? argv : argv-1;
177
178 if ((pwd = getpwnam(user)) == NULL)
179 errx(1, "unknown login %s", user);
180
181 if (ruid
182 #ifdef KERBEROS
183 && (!use_kerberos || kerberos(username, user, pwd->pw_uid))
184 #endif
185 ) {
186 gid_t groups[NGROUPS_MAX];
187 int numgroups;
188
189 /*
190 * Only allow those in group SUGROUP to su to root.
191 */
192 if (pwd->pw_uid == 0 &&
193 (initgroups(username, usergid) == 0) &&
194 ((numgroups = getgroups(NGROUPS_MAX, groups)) > 0)) {
195 int i;
196 int wheel = 0;
197 gid_t wheelgid = 0;
198 struct group *wheelgroup;
199
200 if ((wheelgroup = getgrnam(SUGROUP)) != NULL) {
201 wheelgid = wheelgroup->gr_gid;
202
203 for (i = 0; i < numgroups; i++) {
204 if (groups[i] == wheelgid) {
205 wheel = 1;
206 break;
207 }
208 }
209 } else {
210 /* If we can't get the gid for SUGROUP, then let anyone su */
211 warnx("unknown secondary group %s; anyone can su %s.", SUGROUP, user);
212 wheel = 1;
213 }
214
215 if (!wheel)
216 errx(1,
217 "you are not listed in the correct secondary group (%s) to su %s.",
218 SUGROUP, user);
219 }
220
221 /* if target requires a password, verify it */
222 if (*pwd->pw_passwd) {
223 p = getpass("Password:");
224 #ifdef SKEY
225 if (strcasecmp(p, "s/key") == 0) {
226 if (skey_haskey(user))
227 errx(1, "Sorry, you have no s/key.");
228 else {
229 if (skey_authenticate(user)) {
230 goto badlogin;
231 }
232 }
233
234 } else
235 #endif
236 if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
237 #ifdef SKEY
238 badlogin:
239 #endif
240 fprintf(stderr, "Sorry\n");
241 syslog(LOG_AUTH|LOG_WARNING,
242 "BAD SU %s to %s%s", username,
243 user, ontty());
244 exit(1);
245 }
246 }
247 }
248
249 if (asme) {
250 /* if asme and non-standard target shell, must be root */
251 if (!chshell(pwd->pw_shell) && ruid)
252 errx(1,"permission denied (shell).");
253 } else if (pwd->pw_shell && *pwd->pw_shell) {
254 shell = pwd->pw_shell;
255 iscsh = UNSET;
256 } else {
257 shell = _PATH_BSHELL;
258 iscsh = NO;
259 }
260
261 if ((p = strrchr(shell, '/')) != NULL)
262 avshell = p+1;
263 else
264 avshell = shell;
265
266 /* if we're forking a csh, we want to slightly muck the args */
267 if (iscsh == UNSET)
268 iscsh = strstr(avshell, "csh") ? YES : NO;
269
270 /* set permissions */
271 if (setgid(pwd->pw_gid) < 0)
272 err(1, "setgid");
273 if (initgroups(user, pwd->pw_gid))
274 errx(1, "initgroups failed");
275 if (setuid(pwd->pw_uid) < 0)
276 err(1, "setuid");
277
278 if (!asme) {
279 if (asthem) {
280 p = getenv("TERM");
281 cleanenv[0] = NULL;
282 environ = cleanenv;
283 (void)setenv("PATH", _PATH_DEFPATH, 1);
284 if (p)
285 (void)setenv("TERM", p, 1);
286 if (chdir(pwd->pw_dir) < 0)
287 errx(1, "no directory");
288 }
289 if (asthem || pwd->pw_uid)
290 (void)setenv("USER", pwd->pw_name, 1);
291 (void)setenv("HOME", pwd->pw_dir, 1);
292 (void)setenv("SHELL", shell, 1);
293 }
294
295 if (iscsh == YES) {
296 if (fastlogin)
297 *np-- = "-f";
298 if (asme)
299 *np-- = "-m";
300 }
301
302 if (asthem) {
303 avshellbuf[0] = '-';
304 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
305 avshell = avshellbuf;
306 } else if (iscsh == YES) {
307 /* csh strips the first character... */
308 avshellbuf[0] = '_';
309 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
310 avshell = avshellbuf;
311 }
312 *np = avshell;
313
314 if (pwd->pw_change || pwd->pw_expire)
315 (void)gettimeofday(&tp, (struct timezone *)NULL);
316 if (pwd->pw_change) {
317 if (tp.tv_sec >= pwd->pw_change) {
318 (void)printf("%s -- %s's password has expired.\n",
319 (ruid ? "Sorry" : "Note"), user);
320 if (ruid != 0)
321 exit(1);
322 } else if (pwd->pw_change - tp.tv_sec <
323 _PASSWORD_WARNDAYS * SECSPERDAY)
324 (void)printf("Warning: %s's password expires on %s",
325 user, ctime(&pwd->pw_change));
326 }
327 if (pwd->pw_expire) {
328 if (tp.tv_sec >= pwd->pw_expire) {
329 (void)printf("%s -- %s's account has expired.\n",
330 (ruid ? "Sorry" : "Note"), user);
331 if (ruid != 0)
332 exit(1);
333 } else if (pwd->pw_expire - tp.tv_sec <
334 _PASSWORD_WARNDAYS * SECSPERDAY)
335 (void)printf("Warning: %s's account expires on %s",
336 user, ctime(&pwd->pw_expire));
337 }
338 if (ruid != 0)
339 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
340 username, user, ontty());
341
342 (void)setpriority(PRIO_PROCESS, 0, prio);
343
344 execv(shell, np);
345 err(1, "%s", shell);
346 /* NOTREACHED */
347 }
348
349 static int
350 chshell(sh)
351 const char *sh;
352 {
353 const char *cp;
354
355 while ((cp = getusershell()) != NULL)
356 if (!strcmp(cp, sh))
357 return (1);
358 return (0);
359 }
360
361 static char *
362 ontty()
363 {
364 char *p;
365 static char buf[MAXPATHLEN + 4];
366
367 buf[0] = 0;
368 if ((p = ttyname(STDERR_FILENO)) != NULL)
369 (void)snprintf(buf, sizeof buf, " on %s", p);
370 return (buf);
371 }
372
373 #ifdef KERBEROS
374 static int
375 kerberos(username, user, uid)
376 char *username, *user;
377 int uid;
378 {
379 KTEXT_ST ticket;
380 AUTH_DAT authdata;
381 struct hostent *hp;
382 int kerno;
383 u_long faddr;
384 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
385 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
386
387 if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
388 return (1);
389 if (koktologin(username, lrealm, user) && !uid) {
390 warnx("kerberos: not in %s's ACL.", user);
391 return (1);
392 }
393 (void)(void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
394 user, getuid());
395
396 (void)setenv("KRBTKFILE", krbtkfile, 1);
397 (void)krb_set_tkt_string(krbtkfile);
398 /*
399 * Set real as well as effective ID to 0 for the moment,
400 * to make the kerberos library do the right thing.
401 */
402 if (setuid(0) < 0) {
403 warn("setuid");
404 return (1);
405 }
406
407 /*
408 * Little trick here -- if we are su'ing to root,
409 * we need to get a ticket for "xxx.root", where xxx represents
410 * the name of the person su'ing. Otherwise (non-root case),
411 * we need to get a ticket for "yyy.", where yyy represents
412 * the name of the person being su'd to, and the instance is null
413 *
414 * We should have a way to set the ticket lifetime,
415 * with a system default for root.
416 */
417 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
418 (uid == 0 ? "root" : ""), lrealm,
419 "krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
420
421 if (kerno != KSUCCESS) {
422 if (kerno == KDC_PR_UNKNOWN) {
423 warnx("kerberos: principal unknown: %s.%s@%s",
424 (uid == 0 ? username : user),
425 (uid == 0 ? "root" : ""), lrealm);
426 return (1);
427 }
428 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
429 syslog(LOG_NOTICE|LOG_AUTH,
430 "BAD Kerberos SU: %s to %s%s: %s",
431 username, user, ontty(), krb_err_txt[kerno]);
432 return (1);
433 }
434
435 if (chown(krbtkfile, uid, -1) < 0) {
436 warn("chown");
437 (void)unlink(krbtkfile);
438 return (1);
439 }
440
441 (void)setpriority(PRIO_PROCESS, 0, -2);
442
443 if (gethostname(hostname, sizeof(hostname)) == -1) {
444 warn("gethostname");
445 dest_tkt();
446 return (1);
447 }
448 hostname[sizeof(hostname) - 1] = '\0';
449
450 (void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
451 savehost[sizeof(savehost) - 1] = '\0';
452
453 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
454
455 if (kerno == KDC_PR_UNKNOWN) {
456 warnx("Warning: TGT not verified.");
457 syslog(LOG_NOTICE|LOG_AUTH,
458 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
459 username, user, ontty(), krb_err_txt[kerno],
460 "rcmd", savehost);
461 } else if (kerno != KSUCCESS) {
462 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
463 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
464 username, user, ontty(), krb_err_txt[kerno]);
465 dest_tkt();
466 return (1);
467 } else {
468 if (!(hp = gethostbyname(hostname))) {
469 warnx("can't get addr of %s", hostname);
470 dest_tkt();
471 return (1);
472 }
473 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
474
475 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
476 &authdata, "")) != KSUCCESS) {
477 warnx("kerberos: unable to verify rcmd ticket: %s\n",
478 krb_err_txt[kerno]);
479 syslog(LOG_NOTICE|LOG_AUTH,
480 "failed su: %s to %s%s: %s", username,
481 user, ontty(), krb_err_txt[kerno]);
482 dest_tkt();
483 return (1);
484 }
485 }
486 return (0);
487 }
488
489 static int
490 koktologin(name, realm, toname)
491 char *name, *realm, *toname;
492 {
493 AUTH_DAT *kdata;
494 AUTH_DAT kdata_st;
495
496 kdata = &kdata_st;
497 memset((char *)kdata, 0, sizeof(*kdata));
498 (void)strncpy(kdata->pname, name, sizeof(kdata->pname) - 1);
499 (void)strncpy(kdata->pinst,
500 ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof(kdata->pinst) - 1);
501 (void)strncpy(kdata->prealm, realm, sizeof(kdata->prealm) - 1);
502 return (kuserok(kdata, toname));
503 }
504 #endif