/* -*-c-*-
*
- * $Id: become.c,v 1.6 1997/09/05 13:47:44 mdw Exp $
+ * $Id: become.c,v 1.10 1997/09/17 10:14:10 mdw Exp $
*
* Main code for `become'
*
/*----- Revision history --------------------------------------------------*
*
* $Log: become.c,v $
- * Revision 1.6 1997/09/05 13:47:44 mdw
+ * 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.
*
#include <ctype.h>
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include <grp.h>
#include <pwd.h>
#include <syslog.h>
#include <unistd.h>
enum {
l_preserve, /* Preserve the environment */
- l_user, /* Update who I am */
+ l_setuser, /* Update who I am */
l_login /* Do a full login */
};
+/* --- Group behaviour types --- *
+ *
+ * Note that these make a handy bitfield.
+ */
+
+#ifdef HAVE_SETGROUPS
+
+enum {
+ g_unset = 0, /* Nobody's set a preference */
+ g_keep = 1, /* Leave the group memberships */
+ g_replace = 2, /* Replace group memberships */
+ g_merge = (g_keep | g_replace) /* Merge the group memberships */
+};
+
+#endif
+
/*----- Static variables --------------------------------------------------*/
static sym_table bc__env;
if (val) {
e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
- if (~e->f & envFlag_preserve || force) {
+ if (!f || ~e->f & envFlag_preserve || force) {
if (f)
free(e->val);
bc__setenv(e, val);
return (e);
}
+/* --- @bc__addGroups@ --- *
+ *
+ * Arguments: @gid_t *g@ = pointer to a group array
+ * @int *png@ = pointer to number of entries in the array
+ * @const gid_t *a@ = pointer to groups to add
+ * @int na@ = number of groups to add
+ *
+ * Returns: Zero if it was OK, nonzero if we should stop now.
+ *
+ * Use: Adds groups to a groups array.
+ */
+
+static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
+{
+ int i, j;
+ int ng = *png;
+
+ for (i = 0; i < na; i++) {
+
+ /* --- Ensure this group isn't already in the list --- */
+
+ for (j = 0; j < ng; j++) {
+ if (a[i] == g[j])
+ goto next_group;
+ }
+
+ /* --- See if there's room for more --- */
+
+ if (ng > NGROUPS_MAX) {
+ moan("too many groups (system limit exceeded -- some have been lost");
+ *png = ng;
+ return (-1);
+ }
+
+ /* --- Add the group --- */
+
+ g[ng++] = a[i];
+ next_group:;
+ }
+
+ *png = ng;
+ return (0);
+}
+
/* --- @bc__banner@ --- *
*
* Arguments: @FILE *fp@ = stream to write on
"-s, --su, --set-user Set environment variables to reflect USER\n"
"-l, --login Really log in as USER\n"
"\n"
+"-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
+#ifdef HAVE_SETGROUPS
+"-k, --keep-groups Keep your current set of groups\n"
+"-m, --merge-groups Merge the lists of groups\n"
+"-r, --replace-groups Replace the list of groups\n"
+#endif
+"\n"
"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
"\n"
"-d, --daemon Start a daemon\n"
/* --- Become server setup parameters --- */
char *conffile = file_RULES; /* Default config file for daemon */
- int port = -1; /* Default port for daemon */
+ int port = 0; /* Default port for daemon */
/* --- Miscellanous shared variables --- */
unsigned flags = 0; /* Various useful flags */
int style = DEFAULT_LOGIN_STYLE; /* Login style */
+ gid_t group = -1; /* Default group to set */
+ int gstyle = g_unset; /* No group style set yet */
+
+#ifdef HAVE_SETGROUPS
+ gid_t groups[NGROUPS_MAX]; /* Set of groups */
+ int ngroups; /* Number of groups in the set */
+#endif
/* --- Default argument list executes a shell command --- */
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 */
+ f_setuid = 16, /* We're running setuid */
+ f_havegroup = 32 /* Set a default group */
};
/* --- Set up the program name --- */
{ "set-user", 0, 0, 's' },
{ "login", 0, 0, 'l' },
+ /* --- Group style options --- */
+
+ { "group", gFlag_argReq, 0, 'g' },
+#ifdef HAVE_SETGROUPS
+ { "keep-groups", 0, 0, 'k' },
+ { "merge-groups", 0, 0, 'm' },
+ { "replace-groups", 0, 0, 'r' },
+#endif
+
/* --- Command to run options --- */
{ "command", gFlag_argReq, 0, 'c' },
};
i = mdwopt(argc, argv,
- "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
+ "-" /* Return non-options as options */
+ "huv" /* Asking for help */
+ "esl" /* Login style options */
+#ifdef HAVE_SETGROUPS
+ "g:kmr" /* Group style options */
+#else
+ "g:" /* Group (without @setgroups@) */
+#endif
+ "c:" /* Command to run options */
+ "dp:f:" /* Server options */
+#ifdef TRACING
+ "I:T::L::" /* Tracing options */
+#endif
+ ,
opts, 0, 0, gFlag_envVar);
if (i < 0)
goto done_options;
style = l_preserve;
break;
case 's':
- style = l_user;
+ style = l_setuser;
break;
case 'l':
style = l_login;
break;
+ /* --- Group style options --- */
+
+ case 'g':
+ if (isdigit((unsigned char)optarg[0]))
+ group = atoi(optarg);
+ else {
+ struct group *gr = getgrnam(optarg);
+ if (!gr)
+ die("unknown group `%s'", optarg);
+ group = gr->gr_gid;
+ }
+ flags |= f_havegroup;
+ break;
+
+ case 'k':
+ gstyle = g_keep;
+ break;
+ case 'm':
+ gstyle = g_merge;
+ break;
+ case 'r':
+ gstyle = g_replace;
+ break;
+
/* --- Command to run options --- */
case 'c':
/* --- Server options --- */
case 'p':
- port = atoi(optarg);
+ if (isdigit((unsigned char)optarg[0]))
+ port = htons(atoi(optarg));
+ else {
+ struct servent *s = getservbyname(optarg, "udp");
+ if (!s)
+ die("unknown service name `%s'", optarg);
+ port = s->s_port;
+ }
break;
case 'd':
flags |= f_daemon;
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));
+ uid_t eu = geteuid(), ru = getuid();
+
+#ifdef HAVE_SETREUID
+ if (setreuid(eu, ru))
+#else
+ if (seteuid(ru))
+#endif
+ {
+ die("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",
optarg, strerror(errno));
}
+
+#ifdef HAVE_SETREUID
+ if (setreuid(ru, eu))
+#else
+ if (seteuid(eu))
+#endif
+ die("couldn't regain privileges: %s", strerror(errno));
}
traceon(fp, TRACE_DFL);
trace(TRACE_MISC, "become: tracing enabled");
}
bc__write(stdout,
"\n"
-"Also, `+' and `-' options are recognised to turn on and off vavrious\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"
/* --- None of the above --- */
if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
- if (who == 0)
+ if (!who) {
who = optarg;
- else {
+ break;
+ } else {
optind--;
goto done_options;
}
memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
}
+ /* --- Fiddle with group ownerships a bit --- */
+
+ {
+#ifdef HAVE_SETGROUPS
+ gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
+ int n_fgr, n_tgr;
+#endif
+
+ /* --- Set the default login group, if there is one --- */
+
+ if (~flags & f_havegroup)
+ group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
+
+#ifndef HAVE_SETGROUPS
+
+ /* --- Check that it's valid --- */
+
+ if (group != from_pw->pw_gid && group != to_pw->pw_gid)
+ die("invalid default group");
+
+#else
+
+ /* --- Set the default group style --- */
+
+ if (gstyle == g_unset)
+ gstyle = (style == l_login) ? g_replace : g_merge;
+
+ /* --- Read in my current set of groups --- */
+
+ n_fgr = getgroups(NGROUPS_MAX, from_gr);
+
+ /* --- Now read the groups for the target user --- *
+ *
+ * Do this even if I'm using the @g_keep@ style -- I'll use it for
+ * checking that @group@ is valid.
+ */
+
+ {
+ struct group *gr;
+ char **p;
+
+ n_tgr = 0;
+ to_gr[n_tgr++] = to_pw->pw_gid;
+
+ setgrent();
+ while ((gr = getgrent()) != 0) {
+ if (gr->gr_gid == to_gr[0])
+ continue;
+ for (p = gr->gr_mem; *p; p++) {
+ if (strcmp(to_pw->pw_name, *p) == 0) {
+ to_gr[n_tgr++] = gr->gr_gid;
+ if (n_tgr >= NGROUPS_MAX)
+ goto done_groups;
+ break;
+ }
+ }
+ }
+
+ done_groups:
+ endgrent();
+ }
+
+ /* --- Check that @group@ is reasonable --- */
+
+ {
+ int i;
+
+ if (group == getgid() || group == from_pw->pw_gid)
+ goto group_ok;
+ for (i = 0; i < n_fgr; i++) {
+ if (group == from_gr[i])
+ goto group_ok;
+ }
+ for (i = 0; i < n_tgr; i++) {
+ if (group == to_gr[i])
+ goto group_ok;
+ }
+ die("invalid default group");
+ group_ok:;
+ }
+
+ /* --- Phew. Now comes the hard bit --- */
+
+ {
+ gid_t ga[4];
+ int i;
+
+ i = 0;
+ ga[i++] = group;
+ if (gstyle & g_keep) {
+ ga[i++] = getgid();
+ ga[i++] = from_pw->pw_gid;
+ }
+ if (gstyle & g_replace)
+ ga[i++] = to_pw->pw_gid;
+
+ /* --- Style authorities will become apoplectic if shown this --- *
+ *
+ * As far as I can see, it's the neatest way of writing it.
+ */
+
+ ngroups = 0;
+ (void)(bc__addGroups(groups, &ngroups, ga, i) ||
+ ((gstyle & g_keep) &&
+ bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
+ ((gstyle & g_replace) &&
+ bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
+ }
+
+#endif
+
+ /* --- Trace the results of all this --- */
+
+ T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
+
+#ifdef HAVE_SETGROUPS
+ IF_TRACING(TRACE_SETUP, {
+ int i;
+
+ for (i = 1; i < ngroups; i++)
+ trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
+ })
+#endif
+ }
+
/* --- Shell commands are easy --- */
if (cmd) {
/* --- An su-like login needs slightly less effort --- */
- case l_user:
+ case l_setuser:
shell[0] = to_pw->pw_shell;
shell[1] = 0;
todo = shell;
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.
+ * 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[] = {
size_t sz;
unsigned f;
sym_iter i;
-
+
/* --- Stage one. Preserve display-specific variables --- */
for (pp = preserve; *pp; pp++) {
}
} /* Fall through */
- case l_user:
+ case l_setuser:
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);
q = e->val + 5;
for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
- if (p[0] == '.')
+ if (p[0] != '/')
continue;
while (*p)
*q++ = *p++;
p = *pp + 1;
if (memcmp(e->_base.name, p, strlen(p)) == 0)
goto expunge;
- } else if (strcmp(e->_base.name, p) == 0)
+ } else if (strcmp(e->_base.name, *pp) == 0)
goto expunge;
}
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 */
+ S_ISREG(st.st_mode)) /* Check it's a file */
break;
}
*
* 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.
+ * confusion. Otherwise, do what was wanted anyway. Also, don't bother
+ * checking if we're already root -- root can do anything anyway, and at
+ * least this way we get some logging done, and offer a more friendly
+ * front-end.
*/
if (rq.from == rq.to) {
exit(0);
}
} else {
- int a = check(&rq);
+ int a = (rq.from == 0) || check(&rq);
syslog(LOG_INFO,
"permission %s for %s to become %s to run `%s'",
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);
}
+
+#ifdef HAVE_SETGROUPS
+ if (setgroups(ngroups, groups) < 0)
+ die("couldn't set groups: %s", strerror(errno));
+#endif
+
+ if (setgid(group) < 0)
+ die("couldn't set default group: %s", strerror(errno));
+ if (setuid(rq.to) < 0)
+ die("couldn't set uid: %s", strerror(errno));
+
+ /* --- Finally, call the program --- */
+
+ fflush(0);
+ execve(rq.cmd, todo, env);
+ die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+ return (127);
}
/*----- That's all, folks -------------------------------------------------*/