/* -*-c-*-
*
- * $Id: become.c,v 1.18 1998/06/26 10:32:54 mdw Exp $
+ * $Id$
*
* Main code for `become'
*
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: become.c,v $
- * Revision 1.18 1998/06/26 10:32:54 mdw
- * Cosmetic change: use sizeof(destination) in memcpy.
- *
- * Revision 1.17 1998/06/18 15:06:59 mdw
- * Close log before execing program to avoid leaving a socket open.
- *
- * Revision 1.16 1998/04/23 13:21:04 mdw
- * Small tweaks. Support no-network configuration option, and rearrange
- * the help text a little.
- *
- * Revision 1.15 1998/01/13 11:10:44 mdw
- * Add `TZ' to the list of variables to be preserved.
- *
- * Revision 1.14 1998/01/12 16:45:39 mdw
- * Fix copyright date.
- *
- * Revision 1.13 1997/09/26 09:14:57 mdw
- * Merged blowfish branch into trunk.
- *
- * Revision 1.12 1997/09/25 16:04:48 mdw
- * Change directory after becoming someone else, instead of before. This
- * avoids problems with root-squashed NFS mounts.
- *
- * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
- * Use the Blowfish encryption algorithm instead of IDEA. This is partly
- * because I prefer Blowfish (without any particularly strong evidence) but
- * mainly because IDEA is patented and Blowfish isn't.
- *
- * Revision 1.11 1997/09/24 09:48:45 mdw
- * Fix (scary) overrun bug in group allocation stuff.
- *
- * Revision 1.10 1997/09/17 10:14:10 mdw
- * Fix a typo. Support service names in `--port' option.
- *
- * Revision 1.9 1997/09/10 10:28:05 mdw
- * Allow default port to be given as a service name or port number. Handle
- * groups properly (lots of options here).
- *
- * Revision 1.8 1997/09/08 13:56:24 mdw
- * Change criteria for expunging items from the user's PATH: instead of
- * removing things starting with `.', remove things not starting with `/'.
- *
- * Revision 1.7 1997/09/08 13:43:20 mdw
- * Change userid when creating tracefiles rather than fiddling with
- * `access': it works rather better. Also, insert some stdio buffer
- * flushing to ensure tracedumps are completely written.
- *
- * 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.
- *
- * 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
- *
- */
-
/*----- Header files ------------------------------------------------------*/
/* --- ANSI headers --- */
#include <pwd.h>
#include <syslog.h>
#include <unistd.h>
+#include <fcntl.h>
extern char **environ;
+/* --- mLib --- */
+
+#include <mLib/alloc.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
/* --- Local headers --- */
#include "become.h"
#include "check.h"
#include "daemon.h"
#include "lexer.h"
-#include "mdwopt.h"
#include "name.h"
-#include "parser.h"
+#include "parse.h"
#include "rule.h"
-#include "sym.h"
-#include "utils.h"
#include "userdb.h"
/*----- Type definitions --------------------------------------------------*/
{
bc__write(fp,
"Usage: \n"
- " $ -c <shell-command> <user>\n"
- " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
+ " $ -c SHELL-COMMAND USER\n"
+ " $ [ENV-VAR] USER [COMMAND [ARGUMENTS]...]\n"
#ifndef NONETWORK
- " $ -d [-p <port>] [-f <config-file>]\n"
+ " $ -d [-p PORT] [-f CONFIG-FILE]\n"
#endif
);
}
#ifndef NONETWORK
"\n"
"-d, --daemon Start a daemon\n"
+"-n, --nofork In daemon mode, don't fork into background\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"
#endif
);
-#ifdef TRACING
+#ifndef NTRACE
bc__write(fp, "\n");
if (!suid) {
bc__write(fp,
/* --- Definitions for the various flags --- */
- enum {
- f_daemon = 1, /* Start up in daemon mode */
- f_duff = 2, /* Fault in arguments */
- f_shell = 4, /* Run a default shell */
- f_dummy = 8, /* Don't actually do anything */
- f_setuid = 16, /* We're running setuid */
- f_havegroup = 32 /* Set a default group */
- };
+#define f_daemon 1u /* Start up in daemon mode */
+#define f_duff 2u /* Fault in arguments */
+#define f_shell 4u /* Run a default shell */
+#define f_dummy 8u /* Don't actually do anything */
+#define f_setuid 16u /* We're running setuid */
+#define f_havegroup 32u /* Set a default group */
+#define f_nofork 64u /* Don't fork into background */
/* --- Set up the program name --- */
if (getuid() != geteuid())
flags |= f_setuid;
+ /* --- Make sure standard file descriptors are open --- */
+
+ {
+ int fd;
+ do {
+ if ((fd = open("/dev/null", O_RDWR)) < 0)
+ die(1, "couldn't open /dev/null: %s", strerror(errno));
+ } while (fd <= STDERR_FILENO);
+ close(fd);
+ }
+
/* --- Read the environment into a hashtable --- */
{
char **p;
- sym_createTable(&bc__env);
+ sym_create(&bc__env);
for (p = environ; *p; p++)
bc__putenv(0, *p, 0, 0);
}
/* --- Group style options --- */
- { "group", gFlag_argReq, 0, 'g' },
+ { "group", OPTF_ARGREQ, 0, 'g' },
#ifdef HAVE_SETGROUPS
{ "keep-groups", 0, 0, 'k' },
{ "merge-groups", 0, 0, 'm' },
/* --- Command to run options --- */
- { "command", gFlag_argReq, 0, 'c' },
+ { "command", OPTF_ARGREQ, 0, 'c' },
/* --- Server options --- */
#ifndef NONETWORK
{ "daemon", 0, 0, 'd' },
- { "port", gFlag_argReq, 0, 'p' },
- { "config-file", gFlag_argReq, 0, 'f' },
+ { "nofork", 0, 0, 'n' },
+ { "port", OPTF_ARGREQ, 0, 'p' },
+ { "config-file", OPTF_ARGREQ, 0, 'f' },
#endif
/* --- Tracing options --- */
-#ifdef TRACING
- { "impersonate", gFlag_argReq, 0, 'I' },
- { "trace", gFlag_argOpt, 0, 'T' },
- { "trace-level", gFlag_argOpt, 0, 'L' },
+#ifndef NTRACE
+ { "impersonate", OPTF_ARGREQ, 0, 'I' },
+ { "trace", OPTF_ARGOPT, 0, 'T' },
+ { "trace-level", OPTF_ARGOPT, 0, 'L' },
#endif
{ 0, 0, 0, 0 }
#endif
"c:" /* Command to run options */
#ifndef NONETWORK
- "dp:f:" /* Server options */
+ "dnp:f:" /* Server options */
#endif
-#ifdef TRACING
+#ifndef NTRACE
"I:T::L::" /* Tracing options */
#endif
,
else {
struct group *gr = getgrnam(optarg);
if (!gr)
- die("unknown group `%s'", optarg);
+ die(1, "unknown group `%s'", optarg);
group = gr->gr_gid;
}
flags |= f_havegroup;
else {
struct servent *s = getservbyname(optarg, "udp");
if (!s)
- die("unknown service name `%s'", optarg);
+ die(1, "unknown service name `%s'", optarg);
port = s->s_port;
}
break;
case 'd':
flags |= f_daemon;
break;
+ case 'n':
+ flags |= f_nofork;
+ break;
case 'f':
conffile = optarg;
break;
* allow it if we're running setuid. Disable the actual login anyway.
*/
-#ifdef TRACING
+#ifndef NTRACE
case 'I':
if (flags & f_setuid)
else
pw = getpwnam(optarg);
if (!pw)
- die("can't impersonate unknown user `%s'", optarg);
+ die(1, "can't impersonate unknown user `%s'", optarg);
from_pw = userdb_copyUser(pw);
rq.from = from_pw->pw_uid;
flags |= f_dummy;
* to!
*/
-#ifdef TRACING
+#ifndef TRACE
case 'T': {
FILE *fp;
if (seteuid(ru))
#endif
{
- die("couldn't temporarily give up privileges: %s",
+ die(1, "couldn't temporarily give up privileges: %s",
strerror(errno));
}
if ((fp = fopen(optarg, "w")) == 0) {
- die("couldn't open trace file `%s' for writing: %s",
+ die(1, "couldn't open trace file `%s' for writing: %s",
optarg, strerror(errno));
}
#else
if (seteuid(eu))
#endif
- die("couldn't regain privileges: %s", strerror(errno));
+ die(1, "couldn't regain privileges: %s", strerror(errno));
}
- traceon(fp, TRACE_DFL);
+ trace_on(fp, TRACE_DFL);
trace(TRACE_MISC, "become: tracing enabled");
} break;
/* --- Setting trace levels --- */
-#ifdef TRACING
+#ifndef NTRACE
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[] = {
+ static trace_opt lvltbl[] = {
{ 'm', TRACE_MISC, "miscellaneous messages" },
{ 's', TRACE_SETUP, "building the request block" },
{ 'r', TRACE_RULE, "ruleset scanning" },
{ '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 various\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);
+ trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
+ (flags & f_setuid) ? TRACE_PRIV : 0));
} break;
#endif
#ifndef NONETWORK
if (flags & f_daemon) {
T( trace(TRACE_MISC, "become: daemon mode requested"); )
- daemon_init(conffile, port);
+ daemon_init(conffile, port, (flags & f_nofork) ? df_nofork : 0);
exit(0);
}
#endif
else
pw = getpwnam(who);
if (!pw)
- die("unknown user `%s'", who);
+ die(1, "unknown user `%s'", who);
to_pw = userdb_copyUser(pw);
rq.to = pw->pw_uid;
}
rq.from = getuid();
pw = getpwuid(rq.from);
if (!pw)
- die("who are you? (can't find user %li)", (long)rq.from);
+ die(1, "who are you? (can't find user %li)", (long)rq.from);
from_pw = userdb_copyUser(pw);
}
struct hostent *he;
uname(&u);
if ((he = gethostbyname(u.nodename)) == 0)
- die("who am I? (can't resolve `%s')", u.nodename);
+ die(1, "who am I? (can't resolve `%s')", u.nodename);
memcpy(&rq.host, he->h_addr, sizeof(rq.host));
}
/* --- Check that it's valid --- */
if (group != from_pw->pw_gid && group != to_pw->pw_gid)
- die("invalid default group");
+ die(1, "invalid default group");
#else
if (group == to_gr[i])
goto group_ok;
}
- die("invalid default group");
+ die(1, "invalid default group");
group_ok:;
}
sz = 0;
- for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
+ for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
/* --- Login style expunges all unpreserved variables --- */
for (pp = banned; *pp; pp++) {
if (**pp == '-') {
p = *pp + 1;
- if (memcmp(e->_base.name, p, strlen(p)) == 0)
+ if (strncmp(e->_base.name, p, strlen(p)) == 0)
goto expunge;
} else if (strcmp(e->_base.name, *pp) == 0)
goto expunge;
env = qq = xmalloc((sz + 1) * sizeof(*qq));
- for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
+ for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
*qq++ = e->val;
*qq++ = 0;
}
if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
continue;
- /* --- Now build the pathname and check it --- */
+ /* --- Now build the pathname and check it --- *
+ *
+ * Issue: user can take advantage of these privileges to decide whether
+ * a program with a given name exists. I'm not sure that's
+ * particularly significant: it only works on regular files with
+ * execute permissions, and if you're relying on the names of these
+ * being secret to keep your security up, then you're doing something
+ * deeply wrong anyway. On the other hand, it's useful to allow people
+ * to be able to execute programs and scripts which they wouldn't
+ * otherwise have access to. [This problem was brought up on
+ * Bugtraq, as a complaint against sudo.]
+ */
- sprintf(rq.cmd, "%s/%s", p, todo[0]);
+ if (!*p) p = ".";
+ sprintf(rq.cmd, "%s/%s", p, binary);
if (stat(rq.cmd, &st) == 0 && /* Check it exists */
st.st_mode & 0111 && /* Check it's executable */
S_ISREG(st.st_mode)) /* Check it's a file */
}
if (!p)
- die("couldn't find `%s' in path", todo[0]);
+ die(1, "couldn't find `%s' in path", todo[0]);
binary = rq.cmd;
free(path);
}
q = binary;
if (*q != '/') {
if (!getcwd(b, sizeof(b)))
- die("couldn't read current directory: %s", strerror(errno));
+ die(1, "couldn't read current directory: %s", strerror(errno));
p += strlen(p);
*p++ = '/';
}
*/
if (p >= b + sizeof(b) - 1)
- die("internal error: buffer overflow while canonifying path");
+ die(1, "internal error: buffer overflow while canonifying path");
/* --- Reduce multiple slashes to just one --- */
rq.cmd);
if (!a)
- die("permission denied");
+ die(1, "permission denied");
}
/* --- Now do the job --- */
#ifdef HAVE_SETGROUPS
if (setgroups(ngroups, groups) < 0)
- die("couldn't set groups: %s", strerror(errno));
+ die(1, "couldn't set groups: %s", strerror(errno));
#endif
if (setgid(group) < 0)
- die("couldn't set default group: %s", strerror(errno));
+ die(1, "couldn't set default group: %s", strerror(errno));
if (setuid(rq.to) < 0)
- die("couldn't set uid: %s", strerror(errno));
+ die(1, "couldn't set uid: %s", strerror(errno));
/* --- If this was a login, change current directory --- */
fflush(0);
closelog();
execve(rq.cmd, todo, env);
- die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+ die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
return (127);
}