su.c revision 1.38 1 /* $NetBSD: su.c,v 1.38 2000/01/25 02:19:19 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.38 2000/01/25 02:19:19 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
349 if (asthem || pwd->pw_uid)
350 (void)setenv("USER", pwd->pw_name, 1);
351 (void)setenv("HOME", pwd->pw_dir, 1);
352 (void)setenv("SHELL", shell, 1);
353 }
354
355 if (iscsh == YES) {
356 if (fastlogin)
357 *np-- = "-f";
358 if (asme)
359 *np-- = "-m";
360 }
361
362 if (asthem) {
363 avshellbuf[0] = '-';
364 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
365 avshell = avshellbuf;
366 } else if (iscsh == YES) {
367 /* csh strips the first character... */
368 avshellbuf[0] = '_';
369 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
370 avshell = avshellbuf;
371 }
372 *np = avshell;
373
374 #ifdef BSD4_4
375 if (pwd->pw_change || pwd->pw_expire)
376 (void)gettimeofday(&tp, (struct timezone *)NULL);
377 if (pwd->pw_change) {
378 if (tp.tv_sec >= pwd->pw_change) {
379 (void)printf("%s -- %s's password has expired.\n",
380 (ruid ? "Sorry" : "Note"), user);
381 if (ruid != 0)
382 exit(1);
383 } else if (pwd->pw_change - tp.tv_sec < pw_warntime)
384 (void)printf("Warning: %s's password expires on %s",
385 user, ctime(&pwd->pw_change));
386 }
387 if (pwd->pw_expire) {
388 if (tp.tv_sec >= pwd->pw_expire) {
389 (void)printf("%s -- %s's account has expired.\n",
390 (ruid ? "Sorry" : "Note"), user);
391 if (ruid != 0)
392 exit(1);
393 } else if (pwd->pw_expire - tp.tv_sec <
394 _PASSWORD_WARNDAYS * SECSPERDAY)
395 (void)printf("Warning: %s's account expires on %s",
396 user, ctime(&pwd->pw_expire));
397 }
398 #endif
399 if (ruid != 0)
400 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
401 username, pwd->pw_name, ontty());
402
403 (void)setpriority(PRIO_PROCESS, 0, prio);
404 #ifdef LOGIN_CAP
405 if (setusercontext(lc, pwd, pwd->pw_uid,
406 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
407 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
408 err(1, "setting user context");
409 #endif
410
411 execv(shell, np);
412 err(1, "%s", shell);
413 /* NOTREACHED */
414 }
415
416 static int
417 chshell(sh)
418 const char *sh;
419 {
420 const char *cp;
421
422 while ((cp = getusershell()) != NULL)
423 if (!strcmp(cp, sh))
424 return (1);
425 return (0);
426 }
427
428 static char *
429 ontty()
430 {
431 char *p;
432 static char buf[MAXPATHLEN + 4];
433
434 buf[0] = 0;
435 if ((p = ttyname(STDERR_FILENO)) != NULL)
436 (void)snprintf(buf, sizeof buf, " on %s", p);
437 return (buf);
438 }
439
440 #ifdef KERBEROS
441 static int
442 kerberos(username, user, uid)
443 char *username, *user;
444 int uid;
445 {
446 KTEXT_ST ticket;
447 AUTH_DAT authdata;
448 struct hostent *hp;
449 int kerno;
450 u_long faddr;
451 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
452 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
453
454 if (krb_get_lrealm(lrealm, 1) != KSUCCESS ||
455 strcmp(lrealm, KRB_REALM) == 0)
456 return (1);
457 if (koktologin(username, lrealm, user) && !uid) {
458 warnx("kerberos: not in %s's ACL.", user);
459 return (1);
460 }
461 (void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
462 user, getuid());
463
464 (void)setenv("KRBTKFILE", krbtkfile, 1);
465 (void)krb_set_tkt_string(krbtkfile);
466 /*
467 * Set real as well as effective ID to 0 for the moment,
468 * to make the kerberos library do the right thing.
469 */
470 if (setuid(0) < 0) {
471 warn("setuid");
472 return (1);
473 }
474
475 /*
476 * Little trick here -- if we are su'ing to root,
477 * we need to get a ticket for "xxx.root", where xxx represents
478 * the name of the person su'ing. Otherwise (non-root case),
479 * we need to get a ticket for "yyy.", where yyy represents
480 * the name of the person being su'd to, and the instance is null
481 *
482 * We should have a way to set the ticket lifetime,
483 * with a system default for root.
484 */
485 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
486 (uid == 0 ? "root" : ""), lrealm,
487 "krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
488
489 if (kerno != KSUCCESS) {
490 if (kerno == KDC_PR_UNKNOWN) {
491 warnx("kerberos: principal unknown: %s.%s@%s",
492 (uid == 0 ? username : user),
493 (uid == 0 ? "root" : ""), lrealm);
494 return (1);
495 }
496 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
497 syslog(LOG_NOTICE|LOG_AUTH,
498 "BAD Kerberos SU: %s to %s%s: %s",
499 username, user, ontty(), krb_err_txt[kerno]);
500 return (1);
501 }
502
503 if (chown(krbtkfile, uid, -1) < 0) {
504 warn("chown");
505 (void)unlink(krbtkfile);
506 return (1);
507 }
508
509 (void)setpriority(PRIO_PROCESS, 0, -2);
510
511 if (gethostname(hostname, sizeof(hostname)) == -1) {
512 warn("gethostname");
513 dest_tkt();
514 return (1);
515 }
516 hostname[sizeof(hostname) - 1] = '\0';
517
518 (void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
519 savehost[sizeof(savehost) - 1] = '\0';
520
521 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
522
523 if (kerno == KDC_PR_UNKNOWN) {
524 warnx("Warning: TGT not verified.");
525 syslog(LOG_NOTICE|LOG_AUTH,
526 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
527 username, user, ontty(), krb_err_txt[kerno],
528 "rcmd", savehost);
529 } else if (kerno != KSUCCESS) {
530 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
531 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
532 username, user, ontty(), krb_err_txt[kerno]);
533 dest_tkt();
534 return (1);
535 } else {
536 if (!(hp = gethostbyname(hostname))) {
537 warnx("can't get addr of %s", hostname);
538 dest_tkt();
539 return (1);
540 }
541 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
542
543 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
544 &authdata, "")) != KSUCCESS) {
545 warnx("kerberos: unable to verify rcmd ticket: %s\n",
546 krb_err_txt[kerno]);
547 syslog(LOG_NOTICE|LOG_AUTH,
548 "failed su: %s to %s%s: %s", username,
549 user, ontty(), krb_err_txt[kerno]);
550 dest_tkt();
551 return (1);
552 }
553 }
554 return (0);
555 }
556
557 static int
558 koktologin(name, realm, toname)
559 char *name, *realm, *toname;
560 {
561 AUTH_DAT *kdata;
562 AUTH_DAT kdata_st;
563
564 kdata = &kdata_st;
565 memset((char *)kdata, 0, sizeof(*kdata));
566 (void)strncpy(kdata->pname, name, sizeof(kdata->pname) - 1);
567 (void)strncpy(kdata->pinst,
568 ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof(kdata->pinst) - 1);
569 (void)strncpy(kdata->prealm, realm, sizeof(kdata->prealm) - 1);
570 return (kuserok(kdata, toname));
571 }
572 #endif
573