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