X-Git-Url: https://git.distorted.org.uk/~mdw/become/blobdiff_plain/c4f2d992e4a0fc068281376d89ec38de56dc2f58..569b8891b31062e078bc7c1e25a13e04ddde8e53:/src/become.c diff --git a/src/become.c b/src/become.c index c2d8bcf..6f5c236 100644 --- a/src/become.c +++ b/src/become.c @@ -1,13 +1,13 @@ /* -*-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' * @@ -22,14 +22,23 @@ * 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 * */ @@ -43,6 +52,7 @@ #include #include #include +#include /* --- Unix headers --- */ @@ -60,6 +70,8 @@ #include #include +extern char **environ; + /* --- Local headers --- */ #include "become.h" @@ -72,6 +84,7 @@ #include "parser.h" #include "rule.h" #include "utils.h" +#include "userdb.h" /*----- Main code ---------------------------------------------------------*/ @@ -95,7 +108,7 @@ static void bc__write(FILE *fp, const char *p) /* --- 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. */ @@ -143,16 +156,35 @@ static void bc__usage(FILE *fp) " $ -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 + * @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); @@ -174,11 +206,20 @@ static void bc__help(FILE *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@ --- * @@ -193,53 +234,98 @@ static void bc__help(FILE *fp) 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; @@ -252,12 +338,145 @@ int main(int argc, char *argv[]) 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; @@ -271,6 +490,7 @@ int main(int argc, char *argv[]) /* --- Switch to daemon mode if requested --- */ if (flags & f_daemon) { + T( trace(TRACE_MISC, "become: daemon mode requested"); ) daemon_init(conffile, port); exit(0); } @@ -284,22 +504,34 @@ int main(int argc, char *argv[]) { 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 --- */ @@ -312,46 +544,177 @@ int main(int argc, char *argv[]) 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; @@ -359,9 +722,10 @@ int main(int argc, char *argv[]) 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 --- */ @@ -373,7 +737,7 @@ int main(int argc, char *argv[]) /* --- 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)); @@ -385,14 +749,14 @@ int main(int argc, char *argv[]) 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 --- */ @@ -437,27 +801,28 @@ int main(int argc, char *argv[]) *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"); @@ -465,16 +830,18 @@ int main(int argc, char *argv[]) /* --- 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 -------------------------------------------------*/