1 /* $NetBSD: su.c,v 1.67 2008/04/05 15:59:39 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.67 2008/04/05 15:59:39 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
;
115 char *userpass
, *class;
116 char shellbuf
[MAXPATHLEN
], avshellbuf
[MAXPATHLEN
];
117 time_t pw_warntime
= _PASSWORD_WARNDAYS
* SECSPERDAY
;
121 #ifdef ALLOW_GROUP_CHANGE
125 (void)setprogname(argv
[0]);
126 asme
= asthem
= fastlogin
= 0;
128 shell
= class = NULL
;
129 while ((ch
= getopt(argc
, argv
, ARGSTR
)) != -1)
160 (void)fprintf(stderr
,
161 #ifdef ALLOW_GROUP_CHANGE
162 "usage: %s [%s] [login[:group] [shell arguments]]\n",
164 "usage: %s [%s] [login [shell arguments]]\n",
166 getprogname(), ARGSTR
);
171 /* Lower the priority so su runs faster */
173 prio
= getpriority(PRIO_PROCESS
, 0);
177 (void)setpriority(PRIO_PROCESS
, 0, -2);
178 openlog("su", 0, LOG_AUTH
);
180 /* get current login name and shell */
182 username
= getlogin();
183 if (username
== NULL
|| (pwd
= getpwnam(username
)) == NULL
||
185 pwd
= getpwuid(ruid
);
187 errx(EXIT_FAILURE
, "who are you?");
188 username
= estrdup(pwd
->pw_name
);
189 userpass
= estrdup(pwd
->pw_passwd
);
192 if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
193 (void)estrlcpy(shellbuf
, pwd
->pw_shell
, sizeof(shellbuf
));
196 shell
= _PATH_BSHELL
;
200 /* get target login information, default to root */
201 user
= *argv
? *argv
: "root";
202 np
= *argv
? argv
: argv
- 1;
204 #ifdef ALLOW_GROUP_CHANGE
205 if ((p
= strchr(user
, ':')) != NULL
) {
212 #ifdef ALLOW_EMPTY_USER
213 if (user
[0] == '\0' && gname
!= NULL
)
217 if ((pwd
= getpwnam(user
)) == NULL
)
218 errx(EXIT_FAILURE
, "unknown login `%s'", user
);
221 /* force the usage of specified class */
224 errx(EXIT_FAILURE
, "Only root may use -c");
226 pwd
->pw_class
= class;
228 if ((lc
= login_getclass(pwd
->pw_class
)) == NULL
)
229 errx(EXIT_FAILURE
, "Unknown class %s\n", pwd
->pw_class
);
231 pw_warntime
= (time_t)login_getcaptime(lc
, "password-warn",
232 _PASSWORD_WARNDAYS
* SECSPERDAY
,
233 _PASSWORD_WARNDAYS
* SECSPERDAY
);
238 && (!use_kerberos
|| kerberos5(username
, user
, pwd
->pw_uid
))
241 char *pass
= pwd
->pw_passwd
;
242 int ok
= pwd
->pw_uid
!= 0;
246 * Allow those in group rootauth to su to root, by supplying
247 * their own password.
250 if ((ok
= check_ingroup(-1, SU_ROOTAUTH
, username
, 0))) {
257 * Only allow those in group SU_GROUP to su to root,
258 * but only if that group has any members.
259 * If SU_GROUP has no members, allow anyone to su root
262 ok
= check_ingroup(-1, SU_GROUP
, username
, 1);
265 "you are not listed in the correct secondary group (%s) to su %s.",
267 /* if target requires a password, verify it */
268 if (*pass
&& pwd
->pw_uid
!= ruid
) { /* XXX - OK? */
269 p
= getpass("Password:");
271 if (strcasecmp(p
, "s/key") == 0) {
272 if (skey_haskey(user
))
274 "Sorry, you have no s/key.");
276 if (skey_authenticate(user
)) {
283 if (strcmp(pass
, crypt(p
, pass
)) != 0) {
287 (void)fprintf(stderr
, "Sorry\n");
289 "BAD SU %s to %s%s", username
,
290 pwd
->pw_name
, ontty());
297 /* if asme and non-standard target shell, must be root */
298 if (chshell(pwd
->pw_shell
) == 0 && ruid
)
299 errx(EXIT_FAILURE
, "permission denied (shell).");
300 } else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
301 shell
= pwd
->pw_shell
;
304 shell
= _PATH_BSHELL
;
308 if ((p
= strrchr(shell
, '/')) != NULL
)
313 /* if we're forking a csh, we want to slightly muck the args */
315 iscsh
= strstr(avshell
, "csh") ? YES
: NO
;
317 /* set permissions */
319 # ifdef ALLOW_GROUP_CHANGE
320 /* if we aren't changing users, keep the current group members */
321 if (ruid
!= pwd
->pw_uid
&&
322 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) == -1)
323 err(EXIT_FAILURE
, "setting user context");
325 addgroup(lc
, gname
, pwd
, ruid
, GROUP_PASSWORD
);
327 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
328 (u_int
)(asthem
? (LOGIN_SETPRIORITY
| LOGIN_SETUMASK
) : 0) |
329 LOGIN_SETRESOURCES
| LOGIN_SETUSER
) == -1)
330 err(EXIT_FAILURE
, "setting user context");
332 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
333 (u_int
)(asthem
? (LOGIN_SETPRIORITY
| LOGIN_SETUMASK
) : 0) |
334 LOGIN_SETRESOURCES
| LOGIN_SETGROUP
| LOGIN_SETUSER
) == -1)
335 err(EXIT_FAILURE
, "setting user context");
338 if (setgid(pwd
->pw_gid
) == -1)
339 err(EXIT_FAILURE
, "setgid");
340 /* if we aren't changing users, keep the current group members */
341 if (ruid
!= pwd
->pw_uid
&& initgroups(user
, pwd
->pw_gid
) != 0)
342 errx(EXIT_FAILURE
, "initgroups failed");
343 # ifdef ALLOW_GROUP_CHANGE
344 addgroup(/*EMPTY*/, gname
, pwd
, ruid
, GROUP_PASSWORD
);
346 if (setuid(pwd
->pw_uid
) == -1)
347 err(EXIT_FAILURE
, "setuid");
353 /* Create an empty environment */
354 environ
= emalloc(sizeof(char *));
357 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
358 LOGIN_SETPATH
) == -1)
359 err(EXIT_FAILURE
, "setting user context");
361 (void)setenv("PATH", _PATH_DEFPATH
, 1);
364 (void)setenv("TERM", p
, 1);
365 if (gohome
&& chdir(pwd
->pw_dir
) == -1)
366 errx(EXIT_FAILURE
, "no directory");
369 if (asthem
|| pwd
->pw_uid
) {
370 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
371 (void)setenv("USER", pwd
->pw_name
, 1);
373 (void)setenv("HOME", pwd
->pw_dir
, 1);
374 (void)setenv("SHELL", shell
, 1);
376 (void)setenv("SU_FROM", username
, 1);
380 *np
-- = __UNCONST("-f");
382 *np
-- = __UNCONST("-m");
385 (void)unsetenv("ENV");
390 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
391 avshell
= avshellbuf
;
392 } else if (iscsh
== YES
) {
393 /* csh strips the first character... */
395 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
396 avshell
= avshellbuf
;
398 *np
= __UNCONST(avshell
);
401 if (pwd
->pw_change
|| pwd
->pw_expire
)
402 (void)gettimeofday(&tp
, (struct timezone
*)NULL
);
403 if (pwd
->pw_change
) {
404 if (tp
.tv_sec
>= pwd
->pw_change
) {
405 (void)printf("%s -- %s's password has expired.\n",
406 (ruid
? "Sorry" : "Note"), user
);
409 } else if (pwd
->pw_change
- tp
.tv_sec
< pw_warntime
)
410 (void)printf("Warning: %s's password expires on %s",
411 user
, ctime(&pwd
->pw_change
));
413 if (pwd
->pw_expire
) {
414 if (tp
.tv_sec
>= pwd
->pw_expire
) {
415 (void)printf("%s -- %s's account has expired.\n",
416 (ruid
? "Sorry" : "Note"), user
);
419 } else if (pwd
->pw_expire
- tp
.tv_sec
<
420 _PASSWORD_WARNDAYS
* SECSPERDAY
)
421 (void)printf("Warning: %s's account expires on %s",
422 user
, ctime(&pwd
->pw_expire
));
426 syslog(LOG_NOTICE
, "%s to %s%s",
427 username
, pwd
->pw_name
, ontty());
429 /* Raise our priority back to what we had before */
430 (void)setpriority(PRIO_PROCESS
, 0, prio
);
432 (void)execv(shell
, np
);
433 err(EXIT_FAILURE
, "%s", shell
);
440 kerberos5(char *username
, const char *user
, uid_t uid
)
443 krb5_context context
;
444 krb5_principal princ
= NULL
;
445 krb5_ccache ccache
, ccache2
;
447 const char *filename
;
449 ret
= krb5_init_context(&context
);
453 if (strcmp(user
, "root") == 0)
454 ret
= krb5_make_principal(context
, &princ
,
455 NULL
, username
, "root", NULL
);
457 ret
= krb5_make_principal(context
, &princ
,
461 if (!krb5_kuserok(context
, princ
, user
) && !uid
) {
462 warnx("kerberos5: not in %s's ACL.", user
);
465 ret
= krb5_cc_gen_new(context
, &krb5_mcc_ops
, &ccache
);
468 ret
= krb5_verify_user_lrealm(context
, princ
, ccache
, NULL
, TRUE
,
471 krb5_cc_destroy(context
, ccache
);
473 case KRB5_LIBOS_PWDINTR
:
475 case KRB5KRB_AP_ERR_BAD_INTEGRITY
:
476 case KRB5KRB_AP_ERR_MODIFIED
:
477 krb5_warnx(context
, "Password incorrect");
480 krb5_warn(context
, ret
, "krb5_verify_user");
485 ret
= krb5_cc_gen_new(context
, &krb5_fcc_ops
, &ccache2
);
487 krb5_cc_destroy(context
, ccache
);
490 ret
= krb5_cc_copy_cache(context
, ccache
, ccache2
);
492 krb5_cc_destroy(context
, ccache
);
493 krb5_cc_destroy(context
, ccache2
);
497 filename
= krb5_cc_get_name(context
, ccache2
);
498 (void)easprintf(&cc_name
, "%s:%s", krb5_cc_get_type(context
, ccache2
),
500 if (chown(filename
, uid
, NOGROUP
) == -1) {
501 warn("chown %s", filename
);
503 krb5_cc_destroy(context
, ccache
);
504 krb5_cc_destroy(context
, ccache2
);
508 (void)setenv("KRB5CCNAME", cc_name
, 1);
510 krb5_cc_close(context
, ccache2
);
511 krb5_cc_destroy(context
, ccache
);
516 krb5_free_principal(context
, princ
);
517 krb5_free_context(context
);
520 #endif /* KERBEROS5 */
523 check_ingroup(int gid
, const char *gname
, const char *user
, int ifempty
)
527 #ifdef SU_INDIRECT_GROUP
535 gr
= getgrgid((gid_t
) gid
);
537 gr
= getgrnam(gname
);
540 * XXX we are relying on the fact that we only set ifempty when
541 * calling to check for SU_GROUP and that is the only time a
542 * missing group is acceptable.
546 if (!*gr
->gr_mem
) /* empty */
550 * Ok, first see if user is in gr_mem
552 for (g
= gr
->gr_mem
; *g
; ++g
) {
553 if (strcmp(*g
, user
) == 0)
555 #ifdef SU_INDIRECT_GROUP
556 ++n
; /* count them */
559 #ifdef SU_INDIRECT_GROUP
562 * Now we need to duplicate the gr_mem list, and recurse for
563 * each member to see if it is a group, and if so whether user is
566 gr_mem
= emalloc((n
+ 1) * sizeof (char *));
567 for (g
= gr
->gr_mem
, i
= 0; *g
; ++g
) {
568 gr_mem
[i
] = estrdup(*g
);
573 for (g
= gr_mem
; ok
== 0 && *g
; ++g
) {
575 * If we get this far we don't accept empty/missing groups.
577 ok
= check_ingroup(-1, *g
, user
, 0);
579 for (g
= gr_mem
; *g
; ++g
) {