Remove kerberosIV from the build.
[dragonfly.git] / usr.bin / su / su.c
1 /*
2  * Copyright (c) 1988, 1993, 1994
3  *      The Regents of the University of California.  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  * @(#) Copyright (c) 1988, 1993, 1994 The Regents of the University of California.  All rights reserved.
34  * @(#)su.c     8.3 (Berkeley) 4/2/94
35  * $FreeBSD: src/usr.bin/su/su.c,v 1.34.2.4 2002/06/16 21:04:15 nectar Exp $
36  * $DragonFly: src/usr.bin/su/su.c,v 1.3 2003/08/05 07:45:44 asmodai Exp $
37  */
38
39 #include <sys/param.h>
40 #include <sys/time.h>
41 #include <sys/resource.h>
42
43 #include <err.h>
44 #include <errno.h>
45 #include <grp.h>
46 #include <paths.h>
47 #include <pwd.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <syslog.h>
52 #include <unistd.h>
53 #include <libutil.h>
54
55 #ifdef LOGIN_CAP
56 #include <login_cap.h>
57 #endif
58
59 #ifdef  SKEY
60 #include <skey.h>
61 #endif
62
63 #ifdef KERBEROS5
64 #include <krb5.h>
65
66 static long get_su_principal(krb5_context context, const char *target_user,
67     const char *current_user, char **su_principal_name,
68     krb5_principal *su_principal);
69 static long kerberos5(krb5_context context, const char *current_user,
70     const char *target_user, krb5_principal su_principal,
71     const char *pass);
72
73 int use_kerberos5 = 1;
74 #endif
75
76 #ifdef LOGIN_CAP
77 #define LOGIN_CAP_ARG(x) x
78 #else
79 #define LOGIN_CAP_ARG(x)
80 #endif
81 #if defined(KERBEROS5)
82 #define KERBEROS_ARG(x) x
83 #else
84 #define KERBEROS_ARG(x)
85 #endif
86 #define COMMON_ARG(x) x
87 #define ARGSTR  "-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K")
88
89 char   *ontty __P((void));
90 int     chshell __P((char *));
91 static void usage __P((void));
92
93 int
94 main(argc, argv)
95         int argc;
96         char **argv;
97 {
98         extern char **environ;
99         struct passwd *pwd;
100 #ifdef WHEELSU
101         char *targetpass;
102         int iswheelsu;
103 #endif /* WHEELSU */
104         char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np;
105         struct group *gr;
106         uid_t ruid;
107         gid_t gid;
108         int asme, ch, asthem, fastlogin, prio, i;
109         enum { UNSET, YES, NO } iscsh = UNSET;
110 #ifdef LOGIN_CAP
111         login_cap_t *lc;
112         char *class=NULL;
113         int setwhat;
114 #endif
115 #if defined(KERBEROS5)
116         char *k;
117 #endif
118 #ifdef KERBEROS5
119         char *su_principal_name, *ccname;
120         krb5_context context;
121         krb5_principal su_principal;
122 #endif
123         char shellbuf[MAXPATHLEN];
124
125 #ifdef WHEELSU
126         iswheelsu =
127 #endif /* WHEELSU */
128         asme = asthem = fastlogin = 0;
129         user = "root";
130         while((ch = getopt(argc, argv, ARGSTR)) != -1) 
131                 switch((char)ch) {
132 #if defined(KERBEROS5)
133                 case 'K':
134                         use_kerberos5 = 0;
135                         break;
136 #endif
137                 case 'f':
138                         fastlogin = 1;
139                         break;
140                 case '-':
141                 case 'l':
142                         asme = 0;
143                         asthem = 1;
144                         break;
145                 case 'm':
146                         asme = 1;
147                         asthem = 0;
148                         break;
149 #ifdef LOGIN_CAP
150                 case 'c':
151                         class = optarg;
152                         break;
153 #endif
154                 case '?':
155                 default:
156                         usage();
157                 }
158
159         if (optind < argc)
160                 user = argv[optind++];
161
162         if (strlen(user) > MAXLOGNAME - 1) {
163                 (void)fprintf(stderr, "su: username too long.\n");
164                 exit(1);
165         }
166                 
167         if (user == NULL)
168                 usage();
169
170         if ((nargv = malloc (sizeof (char *) * (argc + 4))) == NULL) {
171             errx(1, "malloc failure");
172         }
173
174         nargv[argc + 3] = NULL;
175         for (i = argc; i >= optind; i--)
176             nargv[i + 3] = argv[i];
177         np = &nargv[i + 3];
178
179         argv += optind;
180
181 #if defined(KERBEROS5)
182         k = auth_getval("auth_list");
183         if (k && !strstr(k, "kerberos")) {
184             use_kerberos5 = 0;
185         }
186         su_principal_name = NULL;
187         su_principal = NULL;
188         if (krb5_init_context(&context) != 0)
189                 use_kerberos5 = 0;
190 #endif
191         errno = 0;
192         prio = getpriority(PRIO_PROCESS, 0);
193         if (errno)
194                 prio = 0;
195         (void)setpriority(PRIO_PROCESS, 0, -2);
196         openlog("su", LOG_CONS, 0);
197
198         /* get current login name and shell */
199         ruid = getuid();
200         username = getlogin();
201         if (username == NULL || (pwd = getpwnam(username)) == NULL ||
202             pwd->pw_uid != ruid)
203                 pwd = getpwuid(ruid);
204         if (pwd == NULL)
205                 errx(1, "who are you?");
206         username = strdup(pwd->pw_name);
207         gid = pwd->pw_gid;
208         if (username == NULL)
209                 err(1, NULL);
210         if (asme) {
211                 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
212                         /* copy: pwd memory is recycled */
213                         shell = strncpy(shellbuf,  pwd->pw_shell, sizeof shellbuf);
214                         shellbuf[sizeof shellbuf - 1] = '\0';
215                 } else {
216                         shell = _PATH_BSHELL;
217                         iscsh = NO;
218                 }
219         }
220
221         /* get target login information, default to root */
222         if ((pwd = getpwnam(user)) == NULL) {
223                 errx(1, "unknown login: %s", user);
224         }
225 #ifdef LOGIN_CAP
226         if (class==NULL) {
227                 lc = login_getpwclass(pwd);
228         } else {
229                 if (ruid)
230                         errx(1, "only root may use -c");
231                 lc = login_getclass(class);
232                 if (lc == NULL)
233                         errx(1, "unknown class: %s", class);
234         }
235 #endif
236
237 #ifdef WHEELSU
238         targetpass = strdup(pwd->pw_passwd);
239 #endif /* WHEELSU */
240
241         if (ruid) {
242 #ifdef KERBEROS5
243                 if (use_kerberos5) {
244                         if (get_su_principal(context, user, username,
245                             &su_principal_name, &su_principal) != 0 ||
246                             !krb5_kuserok(context, su_principal, user)) {
247                                 warnx("kerberos5: not in %s's ACL.", user);
248                                 use_kerberos5 = 0;
249                         }
250                 }
251 #endif
252                 {
253                         /*
254                          * Only allow those with pw_gid==0 or those listed in
255                          * group zero to su to root.  If group zero entry is
256                          * missing or empty, then allow anyone to su to root.
257                          * iswheelsu will only be set if the user is EXPLICITLY
258                          * listed in group zero.
259                          */
260                         if (pwd->pw_uid == 0 && (gr = getgrgid((gid_t)0)) &&
261                             gr->gr_mem && *(gr->gr_mem))
262                                 for (g = gr->gr_mem;; ++g) {
263                                         if (!*g) {
264                                                 if (gid == 0)
265                                                         break;
266                                                 else
267                                                         errx(1,
268                              "you are not in the correct group (%s) to su %s.",
269                                                             gr->gr_name,
270                                                             user);
271                                         }
272                                         if (strcmp(username, *g) == 0) {
273 #ifdef WHEELSU
274                                                 iswheelsu = 1;
275 #endif /* WHEELSU */
276                                                 break;
277                                         }
278                                 }
279                 }
280                 /* if target requires a password, verify it */
281                 if (*pwd->pw_passwd) {
282 #ifdef  SKEY
283 #ifdef WHEELSU
284                         if (iswheelsu) {
285                                 pwd = getpwnam(username);
286                         }
287 #endif /* WHEELSU */
288                         p = skey_getpass("Password:", pwd, 1);
289                         if (!(!strcmp(pwd->pw_passwd, skey_crypt(p, pwd->pw_passwd, pwd, 1))
290 #ifdef WHEELSU
291                               || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass)))
292 #endif /* WHEELSU */
293                               )) {
294 #else
295                         p = getpass("Password:");
296                         if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
297 #endif
298 #ifdef KERBEROS5
299                                 if (use_kerberos5 && kerberos5(context,
300                                     username, user, su_principal, p) == 0)
301                                         goto authok;
302 #endif
303                                 fprintf(stderr, "Sorry\n");
304                                 syslog(LOG_AUTH|LOG_WARNING,
305                                     "BAD SU %s to %s%s", username, user,
306                                     ontty());
307                                 exit(1);
308                         }
309 #if defined(KERBEROS5)
310                 authok:
311 #endif
312 #ifdef WHEELSU
313                         if (iswheelsu) {
314                                 pwd = getpwnam(user);
315                         }
316 #endif /* WHEELSU */
317                 }
318                 if (pwd->pw_expire && time(NULL) >= pwd->pw_expire) {
319                         fprintf(stderr, "Sorry - account expired\n");
320                         syslog(LOG_AUTH|LOG_WARNING,
321                                 "BAD SU %s to %s%s", username,
322                                 user, ontty());
323                         exit(1);
324                 }
325         }
326
327         if (asme) {
328                 /* if asme and non-standard target shell, must be root */
329                 if (!chshell(pwd->pw_shell) && ruid)
330                         errx(1, "permission denied (shell).");
331         } else if (pwd->pw_shell && *pwd->pw_shell) {
332                 shell = pwd->pw_shell;
333                 iscsh = UNSET;
334         } else {
335                 shell = _PATH_BSHELL;
336                 iscsh = NO;
337         }
338
339         /* if we're forking a csh, we want to slightly muck the args */
340         if (iscsh == UNSET) {
341                 p = strrchr(shell, '/');
342                 if (p)
343                         ++p;
344                 else
345                         p = shell;
346                 if ((iscsh = strcmp(p, "csh") ? NO : YES) == NO)
347                     iscsh = strcmp(p, "tcsh") ? NO : YES;
348         }
349
350         (void)setpriority(PRIO_PROCESS, 0, prio);
351
352 #ifdef LOGIN_CAP
353         /* Set everything now except the environment & umask */
354         setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
355         /*
356          * Don't touch resource/priority settings if -m has been
357          * used or -l and -c hasn't, and we're not su'ing to root.
358          */
359         if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
360                 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
361         if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
362                 err(1, "setusercontext");
363 #else
364         /* set permissions */
365         if (setgid(pwd->pw_gid) < 0)
366                 err(1, "setgid");
367         if (initgroups(user, pwd->pw_gid))
368                 errx(1, "initgroups failed");
369         if (setuid(pwd->pw_uid) < 0)
370                 err(1, "setuid");
371 #endif
372
373         if (!asme) {
374                 if (asthem) {
375                         p = getenv("TERM");
376 #ifdef KERBEROS5
377                         ccname = getenv("KRB5CCNAME");
378 #endif
379                         if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
380                                 errx(1, "calloc");
381                         cleanenv[0] = NULL;
382                         environ = cleanenv;
383 #ifdef LOGIN_CAP
384                         /* set the su'd user's environment & umask */
385                         setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV);
386 #else
387                         (void)setenv("PATH", _PATH_DEFPATH, 1);
388 #endif
389                         if (p)
390                                 (void)setenv("TERM", p, 1);
391 #ifdef KERBEROS5
392                         if (ccname)
393                                 (void)setenv("KRB5CCNAME", ccname, 1);
394 #endif
395                         if (chdir(pwd->pw_dir) < 0)
396                                 errx(1, "no directory");
397                 }
398                 if (asthem || pwd->pw_uid)
399                         (void)setenv("USER", pwd->pw_name, 1);
400                 (void)setenv("HOME", pwd->pw_dir, 1);
401                 (void)setenv("SHELL", shell, 1);
402         }
403         if (iscsh == YES) {
404                 if (fastlogin)
405                         *np-- = "-f";
406                 if (asme)
407                         *np-- = "-m";
408         }
409
410         /* csh strips the first character... */
411         *np = asthem ? "-su" : iscsh == YES ? "_su" : "su";
412
413         if (ruid != 0)
414                 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s",
415                     username, user, ontty());
416
417 #ifdef LOGIN_CAP
418         login_close(lc);
419 #endif
420
421         execv(shell, np);
422         err(1, "%s", shell);
423 }
424
425 static void
426 usage()
427 {
428         (void)fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n",
429             KERBEROS_ARG("K") COMMON_ARG("flm"),
430 #ifdef LOGIN_CAP
431             "[-c class] "
432 #else
433             ""
434 #endif
435             );
436         exit(1);
437 }
438
439 int
440 chshell(sh)
441         char *sh;
442 {
443         int  r = 0;
444         char *cp;
445
446         setusershell();
447         while (!r && (cp = getusershell()) != NULL)
448                 r = strcmp(cp, sh) == 0;
449         endusershell();
450         return r;
451 }
452
453 char *
454 ontty()
455 {
456         char *p;
457         static char buf[MAXPATHLEN + 4];
458
459         buf[0] = 0;
460         p = ttyname(STDERR_FILENO);
461         if (p)
462                 snprintf(buf, sizeof(buf), " on %s", p);
463         return (buf);
464 }
465
466 #ifdef KERBEROS5
467 const char superuser[] = "root";
468
469 /* Authenticate using Kerberos 5.
470  *   context           -- An initialized krb5_context.
471  *   current_user      -- The current username.
472  *   target_user       -- The target account name.
473  *   su_principal      -- The target krb5_principal.
474  *   pass              -- The user's password.
475  * Note that a valid keytab in the default location with a host entry
476  * must be available.
477  * Returns 0 if authentication was successful, or a com_err error code if
478  * it was not.
479  */
480 static long
481 kerberos5(krb5_context context, const char *current_user,
482     const char *target_user, krb5_principal su_principal,
483     const char *pass)
484 {
485         krb5_creds       creds;
486         krb5_get_init_creds_opt gic_opt;
487         krb5_verify_init_creds_opt vic_opt;
488         long             rv;
489
490         krb5_get_init_creds_opt_init(&gic_opt);
491         krb5_verify_init_creds_opt_init(&vic_opt);
492         rv = krb5_get_init_creds_password(context, &creds, su_principal,
493             pass, NULL, NULL, 0, NULL, &gic_opt);
494         if (rv != 0) {
495                 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
496                     current_user, target_user, ontty(),
497                     krb5_get_err_text(context, rv));
498                 return (rv);
499         }
500         krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
501         rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
502             &vic_opt);
503         krb5_free_cred_contents(context, &creds);
504         if (rv != 0) {
505                 syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s to %s%s: %s",
506                     current_user, target_user, ontty(),
507                     krb5_get_err_text(context, rv));
508                 return (rv);
509         }
510         return (0);
511 }
512
513 /* Determine the target principal given the current user and the target user.
514  *   context           -- An initialized krb5_context.
515  *   target_user       -- The target username.
516  *   current_user      -- The current username.
517  *   su_principal_name -- (out) The target principal name.
518  *   su_principal      -- (out) The target krb5_principal.
519  *
520  * When target_user is `root', the su_principal will be a `root
521  * instance', e.g. `luser/root@REA.LM'.  Otherwise, the su_principal
522  * will simply be the current user's default principal name.  Note that
523  * in any case, if KRB5CCNAME is set and a credentials cache exists, the
524  * principal name found there will be the `starting point', rather than
525  * the current_user parameter.
526  *
527  * Returns 0 for success, or a com_err error code on failure.
528  */
529 static long
530 get_su_principal(krb5_context context, const char *target_user,
531     const char *current_user, char **su_principal_name,
532     krb5_principal *su_principal)
533 {
534         krb5_principal   default_principal;
535         krb5_ccache      ccache;
536         char            *principal_name, *ccname, *p;
537         long             rv;
538         uid_t            euid, ruid;
539
540         *su_principal = NULL;
541         default_principal = NULL;
542         /* Lower privs while messing about with the credentials
543          * cache.
544          */
545         ruid = getuid();
546         euid = geteuid();
547         rv = seteuid(getuid());
548         if (rv != 0)
549                 return (errno);
550         p = getenv("KRB5CCNAME");
551         if (p != NULL)
552                 ccname = strdup(p);
553         else
554                 (void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT,
555                     (unsigned long)ruid);
556         if (ccname == NULL)
557                 return (errno);
558         rv = krb5_cc_resolve(context, ccname, &ccache);
559         free(ccname);
560         if (rv == 0) {
561                 rv = krb5_cc_get_principal(context, ccache,
562                     &default_principal);
563                 krb5_cc_close(context, ccache);
564                 if (rv != 0)
565                         default_principal = NULL; /* just to be safe */
566         }
567         rv = seteuid(euid);
568         if (rv != 0)
569                 return (errno);
570         if (default_principal == NULL) {
571                 rv = krb5_make_principal(context, &default_principal, NULL,
572                     current_user, NULL);
573                 if (rv != 0) {
574                         warnx("Could not determine default principal name.");
575                         return (rv);
576                 }
577         }
578         /* Now that we have some principal, if the target account is
579          * `root', then transform it into a `root' instance, e.g.
580          * `user@REA.LM' -> `user/root@REA.LM'.
581          */
582         rv = krb5_unparse_name(context, default_principal, &principal_name);
583         krb5_free_principal(context, default_principal);
584         if (rv != 0) {
585                 warnx("krb5_unparse_name: %s", krb5_get_err_text(context, rv));
586                 return (rv);
587         }
588         if (strcmp(target_user, superuser) == 0) {
589                 p = strrchr(principal_name, '@');
590                 if (p == NULL) {
591                         warnx("malformed principal name `%s'", principal_name);
592                         free(principal_name);
593                         return (rv);
594                 }
595                 *p++ = '\0';
596                 (void)asprintf(su_principal_name, "%s/%s@%s", principal_name,
597                     superuser, p);
598                 free(principal_name);
599         } else 
600                 *su_principal_name = principal_name;
601         if (*su_principal_name == NULL)
602                 return errno;
603         rv = krb5_parse_name(context, *su_principal_name, &default_principal);
604         if (rv != 0) {
605                 warnx("krb5_parse_name `%s': %s", *su_principal_name,
606                     krb5_get_err_text(context, rv));
607                 free(*su_principal_name);
608                 return (rv);
609         }
610         *su_principal = default_principal;
611         return 0;
612 }
613
614 #endif