su.c revision 1.37 1 /* $NetBSD: su.c,v 1.37 2000/01/14 02:39:14 mjl 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.37 2000/01/14 02:39:14 mjl 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 LOGIN_CAP
71 #include <login_cap.h>
72 #endif
73
74 #ifdef KERBEROS
75 #include <kerberosIV/des.h>
76 #include <kerberosIV/krb.h>
77 #include <netdb.h>
78
79 #define ARGSTRX "-Kflm"
80
81 int use_kerberos = 1;
82
83 static int kerberos __P((char *, char *, int));
84 static int koktologin __P((char *, char *, char *));
85
86 #else
87 #define ARGSTRX "-flm"
88 #endif
89
90 #ifndef SUGROUP
91 #define SUGROUP "wheel"
92 #endif
93
94 #ifdef LOGIN_CAP
95 #define ARGSTR ARGSTRX "c:"
96 #else
97 #define ARGSTR ARGSTRX
98 #endif
99
100 int main __P((int, char **));
101
102 static int chshell __P((const char *));
103 static char *ontty __P((void));
104
105
106 int
107 main(argc, argv)
108 int argc;
109 char **argv;
110 {
111 extern char *__progname;
112 extern char **environ;
113 struct passwd *pwd;
114 char *p;
115 struct group *gr;
116 #ifdef BSD4_4
117 struct timeval tp;
118 #endif
119 uid_t ruid;
120 int asme, ch, asthem, fastlogin, prio;
121 enum { UNSET, YES, NO } iscsh = UNSET;
122 char *user, *shell, *avshell, *username, **np;
123 char *userpass, *class;
124 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
125 time_t pw_warntime = _PASSWORD_WARNDAYS * SECSPERDAY;
126 #ifdef LOGIN_CAP
127 login_cap_t *lc;
128 #endif
129
130 asme = asthem = fastlogin = 0;
131 shell = class = NULL;
132 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
133 switch((char)ch) {
134 #ifdef KERBEROS
135 case 'K':
136 use_kerberos = 0;
137 break;
138 #endif
139 #ifdef LOGIN_CAP
140 case 'c':
141 class = optarg;
142 break;
143 #endif
144 case 'f':
145 fastlogin = 1;
146 break;
147 case '-':
148 case 'l':
149 asme = 0;
150 asthem = 1;
151 break;
152 case 'm':
153 asme = 1;
154 asthem = 0;
155 break;
156 case '?':
157 default:
158 (void)fprintf(stderr,
159 "Usage: %s [%s] [login [shell arguments]]\n",
160 __progname, ARGSTR);
161 exit(1);
162 }
163 argv += optind;
164
165 errno = 0;
166 prio = getpriority(PRIO_PROCESS, 0);
167 if (errno)
168 prio = 0;
169 (void)setpriority(PRIO_PROCESS, 0, -2);
170 openlog("su", LOG_CONS, 0);
171
172 /* get current login name and shell */
173 ruid = getuid();
174 username = getlogin();
175 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
176 pwd->pw_uid != ruid)
177 pwd = getpwuid(ruid);
178 if (pwd == NULL)
179 errx(1, "who are you?");
180 username = strdup(pwd->pw_name);
181 userpass = strdup(pwd->pw_passwd);
182 if (username == NULL || userpass == NULL)
183 err(1, "strdup");
184
185
186 if (asme) {
187 if (pwd->pw_shell && *pwd->pw_shell) {
188 shell = strncpy(shellbuf, pwd->pw_shell,
189 sizeof(shellbuf) - 1);
190 shellbuf[sizeof(shellbuf) - 1] = '\0';
191 } else {
192 shell = _PATH_BSHELL;
193 iscsh = NO;
194 }
195 }
196 /* get target login information, default to root */
197 user = *argv ? *argv : "root";
198 np = *argv ? argv : argv-1;
199
200 if ((pwd = getpwnam(user)) == NULL)
201 errx(1, "unknown login %s", user);
202
203 #ifdef LOGIN_CAP
204 /* force the usage of specified class */
205 if (class) {
206 if (ruid)
207 errx(1, "Only root may use -c");
208
209 pwd->pw_class = class;
210 }
211 lc = login_getclass(pwd->pw_class);
212
213 pw_warntime = login_getcaptime(lc, "password-warn",
214 _PASSWORD_WARNDAYS * SECSPERDAY,
215 _PASSWORD_WARNDAYS * SECSPERDAY);
216 #endif
217
218 if (ruid
219 #ifdef KERBEROS
220 && (!use_kerberos || kerberos(username, user, pwd->pw_uid))
221 #endif
222 ) {
223 char *pass = pwd->pw_passwd;
224 int ok = pwd->pw_uid != 0;
225 char **g;
226
227 #ifdef ROOTAUTH
228 /*
229 * Allow those in group rootauth to su to root, by supplying
230 * their own password.
231 */
232 if (!ok && (gr = getgrnam(ROOTAUTH)))
233 for (g = gr->gr_mem;; ++g) {
234 if (!*g) {
235 ok = 0;
236 break;
237 }
238 if (!strcmp(username, *g)) {
239 pass = userpass;
240 user = username;
241 ok = 1;
242 break;
243 }
244 }
245 #endif
246 /*
247 * Only allow those in group SUGROUP to su to root,
248 * but only if that group has any members.
249 * If SUGROUP has no members, allow anyone to su root
250 */
251 if (!ok) {
252 if ( !(gr = getgrnam(SUGROUP)) || !*gr->gr_mem)
253 ok = 1;
254 else
255 for (g = gr->gr_mem; ; g++) {
256 if (*g == NULL) {
257 ok = 0;
258 break;
259 }
260 if (strcmp(username, *g) == 0) {
261 ok = 1;
262 break;
263 }
264 }
265 }
266 if (!ok)
267 errx(1,
268 "you are not listed in the correct secondary group (%s) to su %s.",
269 SUGROUP, user);
270 /* if target requires a password, verify it */
271 if (*pass) {
272 p = getpass("Password:");
273 #ifdef SKEY
274 if (strcasecmp(p, "s/key") == 0) {
275 if (skey_haskey(user))
276 errx(1, "Sorry, you have no s/key.");
277 else {
278 if (skey_authenticate(user)) {
279 goto badlogin;
280 }
281 }
282
283 } else
284 #endif
285 if (strcmp(pass, crypt(p, pass))) {
286 #ifdef SKEY
287 badlogin:
288 #endif
289 fprintf(stderr, "Sorry\n");
290 syslog(LOG_AUTH|LOG_WARNING,
291 "BAD SU %s to %s%s", username,
292 pwd->pw_name, ontty());
293 exit(1);
294 }
295 }
296 }
297
298 if (asme) {
299 /* if asme and non-standard target shell, must be root */
300 if (!chshell(pwd->pw_shell) && ruid)
301 errx(1,"permission denied (shell).");
302 } else if (pwd->pw_shell && *pwd->pw_shell) {
303 shell = pwd->pw_shell;
304 iscsh = UNSET;
305 } else {
306 shell = _PATH_BSHELL;
307 iscsh = NO;
308 }
309
310 if ((p = strrchr(shell, '/')) != NULL)
311 avshell = p+1;
312 else
313 avshell = shell;
314
315 /* if we're forking a csh, we want to slightly muck the args */
316 if (iscsh == UNSET)
317 iscsh = strstr(avshell, "csh") ? YES : NO;
318
319 #ifndef LOGIN_CAP /* This is done by setusercontext() */
320 /* set permissions */
321 if (setgid(pwd->pw_gid) < 0)
322 err(1, "setgid");
323 if (initgroups(user, pwd->pw_gid))
324 errx(1, "initgroups failed");
325 if (setuid(pwd->pw_uid) < 0)
326 err(1, "setuid");
327 #endif
328
329 if (!asme) {
330 if (asthem) {
331 p = getenv("TERM");
332 /* Create an empty environment */
333 if ((environ = malloc(sizeof(char *))) == NULL)
334 err(1, NULL);
335 environ[0] = NULL;
336 #ifdef LOGIN_CAP
337 if (setusercontext(lc,
338 pwd, pwd->pw_uid, LOGIN_SETPATH))
339 err(1, "setting user context");
340 #else
341 (void)setenv("PATH", _PATH_DEFPATH, 1);
342 #endif
343 if (p)
344 (void)setenv("TERM", p, 1);
345 if (chdir(pwd->pw_dir) < 0)
346 errx(1, "no directory");
347 }
348 #ifdef LOGIN_CAP
349 else if (pwd->pw_uid == 0)
350 if (setusercontext(lc,
351 pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK))
352 err(1, "setting path");
353 #endif
354 if (asthem || pwd->pw_uid)
355 (void)setenv("USER", pwd->pw_name, 1);
356 (void)setenv("HOME", pwd->pw_dir, 1);
357 (void)setenv("SHELL", shell, 1);
358 }
359
360 if (iscsh == YES) {
361 if (fastlogin)
362 *np-- = "-f";
363 if (asme)
364 *np-- = "-m";
365 }
366
367 if (asthem) {
368 avshellbuf[0] = '-';
369 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
370 avshell = avshellbuf;
371 } else if (iscsh == YES) {
372 /* csh strips the first character... */
373 avshellbuf[0] = '_';
374 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
375 avshell = avshellbuf;
376 }
377 *np = avshell;
378
379 #ifdef BSD4_4
380 if (pwd->pw_change || pwd->pw_expire)
381 (void)gettimeofday(&tp, (struct timezone *)NULL);
382 if (pwd->pw_change) {
383 if (tp.tv_sec >= pwd->pw_change) {
384 (void)printf("%s -- %s's password has expired.\n",
385 (ruid ? "Sorry" : "Note"), user);
386 if (ruid != 0)
387 exit(1);
388 } else if (pwd->pw_change - tp.tv_sec < pw_warntime)
389 (void)printf("Warning: %s's password expires on %s",
390 user, ctime(&pwd->pw_change));
391 }
392 if (pwd->pw_expire) {
393 if (tp.tv_sec >= pwd->pw_expire) {
394 (void)printf("%s -- %s's account has expired.\n",
395 (ruid ? "Sorry" : "Note"), user);
396 if (ruid != 0)
397 exit(1);
398 } else if (pwd->pw_expire - tp.tv_sec <
399 _PASSWORD_WARNDAYS * SECSPERDAY)
400 (void)printf("Warning: %s's account expires on %s",
401 user, ctime(&pwd->pw_expire));
402 }
403 #endif
404 if (ruid != 0)
405 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
406 username, pwd->pw_name, ontty());
407
408 (void)setpriority(PRIO_PROCESS, 0, prio);
409 #ifdef LOGIN_CAP
410 if (setusercontext(lc, pwd, pwd->pw_uid,
411 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
412 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
413 err(1, "setting user context");
414 #endif
415
416 execv(shell, np);
417 err(1, "%s", shell);
418 /* NOTREACHED */
419 }
420
421 static int
422 chshell(sh)
423 const char *sh;
424 {
425 const char *cp;
426
427 while ((cp = getusershell()) != NULL)
428 if (!strcmp(cp, sh))
429 return (1);
430 return (0);
431 }
432
433 static char *
434 ontty()
435 {
436 char *p;
437 static char buf[MAXPATHLEN + 4];
438
439 buf[0] = 0;
440 if ((p = ttyname(STDERR_FILENO)) != NULL)
441 (void)snprintf(buf, sizeof buf, " on %s", p);
442 return (buf);
443 }
444
445 #ifdef KERBEROS
446 static int
447 kerberos(username, user, uid)
448 char *username, *user;
449 int uid;
450 {
451 KTEXT_ST ticket;
452 AUTH_DAT authdata;
453 struct hostent *hp;
454 int kerno;
455 u_long faddr;
456 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
457 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
458
459 if (krb_get_lrealm(lrealm, 1) != KSUCCESS ||
460 strcmp(lrealm, KRB_REALM) == 0)
461 return (1);
462 if (koktologin(username, lrealm, user) && !uid) {
463 warnx("kerberos: not in %s's ACL.", user);
464 return (1);
465 }
466 (void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
467 user, getuid());
468
469 (void)setenv("KRBTKFILE", krbtkfile, 1);
470 (void)krb_set_tkt_string(krbtkfile);
471 /*
472 * Set real as well as effective ID to 0 for the moment,
473 * to make the kerberos library do the right thing.
474 */
475 if (setuid(0) < 0) {
476 warn("setuid");
477 return (1);
478 }
479
480 /*
481 * Little trick here -- if we are su'ing to root,
482 * we need to get a ticket for "xxx.root", where xxx represents
483 * the name of the person su'ing. Otherwise (non-root case),
484 * we need to get a ticket for "yyy.", where yyy represents
485 * the name of the person being su'd to, and the instance is null
486 *
487 * We should have a way to set the ticket lifetime,
488 * with a system default for root.
489 */
490 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
491 (uid == 0 ? "root" : ""), lrealm,
492 "krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
493
494 if (kerno != KSUCCESS) {
495 if (kerno == KDC_PR_UNKNOWN) {
496 warnx("kerberos: principal unknown: %s.%s@%s",
497 (uid == 0 ? username : user),
498 (uid == 0 ? "root" : ""), lrealm);
499 return (1);
500 }
501 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
502 syslog(LOG_NOTICE|LOG_AUTH,
503 "BAD Kerberos SU: %s to %s%s: %s",
504 username, user, ontty(), krb_err_txt[kerno]);
505 return (1);
506 }
507
508 if (chown(krbtkfile, uid, -1) < 0) {
509 warn("chown");
510 (void)unlink(krbtkfile);
511 return (1);
512 }
513
514 (void)setpriority(PRIO_PROCESS, 0, -2);
515
516 if (gethostname(hostname, sizeof(hostname)) == -1) {
517 warn("gethostname");
518 dest_tkt();
519 return (1);
520 }
521 hostname[sizeof(hostname) - 1] = '\0';
522
523 (void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
524 savehost[sizeof(savehost) - 1] = '\0';
525
526 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
527
528 if (kerno == KDC_PR_UNKNOWN) {
529 warnx("Warning: TGT not verified.");
530 syslog(LOG_NOTICE|LOG_AUTH,
531 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
532 username, user, ontty(), krb_err_txt[kerno],
533 "rcmd", savehost);
534 } else if (kerno != KSUCCESS) {
535 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
536 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
537 username, user, ontty(), krb_err_txt[kerno]);
538 dest_tkt();
539 return (1);
540 } else {
541 if (!(hp = gethostbyname(hostname))) {
542 warnx("can't get addr of %s", hostname);
543 dest_tkt();
544 return (1);
545 }
546 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
547
548 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
549 &authdata, "")) != KSUCCESS) {
550 warnx("kerberos: unable to verify rcmd ticket: %s\n",
551 krb_err_txt[kerno]);
552 syslog(LOG_NOTICE|LOG_AUTH,
553 "failed su: %s to %s%s: %s", username,
554 user, ontty(), krb_err_txt[kerno]);
555 dest_tkt();
556 return (1);
557 }
558 }
559 return (0);
560 }
561
562 static int
563 koktologin(name, realm, toname)
564 char *name, *realm, *toname;
565 {
566 AUTH_DAT *kdata;
567 AUTH_DAT kdata_st;
568
569 kdata = &kdata_st;
570 memset((char *)kdata, 0, sizeof(*kdata));
571 (void)strncpy(kdata->pname, name, sizeof(kdata->pname) - 1);
572 (void)strncpy(kdata->pinst,
573 ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof(kdata->pinst) - 1);
574 (void)strncpy(kdata->prealm, realm, sizeof(kdata->prealm) - 1);
575 return (kuserok(kdata, toname));
576 }
577 #endif
578