From 387501aeccded922e560da75c955f664f2faa6cd Mon Sep 17 00:00:00 2001 From: mdw Date: Fri, 5 Sep 1997 11:45:19 +0000 Subject: [PATCH] Add support for different login styles, and environment variable manipulation in a safe and useful way. --- acconfig.h | 9 +- configure.in | 26 ++- src/become.c | 566 ++++++++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 469 insertions(+), 132 deletions(-) diff --git a/acconfig.h b/acconfig.h index 1434352..bc8ad47 100644 --- a/acconfig.h +++ b/acconfig.h @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: acconfig.h,v 1.3 1997/08/07 09:33:05 mdw Exp $ + * $Id: acconfig.h,v 1.4 1997/09/05 11:45:17 mdw Exp $ * * Default settings for `become' config.h * @@ -29,6 +29,10 @@ /*----- Revision history --------------------------------------------------* * * $Log: acconfig.h,v $ + * Revision 1.4 1997/09/05 11:45:17 mdw + * Add support for different login styles, and environment variable + * manipulation in a safe and useful way. + * * Revision 1.3 1997/08/07 09:33:05 mdw * Added `ElectricFence' support. Other minor cosmetic things. * @@ -55,6 +59,9 @@ /* Define to be the size of an int. */ #define SIZEOF_INT 4 +/* Default login style can be @l_preserve@, @l_user@ or @l_login@ */ +#define DEFAULT_LOGIN_STYLE l_preserve + /* This is replaced by `/' by `configure' -- leave alone for DOSness. */ #define PATHSEP '\\' diff --git a/configure.in b/configure.in index 69b0582..66491c9 100644 --- a/configure.in +++ b/configure.in @@ -1,6 +1,6 @@ dnl -*-fundamental-*- dnl -dnl $Id: configure.in,v 1.4 1997/08/20 16:10:56 mdw Exp $ +dnl $Id: configure.in,v 1.5 1997/09/05 11:45:18 mdw Exp $ dnl dnl Source for auto configuration for `become' dnl @@ -28,11 +28,15 @@ dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. dnl----- Revision history --------------------------------------------------- dnl dnl $Log: configure.in,v $ -dnl Revision 1.4 1997/08/20 16:10:56 mdw -dnl Lowercase `mdw_' prefixes to macros. Add a `--with-etcdir=PATH' -dnl option. Update `stamp-h' as required by the Automake docs (silly -dnl me). +dnl Revision 1.5 1997/09/05 11:45:18 mdw +dnl Add support for different login styles, and environment variable +dnl manipulation in a safe and useful way. dnl +# Revision 1.4 1997/08/20 16:10:56 mdw +# Lowercase `mdw_' prefixes to macros. Add a `--with-etcdir=PATH' +# option. Update `stamp-h' as required by the Automake docs (silly +# me). +# dnl Revision 1.3 1997/08/07 09:34:32 mdw dnl Added `ElectricFence' support, and support for the `deep' package dnl structure. @@ -93,6 +97,18 @@ dnl --- Check for some useful functions --- AC_CHECK_FUNCS(getrusage vtimes) +dnl --- Set default become style --- + +AC_ARG_ENABLE([style], +[ --enable-style=STYLE set default style to preserve, su, or login], +[case "$withval" in + preserve) style="l_preserve" ;; + su|user) style="l_user" ;; + login) style="l_login" ;; +esac], +[style="l_preserve"]) +AC_DEFINE_UNQUOTED(DEFAULT_LOGIN_STYLE, $style) + dnl --- Set configuration directory --- AC_ARG_WITH([etcdir], diff --git a/src/become.c b/src/become.c index 6f5c236..c994974 100644 --- a/src/become.c +++ b/src/become.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: become.c,v 1.4 1997/08/20 16:15:13 mdw Exp $ + * $Id: become.c,v 1.5 1997/09/05 11:45:19 mdw Exp $ * * Main code for `become' * @@ -29,7 +29,11 @@ /*----- Revision history --------------------------------------------------* * * $Log: become.c,v $ - * Revision 1.4 1997/08/20 16:15:13 mdw + * 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 @@ -83,9 +87,38 @@ extern char **environ; #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@ --- * @@ -124,6 +157,87 @@ static void bc__write(FILE *fp, const char *p) } } +/* --- @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 @@ -152,28 +266,10 @@ static void bc__usage(FILE *fp) bc__write(fp, "Usage: \n" " $ -c \n" - " $ [ []...]\n" + " $ [] [ []...]\n" " $ -d [-p ] [-f ]\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 @@ -204,21 +300,28 @@ static void bc__help(FILE *fp, int suid) "\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 + bc__write(fp, "\n"); if (!suid) { bc__write(fp, -"--impersonate=USER Claim to be USER when asking the server\n"); +"-I USER, --impersonate=USER Claim to be USER when asking the server\n"); } bc__write(fp, -"--trace=FILE Dump trace information to FILE (boring)\n" -"--trace-level=OPTS Set level of tracing information\n"); +"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n" +"-L OPTS, --trace-level=OPTS Set level of tracing information\n"); #endif } @@ -240,7 +343,8 @@ int main(int argc, char *argv[]) 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 */ @@ -252,6 +356,7 @@ int main(int argc, char *argv[]) /* --- Miscellanous shared variables --- */ unsigned flags = 0; /* Various useful flags */ + int style = DEFAULT_LOGIN_STYLE; /* Login style */ /* --- Default argument list executes a shell command --- */ @@ -279,40 +384,71 @@ int main(int argc, char *argv[]) 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' }, + { "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, flags & f_setuid); exit(0); break; - case 'U': + case 'u': bc__usage(stdout); exit(0); break; @@ -321,14 +457,26 @@ int main(int argc, char *argv[]) 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; @@ -475,6 +623,52 @@ int main(int argc, char *argv[]) #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 '?': @@ -482,6 +676,8 @@ int main(int argc, char *argv[]) break; } } + +done_options: if (flags & f_duff) { bc__usage(stderr); exit(1); @@ -502,21 +698,19 @@ int main(int argc, char *argv[]) /* --- 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; } @@ -558,43 +752,85 @@ int main(int argc, char *argv[]) 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 --- */ + + 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; - /* --- An unadorned becoming requires little work --- */ + /* --- An su-like login needs slightly less effort --- */ - else { - shell[0] = getenv("SHELL"); - if (!shell[0]) - shell[0] = from_pw->pw_shell; - shell[1] = 0; - todo = shell; - binary = todo[0]; + 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 --- * * - * This keeps getting more complicated all the time. + * 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. */ { - size_t i, j, b; - int pass; + /* --- 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. + */ - /* --- Expunge some environment variables --- * + 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. @@ -604,80 +840,158 @@ int main(int argc, char *argv[]) * other well-known dangerous environment variables. */ - static const char *banned[] = { - "LD_", "SHLIB_PATH=", "LIBPATH=", "_RLD_", - "IFS=", "ENV=", "BASH_ENV=", "KRB_CONF=", + static char *banned[] = { + "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_", + "IFS", "ENV", "BASH_ENV", "KRB_CONF", 0 }; - /* --- Do this in two passes --- * - * - * The first pass works out how big the environment block needs to be. - * The second actually fills it. - */ + /* --- Other useful variables --- */ - for (pass = 0; pass < 2; pass++) { - i = j = 0; + sym_env *e; + char *p, *q, *r; + char **pp, **qq; + size_t sz; + unsigned f; + sym_iter i; + + /* --- Stage one. Preserve display-specific variables --- */ - if (flags & f_login) { + for (pp = preserve; *pp; pp++) { + if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0) + e->f |= envFlag_preserve; + } - /* --- This is a login request --- * - * - * Erase the existing environment and build a new one. - */ + /* --- Stage two. Set Become's own variables --- */ - if (!pass) - i += 4; - else { - env[i++] = "PATH=/usr/bin:/bin"; - env[i++] = bc__makeEnv("USER", to_pw->pw_name); - env[i++] = bc__makeEnv("LOGNAME", to_pw->pw_name); - env[i++] = bc__makeEnv("HOME", to_pw->pw_dir); - } - } else { + 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; - /* --- Normal request --- * - * - * Remove dangerous variables from the list. - */ + 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); - for (j = 0; environ[j]; j++) { - for (b = 0; banned[b]; b++) { - if (memcmp(environ[j], banned[b], strlen(banned[b])) == 0) - goto skip_var; + /* --- 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; } - if (pass) - env[i] = environ[j]; - i++; - skip_var:; } - } + } /* 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 --- */ - /* --- Now add our own variables --- * + { + /* --- Find an existing path --- * * - * The following are supplied only to help people construct startup - * scripts. Anyone who relies on them being accurate for - * authentication purposes will get exactly what they deserve. + * 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! */ - if (!pass) - i += 4; - else { - env[i++] = bc__makeEnv("BECOME_OLDUSER", from_pw->pw_name); - env[i++] = bc__makeEnv("BECOME_OLDHOME", from_pw->pw_dir); - env[i++] = bc__makeEnv("BECOME_USER", to_pw->pw_name); - env[i++] = bc__makeEnv("BECOME_HOME", to_pw->pw_dir); + 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; - /* --- Allocate memory after the first pass is complete --- */ + for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) { - if (pass) - env[i] = 0; - i++; + /* --- Login style expunges all unpreserved variables --- */ - if (!pass) - env = xmalloc(i * sizeof(env[0])); + 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)); + + for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) + *qq++ = e->val; + *qq++ = 0; } /* --- Trace the command --- */ -- 2.11.0