Initial import from FreeBSD RELENG_4:
[dragonfly.git] / crypto / kerberosIV / appl / bsd / su.c
1 /*
2  * Copyright (c) 1988 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include "bsd_locl.h"
35
36 RCSID ("$Id: su.c,v 1.70.2.2 2000/12/07 14:04:19 assar Exp $");
37
38 #ifdef SYSV_SHADOW
39 #include "sysv_shadow.h"
40 #endif
41
42 static int kerberos (char *username, char *user, char *realm, int uid);
43 static int chshell (char *sh);
44 static char *ontty (void);
45 static int koktologin (char *name, char *realm, char *toname);
46 static int chshell (char *sh);
47
48 /* Handle '-' option after all the getopt options */
49 #define ARGSTR  "Kkflmti:r:"
50
51 int destroy_tickets = 0;
52 static int use_kerberos = 1;
53 static char *root_inst = "root";
54
55 int
56 main (int argc, char **argv)
57 {
58     struct passwd *pwd;
59     char *p, **g;
60     struct group *gr;
61     uid_t ruid;
62     int asme, ch, asthem, fastlogin, prio;
63     enum { UNSET, YES, NO } iscsh = UNSET;
64     char *user, *shell, *avshell, *username, **np;
65     char shellbuf[MaxPathLen], avshellbuf[MaxPathLen];
66     char *realm = NULL;
67
68     set_progname (argv[0]);
69
70     if (getuid() == 0)
71         use_kerberos = 0;
72
73     asme = asthem = fastlogin = 0;
74     while ((ch = getopt (argc, argv, ARGSTR)) != -1)
75         switch ((char) ch) {
76         case 'K':
77             use_kerberos = 0;
78             break;
79         case 'k':
80             use_kerberos = 1;
81             break;
82         case 'f':
83             fastlogin = 1;
84             break;
85         case 'l':
86             asme = 0;
87             asthem = 1;
88             break;
89         case 'm':
90             asme = 1;
91             asthem = 0;
92             break;
93         case 't':
94             destroy_tickets = 1;
95             break;
96         case 'i':
97             root_inst = optarg;
98             break;
99         case 'r':
100             realm = optarg;
101             break;
102         case '?':
103         default:
104             fprintf (stderr,
105                      "usage: su [-Kkflmt] [-i root-instance] [-r realm] [-] [login]\n");
106             exit (1);
107         }
108     /* Don't handle '-' option with getopt */
109     if (optind < argc && strcmp (argv[optind], "-") == 0) {
110         asme = 0;
111         asthem = 1;
112         optind++;
113     }
114     argv += optind;
115
116     if (use_kerberos) {
117         int fd = open (KEYFILE, O_RDONLY);
118
119         if (fd >= 0)
120             close (fd);
121         else
122             use_kerberos = 0;
123     }
124     errno = 0;
125     prio = getpriority (PRIO_PROCESS, 0);
126     if (errno)
127         prio = 0;
128     setpriority (PRIO_PROCESS, 0, -2);
129     openlog ("su", LOG_CONS, LOG_AUTH);
130
131     /* get current login name and shell */
132     ruid = getuid ();
133     username = getlogin ();
134     if (username == NULL || (pwd = k_getpwnam (username)) == NULL ||
135         pwd->pw_uid != ruid)
136         pwd = k_getpwuid (ruid);
137     if (pwd == NULL)
138         errx (1, "who are you?");
139     username = strdup (pwd->pw_name);
140     if (username == NULL)
141         errx (1, "strdup: out of memory");
142     if (asme) {
143         if (pwd->pw_shell && *pwd->pw_shell) {
144             strlcpy (shellbuf, pwd->pw_shell, sizeof(shellbuf));
145             shell = shellbuf;
146         } else {
147             shell = _PATH_BSHELL;
148             iscsh = NO;
149         }
150     }
151
152     /* get target login information, default to root */
153     user = *argv ? *argv : "root";
154     np = *argv ? argv : argv - 1;
155
156     pwd = k_getpwnam (user);
157     if (pwd == NULL)
158         errx (1, "unknown login %s", user);
159     if (pwd->pw_uid == 0 && strcmp ("root", user) != 0) {
160         syslog (LOG_ALERT, "NIS attack, user %s has uid 0", user);
161         errx (1, "unknown login %s", user);
162     }
163     if (!use_kerberos || kerberos (username, user, realm, pwd->pw_uid)) {
164 #ifndef PASSWD_FALLBACK
165         errx (1, "won't use /etc/passwd authentication");
166 #endif
167         /* getpwnam() is not reentrant and kerberos might use it! */
168         pwd = k_getpwnam (user);
169         if (pwd == NULL)
170             errx (1, "unknown login %s", user);
171         /* only allow those in group zero to su to root. */
172         if (pwd->pw_uid == 0 && (gr = getgrgid ((gid_t) 0)))
173             for (g = gr->gr_mem;; ++g) {
174                 if (!*g) {
175 #if 1
176                     /* if group 0 is empty or only 
177                        contains root su is still ok. */
178                     if (gr->gr_mem[0] == 0)
179                         break;  /* group 0 is empty */
180                     if (gr->gr_mem[1] == 0 &&
181                         strcmp (gr->gr_mem[0], "root") == 0)
182                         break;  /* only root in group 0 */
183 #endif
184                     errx (1, "you are not in the correct group to su %s.",
185                           user);
186                 }
187                 if (!strcmp (username, *g))
188                     break;
189             }
190         /* if target requires a password, verify it */
191         if (ruid && *pwd->pw_passwd) {
192             char prompt[128];
193             char passwd[256];
194
195             snprintf (prompt, sizeof(prompt), "%s's Password: ", pwd->pw_name);
196             if (des_read_pw_string (passwd, sizeof (passwd),
197                                     prompt, 0)) {
198                 memset (passwd, 0, sizeof (passwd));
199                 exit (1);
200             }
201             if (strcmp (pwd->pw_passwd,
202                         crypt (passwd, pwd->pw_passwd))) {
203                 memset (passwd, 0, sizeof (passwd));
204                 syslog (LOG_AUTH | LOG_WARNING,
205                         "BAD SU %s to %s%s", username,
206                         user, ontty ());
207                 errx (1, "Sorry");
208             }
209             memset (passwd, 0, sizeof (passwd));
210         }
211     }
212     if (asme) {
213         /* if asme and non-standard target shell, must be root */
214         if (!chshell (pwd->pw_shell) && ruid)
215             errx (1, "permission denied (shell '%s' not in /etc/shells).",
216                   pwd->pw_shell);
217     } else if (pwd->pw_shell && *pwd->pw_shell) {
218         shell = pwd->pw_shell;
219         iscsh = UNSET;
220     } else {
221         shell = _PATH_BSHELL;
222         iscsh = NO;
223     }
224
225     if ((p = strrchr (shell, '/')) != 0)
226         avshell = p + 1;
227     else
228         avshell = shell;
229
230     /* if we're forking a csh, we want to slightly muck the args */
231     if (iscsh == UNSET)
232         iscsh = strcmp (avshell, "csh") ? NO : YES;
233
234     /* set permissions */
235
236     if (setgid (pwd->pw_gid) < 0)
237         err (1, "setgid");
238     if (initgroups (user, pwd->pw_gid)) {
239         if (errno == E2BIG)    /* Member of too many groups! */
240             warn("initgroups failed.");
241         else
242             errx(1, "initgroups failed.");
243     }
244
245     if (setuid (pwd->pw_uid) < 0)
246         err (1, "setuid");
247
248     if (pwd->pw_uid != 0 && setuid(0) != -1) {
249       syslog(LOG_ALERT | LOG_AUTH,
250              "Failed to drop privileges for user %s", pwd->pw_name);
251       errx(1, "Sorry");
252     }
253
254     if (!asme) {
255         if (asthem) {
256             char *k = getenv ("KRBTKFILE");
257             char *t = getenv ("TERM");
258
259             environ = malloc (10 * sizeof (char *));
260             if (environ == NULL)
261                 err (1, "malloc");
262             environ[0] = NULL;
263             setenv ("PATH", _PATH_DEFPATH, 1);
264             if (t)
265                 setenv ("TERM", t, 1);
266             if (k)
267                 setenv ("KRBTKFILE", k, 1);
268             if (chdir (pwd->pw_dir) < 0)
269                 errx (1, "no directory");
270         }
271         if (asthem || pwd->pw_uid)
272             setenv ("USER", pwd->pw_name, 1);
273         setenv ("HOME", pwd->pw_dir, 1);
274         setenv ("SHELL", shell, 1);
275     }
276     if (iscsh == YES) {
277         if (fastlogin)
278             *np-- = "-f";
279         if (asme)
280             *np-- = "-m";
281     }
282     if (asthem) {
283         snprintf (avshellbuf, sizeof(avshellbuf),
284                   "-%s", avshell);
285         avshell = avshellbuf;
286     } else if (iscsh == YES) {
287         /* csh strips the first character... */
288         snprintf (avshellbuf, sizeof(avshellbuf),
289                   "_%s", avshell);
290         avshell = avshellbuf;
291     }
292     *np = avshell;
293
294     if (ruid != 0)
295         syslog (LOG_NOTICE | LOG_AUTH, "%s to %s%s",
296                 username, user, ontty ());
297
298     setpriority (PRIO_PROCESS, 0, prio);
299
300     if (k_hasafs ()) {
301         int code;
302
303         if (k_setpag () != 0)
304             warn ("setpag");
305         code = krb_afslog (0, 0);
306         if (code != KSUCCESS && code != KDC_PR_UNKNOWN)
307             warnx ("afsklog: %s", krb_get_err_text (code));
308     }
309     if (destroy_tickets)
310         dest_tkt ();
311     execv (shell, np);
312     warn ("execv(%s)", shell);
313     if (getuid () == 0) {
314         execv (_PATH_BSHELL, np);
315         warn ("execv(%s)", _PATH_BSHELL);
316     }
317     exit (1);
318 }
319
320 static int
321 chshell (char *sh)
322 {
323     char *cp;
324
325     while ((cp = getusershell ()) != NULL)
326         if (!strcmp (cp, sh))
327             return (1);
328     return (0);
329 }
330
331 static char *
332 ontty (void)
333 {
334     char *p;
335     static char buf[MaxPathLen + 4];
336
337     buf[0] = 0;
338     if ((p = ttyname (STDERR_FILENO)) != 0)
339         snprintf (buf, sizeof(buf), " on %s", p);
340     return (buf);
341 }
342
343 static int
344 kerberos (char *username, char *user, char *lrealm, int uid)
345 {
346     KTEXT_ST ticket;
347     AUTH_DAT authdata;
348     struct hostent *hp;
349     int kerno;
350     u_long faddr;
351     char tmp_realm[REALM_SZ], krbtkfile[MaxPathLen];
352     char hostname[MaxHostNameLen], savehost[MaxHostNameLen];
353     int n;
354     int allowed = 0;
355
356     if (lrealm != NULL) {
357         allowed = koktologin (username, lrealm, user) == 0;
358     } else {
359         for (n = 1; !allowed && krb_get_lrealm (tmp_realm, n) == KSUCCESS; ++n)
360             allowed = koktologin (username, tmp_realm, user) == 0;
361         lrealm = tmp_realm;
362     }
363     if (!allowed && !uid) {
364 #ifndef PASSWD_FALLBACK
365         warnx ("not in %s's ACL.", user);
366 #endif
367         return (1);
368     }
369     snprintf (krbtkfile, sizeof(krbtkfile),
370               "%s_%s_to_%s_%u", TKT_ROOT, username, user,
371              (unsigned) getpid ());
372
373     setenv ("KRBTKFILE", krbtkfile, 1);
374     krb_set_tkt_string (krbtkfile);
375     /*
376      * Set real as well as effective ID to 0 for the moment,
377      * to make the kerberos library do the right thing.
378      */
379     if (setuid(0) < 0) {
380         warn("setuid");
381         return (1);
382     }
383
384     /*
385      * Little trick here -- if we are su'ing to root, we need to get a ticket
386      * for "xxx.root", where xxx represents the name of the person su'ing.
387      * Otherwise (non-root case), we need to get a ticket for "yyy.", where
388      * yyy represents the name of the person being su'd to, and the instance
389      * is null 
390      *
391      * We should have a way to set the ticket lifetime, with a system default
392      * for root. 
393      */
394     {
395         char prompt[128];
396         char passw[256];
397
398         snprintf (prompt, sizeof(prompt),
399                   "%s's Password: ",
400                   krb_unparse_name_long ((uid == 0 ? username : user),
401                                          (uid == 0 ? root_inst : ""),
402                                          lrealm));
403         if (des_read_pw_string (passw, sizeof (passw), prompt, 0)) {
404             memset (passw, 0, sizeof (passw));
405             return (1);
406         }
407         if (strlen(passw) == 0)
408             return (1);         /* Empty passwords is not allowed */
409         kerno = krb_get_pw_in_tkt ((uid == 0 ? username : user),
410                                    (uid == 0 ? root_inst : ""), lrealm,
411                                    KRB_TICKET_GRANTING_TICKET,
412                                    lrealm,
413                                    DEFAULT_TKT_LIFE,
414                                    passw);
415         memset (passw, 0, strlen (passw));
416     }
417
418     if (kerno != KSUCCESS) {
419         if (kerno == KDC_PR_UNKNOWN) {
420             warnx ("principal unknown: %s",
421                    krb_unparse_name_long ((uid == 0 ? username : user),
422                                           (uid == 0 ? root_inst : ""),
423                                           lrealm));
424             return (1);
425         }
426         warnx ("unable to su: %s", krb_get_err_text (kerno));
427         syslog (LOG_NOTICE | LOG_AUTH,
428                 "BAD SU: %s to %s%s: %s",
429                 username, user, ontty (), krb_get_err_text (kerno));
430         return (1);
431     }
432     if (chown (krbtkfile, uid, -1) < 0) {
433         warn ("chown");
434         unlink (krbtkfile);
435         return (1);
436     }
437     setpriority (PRIO_PROCESS, 0, -2);
438
439     if (gethostname (hostname, sizeof (hostname)) == -1) {
440         warn ("gethostname");
441         dest_tkt ();
442         return (1);
443     }
444     strlcpy (savehost, krb_get_phost (hostname), sizeof (savehost));
445
446     for (n = 1; krb_get_lrealm (tmp_realm, n) == KSUCCESS; ++n) {
447         kerno = krb_mk_req (&ticket, "rcmd", savehost, tmp_realm, 33);
448         if (kerno == 0)
449             break;
450     }
451
452     if (kerno == KDC_PR_UNKNOWN) {
453         warnx ("Warning: TGT not verified.");
454         syslog (LOG_NOTICE | LOG_AUTH,
455                 "%s to %s%s, TGT not verified (%s); "
456                 "%s.%s not registered?",
457                 username, user, ontty (), krb_get_err_text (kerno),
458                 "rcmd", savehost);
459 #ifdef KLOGIN_PARANOID
460         /*
461          * if the "VERIFY_SERVICE" doesn't exist in the KDC for this host, *
462          * don't allow kerberos login, also log the error condition. 
463          */
464         warnx ("Trying local password!");
465         return (1);
466 #endif
467     } else if (kerno != KSUCCESS) {
468         warnx ("Unable to use TGT: %s", krb_get_err_text (kerno));
469         syslog (LOG_NOTICE | LOG_AUTH, "failed su: %s to %s%s: %s",
470                 username, user, ontty (), krb_get_err_text (kerno));
471         dest_tkt ();
472         return (1);
473     } else {
474         if (!(hp = gethostbyname (hostname))) {
475             warnx ("can't get addr of %s", hostname);
476             dest_tkt ();
477             return (1);
478         }
479         memcpy (&faddr, hp->h_addr, sizeof (faddr));
480
481         if ((kerno = krb_rd_req (&ticket, "rcmd", savehost, faddr,
482                                  &authdata, "")) != KSUCCESS) {
483             warnx ("unable to verify rcmd ticket: %s",
484                    krb_get_err_text (kerno));
485             syslog (LOG_NOTICE | LOG_AUTH,
486                     "failed su: %s to %s%s: %s", username,
487                     user, ontty (), krb_get_err_text (kerno));
488             dest_tkt ();
489             return (1);
490         }
491     }
492     if (!destroy_tickets)
493         fprintf (stderr, "Don't forget to kdestroy before exiting the shell.\n");
494     return (0);
495 }
496
497 static int
498 koktologin (char *name, char *realm, char *toname)
499 {
500     return krb_kuserok (name,
501                         strcmp (toname, "root") == 0 ? root_inst : "",
502                         realm,
503                         toname);
504 }