/* -*-c-*-
*
- * $Id: become.c,v 1.3 1997/08/07 16:28:59 mdw Exp $
+ * $Id: become.c,v 1.6 1997/09/05 13:47:44 mdw Exp $
*
* Main code for `become'
*
/*----- Revision history --------------------------------------------------*
*
* $Log: become.c,v $
+ * Revision 1.6 1997/09/05 13:47:44 mdw
+ * Make the `-L' (trace-level) option's argument optional, like the long
+ * version is.
+ *
+ * Revision 1.5 1997/09/05 11:45:19 mdw
+ * Add support for different login styles, and environment variable
+ * manipulation in a safe and useful way.
+ *
+ * Revision 1.4 1997/08/20 16:15:13 mdw
+ * Overhaul of environment handling. Fix daft bug in path search code.
+ *
* Revision 1.3 1997/08/07 16:28:59 mdw
* Do something useful when users attempt to become themselves.
*
#include "name.h"
#include "parser.h"
#include "rule.h"
+#include "sym.h"
#include "utils.h"
#include "userdb.h"
+/*----- Type definitions --------------------------------------------------*/
+
+/* --- Symbol table entry for an environment variable --- */
+
+typedef struct sym_env {
+ sym_base _base; /* Symbol table information */
+ unsigned f; /* Flags word (see below) */
+ char *val; /* Pointer to variable value */
+} sym_env;
+
+/* --- Environment variable flags --- */
+
+enum {
+ envFlag_preserve = 1
+};
+
+/* --- Login behaviour types --- */
+
+enum {
+ l_preserve, /* Preserve the environment */
+ l_user, /* Update who I am */
+ l_login /* Do a full login */
+};
+
+/*----- Static variables --------------------------------------------------*/
+
+static sym_table bc__env;
+
/*----- Main code ---------------------------------------------------------*/
/* --- @bc__write@ --- *
/* --- Try to be a little efficient --- *
*
- * Gather up non-`$' characters using @strcpn@ and spew them out really
+ * Gather up non-`$' characters using @strcspn@ and spew them out really
* quickly.
*/
}
}
+/* --- @bc__setenv@ --- *
+ *
+ * Arguments: @sym_env *e@ = pointer to environment variable block
+ * @const char *val@ = value to set
+ *
+ * Returns: ---
+ *
+ * Use: Sets an environment variable block to the right value.
+ */
+
+static void bc__setenv(sym_env *e, const char *val)
+{
+ e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
+ sprintf(e->val, "%s=%s", e->_base.name, val);
+}
+
+/* --- @bc__putenv@ --- *
+ *
+ * Arguments: @const char *var@ = name of the variable to set, or 0 if
+ * this is embedded in the value string
+ * @const char *val@ = value to set, or 0 if the variable must
+ * be removed
+ * @unsigned int fl@ = flags to set
+ * @unsigned int force@ = force overwrite of preserved variables
+ *
+ * Returns: Pointer to symbol block, or zero if it was deleted.
+ *
+ * Use: Puts an item into the environment.
+ */
+
+static sym_env *bc__putenv(const char *var, const char *val,
+ unsigned int fl, unsigned int force)
+{
+ unsigned int f;
+ sym_env *e;
+ char *q = 0;
+ size_t sz;
+
+ /* --- Sort out embedded variable names --- */
+
+ if (!var) {
+ const char *p = strchr(val, '=');
+
+ if (p) {
+ sz = p - val;
+ q = xmalloc(sz + 1);
+ memcpy(q, val, sz);
+ q[sz] = 0;
+ var = q;
+ val = p + 1;
+ } else {
+ var = val;
+ val = 0;
+ }
+ }
+
+ /* --- Find the variable block --- */
+
+ if (val) {
+ e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
+ if (~e->f & envFlag_preserve || force) {
+ if (f)
+ free(e->val);
+ bc__setenv(e, val);
+ }
+ } else {
+ e = sym_find(&bc__env, var, -1, 0, 0);
+ if (e && (force || ~e->f & envFlag_preserve))
+ sym_remove(&bc__env, e);
+ e = 0;
+ }
+
+ /* --- Tidy up and return --- */
+
+ if (q)
+ free(q);
+ if (e)
+ e->f = fl;
+ return (e);
+}
+
/* --- @bc__banner@ --- *
*
* Arguments: @FILE *fp@ = stream to write on
bc__write(fp,
"Usage: \n"
" $ -c <shell-command> <user>\n"
- " $ <user> [<command> [<arguments>]...]\n"
+ " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
" $ -d [-p <port>] [-f <config-file>]\n");
}
-/* --- @bc__makeEnv@ --- *
- *
- * Arguments: @const char *var@ = name of an environment variable
- * @const char *value@ = value of the variable
- *
- * Returns: A pointer to an allocated block assigning the value to the
- * name.
- *
- * Use: Constructs environment mappings.
- */
-
-static char *bc__makeEnv(const char *var, const char *value)
-{
- char *p = xmalloc(strlen(var) + strlen(value) + 2);
- sprintf(p, "%s=%s", var, value);
- return (p);
-}
-
/* --- @bc__help@ --- *
*
* Arguments: @FILE *fp@ = stream to write on
+ * @int suid@ = whether we're running set-uid
*
* Returns: ---
*
* Use: Displays a help message for this excellent piece of software.
*/
-static void bc__help(FILE *fp)
+static void bc__help(FILE *fp, int suid)
{
bc__banner(fp);
putc('\n', fp);
"\n"
"Options available are:\n"
"\n"
-"-h, --help Display this help text\n"
-"-v, --version Display the version number of this copy of $\n"
-"-l, --login Really log in as the user\n"
-"-c, --command=CMD Run the (Bourne) shell command CMD\n"
-"-d, --daemon Start up a daemon, to accept requests from clients\n"
-"-p, --port=PORT In daemon mode, listen on PORT\n"
-"-f, --config-file=FILE In daemon mode, read config from FILE\n"
+"-h, --help Display this help text\n"
+"-u, --usage Display a short usage summary\n"
+"-v, --version Display $'s version number\n"
+"\n"
+"-e, --preserve-environment Try to preserve the current environment\n"
+"-s, --su, --set-user Set environment variables to reflect USER\n"
+"-l, --login Really log in as USER\n"
+"\n"
+"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
+"\n"
+"-d, --daemon Start a daemon\n"
+"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
+"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
#ifdef TRACING
-"--impersonate=USER Claim to be USER when asking the server\n"
-"--trace=FILE Dump trace information to FILE (boring)\n"
-"--trace-level=OPTS Set level of tracing information\n"
+ bc__write(fp, "\n");
+ if (!suid) {
+ bc__write(fp,
+"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
+ }
+ bc__write(fp,
+"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
+"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
#endif
-);
}
/* --- @main@ --- *
char *cmd = 0; /* Shell command to execute */
char *binary = "/bin/sh"; /* Default binary to execute */
char **env = environ; /* Default environment to pass */
- char **todo; /* Pointer to argument list */
+ char **todo = 0; /* Pointer to argument list */
+ char *who = 0; /* Who we're meant to become */
struct passwd *from_pw = 0; /* User we are right now */
struct passwd *to_pw = 0; /* User we want to become */
/* --- Miscellanous shared variables --- */
unsigned flags = 0; /* Various useful flags */
+ int style = DEFAULT_LOGIN_STYLE; /* Login style */
/* --- Default argument list executes a shell command --- */
0 /* Terminator */
};
- /* --- Login request replaces the environment with this --- */
-
- static char *mangled_env[] = {
- "PATH=/bin:/usr/bin", /* Default `clean' path*/
- 0, /* User's name (`USER') */
- 0, /* User's name (`LOGNAME') */
- 0, /* Home directory (`HOME') */
- 0 /* Terminator */
- };
-
/* --- Definitions for the various flags --- */
enum {
if (getuid() != geteuid())
flags |= f_setuid;
+ /* --- Read the environment into a hashtable --- */
+
+ {
+ char **p;
+
+ sym_createTable(&bc__env);
+ for (p = environ; *p; p++)
+ bc__putenv(0, *p, 0, 0);
+ }
+
/* --- Parse some command line arguments --- */
for (;;) {
int i;
static struct option opts[] = {
+
+ /* --- Asking for help --- */
+
{ "help", 0, 0, 'h' },
+ { "usage", 0, 0, 'u' },
{ "version", 0, 0, 'v' },
+
+ /* --- Login style options --- */
+
+ { "preserve-environment", 0, 0, 'e' },
+ { "su", 0, 0, 's' },
+ { "set-user", 0, 0, 's' },
{ "login", 0, 0, 'l' },
+
+ /* --- Command to run options --- */
+
{ "command", gFlag_argReq, 0, 'c' },
+
+ /* --- Server options --- */
+
{ "daemon", 0, 0, 'd' },
{ "port", gFlag_argReq, 0, 'p' },
{ "config-file", gFlag_argReq, 0, 'f' },
+
+ /* --- Tracing options --- */
+
#ifdef TRACING
{ "impersonate", gFlag_argReq, 0, 'I' },
{ "trace", gFlag_argOpt, 0, 'T' },
{ "trace-level", gFlag_argOpt, 0, 'L' },
#endif
+
{ 0, 0, 0, 0 }
};
- i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
+ i = mdwopt(argc, argv,
+ "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
+ opts, 0, 0, gFlag_envVar);
if (i < 0)
- break;
+ goto done_options;
switch (i) {
- /* --- Standard help and version options --- */
+ /* --- Asking for help --- */
case 'h':
- bc__help(stdout);
+ bc__help(stdout, flags & f_setuid);
+ exit(0);
+ break;
+ case 'u':
+ bc__usage(stdout);
exit(0);
break;
case 'v':
exit(0);
break;
- /* --- Various simple options --- */
+ /* --- Login style options --- */
+ case 'e':
+ style = l_preserve;
+ break;
+ case 's':
+ style = l_user;
+ break;
case 'l':
- flags |= f_login;
+ style = l_login;
break;
+
+ /* --- Command to run options --- */
+
case 'c':
cmd = optarg;
break;
+
+ /* --- Server options --- */
+
case 'p':
port = atoi(optarg);
break;
*/
#ifdef TRACING
+
case 'I':
if (flags & f_setuid)
moan("shan't allow impersonation while running setuid");
flags |= f_dummy;
}
break;
+
#endif
/* --- Tracing support --- *
#endif
+ /* --- Something that wasn't an option --- *
+ *
+ * The following nasties are supported:
+ *
+ * * NAME=VALUE -- preserve NAME, and give it a VALUE
+ * * NAME= -- preserve NAME, and give it an empty value
+ * * NAME- -- delete NAME
+ * * NAME! -- preserve NAME with existing value
+ *
+ * Anything else is either the user name (which is OK) or the start of
+ * the command (in which case I stop and read the rest of the command).
+ */
+
+ case 0: {
+ size_t sz = strcspn(optarg, "=-!");
+ sym_env *e;
+
+ /* --- None of the above --- */
+
+ if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
+ if (who == 0)
+ who = optarg;
+ else {
+ optind--;
+ goto done_options;
+ }
+ }
+
+ /* --- Do the appropriate thing --- */
+
+ switch (optarg[sz]) {
+ case '=':
+ bc__putenv(0, optarg, envFlag_preserve, 1);
+ break;
+ case '-':
+ optarg[sz] = 0;
+ bc__putenv(optarg, 0, 0, 1);
+ break;
+ case '!':
+ optarg[sz] = 0;
+ if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
+ e->f |= envFlag_preserve;
+ break;
+ }
+ } break;
+
/* --- Something I didn't understand has occurred --- */
case '?':
break;
}
}
+
+done_options:
if (flags & f_duff) {
bc__usage(stderr);
exit(1);
/* --- Pick out the uid --- */
{
- const char *u;
struct passwd *pw;
- if (optind >= argc) {
+ if (!who) {
bc__usage(stderr);
exit(1);
}
- u = argv[optind++];
- if (isdigit((unsigned char)u[0]))
- pw = getpwuid(atoi(u));
+ if (isdigit((unsigned char)who[0]))
+ pw = getpwuid(atoi(who));
else
- pw = getpwnam(u);
+ pw = getpwnam(who);
if (!pw)
- die("unknown user `%s'", u);
+ die("unknown user `%s'", who);
to_pw = userdb_copyUser(pw);
rq.to = pw->pw_uid;
}
binary = todo[0];
}
- /* --- A login request needs a little bit of work --- */
+ else switch (style) {
- else if (flags & f_login) {
- const char *p = strrchr(to_pw->pw_shell, '/');
- if (p)
- p++;
- else
- p = to_pw->pw_shell;
- shell[0] = xmalloc(strlen(p) + 2);
- shell[0][0] = '-';
- strcpy(shell[0] + 1, p);
- shell[1] = 0;
- todo = shell;
- binary = to_pw->pw_shell;
- }
+ /* --- An unadorned becoming requires little work --- */
- /* --- An unadorned becoming requires little work --- */
+ case l_preserve:
+ shell[0] = getenv("SHELL");
+ if (!shell[0])
+ shell[0] = from_pw->pw_shell;
+ shell[1] = 0;
+ todo = shell;
+ binary = todo[0];
+ break;
- else {
- shell[0] = from_pw->pw_shell;
- shell[1] = 0;
- todo = shell;
- binary = todo[0];
+ /* --- An su-like login needs slightly less effort --- */
+
+ case l_user:
+ shell[0] = to_pw->pw_shell;
+ shell[1] = 0;
+ todo = shell;
+ binary = todo[0];
+ break;
+
+ /* --- A login request needs a little bit of work --- */
+
+ case l_login: {
+ const char *p = strrchr(to_pw->pw_shell, '/');
+
+ if (p)
+ p++;
+ else
+ p = to_pw->pw_shell;
+ shell[0] = xmalloc(strlen(p) + 2);
+ shell[0][0] = '-';
+ strcpy(shell[0] + 1, p);
+ shell[1] = 0;
+ todo = shell;
+ binary = to_pw->pw_shell;
+ chdir(to_pw->pw_dir);
+ } break;
}
- /* --- Mangle the environment if login flag given --- */
+ /* --- Mangle the environment --- *
+ *
+ * This keeps getting more complicated all the time. (How true. Now I've
+ * got all sorts of nasty environment mangling to do.)
+ *
+ * The environment stuff now happens in seven phases:
+ *
+ * 1. Mark very special variables to be preserved. Currently only TERM
+ * and DISPLAY are treated in this way.
+ *
+ * 2. Set and preserve Become's own environment variables.
+ *
+ * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
+ * SHELL and MAIL) if we're being `su'-like or `login'-like.
+ *
+ * 4. If we're preserving the environment or being `su'-like, process the
+ * PATH variable a little. Otherwise reset it to something
+ * appropriate.
+ *
+ * 5. If we're being `login'-like, expunge all unpreserved variables.
+ *
+ * 6. Expunge any security-critical variables.
+ *
+ * 7. Build a new environment table to pass to child processes.
+ */
+
+ {
+ /* --- Variables to be preserved always --- *
+ *
+ * A user can explicitly expunge a variable in this list, in which case
+ * we never get to see it here.
+ */
+
+ static char *preserve[] = {
+ "TERM", "DISPLAY", 0
+ };
+
+ /* --- Variables to be expunged --- *
+ *
+ * Any environment string which has one of the following as a prefix
+ * will be expunged from the environment passed to the called process.
+ * The first line lists variables which have been used to list search
+ * paths for shared libraries: by manipulating these, an attacker could
+ * replace a standard library with one of his own. The second line lists
+ * other well-known dangerous environment variables.
+ */
+
+ static char *banned[] = {
+ "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
+ "IFS", "ENV", "BASH_ENV", "KRB_CONF",
+ 0
+ };
+
+ /* --- Other useful variables --- */
+
+ sym_env *e;
+ char *p, *q, *r;
+ char **pp, **qq;
+ size_t sz;
+ unsigned f;
+ sym_iter i;
+
+ /* --- Stage one. Preserve display-specific variables --- */
+
+ for (pp = preserve; *pp; pp++) {
+ if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
+ e->f |= envFlag_preserve;
+ }
+
+ /* --- Stage two. Set Become's own variables --- */
+
+ e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
+ if (!f)
+ bc__setenv(e, from_pw->pw_name);
+ e->f |= envFlag_preserve;
+
+ e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
+ if (!f)
+ bc__setenv(e, from_pw->pw_dir);
+ e->f |= envFlag_preserve;
+
+ bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
+ bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
+ bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
+ bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
+
+ /* --- Stage three. Set user identity --- */
+
+ switch (style) {
+ case l_login: {
+ static char *maildirs[] = {
+ "/var/spool/mail", "/var/mail",
+ "/usr/spool/mail", "/usr/mail",
+ 0
+ };
+ struct stat s;
+ char b[128];
+
+ for (pp = maildirs; *pp; pp++) {
+ if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
+ sprintf(b, "%s/%s", *pp, to_pw->pw_name);
+ bc__putenv("MAIL", b, envFlag_preserve, 0);
+ break;
+ }
+ }
+ } /* Fall through */
+
+ case l_user:
+ bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
+ bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
+ bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
+ bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
+ break;
+ }
+
+ /* --- Stage four. Set the user's PATH properly --- */
+
+ {
+ /* --- Find an existing path --- *
+ *
+ * If there's no path, or this is a login, then set a default path,
+ * unless we're meant to preserve the existing one. Whew!
+ */
+
+ e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
+
+ if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
+ bc__putenv("PATH",
+ rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
+ envFlag_preserve, 0);
+ } else {
+
+ /* --- Find the string --- */
+
+ e->f = envFlag_preserve;
+ p = strchr(e->val, '=') + 1;
+ r = e->val;
+
+ /* --- Write the new version to a dynamically allocated buffer --- */
+
+ e->val = xmalloc(4 + 1 + strlen(p) + 1);
+ strcpy(e->val, "PATH=");
+ q = e->val + 5;
+
+ for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
+ if (p[0] == '.')
+ continue;
+ while (*p)
+ *q++ = *p++;
+ *q++ = ':';
+ }
+ q[-1] = 0;
+
+ /* --- Done! --- */
+
+ free(r);
+ }
+ }
+
+ /* --- Stages five and six. Expunge variables and count numbers --- *
+ *
+ * Folded together, so I only need one pass through the table. Also
+ * count the number of variables needed at this time.
+ */
+
+ sz = 0;
+
+ for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
+
+ /* --- Login style expunges all unpreserved variables --- */
+
+ if (style == l_login && ~e->f & envFlag_preserve)
+ goto expunge;
+
+ /* --- Otherwise just check the name against the list --- */
+
+ for (pp = banned; *pp; pp++) {
+ if (**pp == '-') {
+ p = *pp + 1;
+ if (memcmp(e->_base.name, p, strlen(p)) == 0)
+ goto expunge;
+ } else if (strcmp(e->_base.name, p) == 0)
+ goto expunge;
+ }
+
+ sz++;
+ continue;
+
+ expunge:
+ sym_remove(&bc__env, e);
+ }
+
+ /* --- Stage seven. Build the new environment block --- */
+
+ env = qq = xmalloc((sz + 1) * sizeof(*qq));
- if (flags & f_login) {
- env = mangled_env;
- env[1] = bc__makeEnv("USER", to_pw->pw_name);
- env[2] = bc__makeEnv("LOGNAME", to_pw->pw_name);
- env[3] = bc__makeEnv("HOME", to_pw->pw_dir);
+ for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
+ *qq++ = e->val;
+ *qq++ = 0;
}
/* --- Trace the command --- */
p = "/bin:/usr/bin";
path = xstrdup(p);
- for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
+ for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
- /* --- SECURITY: check length of string before copying --- */
+ /* --- Check length of string before copying --- */
if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
continue;
while (*q) {
- /* --- SECURITY: check for buffer overflows here --- *
+ /* --- Check for buffer overflows here --- *
*
* I write at most one byte per iteration so this is OK. Remember to
* allow one for the null byte.