su.c revision 1.40 1 /* $NetBSD: su.c,v 1.40 2000/07/10 01:45:24 assar 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.40 2000/07/10 01:45:24 assar 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 (void)setenv("SU_FROM", username, 1);
355
356 if (iscsh == YES) {
357 if (fastlogin)
358 *np-- = "-f";
359 if (asme)
360 *np-- = "-m";
361 }
362
363 if (asthem) {
364 avshellbuf[0] = '-';
365 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
366 avshell = avshellbuf;
367 } else if (iscsh == YES) {
368 /* csh strips the first character... */
369 avshellbuf[0] = '_';
370 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
371 avshell = avshellbuf;
372 }
373 *np = avshell;
374
375 #ifdef BSD4_4
376 if (pwd->pw_change || pwd->pw_expire)
377 (void)gettimeofday(&tp, (struct timezone *)NULL);
378 if (pwd->pw_change) {
379 if (tp.tv_sec >= pwd->pw_change) {
380 (void)printf("%s -- %s's password has expired.\n",
381 (ruid ? "Sorry" : "Note"), user);
382 if (ruid != 0)
383 exit(1);
384 } else if (pwd->pw_change - tp.tv_sec < pw_warntime)
385 (void)printf("Warning: %s's password expires on %s",
386 user, ctime(&pwd->pw_change));
387 }
388 if (pwd->pw_expire) {
389 if (tp.tv_sec >= pwd->pw_expire) {
390 (void)printf("%s -- %s's account has expired.\n",
391 (ruid ? "Sorry" : "Note"), user);
392 if (ruid != 0)
393 exit(1);
394 } else if (pwd->pw_expire - tp.tv_sec <
395 _PASSWORD_WARNDAYS * SECSPERDAY)
396 (void)printf("Warning: %s's account expires on %s",
397 user, ctime(&pwd->pw_expire));
398 }
399 #endif
400 if (ruid != 0)
401 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
402 username, pwd->pw_name, ontty());
403
404 (void)setpriority(PRIO_PROCESS, 0, prio);
405 #ifdef LOGIN_CAP
406 if (setusercontext(lc, pwd, pwd->pw_uid,
407 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
408 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
409 err(1, "setting user context");
410 #endif
411
412 execv(shell, np);
413 err(1, "%s", shell);
414 /* NOTREACHED */
415 }
416
417 static int
418 chshell(sh)
419 const char *sh;
420 {
421 const char *cp;
422
423 while ((cp = getusershell()) != NULL)
424 if (!strcmp(cp, sh))
425 return (1);
426 return (0);
427 }
428
429 static char *
430 ontty()
431 {
432 char *p;
433 static char buf[MAXPATHLEN + 4];
434
435 buf[0] = 0;
436 if ((p = ttyname(STDERR_FILENO)) != NULL)
437 (void)snprintf(buf, sizeof buf, " on %s", p);
438 return (buf);
439 }
440
441 #ifdef KERBEROS
442 static int
443 kerberos(username, user, uid)
444 char *username, *user;
445 int uid;
446 {
447 KTEXT_ST ticket;
448 AUTH_DAT authdata;
449 struct hostent *hp;
450 int kerno;
451 u_long faddr;
452 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
453 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
454
455 if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
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 {
486 char prompt[128];
487 char passw[256];
488
489 (void)snprintf (prompt, sizeof(prompt),
490 "%s's Password: ",
491 krb_unparse_name_long ((uid == 0 ? username : user),
492 (uid == 0 ? "root" : ""),
493 lrealm));
494 if (des_read_pw_string (passw, sizeof (passw), prompt, 0)) {
495 memset (passw, 0, sizeof (passw));
496 return (1);
497 }
498 if (strlen(passw) == 0)
499 return (1); /* Empty passwords are not allowed */
500
501 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
502 (uid == 0 ? "root" : ""), lrealm,
503 KRB_TICKET_GRANTING_TICKET,
504 lrealm,
505 DEFAULT_TKT_LIFE,
506 passw);
507 memset (passw, 0, strlen (passw));
508 }
509
510 if (kerno != KSUCCESS) {
511 if (kerno == KDC_PR_UNKNOWN) {
512 warnx("kerberos: principal unknown: %s.%s@%s",
513 (uid == 0 ? username : user),
514 (uid == 0 ? "root" : ""), lrealm);
515 return (1);
516 }
517 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
518 syslog(LOG_NOTICE|LOG_AUTH,
519 "BAD Kerberos SU: %s to %s%s: %s",
520 username, user, ontty(), krb_err_txt[kerno]);
521 return (1);
522 }
523
524 if (chown(krbtkfile, uid, -1) < 0) {
525 warn("chown");
526 (void)unlink(krbtkfile);
527 return (1);
528 }
529
530 (void)setpriority(PRIO_PROCESS, 0, -2);
531
532 if (gethostname(hostname, sizeof(hostname)) == -1) {
533 warn("gethostname");
534 dest_tkt();
535 return (1);
536 }
537 hostname[sizeof(hostname) - 1] = '\0';
538
539 (void)strlcpy(savehost, krb_get_phost(hostname), sizeof(savehost));
540 savehost[sizeof(savehost) - 1] = '\0';
541
542 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
543
544 if (kerno == KDC_PR_UNKNOWN) {
545 warnx("Warning: TGT not verified.");
546 syslog(LOG_NOTICE|LOG_AUTH,
547 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
548 username, user, ontty(), krb_err_txt[kerno],
549 "rcmd", savehost);
550 } else if (kerno != KSUCCESS) {
551 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
552 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
553 username, user, ontty(), krb_err_txt[kerno]);
554 dest_tkt();
555 return (1);
556 } else {
557 if (!(hp = gethostbyname(hostname))) {
558 warnx("can't get addr of %s", hostname);
559 dest_tkt();
560 return (1);
561 }
562 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
563
564 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
565 &authdata, "")) != KSUCCESS) {
566 warnx("kerberos: unable to verify rcmd ticket: %s\n",
567 krb_err_txt[kerno]);
568 syslog(LOG_NOTICE|LOG_AUTH,
569 "failed su: %s to %s%s: %s", username,
570 user, ontty(), krb_err_txt[kerno]);
571 dest_tkt();
572 return (1);
573 }
574 }
575 return (0);
576 }
577
578 static int
579 koktologin(name, realm, toname)
580 char *name, *realm, *toname;
581 {
582 return krb_kuserok(name,
583 strcmp (toname, "root") == 0 ? "root" : "",
584 realm,
585 toname);
586 }
587 #endif
588