1 /* $NetBSD: su.c,v 1.72 2015/06/16 22:54:11 christos Exp $ */
4 * Copyright (c) 1988 The Regents of the University of California.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/cdefs.h>
34 __COPYRIGHT("@(#) Copyright (c) 1988\
35 The Regents of the University of California. All rights reserved.");
40 static char sccsid
[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/
42 __RCSID("$NetBSD: su.c,v 1.72 2015/06/16 22:54:11 christos Exp $");
46 #include <sys/param.h>
48 #include <sys/resource.h>
67 #include <login_cap.h>
74 #ifdef ALLOW_GROUP_CHANGE
80 #define ARGSTRX "-Kdflm"
81 static int kerberos5(char *, const char *, uid_t
);
84 #define ARGSTRX "-dflm"
88 #define SU_GROUP "wheel"
91 #define GROUP_PASSWORD "Group Password:"
94 #define ARGSTR ARGSTRX "c:"
96 #define ARGSTR ARGSTRX
99 static int check_ingroup(int, const char *, const char *, int);
102 main(int argc
, char **argv
)
104 extern char **environ
;
111 int asme
, ch
, asthem
, fastlogin
, prio
, gohome
;
112 enum { UNSET
, YES
, NO
} iscsh
= UNSET
;
113 const char *user
, *shell
, *avshell
;
114 char *username
, **np
;
119 char shellbuf
[MAXPATHLEN
], avshellbuf
[MAXPATHLEN
];
120 time_t pw_warntime
= _PASSWORD_WARNDAYS
* SECSPERDAY
;
124 #ifdef ALLOW_GROUP_CHANGE
128 (void)setprogname(argv
[0]);
129 asme
= asthem
= fastlogin
= 0;
131 shell
= class = NULL
;
132 while ((ch
= getopt(argc
, argv
, ARGSTR
)) != -1)
163 (void)fprintf(stderr
,
164 #ifdef ALLOW_GROUP_CHANGE
165 "usage: %s [%s] [login[:group] [shell arguments]]\n",
167 "usage: %s [%s] [login [shell arguments]]\n",
169 getprogname(), ARGSTR
);
174 /* Lower the priority so su runs faster */
176 prio
= getpriority(PRIO_PROCESS
, 0);
180 (void)setpriority(PRIO_PROCESS
, 0, -2);
181 openlog("su", 0, LOG_AUTH
);
183 /* get current login name and shell */
185 username
= getlogin();
186 if (username
== NULL
|| (pwd
= getpwnam(username
)) == NULL
||
188 pwd
= getpwuid(ruid
);
190 errx(EXIT_FAILURE
, "who are you?");
191 username
= estrdup(pwd
->pw_name
);
193 userpass
= estrdup(pwd
->pw_passwd
);
197 if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
198 (void)estrlcpy(shellbuf
, pwd
->pw_shell
, sizeof(shellbuf
));
201 shell
= _PATH_BSHELL
;
205 /* get target login information, default to root */
206 user
= *argv
? *argv
: "root";
207 np
= *argv
? argv
: argv
- 1;
209 #ifdef ALLOW_GROUP_CHANGE
210 if ((p
= strchr(user
, ':')) != NULL
) {
217 #ifdef ALLOW_EMPTY_USER
218 if (user
[0] == '\0' && gname
!= NULL
)
222 if ((pwd
= getpwnam(user
)) == NULL
)
223 errx(EXIT_FAILURE
, "unknown login `%s'", user
);
226 /* force the usage of specified class */
229 errx(EXIT_FAILURE
, "Only root may use -c");
231 pwd
->pw_class
= class;
233 if ((lc
= login_getclass(pwd
->pw_class
)) == NULL
)
234 errx(EXIT_FAILURE
, "Unknown class %s", pwd
->pw_class
);
236 pw_warntime
= (time_t)login_getcaptime(lc
, "password-warn",
237 _PASSWORD_WARNDAYS
* SECSPERDAY
,
238 _PASSWORD_WARNDAYS
* SECSPERDAY
);
243 && (!use_kerberos
|| kerberos5(username
, user
, pwd
->pw_uid
))
246 char *pass
= pwd
->pw_passwd
;
247 int ok
= pwd
->pw_uid
!= 0;
251 * Allow those in group rootauth to su to root, by supplying
252 * their own password.
255 if ((ok
= check_ingroup(-1, SU_ROOTAUTH
, username
, 0))) {
262 * Only allow those in group SU_GROUP to su to root,
263 * but only if that group has any members.
264 * If SU_GROUP has no members, allow anyone to su root
267 ok
= check_ingroup(-1, SU_GROUP
, username
, 1);
270 "you are not listed in the correct secondary group (%s) to su %s.",
272 /* if target requires a password, verify it */
273 if (*pass
&& pwd
->pw_uid
!= ruid
) { /* XXX - OK? */
274 p
= getpass("Password:");
276 if (strcasecmp(p
, "s/key") == 0) {
277 if (skey_haskey(user
))
279 "Sorry, you have no s/key.");
281 if (skey_authenticate(user
)) {
288 if (strcmp(pass
, crypt(p
, pass
)) != 0) {
292 (void)fprintf(stderr
, "Sorry\n");
294 "BAD SU %s to %s%s", username
,
295 pwd
->pw_name
, ontty());
302 /* if asme and non-standard target shell, must be root */
303 if (chshell(pwd
->pw_shell
) == 0 && ruid
)
304 errx(EXIT_FAILURE
, "permission denied (shell).");
305 } else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
306 shell
= pwd
->pw_shell
;
309 shell
= _PATH_BSHELL
;
313 if ((p
= strrchr(shell
, '/')) != NULL
)
318 /* if we're forking a csh, we want to slightly muck the args */
320 iscsh
= strstr(avshell
, "csh") ? YES
: NO
;
322 /* set permissions */
324 # ifdef ALLOW_GROUP_CHANGE
325 /* if we aren't changing users, keep the current group members */
326 if (ruid
!= pwd
->pw_uid
&&
327 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) == -1)
328 err(EXIT_FAILURE
, "setting user context");
330 addgroup(lc
, gname
, pwd
, ruid
, GROUP_PASSWORD
);
332 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
333 (u_int
)(asthem
? (LOGIN_SETPRIORITY
| LOGIN_SETUMASK
) : 0) |
334 LOGIN_SETRESOURCES
| LOGIN_SETUSER
) == -1)
335 err(EXIT_FAILURE
, "setting user context");
337 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
338 (u_int
)(asthem
? (LOGIN_SETPRIORITY
| LOGIN_SETUMASK
) : 0) |
339 LOGIN_SETRESOURCES
| LOGIN_SETGROUP
| LOGIN_SETUSER
) == -1)
340 err(EXIT_FAILURE
, "setting user context");
343 if (setgid(pwd
->pw_gid
) == -1)
344 err(EXIT_FAILURE
, "setgid");
345 /* if we aren't changing users, keep the current group members */
346 if (ruid
!= pwd
->pw_uid
&& initgroups(user
, pwd
->pw_gid
) != 0)
347 errx(EXIT_FAILURE
, "initgroups failed");
348 # ifdef ALLOW_GROUP_CHANGE
349 addgroup(/*EMPTY*/, gname
, pwd
, ruid
, GROUP_PASSWORD
);
351 if (setuid(pwd
->pw_uid
) == -1)
352 err(EXIT_FAILURE
, "setuid");
358 /* Create an empty environment */
359 environ
= emalloc(sizeof(char *));
362 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
363 LOGIN_SETPATH
) == -1)
364 err(EXIT_FAILURE
, "setting user context");
366 (void)setenv("PATH", _PATH_DEFPATH
, 1);
369 (void)setenv("TERM", p
, 1);
370 if (gohome
&& chdir(pwd
->pw_dir
) == -1)
371 errx(EXIT_FAILURE
, "no directory");
374 if (asthem
|| pwd
->pw_uid
) {
375 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
376 (void)setenv("USER", pwd
->pw_name
, 1);
378 (void)setenv("HOME", pwd
->pw_dir
, 1);
379 (void)setenv("SHELL", shell
, 1);
381 (void)setenv("SU_FROM", username
, 1);
385 *np
-- = __UNCONST("-f");
387 *np
-- = __UNCONST("-m");
390 (void)unsetenv("ENV");
395 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
396 avshell
= avshellbuf
;
397 } else if (iscsh
== YES
) {
398 /* csh strips the first character... */
400 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
401 avshell
= avshellbuf
;
403 *np
= __UNCONST(avshell
);
406 if (pwd
->pw_change
|| pwd
->pw_expire
)
407 (void)gettimeofday(&tp
, NULL
);
408 if (pwd
->pw_change
) {
409 if (tp
.tv_sec
>= pwd
->pw_change
) {
410 (void)printf("%s -- %s's password has expired.\n",
411 (ruid
? "Sorry" : "Note"), user
);
414 } else if (pwd
->pw_change
- tp
.tv_sec
< pw_warntime
)
415 (void)printf("Warning: %s's password expires on %s",
416 user
, ctime(&pwd
->pw_change
));
418 if (pwd
->pw_expire
) {
419 if (tp
.tv_sec
>= pwd
->pw_expire
) {
420 (void)printf("%s -- %s's account has expired.\n",
421 (ruid
? "Sorry" : "Note"), user
);
424 } else if (pwd
->pw_expire
- tp
.tv_sec
<
425 _PASSWORD_WARNDAYS
* SECSPERDAY
)
426 (void)printf("Warning: %s's account expires on %s",
427 user
, ctime(&pwd
->pw_expire
));
431 syslog(LOG_NOTICE
, "%s to %s%s",
432 username
, pwd
->pw_name
, ontty());
434 /* Raise our priority back to what we had before */
435 (void)setpriority(PRIO_PROCESS
, 0, prio
);
437 (void)execv(shell
, np
);
438 err(EXIT_FAILURE
, "%s", shell
);
445 kerberos5(char *username
, const char *user
, uid_t uid
)
448 krb5_context context
;
449 krb5_principal princ
= NULL
;
450 krb5_ccache ccache
, ccache2
;
452 const char *filename
;
454 ret
= krb5_init_context(&context
);
458 if (strcmp(user
, "root") == 0)
459 ret
= krb5_make_principal(context
, &princ
,
460 NULL
, username
, "root", NULL
);
462 ret
= krb5_make_principal(context
, &princ
,
466 if (!krb5_kuserok(context
, princ
, user
) && !uid
) {
467 warnx("kerberos5: not in %s's ACL.", user
);
470 ret
= krb5_cc_new_unique(context
, krb5_mcc_ops
.prefix
, NULL
, &ccache
);
473 ret
= krb5_verify_user_lrealm(context
, princ
, ccache
, NULL
, TRUE
,
476 krb5_cc_destroy(context
, ccache
);
478 case KRB5_LIBOS_PWDINTR
:
480 case KRB5KRB_AP_ERR_BAD_INTEGRITY
:
481 case KRB5KRB_AP_ERR_MODIFIED
:
482 krb5_warnx(context
, "Password incorrect");
485 krb5_warn(context
, ret
, "krb5_verify_user");
490 ret
= krb5_cc_new_unique(context
, krb5_mcc_ops
.prefix
, NULL
, &ccache2
);
492 krb5_cc_destroy(context
, ccache
);
495 ret
= krb5_cc_copy_cache(context
, ccache
, ccache2
);
497 krb5_cc_destroy(context
, ccache
);
498 krb5_cc_destroy(context
, ccache2
);
502 filename
= krb5_cc_get_name(context
, ccache2
);
503 (void)easprintf(&cc_name
, "%s:%s", krb5_cc_get_type(context
, ccache2
),
505 if (chown(filename
, uid
, NOGROUP
) == -1) {
506 warn("chown %s", filename
);
508 krb5_cc_destroy(context
, ccache
);
509 krb5_cc_destroy(context
, ccache2
);
513 (void)setenv("KRB5CCNAME", cc_name
, 1);
515 krb5_cc_close(context
, ccache2
);
516 krb5_cc_destroy(context
, ccache
);
521 krb5_free_principal(context
, princ
);
522 krb5_free_context(context
);
525 #endif /* KERBEROS5 */
528 check_ingroup(int gid
, const char *gname
, const char *user
, int ifempty
)
532 #ifdef SU_INDIRECT_GROUP
540 gr
= getgrgid((gid_t
) gid
);
542 gr
= getgrnam(gname
);
545 * XXX we are relying on the fact that we only set ifempty when
546 * calling to check for SU_GROUP and that is the only time a
547 * missing group is acceptable.
551 if (!*gr
->gr_mem
) /* empty */
555 * Ok, first see if user is in gr_mem
557 for (g
= gr
->gr_mem
; *g
; ++g
) {
558 if (strcmp(*g
, user
) == 0)
560 #ifdef SU_INDIRECT_GROUP
561 ++n
; /* count them */
564 #ifdef SU_INDIRECT_GROUP
567 * Now we need to duplicate the gr_mem list, and recurse for
568 * each member to see if it is a group, and if so whether user is
571 gr_mem
= emalloc((n
+ 1) * sizeof (char *));
572 for (g
= gr
->gr_mem
, i
= 0; *g
; ++g
) {
573 gr_mem
[i
] = estrdup(*g
);
578 for (g
= gr_mem
; ok
== 0 && *g
; ++g
) {
580 * If we get this far we don't accept empty/missing groups.
582 ok
= check_ingroup(-1, *g
, user
, 0);
584 for (g
= gr_mem
; *g
; ++g
) {