home *** CD-ROM | disk | FTP | other *** search
- /* su.c - proper su for horrible value-added SCO Unix. */
-
- /* By Eamonn McManus, Datacode Communications Ltd <em@dce.ie>, August 1990.
- This program is not copyrighted.
-
- Usage: su [-s shell] [-] [user [shell-args]].
-
- Sets user id to the named user (default root) and execs the named shell
- (default their login shell). If the invoking user is not root, the
- specified user's login password must be supplied, and the -s option is
- not allowed. The shell-args if supplied are usually -c "commandline"
- to do just that one command under control of the shell.
-
- Usage differs from standard su in that you cannot say, e.g., "su -c cmd";
- you must specify a user to su to except if there are no shell-args.
-
- If a group called `wheel' exists in /etc/group, only members of that
- group can su to root. The name `wheel' dates from TOPS-20 at least,
- but I don't know what it means. 4.3 BSD re-invented it.
-
- Standard su clears the environment to a few standard entries when you
- do `su -'. We don't bother doing that, but just change a few key
- entries.
- */
-
- #ifndef __STDC__
- #define const /* nothing */
- #endif
-
- #ifndef lint
- static const char rcsid[] =
- "$Id: su.c,v 1.1 90/09/28 16:22:21 em Release $";
- #endif
-
- #ifndef SecureWare
- #define SecureWare 1 /* Appears to be necessary, if regrettable. */
- #endif
-
- #include <stdio.h>
- #ifdef __STDC__
- #include <stdlib.h>
- #endif
- #include <string.h>
- #include <ctype.h>
- #include <pwd.h>
- #include <grp.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #if SecureWare
- #include <sys/security.h>
- #include <prot.h>
- #endif
- #include <errno.h>
-
- #define DEFAULTS_FILE "/etc/default/su"
-
- #ifdef __STDC__ /* <prototypes.h> is worthless, conflicts with <stdlib.h>. */
-
- extern time_t time(long *tloc);
- extern const char *ctime(const time_t *clock);
- extern const char *ttyname(int fildes);
- extern const char *oksetluid(int uid);
- extern char *getpasswd(const char *prompt, int max_size);
- extern char *crypt(const char *key, const char *salt);
- /* Declare everything for gcc -Wall. */
- extern int getuid(void), fork(void), setgid(int gid), setuid(int uid);
- extern int putenv(const char *envstring);
- extern unsigned sleep(unsigned seconds);
- extern int execv(const char *path, const char *const* arg);
- extern pid_t waitpid(pid_t pid, int *status, int options);
- extern int umask(int cmask);
- extern int chdir(const char *dir);
-
- typedef void *pointer;
-
- static void su(const struct passwd *pwd, int islogin, const char *shell,
- const char **shargs);
- static void crash(const char *why, const char *what);
- static void logsu(const char *sutype, const char *sumsg, const char *suarg);
- static int readdefault(const char *defaultname);
- static const char *defaultvalue(const char *settingname);
- static char *xstrdup(const char *str);
- static pointer xmalloc(int size);
- static void xputenv(const char *name, const char *value);
- static struct passwd *xgetpwnam(const char *name);
- static struct passwd *xgetpwuid(int uid);
- static struct passwd *whoami(void);
-
- #else /* !STDC */
-
- extern char *getpass(), *getpasswd();
- extern char *crypt();
- extern char *ttyname();
- extern char *oksetluid();
- extern char *malloc();
-
- typedef char *pointer;
-
- static void su(), logsu(), crash(), xputenv();
- static char *defaultvalue(), *xstrdup();
- static pointer xmalloc();
- static struct passwd *xgetpwnam(), *xgetpwuid(), *whoami();
-
- #endif /* !STDC */
-
-
- const char *progname = "su";
-
-
- int main(argc, argv)
- int argc;
- const char **const argv;
- {
- int i;
- int login = 0;
- struct passwd *pwd;
- const char *user = "root", *shell = NULL;
- if (argc > 0)
- progname = argv[0];
- (void) readdefault(DEFAULTS_FILE);
- for (i = 1; i < argc && argv[i][0] == '-'; i++) {
- switch (argv[i][1]) {
- case '\0':
- login = 1; break;
- case 's': /* Specify different shell from user's own. */
- if (argv[i][2])
- shell = &argv[i][2];
- else {
- if (++i >= argc)
- goto usage;
- shell = argv[i];
- }
- if (getuid() != 0) {
- logsu("BADSU", "tried to say -s ", shell);
- crash("*-s", "only superuser can specify -s");
- }
- break;
- default:
- usage:
- fprintf(stderr, "Usage: %s [-s shell] [-] [user [shell-args]]\n",
- progname);
- exit(1);
- }
- }
- if (i < argc) {
- user = argv[i];
- i++;
- }
- if ((pwd = xgetpwnam(user)) == NULL)
- crash("*no such user", user);
- /* Root isn't asked for a password; everyone else is unless su'ing
- to themselves. */
- if (getuid() != 0 && getuid() != pwd->pw_uid) {
- char *pass, *crpass, *realpass;
- #if SecureWare
- struct pr_passwd *ugh;
- int origumask;
- #endif
- /* If becoming root, make sure we're on the wheel (whatever that
- means). */
- if (pwd->pw_uid == 0) {
- struct group *grp = getgrnam("wheel");
- if (grp != NULL) {
- struct passwd *me = whoami();
- char **mem;
- for (mem = grp->gr_mem; *mem != NULL; mem++)
- if (strcmp(*mem, me->pw_name) == 0)
- break;
- if (*mem == NULL) {
- logsu("BADSU", "to root ", "but not on wheel");
- fprintf(stderr, "Not in wheel group\n");
- exit(2);
- }
- }
- }
- #if !SecureWare /* Standard su, untested. */
- if ((pass = getpass("Password:")) == NULL)
- crash("getpass", user);
- if ((crpass = crypt(pass, pwd->pw_passwd)) == NULL)
- crash("crypt", user);
- realpass = pwd->pw_passwd;
- #else /* SecureWare */
- origumask = umask(0); (void) umask(origumask);
- set_auth_parameters(argc, argv);
- /* OBNOXIOUS MISFEATURE: above call sets the umask to 077. If I want
- the umask to be changed, I'll ASK for it to be changed. Grrr. */
- (void) umask(origumask);
- if ((ugh = getprpwnam(user)) == NULL)
- crash("get protected password", user);
- if ((pass = getpasswd("Password:", AUTH_MAX_PASSWD_LENGTH)) == NULL)
- crash("getpasswd", user);
- /* Use the undocumented bigcrypt() routine which crypts a password
- in pieces if it is longer than 8 characters. */
- if ((crpass = bigcrypt(pass, ugh->ufld.fd_encrypt)) == NULL)
- crash("crypt", user);
- /* I don't think crypt can fail, but may as well test. */
- #if 0 /* DEBUG */
- fprintf(stderr, "user %s entered %s crypted %s really %s\n",
- user, pass, crpass, ugh->ufld.fd_encrypt);
- #endif
- realpass = ugh->ufld.fd_encrypt;
- #endif /* SecureWare */
- if (strcmp(crpass, realpass) != 0) {
- logsu("BADSU", "wrong password for ", user);
- sleep(1); /* Make dictionary searches etc harder. */
- fprintf(stderr, "Sorry\n");
- exit(2);
- }
- }
- logsu("SU", "to ", user);
- /* We overwrite argv[i - 1] with the name of the shell so we can use
- the rest of the array as an argument to execv().
- The stupid Microsoft compiler warns about the ?: below. Ignore it.
- Better still, use GNU C. */
- su(pwd, login, shell ? shell : pwd->pw_shell, argv + i - 1);
- return 0; /* Not reached. */
- }
-
-
- /* su to the named user and uid. If islogin, pretend we are logging in,
- to the extent of setting HOME and LOGNAME environment variables,
- prefixing argv[0] of the shell with "-", and changing to the new
- user's directory. */
- static void su(pwd, islogin, shell, shargs)
- const struct passwd *pwd;
- int islogin;
- const char *shell;
- const char **shargs;
- {
- if (shell == NULL || *shell == '\0')
- shell = "/bin/sh";
- shargs[0] = strrchr(shell, '/');
- if (shargs[0] != NULL)
- shargs[0]++;
- else shargs[0] = shell;
- if (islogin) { /* su - user */
- if (strcmp(shargs[0], "sh") == 0)
- shargs[0] = "-su"; /* As per man page. */
- else {
- char *p = xmalloc(strlen(shargs[0]) + sizeof("-"));
- (void) sprintf(p, "-%s", shargs[0]);
- shargs[0] = p;
- }
- /* Change environment; probably not necessary as shell will do
- this because it's invoked with the leading "-". */
- xputenv("LOGNAME", pwd->pw_name);
- }
- xputenv("HOME", pwd->pw_dir);
- xputenv("SHELL", shell);
- #if SecureWare
- {
- const char *err;
- if ((err = oksetluid(pwd->pw_uid)) != NULL)
- crash("setluid", err);
- }
- #endif
- if (setgid(pwd->pw_gid) < 0)
- crash("setgid", pwd->pw_name);
- if (setuid(pwd->pw_uid) < 0)
- crash("setuid", pwd->pw_name);
- if (islogin && chdir(pwd->pw_dir) < 0)
- perror(pwd->pw_dir); /* But su anyway. */
- execv(shell, shargs);
- crash("execl", shell);
- }
-
-
- /* Log an su attempt. The three fields are printed as well as the current
- user, their tty, and the time. */
- static void logsu(sutype, sumsg, suarg)
- const char *sutype, *sumsg, *suarg;
- {
- const char *const*logp;
- static const char *const logfiles[] = {"SULOG", "CONSOLE", NULL};
- struct passwd *pwd;
- char *user;
- if ((pwd = whoami()) == NULL)
- user = "(unknown)";
- else user = pwd->pw_name;
- for (logp = logfiles; *logp != NULL; logp++) {
- FILE *f;
- const char *p;
- time_t now;
- if ((p = defaultvalue(*logp)) == NULL)
- continue;
- if ((f = fopen(p, "a")) == NULL) {
- /* This isn't a fatal error, lest we lock ourselves out of su.
- Giving the message to the su'ing user is imperfect but will
- do. If you want to know why your log isn't getting any su
- messages you can try su. */
- (void) fprintf(stderr, "%s: append to log ", progname);
- perror(p);
- continue;
- }
- (void) fprintf(f, "%s: %s%s: user %s", sutype, sumsg, suarg, user);
- if ((p = ttyname(0)) != NULL)
- (void) fprintf(f, " on %s", p);
- (void) time(&now);
- (void) fprintf(f, ", %s", ctime(&now)); /* ctime() has trailing \n. */
- (void) fclose(f);
- }
- }
-
-
- /* Print message "why: what" to stderr. The what string is printed with
- perror unless the why string begins with *. The * is not printed.
- Exits the program. */
- static void crash(why, what)
- const char *why, *what;
- {
- fprintf(stderr, "%s: ", progname);
- if (*why == '*') {
- fprintf(stderr, "%s", why + 1);
- if (what)
- fprintf(stderr, ": %s", what);
- putc('\n', stderr);
- } else if (what) {
- fprintf(stderr, "%s: ", why);
- perror(what);
- } else perror(why);
- exit(1);
- }
-
-
- static const struct setting {
- const char *name, *value;
- const struct setting *next;
- } *settingp;
-
-
- /* Read the configuration file. Lines in the file can be blank, comments
- beginning with #, or assigments looking like WORD=VALUE. Spaces around
- the WORD and the VALUE are stripped. We store (word, value) pairs
- in a linked list which we can consult later for settings of
- interest. If we don't succeed in opening the file we don't care; there
- will just be no settings. Return status is <0 if there is an error,
- though this doesn't include not being able to open the file. */
- static int readdefault(configname)
- const char *configname;
- {
- FILE *f;
- char buf[512];
- int i;
- char *p;
- struct setting *sp;
- if ((f = fopen(configname, "r")) == NULL)
- return 0;
- while (fgets(buf, sizeof buf, f) != NULL) {
- for (i = strlen(buf); i > 0 && isspace(buf[i - 1]); i--) ;
- buf[i] = '\0'; /* i == 0 => just a \n. */
- for (i = 0; isspace(buf[i]); i++) ;
- if (buf[i] == '\0' || buf[i] == '#')
- continue;
- p = xstrdup(buf + i);
- for (i = 0; p[i] != '='; i++) {
- if (isspace(p[i])) {
- p[i] = '\0';
- do i++; while (isspace(p[i]));
- if (p[i] != '=') {
- (void) fclose(f);
- (void) fprintf(stderr,
- "%s: format error in defaults file:\n%s\n",
- progname, buf);
- return -1;
- }
- break;
- }
- }
- p[i] = '\0';
- sp = (struct setting *) xmalloc((int) sizeof *sp);
- sp->name = p;
- do i++; while (isspace(p[i]));
- sp->value = p + i;
- sp->next = settingp;
- settingp = sp;
- }
- (void) fclose(f);
- return 0;
- }
-
-
- /* Return the value of a default setting. */
- static const char *defaultvalue(settingname)
- const char *settingname;
- {
- const struct setting *sp;
- for (sp = settingp; sp != NULL; sp = sp->next)
- if (strcmp(sp->name, settingname) == 0)
- return sp->value;
- return NULL;
- }
-
-
- /* Safe malloc, 'cos it crashes if it fails. (That's safe.) */
- static pointer xmalloc(size)
- int size;
- {
- pointer p;
- if ((p = malloc((size_t) size)) == NULL)
- crash("malloc", (char *) NULL);
- return p;
- }
-
-
- /* Likewise for strdup. Since strdup isn't standard we write it by hand. */
- static char *xstrdup(str)
- const char *str;
- {
- char *p = xmalloc(strlen(str) + 1);
- return strcpy(p, str);
- }
-
-
- /* Put name=value into the environment. */
- static void xputenv(name, value)
- const char *name, *value;
- {
- int len = strlen(name) + strlen(value) + sizeof("=");
- char *p = xmalloc(len);
- (void) sprintf(p, "%s=%s", name, value);
- if (putenv(p) < 0)
- crash("putenv", p);
- }
-
-
- /* Wrappers for getpw* that copy the returned structure into a malloced
- buffer, so it doesn't get overwritten by subsequent calls. */
- static struct passwd *copypasswd(pwd)
- struct passwd *pwd;
- {
- struct passwd *p;
- if (pwd == NULL)
- return NULL;
- p = (struct passwd *) xmalloc(sizeof *p);
- *p = *pwd;
- /* We only dup the fields we're interested in here; the other ones will
- get splatted the next time you call getpwthing() because the stuff
- they point to will be overwritten. */
- p->pw_name = xstrdup(p->pw_name);
- p->pw_dir = xstrdup(p->pw_dir);
- p->pw_shell = xstrdup(p->pw_shell);
- return p;
- }
-
-
- static struct passwd *xgetpwnam(name)
- const char *name;
- {
- /* You may get a warning from the next line, if you have an inadequate
- <pwd.h> that declares getpwnam(char *name) rather than
- getpwnam(const char *name). */
- return copypasswd(getpwnam(name));
- }
-
-
- static struct passwd *xgetpwuid(uid)
- int uid;
- {
- return copypasswd(getpwuid(uid));
- }
-
-
- static struct passwd *whoami()
- {
- static struct passwd *iam;
- if (iam != NULL)
- return iam;
- else return iam = xgetpwuid(getuid());
- }
-