su.c revision 1.27 1 /* $NetBSD: su.c,v 1.27 1998/10/14 00:56:48 wsanchez 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.27 1998/10/14 00:56:48 wsanchez 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 group *gr;
107 struct timeval tp;
108 uid_t ruid;
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 if (username == NULL)
161 err(1, "strdup");
162
163 if (asme) {
164 if (pwd->pw_shell && *pwd->pw_shell) {
165 shell = strncpy(shellbuf, pwd->pw_shell,
166 sizeof(shellbuf) - 1);
167 shellbuf[sizeof(shellbuf) - 1] = '\0';
168 } else {
169 shell = _PATH_BSHELL;
170 iscsh = NO;
171 }
172 }
173 /* get target login information, default to root */
174 user = *argv ? *argv : "root";
175 np = *argv ? argv : argv-1;
176
177 if ((pwd = getpwnam(user)) == NULL)
178 errx(1, "unknown login %s", user);
179
180 if (ruid
181 #ifdef KERBEROS
182 && (!use_kerberos || kerberos(username, user, pwd->pw_uid))
183 #endif
184 ) {
185 /*
186 * Only allow those in group SUGROUP to su to root,
187 * but only if that group has any members.
188 * If SUGROUP has no members, allow anyone to su root
189 */
190 if (pwd->pw_uid == 0 &&
191 (gr = getgrnam(SUGROUP)) && *gr->gr_mem) {
192 char **g;
193
194 for (g = gr->gr_mem; ; g++) {
195 if (*g == NULL)
196 errx(1,
197 "you are not listed in the correct secondary group (%s) to su %s.",
198 SUGROUP, user);
199 if (strcmp(username, *g) == 0)
200 break;
201 }
202 }
203 /* if target requires a password, verify it */
204 if (*pwd->pw_passwd) {
205 p = getpass("Password:");
206 #ifdef SKEY
207 if (strcasecmp(p, "s/key") == 0) {
208 if (skey_haskey(user))
209 errx(1, "Sorry, you have no s/key.");
210 else {
211 if (skey_authenticate(user)) {
212 goto badlogin;
213 }
214 }
215
216 } else
217 #endif
218 if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
219 #ifdef SKEY
220 badlogin:
221 #endif
222 fprintf(stderr, "Sorry\n");
223 syslog(LOG_AUTH|LOG_WARNING,
224 "BAD SU %s to %s%s", username,
225 user, ontty());
226 exit(1);
227 }
228 }
229 }
230
231 if (asme) {
232 /* if asme and non-standard target shell, must be root */
233 if (!chshell(pwd->pw_shell) && ruid)
234 errx(1,"permission denied (shell).");
235 } else if (pwd->pw_shell && *pwd->pw_shell) {
236 shell = pwd->pw_shell;
237 iscsh = UNSET;
238 } else {
239 shell = _PATH_BSHELL;
240 iscsh = NO;
241 }
242
243 if ((p = strrchr(shell, '/')) != NULL)
244 avshell = p+1;
245 else
246 avshell = shell;
247
248 /* if we're forking a csh, we want to slightly muck the args */
249 if (iscsh == UNSET)
250 iscsh = strstr(avshell, "csh") ? YES : NO;
251
252 /* set permissions */
253 if (setgid(pwd->pw_gid) < 0)
254 err(1, "setgid");
255 if (initgroups(user, pwd->pw_gid))
256 errx(1, "initgroups failed");
257 if (setuid(pwd->pw_uid) < 0)
258 err(1, "setuid");
259
260 if (!asme) {
261 if (asthem) {
262 p = getenv("TERM");
263 cleanenv[0] = NULL;
264 environ = cleanenv;
265 (void)setenv("PATH", _PATH_DEFPATH, 1);
266 if (p)
267 (void)setenv("TERM", p, 1);
268 if (chdir(pwd->pw_dir) < 0)
269 errx(1, "no directory");
270 }
271 if (asthem || pwd->pw_uid)
272 (void)setenv("USER", pwd->pw_name, 1);
273 (void)setenv("HOME", pwd->pw_dir, 1);
274 (void)setenv("SHELL", shell, 1);
275 }
276
277 if (iscsh == YES) {
278 if (fastlogin)
279 *np-- = "-f";
280 if (asme)
281 *np-- = "-m";
282 }
283
284 if (asthem) {
285 avshellbuf[0] = '-';
286 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
287 avshell = avshellbuf;
288 } else if (iscsh == YES) {
289 /* csh strips the first character... */
290 avshellbuf[0] = '_';
291 (void)strncpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 2);
292 avshell = avshellbuf;
293 }
294 *np = avshell;
295
296 if (pwd->pw_change || pwd->pw_expire)
297 (void)gettimeofday(&tp, (struct timezone *)NULL);
298 if (pwd->pw_change) {
299 if (tp.tv_sec >= pwd->pw_change) {
300 (void)printf("%s -- %s's password has expired.\n",
301 (ruid ? "Sorry" : "Note"), user);
302 if (ruid != 0)
303 exit(1);
304 } else if (pwd->pw_change - tp.tv_sec <
305 _PASSWORD_WARNDAYS * SECSPERDAY)
306 (void)printf("Warning: %s's password expires on %s",
307 user, ctime(&pwd->pw_change));
308 }
309 if (pwd->pw_expire) {
310 if (tp.tv_sec >= pwd->pw_expire) {
311 (void)printf("%s -- %s's account has expired.\n",
312 (ruid ? "Sorry" : "Note"), user);
313 if (ruid != 0)
314 exit(1);
315 } else if (pwd->pw_expire - tp.tv_sec <
316 _PASSWORD_WARNDAYS * SECSPERDAY)
317 (void)printf("Warning: %s's account expires on %s",
318 user, ctime(&pwd->pw_expire));
319 }
320 if (ruid != 0)
321 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
322 username, user, ontty());
323
324 (void)setpriority(PRIO_PROCESS, 0, prio);
325
326 execv(shell, np);
327 err(1, "%s", shell);
328 /* NOTREACHED */
329 }
330
331 static int
332 chshell(sh)
333 const char *sh;
334 {
335 const char *cp;
336
337 while ((cp = getusershell()) != NULL)
338 if (!strcmp(cp, sh))
339 return (1);
340 return (0);
341 }
342
343 static char *
344 ontty()
345 {
346 char *p;
347 static char buf[MAXPATHLEN + 4];
348
349 buf[0] = 0;
350 if ((p = ttyname(STDERR_FILENO)) != NULL)
351 (void)snprintf(buf, sizeof buf, " on %s", p);
352 return (buf);
353 }
354
355 #ifdef KERBEROS
356 static int
357 kerberos(username, user, uid)
358 char *username, *user;
359 int uid;
360 {
361 KTEXT_ST ticket;
362 AUTH_DAT authdata;
363 struct hostent *hp;
364 int kerno;
365 u_long faddr;
366 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
367 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
368
369 if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
370 return (1);
371 if (koktologin(username, lrealm, user) && !uid) {
372 warnx("kerberos: not in %s's ACL.", user);
373 return (1);
374 }
375 (void)(void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
376 user, getuid());
377
378 (void)setenv("KRBTKFILE", krbtkfile, 1);
379 (void)krb_set_tkt_string(krbtkfile);
380 /*
381 * Set real as well as effective ID to 0 for the moment,
382 * to make the kerberos library do the right thing.
383 */
384 if (setuid(0) < 0) {
385 warn("setuid");
386 return (1);
387 }
388
389 /*
390 * Little trick here -- if we are su'ing to root,
391 * we need to get a ticket for "xxx.root", where xxx represents
392 * the name of the person su'ing. Otherwise (non-root case),
393 * we need to get a ticket for "yyy.", where yyy represents
394 * the name of the person being su'd to, and the instance is null
395 *
396 * We should have a way to set the ticket lifetime,
397 * with a system default for root.
398 */
399 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
400 (uid == 0 ? "root" : ""), lrealm,
401 "krbtgt", lrealm, DEFAULT_TKT_LIFE, 0);
402
403 if (kerno != KSUCCESS) {
404 if (kerno == KDC_PR_UNKNOWN) {
405 warnx("kerberos: principal unknown: %s.%s@%s",
406 (uid == 0 ? username : user),
407 (uid == 0 ? "root" : ""), lrealm);
408 return (1);
409 }
410 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
411 syslog(LOG_NOTICE|LOG_AUTH,
412 "BAD Kerberos SU: %s to %s%s: %s",
413 username, user, ontty(), krb_err_txt[kerno]);
414 return (1);
415 }
416
417 if (chown(krbtkfile, uid, -1) < 0) {
418 warn("chown");
419 (void)unlink(krbtkfile);
420 return (1);
421 }
422
423 (void)setpriority(PRIO_PROCESS, 0, -2);
424
425 if (gethostname(hostname, sizeof(hostname)) == -1) {
426 warn("gethostname");
427 dest_tkt();
428 return (1);
429 }
430 hostname[sizeof(hostname) - 1] = '\0';
431
432 (void)strncpy(savehost, krb_get_phost(hostname), sizeof(savehost));
433 savehost[sizeof(savehost) - 1] = '\0';
434
435 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
436
437 if (kerno == KDC_PR_UNKNOWN) {
438 warnx("Warning: TGT not verified.");
439 syslog(LOG_NOTICE|LOG_AUTH,
440 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
441 username, user, ontty(), krb_err_txt[kerno],
442 "rcmd", savehost);
443 } else if (kerno != KSUCCESS) {
444 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
445 syslog(LOG_NOTICE|LOG_AUTH, "failed su: %s to %s%s: %s",
446 username, user, ontty(), krb_err_txt[kerno]);
447 dest_tkt();
448 return (1);
449 } else {
450 if (!(hp = gethostbyname(hostname))) {
451 warnx("can't get addr of %s", hostname);
452 dest_tkt();
453 return (1);
454 }
455 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
456
457 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
458 &authdata, "")) != KSUCCESS) {
459 warnx("kerberos: unable to verify rcmd ticket: %s\n",
460 krb_err_txt[kerno]);
461 syslog(LOG_NOTICE|LOG_AUTH,
462 "failed su: %s to %s%s: %s", username,
463 user, ontty(), krb_err_txt[kerno]);
464 dest_tkt();
465 return (1);
466 }
467 }
468 return (0);
469 }
470
471 static int
472 koktologin(name, realm, toname)
473 char *name, *realm, *toname;
474 {
475 AUTH_DAT *kdata;
476 AUTH_DAT kdata_st;
477
478 kdata = &kdata_st;
479 memset((char *)kdata, 0, sizeof(*kdata));
480 (void)strncpy(kdata->pname, name, sizeof(kdata->pname) - 1);
481 (void)strncpy(kdata->pinst,
482 ((strcmp(toname, "root") == 0) ? "root" : ""), sizeof(kdata->pinst) - 1);
483 (void)strncpy(kdata->prealm, realm, sizeof(kdata->prealm) - 1);
484 return (kuserok(kdata, toname));
485 }
486 #endif
487