/* -*-c-*-
*
- * $Id: become.c,v 1.1 1997/07/21 13:47:54 mdw Exp $
+ * $Id: become.c,v 1.4 1997/08/20 16:15:13 mdw Exp $
*
* Main code for `become'
*
* (c) 1997 EBI
*/
-/*----- Licencing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
*
* This file is part of `become'
*
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with `become'; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with `become'; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*----- Revision history --------------------------------------------------*
*
* $Log: become.c,v $
- * Revision 1.1 1997/07/21 13:47:54 mdw
+ * 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.
+ *
+ * Revision 1.2 1997/08/04 10:24:20 mdw
+ * Sources placed under CVS control.
+ *
+ * Revision 1.1 1997/07/21 13:47:54 mdw
* Initial revision
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
/* --- Unix headers --- */
#include <syslog.h>
#include <unistd.h>
+extern char **environ;
+
/* --- Local headers --- */
#include "become.h"
#include "parser.h"
#include "rule.h"
#include "utils.h"
+#include "userdb.h"
/*----- Main code ---------------------------------------------------------*/
/* --- 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.
*/
" $ -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"
"-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"
-"--yacc-debug Dump lots of parser diagnostics (boring)\n");
+"-f, --config-file=FILE In daemon mode, read config from FILE\n");
+#ifdef TRACING
+ if (!suid) {
+ bc__write(fp,
+"--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");
+#endif
}
/* --- @main@ --- *
int main(int argc, char *argv[])
{
- char *cmd = 0;
- static char *shell[] = { "/bin/sh", "-c", 0, 0 };
- char **todo;
- request rq;
- char buff[CMDLEN_MAX];
- char *conffile = file_RULES;
- int port = -1;
+ /* --- Request block setup parameters --- */
- enum {
- f_daemon = 1,
- f_duff = 2
+ request rq; /* Request buffer to build */
+ 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 */
+ struct passwd *from_pw = 0; /* User we are right now */
+ struct passwd *to_pw = 0; /* User we want to become */
+
+ /* --- Become server setup parameters --- */
+
+ char *conffile = file_RULES; /* Default config file for daemon */
+ int port = -1; /* Default port for daemon */
+
+ /* --- Miscellanous shared variables --- */
+
+ unsigned flags = 0; /* Various useful flags */
+
+ /* --- Default argument list executes a shell command --- */
+
+ static char *shell[] = {
+ "/bin/sh", /* Bourne shell */
+ "-c", /* Read from command line */
+ 0, /* Pointer to shell command */
+ 0 /* Terminator */
};
- unsigned flags = 0;
+ /* --- Definitions for the various flags --- */
+
+ enum {
+ f_daemon = 1, /* Start up in daemon mode */
+ f_duff = 2, /* Fault in arguments */
+ f_login = 4, /* Execute as a login shell */
+ f_dummy = 8, /* Don't actually do anything */
+ f_setuid = 16 /* We're running setuid */
+ };
/* --- Set up the program name --- */
ego(argv[0]);
+ clock();
+ if (getuid() != geteuid())
+ flags |= f_setuid;
/* --- Parse some command line arguments --- */
for (;;) {
int i;
- struct option opts[] = {
+ static struct option opts[] = {
{ "help", 0, 0, 'h' },
+ { "usage", 0, 0, 'U' },
{ "version", 0, 0, 'v' },
+ { "login", 0, 0, 'l' },
{ "command", gFlag_argReq, 0, 'c' },
{ "daemon", 0, 0, 'd' },
{ "port", gFlag_argReq, 0, 'p' },
{ "config-file", gFlag_argReq, 0, 'f' },
- { "yacc-debug", 0, 0, 'Y' },
+#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, "hvc:p:df:", opts, 0, 0, 0);
+ i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
if (i < 0)
break;
switch (i) {
+
+ /* --- Standard help and version options --- */
+
case 'h':
- bc__help(stdout);
+ bc__help(stdout, flags & f_setuid);
+ exit(0);
+ break;
+ case 'U':
+ bc__usage(stdout);
exit(0);
break;
case 'v':
bc__banner(stdout);
exit(0);
break;
+
+ /* --- Various simple options --- */
+
+ case 'l':
+ flags |= f_login;
+ break;
case 'c':
cmd = optarg;
break;
case 'f':
conffile = optarg;
break;
- case 'Y':
- if (getuid() == geteuid())
- yydebug = 1;
- else
- moan("won't set debugging mode when running setuid");
+
+ /* --- Pretend to be a different user --- *
+ *
+ * There are all sorts of nasty implications for this option. Don't
+ * allow it if we're running setuid. Disable the actual login anyway.
+ */
+
+#ifdef TRACING
+
+ case 'I':
+ if (flags & f_setuid)
+ moan("shan't allow impersonation while running setuid");
+ else {
+ struct passwd *pw;
+ if (isdigit((unsigned char)optarg[0]))
+ pw = getpwuid(atoi(optarg));
+ else
+ pw = getpwnam(optarg);
+ if (!pw)
+ die("can't impersonate unknown user `%s'", optarg);
+ from_pw = userdb_copyUser(pw);
+ rq.from = from_pw->pw_uid;
+ flags |= f_dummy;
+ }
break;
+
+#endif
+
+ /* --- Tracing support --- *
+ *
+ * Be careful not to zap a file I wouldn't normally be allowed to write
+ * to!
+ */
+
+#ifdef TRACING
+
+ case 'T': {
+ FILE *fp;
+
+ if (optarg == 0 || strcmp(optarg, "-") == 0)
+ fp = stdout;
+ else {
+ if ((flags & f_setuid) && access(optarg, W_OK)) {
+ die("no write permission for trace file file `%s': %s",
+ optarg, strerror(errno));
+ }
+ if ((fp = fopen(optarg, "w")) == 0) {
+ die("couldn't open trace file `%s' for writing: %s",
+ optarg, strerror(errno));
+ }
+ }
+ traceon(fp, TRACE_DFL);
+ trace(TRACE_MISC, "become: tracing enabled");
+ } break;
+
+#endif
+
+ /* --- Setting trace levels --- */
+
+#ifdef TRACING
+
+ case 'L': {
+ int sense = 1;
+ unsigned int lvl = 0, l;
+ const char *p = optarg;
+
+ /* --- Table of tracing facilities --- */
+
+ typedef struct tr {
+ char ch;
+ unsigned int l;
+ const char *help;
+ } tr;
+
+ static tr lvltbl[] = {
+ { 'm', TRACE_MISC, "miscellaneous messages" },
+ { 's', TRACE_SETUP, "building the request block" },
+ { 'd', TRACE_DAEMON, "server process" },
+ { 'r', TRACE_RULE, "ruleset scanning" },
+ { 'c', TRACE_CHECK, "request checking" },
+ { 'l', TRACE_CLIENT, "client process" },
+ { 'R', TRACE_RAND, "random number generator" },
+ { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
+ { 'y', TRACE_YACC, "parsing configuration file" },
+ { 'D', TRACE_DFL, "default tracing options" },
+ { 'A', TRACE_ALL, "all tracing options" },
+ { 0, 0, 0 }
+ };
+ tr *tp;
+
+ /* --- Output some help if there's no arguemnt --- */
+
+ if (!optarg) {
+ bc__banner(stdout);
+ bc__write(stdout,
+ "\n"
+ "Tracing options:\n"
+ "\n");
+ for (tp = lvltbl; tp->l; tp++) {
+ if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
+ printf("%c -- %s\n", tp->ch, tp->help);
+ }
+ bc__write(stdout,
+"\n"
+"Also, `+' and `-' options are recognised to turn on and off vavrious\n"
+"tracing options. For example, `A-r' enables everything except ruleset\n"
+"tracing, and `A-D+c' is everything except the defaults, but with request\n"
+"check tracing.\n"
+);
+ exit(0);
+ }
+
+ while (*p) {
+ if (*p == '+')
+ sense = 1;
+ else if (*p == '-')
+ sense = 0;
+ else {
+ for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
+ ;
+ l = tp->l;
+ if (flags & f_setuid)
+ l &= ~TRACE_PRIV;
+ if (l)
+ lvl = sense ? (lvl | l) : (lvl & ~l);
+ else
+ moan("unknown trace option `%c'", *p);
+ }
+ p++;
+ }
+
+ tracesetlvl(lvl);
+ yydebug = ((lvl & TRACE_YACC) != 0);
+ } break;
+
+#endif
+
+ /* --- Something I didn't understand has occurred --- */
+
case '?':
flags |= f_duff;
break;
/* --- Switch to daemon mode if requested --- */
if (flags & f_daemon) {
+ T( trace(TRACE_MISC, "become: daemon mode requested"); )
daemon_init(conffile, port);
exit(0);
}
{
const char *u;
struct passwd *pw;
+
if (optind >= argc) {
bc__usage(stderr);
exit(1);
}
u = argv[optind++];
- pw = getpwnam(u);
- if (!pw && isdigit(u[0]))
+
+ if (isdigit((unsigned char)u[0]))
pw = getpwuid(atoi(u));
+ else
+ pw = getpwnam(u);
if (!pw)
die("unknown user `%s'", u);
+ to_pw = userdb_copyUser(pw);
rq.to = pw->pw_uid;
}
/* --- Fill in the easy bits of the request --- */
- rq.from = getuid();
+ if (!from_pw) {
+ struct passwd *pw;
+
+ rq.from = getuid();
+ pw = getpwuid(rq.from);
+ if (!pw)
+ die("who are you? (can't find user %li)", (long)rq.from);
+ from_pw = userdb_copyUser(pw);
+ }
/* --- Find the local host address --- */
memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
}
- /* --- Figure out what command to use --- */
+ /* --- Shell commands are easy --- */
if (cmd) {
shell[2] = cmd;
todo = shell;
- } else if (optind < argc) {
+ }
+
+ /* --- A command given on the command line isn't too hard --- */
+
+ else if (optind < argc) {
todo = argv + optind;
- } else {
- struct passwd *pw = getpwuid(rq.from);
- if (!pw)
- die("who are you? (can't find uid %li in database)", (long)rq.from);
- shell[0] = pw->pw_shell;
+ binary = todo[0];
+ }
+
+ /* --- A login request needs a little bit of work --- */
+
+ 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 --- */
+
+ else {
+ shell[0] = getenv("SHELL");
+ if (!shell[0])
+ shell[0] = from_pw->pw_shell;
+ shell[1] = 0;
+ todo = shell;
+ binary = todo[0];
+ }
+
+ /* --- Mangle the environment --- *
+ *
+ * This keeps getting more complicated all the time.
+ */
+
+ {
+ size_t i, j, b;
+ int pass;
+
+ /* --- Expunge some environment variables --- *
+ *
+ * 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 const 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.
+ */
+
+ for (pass = 0; pass < 2; pass++) {
+ i = j = 0;
+
+ if (flags & f_login) {
+
+ /* --- This is a login request --- *
+ *
+ * Erase the existing environment and build a new one.
+ */
+
+ 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 {
+
+ /* --- Normal request --- *
+ *
+ * Remove dangerous variables from the list.
+ */
+
+ 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;
+ }
+ if (pass)
+ env[i] = environ[j];
+ i++;
+ skip_var:;
+ }
+ }
+
+ /* --- Now add our own variables --- *
+ *
+ * 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 (!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);
+ }
+
+ /* --- Allocate memory after the first pass is complete --- */
+
+ if (pass)
+ env[i] = 0;
+ i++;
+
+ if (!pass)
+ env = xmalloc(i * sizeof(env[0]));
+ }
}
+ /* --- Trace the command --- */
+
+ IF_TRACING(TRACE_SETUP, {
+ int i;
+
+ trace(TRACE_SETUP, "setup: from user %s to user %s",
+ from_pw->pw_name, to_pw->pw_name);
+ trace(TRACE_SETUP, "setup: binary == `%s'", binary);
+ for (i = 0; todo[i]; i++)
+ trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
+ for (i = 0; env[i]; i++)
+ trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
+ })
+
/* --- If necessary, resolve the path to the command --- */
- if (!strchr(todo[0], '/')) {
- char *path;
- char *p;
+ if (!strchr(binary, '/')) {
+ char *path, *p;
struct stat st;
- size_t sz;
if ((p = getenv("PATH")) == 0)
p = "/bin:/usr/bin";
- sz = strlen(p) + 1;
- memcpy(path = xmalloc(sz), p, sz);
+ 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(todo[0]) + 2 > sizeof(buff))
+ if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
continue;
/* --- Now build the pathname and check it --- */
- sprintf(buff, "%s/%s", p, todo[0]);
- if (stat(buff, &st) == 0 && /* Check it exists */
+ sprintf(rq.cmd, "%s/%s", p, todo[0]);
+ if (stat(rq.cmd, &st) == 0 && /* Check it exists */
st.st_mode & 0111 && /* Check it's executable */
(st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
break;
if (!p)
die("couldn't find `%s' in path", todo[0]);
- todo[0] = buff;
+ binary = rq.cmd;
free(path);
}
+ T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
/* --- Canonicalise the path string, if necessary --- */
/* --- Insert current directory name if path not absolute --- */
p = b;
- q = todo[0];
+ q = binary;
if (*q != '/') {
if (!getcwd(b, sizeof(b)))
die("couldn't read current directory: %s", strerror(errno));
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.
*/
if (p >= b + sizeof(b) - 1)
- die("buffer overflow -- bad things happened");
+ die("internal error: buffer overflow while canonifying path");
/* --- Reduce multiple slashes to just one --- */
*p++ = 0;
strcpy(rq.cmd, b);
}
+ T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
- /* --- Run the check --- */
+ /* --- Run the check --- *
+ *
+ * If the user is already what she wants to be, then print a warning.
+ * Then, if I was just going to spawn a shell, quit, to reduce user
+ * confusion. Otherwise, do what was wanted anyway.
+ */
- {
+ if (rq.from == rq.to) {
+ moan("you already are `%s'!", to_pw->pw_name);
+ if (!cmd && todo == shell) {
+ moan("(to prevent confusion, I'm not spawning a shell)");
+ exit(0);
+ }
+ } else {
int a = check(&rq);
- char from[16], to[16];
- struct passwd *pw;
-
- if ((pw = getpwuid(rq.from)) != 0)
- sprintf(from, "%.15s", pw->pw_name);
- else
- sprintf(from, "user %lu", (unsigned long)rq.from);
-
- if ((pw = getpwuid(rq.to)) != 0)
- sprintf(to, "%.15s", pw->pw_name);
- else
- sprintf(to, "user %lu", (unsigned long)rq.to);
syslog(LOG_INFO,
"permission %s for %s to become %s to run `%s'",
- a ? "granted" : "denied", from, to, rq.cmd);
+ a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
+ rq.cmd);
if (!a)
die("permission denied");
/* --- Now do the job --- */
-#ifdef TEST_RIG
- printf("ok\n");
- return (0);
-#else
- if (setuid(rq.to) == -1 || seteuid(rq.to) == -1)
- die("couldn't set uid: %s", strerror(errno));
- execv(rq.cmd, todo);
- die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
- return (127);
-#endif
+ T( trace(TRACE_MISC, "become: permission granted"); )
+
+ if (flags & f_dummy) {
+ puts("permission granted");
+ return (0);
+ } else {
+ if (setuid(rq.to) == -1)
+ die("couldn't set uid: %s", strerror(errno));
+ execve(rq.cmd, todo, env);
+ die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+ return (127);
+ }
}
/*----- That's all, folks -------------------------------------------------*/