Modular sudo front-end which loads policy and I/O plugins that do

most the actual work.  Currently relies on dynamic loading using
dlopen().  See doc/plugin.pod for the plugin API.
This commit is contained in:
Todd C. Miller
2010-02-20 09:41:49 -05:00
parent 90c06ad7f2
commit b6a4cf7233
18 changed files with 2445 additions and 452 deletions

208
src/Makefile.in Normal file
View File

@@ -0,0 +1,208 @@
#
# Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# @configure_input@
#
#### Start of system configuration section. ####
srcdir = @srcdir@
devdir = @devdir@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
incdir = $(top_srcdir)/include
# Compiler & tools to use
CC = @CC@
NROFF = nroff -Tascii
# Our install program supports extra flags...
INSTALL = $(SHELL) $(top_srcdir)/install-sh -c
# Libraries
LIBS = @LIBS@ @SUDO_LIBS@ @GETGROUPS_LIB@ @NET_LIBS@
# C preprocessor flags
CPPFLAGS = -I$(incdir) -I$(top_builddir) -I$(srcdir) -I. @CPPFLAGS@
# Usually -O and/or -g
CFLAGS = @CFLAGS@
# Flags to pass to the link stage
LDFLAGS = @LDFLAGS@
# Where to install things...
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
sbindir = @sbindir@
sysconfdir = @sysconfdir@
libexecdir = @libexecdir@
datarootdir = @datarootdir@
noexecfile = @NOEXECFILE@
noexecdir = @NOEXECDIR@
# User and group ids the installed files should be "owned" by
install_uid = 0
install_gid = 0
# Pass in paths and OS dependent defines
DEFS = @OSDEFS@
#### End of system configuration section. ####
SHELL = /bin/sh
PROGS = sudo
# XXX - add back missing ones:
# sudo_edit.c selinux.c
OBJS = sudo.o parse_args.o lbuf.o alloc.o error.o zero_bytes.o \
load_plugins.o conversation.o list.o fmt_string.o tgetpass.o \
fileops.o term.o atobool.o script.o pty.o
LIB_OBJS = @LIBOBJS@
VERSION = @PACKAGE_VERSION@
# XXX - update
SUDODEP = $(srcdir)/sudo.h $(incdir)/alloc.h \
$(incdir)/compat.h $(incdir)/error.h \
$(incdir)/list.h $(incdir)/missing.h \
$(top_builddir)/pathnames.h $(top_builddir)/config.h
all: $(PROGS)
.SUFFIXES: .o .c .h .l .y .man .cat .lo
.c.o:
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $<
.c.lo:
$(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $<
sudo: $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)
sudo_noexec.lo: $(srcdir)/sudo_noexec.c
$(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_noexec.c
sudo_noexec.la: sudo_noexec.lo
$(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir)
# Dependencies (not counting auth functions)
aix.o: $(srcdir)/aix.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/aix.c
alloc.o: $(srcdir)/alloc.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/alloc.c
audit.o: $(srcdir)/audit.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/audit.c
bsm_audit.o: $(srcdir)/bsm_audit.c $(SUDODEP) bsm_audit.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/bsm_audit.c
closefrom.o: $(srcdir)/closefrom.c $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/closefrom.c
error.o: $(srcdir)/error.c $(incdir)/compat.h $(incdir)/error.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/error.c
fileops.o: $(srcdir)/fileops.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/fileops.c
getcwd.o: $(srcdir)/getcwd.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getcwd.c
getline.o: $(srcdir)/getline.c $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getline.c
getprogname.o: $(srcdir)/getprogname.c $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/getprogname.c
isblank.o: $(srcdir)/isblank.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/isblank.c
lbuf.o: $(srcdir)/lbuf.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/lbuf.c
list.o: $(srcdir)/list.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/list.c
memrchr.o: $(srcdir)/memrchr.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/memrchr.c
mkstemp.o: $(srcdir)/mkstemp.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/mkstemp.c
nanosleep.o: $(srcdir)/nanosleep.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/nanosleep.c
pty.o: $(srcdir)/pty.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/pty.c
script.o: $(srcdir)/script.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/script.c
sigaction.o: $(srcdir)/sigaction.c $(incdir)/compat.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sigaction.c
snprintf.o: $(srcdir)/snprintf.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/snprintf.c
strcasecmp.o: $(srcdir)/strcasecmp.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strcasecmp.c
strerror.o: $(srcdir)/strerror.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strerror.c
strlcat.o: $(srcdir)/strlcat.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strlcat.c
strlcpy.o: $(srcdir)/strlcpy.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strlcpy.c
strsignal.o: $(srcdir)/strsignal.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/strsignal.c
selinux.o: $(srcdir)/selinux.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/selinux.c
sudo.o: $(srcdir)/sudo.c $(SUDODEP) sudo_usage.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo.c
sudo_edit.o: $(srcdir)/sudo_edit.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_edit.c
sudo_noexec.o: $(srcdir)/sudo_noexec.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sudo_noexec.c
term.o: $(srcdir)/term.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/term.c
tgetpass.o: $(srcdir)/tgetpass.c $(SUDODEP)
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/tgetpass.c
utimes.o: $(srcdir)/utimes.c $(incdir)/compat.h $(srcdir)/emul/utime.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/utimes.c
zero_bytes.o: $(srcdir)/zero_bytes.c $(incdir)/compat.h $(top_builddir)/config.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/zero_bytes.c
install: install-dirs install-binaries @INSTALL_NOEXEC@
install-dirs:
$(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(sudodir) \
$(DESTDIR)$(noexecdir)
install-binaries: install-dirs $(PROGS)
$(INSTALL) -O $(install_uid) -G $(install_gid) -M 4111 -s sudo $(DESTDIR)$(sudodir)/sudo
rm -f $(DESTDIR)$(sudodir)/sudoedit
ln $(DESTDIR)$(sudodir)/sudo $(DESTDIR)$(sudodir)/sudoedit
if [ -f sesh ]; then $(INSTALL) -O $(install_uid) -G $(install_gid) -M 0111 -s sesh $(DESTDIR)$(libexecdir)/sesh; fi
install-noexec: install-dirs sudo_noexec.la
if [ -f .libs/$(noexecfile) ]; then $(INSTALL) -O $(install_uid) -G $(install_gid) -M 0755 .libs/$(noexecfile) $(DESTDIR)$(noexecdir); fi
check:
@echo nothing to check
clean:
-rm -f *.a *.o *.lo stamp-* $(PROGS) core *.core core.*
mostlyclean: clean
# XXX - remove some
distclean: clean
-rm -rf Makefile pathnames.h config.h config.status config.cache \
config.log libtool sudo_noexec.lo .libs $(GENERATED) \
sudo_usage.h
clobber: distclean
realclean: distclean
rm -f TAGS tags
cleandir: realclean

109
src/conversation.c Normal file
View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 1999-2005, 2007-2009 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
# include <memory.h>
# endif
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* HAVE_STRING_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include "sudo.h"
#include "sudo_plugin.h"
extern int tgetpass_flags; /* XXX */
/*
* Sudo conversation function.
*/
int
sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[])
{
struct sudo_conv_reply *repl;
const struct sudo_conv_message *msg;
char *pass;
int n, flags = tgetpass_flags;
for (n = 0; n < num_msgs; n++) {
msg = &msgs[n];
repl = &replies[n];
switch (msg->msg_type) {
case SUDO_CONV_PROMPT_ECHO_ON:
SET(flags, TGP_ECHO);
case SUDO_CONV_PROMPT_ECHO_OFF:
/* Read the password unless interrupted. */
/* XXX - look up passwd timeout and pass in XXX */
pass = tgetpass(msg->msg, 0, flags);
if (pass == NULL)
goto err;
repl->reply = estrdup(pass);
zero_bytes(pass, strlen(pass));
break;
case SUDO_CONV_INFO_MSG:
if (msg->msg)
(void) puts(msg->msg);
break;
case SUDO_CONV_ERROR_MSG:
if (msg->msg) {
(void) fputs(msg->msg, stderr);
(void) fputc('\n', stderr);
}
break;
default:
goto err;
}
}
return(0);
err:
/* Zero and free allocated memory and return an error. */
do {
repl = &replies[n];
if (repl->reply != NULL) {
zero_bytes(repl->reply, strlen(repl->reply));
free(repl->reply);
repl->reply = NULL;
}
} while (n--);
return(-1);
}

View File

@@ -44,7 +44,7 @@
# include <time.h>
#endif
#ifndef HAVE_TIMESPEC
# include <compat/timespec.h>
# include <emul/timespec.h>
#endif
#include "sudo.h"

63
src/fmt_string.c Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
# include <memory.h>
# endif
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* HAVE_STRING_H */
#include "sudo.h"
/*
* Allocate storage for a name=value string and return it.
*/
char *
fmt_string(const char *var, const char *val)
{
size_t var_len = strlen(var);
size_t val_len = strlen(val);
char *cp, *str;
cp = str = emalloc(var_len + 1 + val_len + 1);
memcpy(cp, var, var_len);
cp += var_len;
*cp++ = '=';
memcpy(cp, val, val_len);
cp += val_len;
*cp = '\0';
return(str);
}

176
src/load_plugins.c Normal file
View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* HAVE_STRING_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <errno.h>
#include <dlfcn.h>
#include "sudo.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
/*
* Read in /etc/sudo.conf
* Returns a list of plugins.
*/
static struct plugin_info_list *
sudo_read_conf(const char *conf_file)
{
FILE *fp;
char *cp, *name, *path;
struct plugin_info *info;
static struct plugin_info_list pil; /* XXX */
if ((fp = fopen(conf_file, "r")) == NULL) {
/* Default values */
info = emalloc(sizeof(*info));
info->symbol_name = "sudoers";
info->path = "sudoers_policy";
info->prev = info;
info->next = NULL;
tq_append(&pil, info);
/* XXX - io plugin too */
goto done;
}
while ((cp = sudo_parseln(fp)) != NULL) {
/* Skip blank or comment lines */
if (*cp == '\0')
continue;
/* Look for a line starting with "Plugin" */
if (strncasecmp(cp, "Plugin", 6) != 0)
continue;
/* Parse line */
if ((name = strtok(cp + 6, " \t")) == NULL ||
(path = strtok(NULL, " \t")) == NULL) {
continue;
}
info = emalloc(sizeof(*info));
info->symbol_name = estrdup(name);
info->path = estrdup(path);
info->prev = info;
info->next = NULL;
tq_append(&pil, info);
}
fclose(fp);
done:
return(&pil);
}
/*
* Load the plugins listed in conf_file.
*/
void
sudo_load_plugins(const char *conf_file,
struct plugin_container *policy_plugin,
struct plugin_container_list *io_plugins)
{
struct generic_plugin *plugin;
struct plugin_container *container;
struct plugin_info *info;
struct plugin_info_list *plugin_list;
struct stat sb;
void *handle;
char path[PATH_MAX];
/* Parse sudo.conf */
plugin_list = sudo_read_conf(conf_file);
if (tq_empty(plugin_list))
errorx(1, "no plugins defined in %s", conf_file);
tq_foreach_fwd(plugin_list, info) {
if (info->path[0] == '/') {
if (strlcpy(path, info->path, sizeof(path) >= sizeof(path)))
errorx(1, "%s: %s", info->path, strerror(ENAMETOOLONG));
} else {
if (snprintf(path, sizeof(path), "%s/%s", _PATH_SUDO_PLUGIN_DIR,
info->path) >= sizeof(path)) {
errorx(1, "%s/%s: %s", _PATH_SUDO_PLUGIN_DIR, info->path,
strerror(ENAMETOOLONG));
}
}
if (stat(path, &sb) != 0)
error(1, "%s", path);
if (sb.st_uid != ROOT_UID)
errorx(1, "%s must be owned by uid %d", path, ROOT_UID);
if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
errorx(1, "%s must be only be writable by owner", path);
/* Open plugin and map in symbol */
handle = dlopen(path, RTLD_NOW);
if (!handle)
errorx(1, "unable to dlopen %s: %s", path, dlerror());
plugin = dlsym(handle, info->symbol_name);
if (!plugin)
errorx(1, "unable to find symbol %s in %s", info->symbol_name, path);
if (plugin->type != SUDO_POLICY_PLUGIN && plugin->type != SUDO_IO_PLUGIN) {
errorx(1, "%s: unknown policy type %d", path, plugin->type);
}
if (SUDO_API_VERSION_GET_MAJOR(plugin->version) != SUDO_API_VERSION_MAJOR) {
errorx(1, "%s: incompatible policy major version %d, expected %d",
path, SUDO_API_VERSION_GET_MAJOR(plugin->version),
SUDO_API_VERSION_MAJOR);
}
if (plugin->type == SUDO_POLICY_PLUGIN) {
if (policy_plugin->handle)
errorx(1, "only a single policy plugin may be loaded");
policy_plugin->handle = handle;
policy_plugin->name = info->symbol_name;
policy_plugin->u.generic = plugin;
} else if (plugin->type == SUDO_IO_PLUGIN) {
container = emalloc(sizeof(*container));
container->prev = container;
container->next = NULL;
container->handle = handle;
container->name = info->symbol_name;
container->u.generic = plugin;
tq_append(io_plugins, container);
}
}
if (policy_plugin->handle == NULL)
errorx(1, "%s: at least one policy plugin must be specified", conf_file);
if (policy_plugin->u.policy->check_policy == NULL)
errorx(1, "policy plugin %s does not include a check_policy method");
}

431
src/parse_args.c Normal file
View File

@@ -0,0 +1,431 @@
/*
* Copyright (c) 1993-1996, 1998-2009 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
#include <config.h>
/* XXX - prune this */
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
# include <memory.h>
# endif
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* HAVE_STRING_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <pwd.h>
#include <grp.h>
#include <sudo_usage.h>
#include "sudo.h"
#include "lbuf.h" /* XXX */
/* For getopt(3) */
extern char *optarg;
extern int optind;
/* XXX - better home for these and extern in header file */
int tgetpass_flags;
int user_closefrom = -1;
const char *list_user, *runas_user, *runas_group;
/*
* Local functions.
*/
static void usage(int) __attribute__((__noreturn__));
static void usage_excl(int) __attribute__((__noreturn__));
/*
* Mapping of command line flags to name/value settings.
*/
struct name_value_pair {
const char *name;
const char *value;
};
static struct sudo_settings {
struct name_value_pair a;
struct name_value_pair c;
struct name_value_pair D;
struct name_value_pair E;
struct name_value_pair g;
struct name_value_pair H;
struct name_value_pair i;
struct name_value_pair k;
struct name_value_pair p;
struct name_value_pair r;
struct name_value_pair t;
struct name_value_pair U;
struct name_value_pair u;
} sudo_settings = {
{ "bsdauth_type" },
{ "login_class" },
{ "debug_level" },
{ "preserve_environment" },
{ "runas_group" },
{ "set_home" },
{ "login_shell" },
{ "ignore_ticket" },
{ "prompt" },
{ "selinux_role" },
{ "selinux_type" },
{ "runas_user" }
};
static struct name_value_pair *setting_pairs[] = {
&sudo_settings.a,
&sudo_settings.c,
&sudo_settings.D,
&sudo_settings.E,
&sudo_settings.g,
&sudo_settings.H,
&sudo_settings.i,
&sudo_settings.p,
&sudo_settings.r,
&sudo_settings.t,
&sudo_settings.u
};
#define settings_size (sizeof(setting_pairs) / sizeof(struct name_value_pair))
/*
* Command line argument parsing.
* Sets nargc and nargv which corresponds to the argc/argv we'll use
* for the command to be run (if we are running one).
*/
int
parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp,
char ***env_addp)
{
int mode = 0; /* what mode is sudo to be run in? */
int flags = 0; /* mode flags */
int valid_flags, ch;
int i, j;
char **settings;
char **env_add;
int nenv = 0;
int env_size = 32;
env_add = emalloc2(env_size, sizeof(char *));
env_add[0] = NULL;
/* First, check to see if we were invoked as "sudoedit". */
if (strcmp(getprogname(), "sudoedit") == 0)
mode = MODE_EDIT;
/* Returns true if the last option string was "--" */
#define got_end_of_args (optind > 1 && argv[optind - 1][0] == '-' && \
argv[optind - 1][1] == '-' && argv[optind - 1][2] == '\0')
/* Returns true if next option is an environment variable */
#define is_envar (optind < argc && argv[optind][0] != '/' && \
strchr(argv[optind], '=') != NULL)
/* Flags allowed when running a command */
valid_flags = MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|
MODE_LOGIN_SHELL|MODE_INVALIDATE|MODE_NONINTERACTIVE|
MODE_PRESERVE_GROUPS|MODE_SHELL;
/* XXX - should fill in settings at the end to avoid dupes */
for (;;) {
/*
* We disable arg permutation for GNU getopt().
* Some trickiness is required to allow environment variables
* to be interspersed with command line options.
*/
if ((ch = getopt(argc, argv, "+Aa:bC:c:D:Eeg:HhiKkLlnPp:r:Sst:U:u:Vv")) != -1) {
switch (ch) {
case 'A':
SET(tgetpass_flags, TGP_ASKPASS);
break;
#ifdef HAVE_BSD_AUTH_H
case 'a':
sudo_settings.a.value = optarg;
break;
#endif
case 'b':
SET(flags, MODE_BACKGROUND);
break;
case 'C':
if ((user_closefrom = atoi(optarg)) < 3) {
warningx("the argument to -C must be at least 3");
usage(1);
}
break;
#ifdef HAVE_LOGIN_CAP_H
case 'c':
sudo_settings.c.value = optarg;
break;
#endif
case 'D':
sudo_settings.D.value = optarg;
break;
case 'E':
sudo_settings.c.value = "true";
break;
case 'e':
if (mode && mode != MODE_EDIT)
usage_excl(1);
mode = MODE_EDIT;
valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE;
break;
case 'g':
runas_group = optarg;
sudo_settings.g.value = optarg;
break;
case 'H':
sudo_settings.H.value = optarg;
break;
case 'h':
if (mode && mode != MODE_HELP) {
if (strcmp(getprogname(), "sudoedit") != 0)
usage_excl(1);
}
mode = MODE_HELP;
valid_flags = 0;
break;
case 'i':
sudo_settings.i.value = "true";
SET(flags, MODE_LOGIN_SHELL);
break;
case 'k':
sudo_settings.k.value = "true";
SET(flags, MODE_INVALIDATE);
break;
case 'K':
sudo_settings.k.value = "true";
if (mode && mode != MODE_KILL)
usage_excl(1);
mode = MODE_KILL;
valid_flags = 0;
break;
case 'l':
if (mode) {
if (mode == MODE_LIST)
SET(flags, MODE_LONG_LIST);
else
usage_excl(1);
}
mode = MODE_LIST;
valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE|MODE_LONG_LIST;
break;
case 'n':
SET(flags, MODE_NONINTERACTIVE);
break;
case 'P':
SET(flags, MODE_PRESERVE_GROUPS);
break;
case 'p':
sudo_settings.i.value = optarg;
break;
#ifdef HAVE_SELINUX
case 'r':
sudo_settings.r.value = optarg;
break;
case 't':
sudo_settings.t.value = optarg;
break;
#endif
case 'S':
SET(tgetpass_flags, TGP_STDIN);
break;
case 's':
SET(flags, MODE_SHELL);
break;
case 'U':
/* XXX - sudo_getpwnam */
if ((getpwnam(optarg)) == NULL)
errorx(1, "unknown user: %s", optarg);
list_user = optarg;
break;
case 'u':
runas_user = optarg;
sudo_settings.u.value = optarg;
break;
case 'v':
if (mode && mode != MODE_VALIDATE)
usage_excl(1);
mode = MODE_VALIDATE;
valid_flags = MODE_INVALIDATE|MODE_NONINTERACTIVE;
break;
case 'V':
if (mode && mode != MODE_VERSION)
usage_excl(1);
mode = MODE_VERSION;
valid_flags = 0;
break;
default:
usage(1);
}
} else if (!got_end_of_args && is_envar) {
if (nenv == env_size - 2) {
env_size *= 2;
env_add = erealloc3(env_add, env_size, sizeof(char *));
}
env_add[nenv++] = argv[optind];
/* Crank optind and resume getopt. */
optind++;
} else {
/* Not an option or an environment variable -- we're done. */
break;
}
}
*nargc = argc - optind;
*nargv = argv + optind;
if (!mode) {
/* Defer -k mode setting until we know whether it is a flag or not */
if (ISSET(flags, MODE_INVALIDATE) && *nargc == 0) {
mode = MODE_INVALIDATE; /* -k by itself */
CLR(flags, MODE_INVALIDATE);
sudo_settings.k.value = NULL;
valid_flags = 0;
} else {
mode = MODE_RUN; /* running a command */
}
}
if (*nargc > 0 && mode == MODE_LIST)
mode = MODE_CHECK;
if (ISSET(flags, MODE_LOGIN_SHELL)) {
if (ISSET(flags, MODE_SHELL)) {
warningx("you may not specify both the `-i' and `-s' options");
usage(1);
}
if (ISSET(flags, MODE_PRESERVE_ENV)) {
warningx("you may not specify both the `-i' and `-E' options");
usage(1);
}
SET(flags, MODE_SHELL);
}
if ((flags & valid_flags) != flags)
usage(1);
if (mode == MODE_EDIT &&
(ISSET(flags, MODE_PRESERVE_ENV) || env_add[0] != NULL)) {
if (ISSET(mode, MODE_PRESERVE_ENV))
warningx("the `-E' option is not valid in edit mode");
if (env_add[0] != NULL)
warningx("you may not specify environment variables in edit mode");
usage(1);
}
if ((runas_user != NULL || runas_group != NULL) &&
!ISSET(mode, MODE_EDIT | MODE_RUN | MODE_CHECK | MODE_VALIDATE)) {
usage(1);
}
if (list_user != NULL && mode != MODE_LIST && mode != MODE_CHECK) {
warningx("the `-U' option may only be used with the `-l' option");
usage(1);
}
if (ISSET(tgetpass_flags, TGP_STDIN) && ISSET(tgetpass_flags, TGP_ASKPASS)) {
warningx("the `-A' and `-S' options may not be used together");
usage(1);
}
if ((*nargc == 0 && mode == MODE_EDIT) ||
(*nargc > 0 && !ISSET(mode, MODE_RUN | MODE_EDIT | MODE_CHECK)))
usage(1);
if (*nargc == 0 && mode == MODE_RUN && !ISSET(flags, MODE_SHELL))
SET(flags, (MODE_IMPLIED_SHELL | MODE_SHELL));
if (mode == MODE_HELP)
usage(0);
/*
* Format setting_pairs into settings array.
*/
settings = emalloc2(settings_size + 1, sizeof (char *));
for (i = 0, j = 0; i < settings_size; i++) {
if (setting_pairs[i]->value) {
settings[j++] = fmt_string(setting_pairs[i]->name,
setting_pairs[i]->value);
}
}
settings[j] = NULL;
*settingsp = settings;
*env_addp = env_add;
return(mode | flags);
}
/*
* Give usage message and exit.
* The actual usage strings are in sudo_usage.h for configure substitution.
* XXX - avoid lbuf usage
*/
static void
usage(int exit_val)
{
struct lbuf lbuf;
char *uvec[6];
int i, ulen;
/*
* Use usage vectors appropriate to the progname.
*/
if (strcmp(getprogname(), "sudoedit") == 0) {
uvec[0] = SUDO_USAGE5 + 3;
uvec[1] = NULL;
} else {
uvec[0] = SUDO_USAGE1;
uvec[1] = SUDO_USAGE2;
uvec[2] = SUDO_USAGE3;
uvec[3] = SUDO_USAGE4;
uvec[4] = SUDO_USAGE5;
uvec[5] = NULL;
}
/*
* Print usage and wrap lines as needed, depending on the
* tty width.
*/
ulen = (int)strlen(getprogname()) + 8;
lbuf_init(&lbuf, NULL, ulen, 0);
for (i = 0; uvec[i] != NULL; i++) {
lbuf_append(&lbuf, "usage: ", getprogname(), uvec[i], NULL);
lbuf_print(&lbuf);
}
lbuf_destroy(&lbuf);
exit(exit_val);
}
/*
* Tell which options are mutually exclusive and exit.
*/
static void
usage_excl(int exit_val)
{
warningx("Only one of the -e, -h, -i, -K, -l, -s, -v or -V options may be specified");
usage(exit_val);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
* Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -58,31 +58,23 @@
#if defined(HAVE_OPENPTY)
int
get_pty(master, slave, name, namesz)
int *master;
int *slave;
char *name;
size_t namesz;
get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
{
struct group *gr;
gid_t ttygid = -1;
if ((gr = sudo_getgrnam("tty")) != NULL)
if ((gr = getgrnam("tty")) != NULL)
ttygid = gr->gr_gid;
if (openpty(master, slave, name, NULL, NULL) != 0)
return(0);
(void) chown(name, runas_pw->pw_uid, ttygid);
(void) chown(name, uid, ttygid);
return(1);
}
#elif defined(HAVE__GETPTY)
int
get_pty(master, slave, name, namesz)
int *master;
int *slave;
char *name;
size_t namesz;
get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
{
char *line;
@@ -95,7 +87,7 @@ get_pty(master, slave, name, namesz)
close(*master);
return(0);
}
(void) chown(line, runas_pw->pw_uid, -1);
(void) chown(line, uid, -1);
strlcpy(name, line, namesz);
return(1);
}
@@ -117,11 +109,7 @@ posix_openpt(oflag)
# endif /* HAVE_POSIX_OPENPT */
int
get_pty(master, slave, name, namesz)
int *master;
int *slave;
char *name;
size_t namesz;
get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
{
char *line;
@@ -148,7 +136,7 @@ get_pty(master, slave, name, namesz)
ioctl(*slave, I_PUSH, "ptem"); /* pseudo tty emulation module */
ioctl(*slave, I_PUSH, "ldterm"); /* line discipline module */
# endif
(void) chown(line, runas_pw->pw_uid, -1);
(void) chown(line, uid, -1);
strlcpy(name, line, namesz);
return(1);
}
@@ -158,17 +146,13 @@ get_pty(master, slave, name, namesz)
static char line[] = "/dev/ptyXX";
int
get_pty(master, slave, name, namesz)
int *master;
int *slave;
char *name;
size_t namesz;
get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid)
{
char *bank, *cp;
struct group *gr;
gid_t ttygid = -1;
if ((gr = sudo_getgrnam("tty")) != NULL)
if ((gr = getgrnam("tty")) != NULL)
ttygid = gr->gr_gid;
for (bank = "pqrs"; *bank != '\0'; bank++) {
@@ -182,7 +166,7 @@ get_pty(master, slave, name, namesz)
continue; /* already in use */
}
line[sizeof("/dev/p") - 2] = 't';
(void) chown(line, runas_pw->pw_uid, ttygid);
(void) chown(line, uid, ttygid);
(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
# ifdef HAVE_REVOKE
(void) revoke(line);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Todd C. Miller <Todd.Miller@courtesan.com>
* Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -33,7 +33,7 @@
#endif /* HAVE_TERMIOS_H */
#include <sys/ioctl.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
# include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
#include <stdio.h>
#ifdef STDC_HEADERS
@@ -68,39 +68,29 @@
#ifdef HAVE_SELINUX
# include <selinux/selinux.h>
#endif
#ifdef HAVE_ZLIB
# include <zlib.h>
#endif
#include "sudo.h"
#include "sudo.h" /* XXX? */
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
#define SFD_MASTER 0
#define SFD_SLAVE 1
#define SFD_LOG 2
#define SFD_OUTPUT 3
#define SFD_TIMING 4
#define SFD_USERTTY 5
#define SFD_USERTTY 2
#define TERM_COOKED 0
#define TERM_CBREAK 1
#define TERM_RAW 2
union script_fd {
FILE *f;
#ifdef HAVE_ZLIB
gzFile g;
#endif
};
struct script_buf {
int len; /* buffer length (how much read in) */
int off; /* write position (how much already consumed) */
char buf[16 * 1024];
};
static int script_fds[6];
static int script_fds[3];
static int ttyout;
/* XXX - use an array of signals instead of just a single variable */
static sig_atomic_t alive = 1;
static sig_atomic_t recvsig = 0;
static sig_atomic_t ttymode = TERM_COOKED;
@@ -114,247 +104,68 @@ static int foreground;
static char slavename[PATH_MAX];
static int suspend_parent __P((int signo, struct script_buf *output,
struct timeval *then, struct timeval *now, union script_fd ofile,
union script_fd tfile));
static void flush_output __P((struct script_buf *output, struct timeval *then,
struct timeval *now, union script_fd ofile, union script_fd tfile));
static void handler __P((int s));
static void script_child __P((char *path, char *argv[], int, int));
static void script_run __P((char *path, char *argv[], int));
static void sigchild __P((int s));
static void sigwinch __P((int s));
static void sync_winsize __P((int src, int dst));
static int suspend_parent(int signo, struct script_buf *output);
static void flush_output(struct script_buf *output);
static void handler(int s);
static int script_child(const char *path, char *argv[], char *envp[], int, int);
static void script_run(const char *path, char *argv[], char *envp[], int);
static void sigchild(int s);
static void sigwinch(int s);
static void sync_winsize(int src, int dst);
extern int get_pty __P((int *master, int *slave, char *name, size_t namesz));
/*
* TODO: run monitor as root?
*/
static int
fdcompar(v1, v2)
const void *v1;
const void *v2;
{
int i = *(int *)v1;
int j = *(int *)v2;
return(script_fds[i] - script_fds[j]);
}
/* sudo.c */
extern struct plugin_container_list io_plugins;
void
script_nextid()
script_setup(uid_t uid)
{
struct stat sb;
char buf[32], *ep;
int fd, i, ch;
unsigned long id = 0;
int len;
ssize_t nread;
char pathbuf[PATH_MAX];
/*
* Create _PATH_SUDO_TRANSCRIPT if it doesn't already exist.
*/
if (stat(_PATH_SUDO_TRANSCRIPT, &sb) != 0) {
if (mkdir(_PATH_SUDO_TRANSCRIPT, S_IRWXU) != 0)
log_error(USE_ERRNO, "Can't mkdir %s", _PATH_SUDO_TRANSCRIPT);
} else if (!S_ISDIR(sb.st_mode)) {
log_error(0, "%s exists but is not a directory (0%o)",
_PATH_SUDO_TRANSCRIPT, (unsigned int) sb.st_mode);
}
/*
* Open sequence file
*/
len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", _PATH_SUDO_TRANSCRIPT);
if (len <= 0 || len >= sizeof(pathbuf)) {
errno = ENAMETOOLONG;
log_error(USE_ERRNO, "%s/seq", pathbuf);
}
fd = open(pathbuf, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
if (fd == -1)
log_error(USE_ERRNO, "cannot open %s", pathbuf);
lock_file(fd, SUDO_LOCK);
/* Read seq number (base 36). */
nread = read(fd, buf, sizeof(buf));
if (nread != 0) {
if (nread == -1)
log_error(USE_ERRNO, "cannot read %s", pathbuf);
id = strtoul(buf, &ep, 36);
if (buf == ep || id >= 2176782336U)
log_error(0, "invalid sequence number %s", pathbuf);
}
id++;
/*
* Convert id to a string and stash in sudo_user.sessid.
* Note that that least significant digits go at the end of the string.
*/
for (i = 5; i >= 0; i--) {
ch = id % 36;
id /= 36;
buf[i] = ch < 10 ? ch + '0' : ch - 10 + 'A';
}
buf[6] = '\n';
/* Stash id logging purposes */
memcpy(sudo_user.sessid, buf, 6);
sudo_user.sessid[6] = '\0';
/* Rewind and overwrite old seq file. */
if (lseek(fd, 0, SEEK_SET) == (off_t)-1 || write(fd, buf, 7) != 7)
log_error(USE_ERRNO, "Can't write to %s", pathbuf);
close(fd);
}
static int
build_idpath(pathbuf)
char *pathbuf;
{
struct stat sb;
int i, len;
if (sudo_user.sessid[0] == '\0')
log_error(0, "tried to build a session id path without a session id");
/*
* Path is of the form /var/log/sudo-session/00/00/01.
*/
len = snprintf(pathbuf, PATH_MAX, "%s/%c%c/%c%c/%c%c", _PATH_SUDO_TRANSCRIPT,
sudo_user.sessid[0], sudo_user.sessid[1], sudo_user.sessid[2],
sudo_user.sessid[3], sudo_user.sessid[4], sudo_user.sessid[5]);
if (len <= 0 && len >= PATH_MAX) {
errno = ENAMETOOLONG;
log_error(USE_ERRNO, "%s/%s", _PATH_SUDO_TRANSCRIPT, sudo_user.sessid);
}
/*
* Create the intermediate subdirs as needed.
*/
for (i = 6; i > 0; i -= 3) {
pathbuf[len - i] = '\0';
if (stat(pathbuf, &sb) != 0) {
if (mkdir(pathbuf, S_IRWXU) != 0)
log_error(USE_ERRNO, "Can't mkdir %s", pathbuf);
} else if (!S_ISDIR(sb.st_mode)) {
log_error(0, "%s: %s", pathbuf, strerror(ENOTDIR));
}
pathbuf[len - i] = '/';
}
return(len);
}
void
script_setup()
{
char pathbuf[PATH_MAX];
int len;
script_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
if (script_fds[SFD_USERTTY] == -1)
log_error(0, "tty required for transcript support"); /* XXX */
errorx(1, "tty required for transcript support");
if (!get_pty(&script_fds[SFD_MASTER], &script_fds[SFD_SLAVE],
slavename, sizeof(slavename)))
log_error(USE_ERRNO, "Can't get pty");
/*
* Build a path containing the session id split into two-digit subdirs,
* so ID 000001 becomes /var/log/sudo-session/00/00/01.
*/
len = build_idpath(pathbuf);
/*
* We create 3 files: a log file, one for the raw session data,
* and one for the timing info.
*/
script_fds[SFD_LOG] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
if (script_fds[SFD_LOG] == -1)
log_error(USE_ERRNO, "Can't create %s", pathbuf);
strlcat(pathbuf, ".scr", sizeof(pathbuf));
script_fds[SFD_OUTPUT] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY,
S_IRUSR|S_IWUSR);
if (script_fds[SFD_OUTPUT] == -1)
log_error(USE_ERRNO, "Can't create %s", pathbuf);
pathbuf[len] = '\0';
strlcat(pathbuf, ".tim", sizeof(pathbuf));
script_fds[SFD_TIMING] = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
if (script_fds[SFD_TIMING] == -1)
log_error(USE_ERRNO, "Can't create %s", pathbuf);
slavename, sizeof(slavename), uid))
error(1, "Can't get pty");
}
int
script_duplow(fd)
int fd;
{
int i, j, indices[6];
/* sort fds so we can dup them safely */
for (i = 0; i < 6; i++)
indices[i] = i;
qsort(indices, 6, sizeof(int), fdcompar);
/* Move pty master/slave and session fds to low numbered fds. */
for (i = 0; i < 6; i++) {
j = indices[i];
if (script_fds[j] != fd) {
#ifdef HAVE_DUP2
dup2(script_fds[j], fd);
#else
close(fd);
dup(script_fds[j]);
close(script_fds[j]);
#endif
}
script_fds[j] = fd++;
}
return(fd);
}
/* Update output and timing files. */
/* Call I/O plugin input method. */
static void
log_output(buf, n, then, now, ofile, tfile)
char *buf;
int n;
struct timeval *then;
struct timeval *now;
union script_fd ofile;
union script_fd tfile;
log_input(char *buf, unsigned int n)
{
struct timeval tv;
struct plugin_container *plugin;
sigset_t omask;
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
#ifdef HAVE_ZLIB
if (def_compress_transcript)
gzwrite(ofile.g, buf, n);
else
#endif
fwrite(buf, 1, n, ofile.f);
timersub(now, then, &tv);
#ifdef HAVE_ZLIB
if (def_compress_transcript)
gzprintf(tfile.g, "%f %d\n",
tv.tv_sec + ((double)tv.tv_usec / 1000000), n);
else
#endif
fprintf(tfile.f, "%f %d\n",
tv.tv_sec + ((double)tv.tv_usec / 1000000), n);
then->tv_sec = now->tv_sec;
then->tv_usec = now->tv_usec;
tq_foreach_fwd(&io_plugins, plugin) {
/* XXX - die if return != TRUE */
if (plugin->u.io->log_input)
plugin->u.io->log_input(buf, n);
}
sigprocmask(SIG_SETMASK, &omask, NULL);
}
/* Call I/O plugin output method. */
static void
log_output(char *buf, unsigned int n)
{
struct plugin_container *plugin;
sigset_t omask;
sigprocmask(SIG_BLOCK, &ttyblock, &omask);
tq_foreach_fwd(&io_plugins, plugin) {
/* XXX - die if return != TRUE */
if (plugin->u.io->log_output)
plugin->u.io->log_output(buf, n);
}
sigprocmask(SIG_SETMASK, &omask, NULL);
}
static void
check_foreground()
check_foreground(void)
{
foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == parent;
if (foreground && !tty_initialized) {
@@ -370,13 +181,7 @@ check_foreground()
* Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
*/
static int
suspend_parent(signo, output, then, now, ofile, tfile)
int signo;
struct script_buf *output;
struct timeval *then;
struct timeval *now;
union script_fd ofile;
union script_fd tfile;
suspend_parent(int signo, struct script_buf *output)
{
sigaction_t sa, osa;
int n, oldmode = ttymode, rval = 0;
@@ -404,7 +209,7 @@ suspend_parent(signo, output, then, now, ofile, tfile)
/* FALLTHROUGH */
case SIGTSTP:
/* Flush any remaining output to master tty. */
flush_output(output, then, now, ofile, tfile);
flush_output(output);
/* Restore original tty mode before suspending. */
if (oldmode != TERM_COOKED) {
@@ -469,19 +274,18 @@ suspend_parent(signo, output, then, now, ofile, tfile)
* controlling tty, belongs to child's session but has its own pgrp.
*/
int
script_execv(path, argv)
char *path;
char *argv[];
script_execve(struct command_details *details, char *argv[], char *envp[],
struct command_status *cstat)
{
sigaction_t sa;
struct script_buf input, output;
struct timeval now, then;
int n, nready, exitcode = 1;
int n, nready;
int relaysig, sv[2];
fd_set *fdsr, *fdsw;
FILE *idfile;
union script_fd ofile, tfile;
int rbac_enabled = 0;
int maxfd;
cstat->type = 0; /* XXX */
#ifdef HAVE_SELINUX
rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
@@ -491,7 +295,7 @@ script_execv(path, argv)
close(script_fds[SFD_SLAVE]);
script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0);
if (script_fds[SFD_SLAVE] == -1)
log_error(USE_ERRNO, "cannot open %s", slavename);
error(1, "cannot open %s", slavename);
}
#endif
@@ -542,8 +346,8 @@ script_execv(path, argv)
* We communicate with the child over a bi-directional pipe.
* Parent sends signal info to child and child sends back wait status.
*/
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) != 0)
log_error(USE_ERRNO, "cannot create sockets");
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
error(1, "cannot create sockets");
if (foreground) {
/* Copy terminal attrs from user tty -> pty slave. */
@@ -559,7 +363,7 @@ script_execv(path, argv)
ttymode == TERM_CBREAK);
} while (!n && errno == EINTR);
if (!n)
log_error(USE_ERRNO, "Can't set terminal to raw mode");
error(1, "Can't set terminal to raw mode");
}
/*
@@ -569,43 +373,22 @@ script_execv(path, argv)
child = fork();
switch (child) {
case -1:
log_error(USE_ERRNO, "fork");
error(1, "fork");
break;
case 0:
/* child */
close(sv[0]);
script_child(path, argv, sv[1], rbac_enabled);
/* NOTREACHED */
break;
if (exec_setup(details) == 0) {
/* headed for execve() */
script_child(details->command, argv, envp, sv[1], rbac_enabled);
}
cstat->type = CMD_ERRNO;
cstat->val = errno;
send(sv[1], cstat, sizeof(*cstat), 0);
_exit(1);
}
close(sv[1]);
if ((idfile = fdopen(script_fds[SFD_LOG], "w")) == NULL)
log_error(USE_ERRNO, "fdopen");
#ifdef HAVE_ZLIB
if (def_compress_transcript) {
if ((ofile.g = gzdopen(script_fds[SFD_OUTPUT], "w")) == NULL)
log_error(USE_ERRNO, "gzdopen");
if ((tfile.g = gzdopen(script_fds[SFD_TIMING], "w")) == NULL)
log_error(USE_ERRNO, "gzdopen");
} else
#endif
{
if ((ofile.f = fdopen(script_fds[SFD_OUTPUT], "w")) == NULL)
log_error(USE_ERRNO, "fdopen");
if ((tfile.f = fdopen(script_fds[SFD_TIMING], "w")) == NULL)
log_error(USE_ERRNO, "fdopen");
}
gettimeofday(&then, NULL);
/* XXX - log more stuff? window size? environment? */
fprintf(idfile, "%ld:%s:%s:%s:%s\n", then.tv_sec, user_name,
runas_pw->pw_name, runas_gr ? runas_gr->gr_name : "", user_tty);
fprintf(idfile, "%s\n", user_cwd);
fprintf(idfile, "%s%s%s\n", user_cmnd, user_args ? " " : "",
user_args ? user_args : "");
fclose(idfile);
n = fcntl(script_fds[SFD_MASTER], F_GETFL, 0);
if (n != -1) {
n |= O_NONBLOCK;
@@ -622,13 +405,19 @@ script_execv(path, argv)
(void) fcntl(STDOUT_FILENO, F_SETFL, n);
}
/* Max fd we will be selecting on. */
maxfd = sv[0];
if (maxfd < script_fds[SFD_MASTER])
maxfd = script_fds[SFD_MASTER];
if (maxfd < script_fds[SFD_USERTTY])
maxfd = script_fds[SFD_USERTTY];
/*
* In the event loop we pass input from user tty to master
* and pass output from master to stdout and ofile. Note that
* we've set things up such that master is > 3 (see sudo.c).
* and pass output from master to stdout and IO plugin.
*/
fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
fdsw = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(&input, sizeof(input));
zero_bytes(&output, sizeof(output));
while (alive) {
@@ -643,8 +432,8 @@ script_execv(path, argv)
if (output.off == output.len)
output.off = output.len = 0;
zero_bytes(fdsw, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(fdsr, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
if (ttymode == TERM_RAW && input.len != sizeof(input.buf))
FD_SET(script_fds[SFD_USERTTY], fdsr);
@@ -658,51 +447,50 @@ script_execv(path, argv)
if (relaysig)
FD_SET(sv[0], fdsw);
nready = select(sv[0] + 1, fdsr, fdsw, NULL, NULL);
nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL);
if (nready == -1) {
if (errno == EINTR)
continue;
log_error(USE_ERRNO, "select failed");
error(1, "select failed");
}
if (FD_ISSET(sv[0], fdsr)) {
/* read child status */
n = read(sv[0], &child_status, sizeof(child_status));
n = recv(sv[0], cstat, sizeof(*cstat), 0);
if (n == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN)
break;
} else if (n != sizeof(child_status)) {
} else if (n != sizeof(*cstat)) {
break; /* EOF? */
}
if (WIFSTOPPED(child_status)) {
/* Suspend parent and tell child how to resume on return. */
if (cstat->type == CMD_WSTATUS) {
if (WIFSTOPPED(cstat->val)) {
/* Suspend parent and tell child how to resume on return. */
#ifdef SCRIPT_DEBUG
warningx("child stopped, suspending parent");
warningx("child stopped, suspending parent");
#endif
relaysig = suspend_parent(WSTOPSIG(child_status),
&output, &then, &now, ofile, tfile);
/* XXX - write relaysig immediately? */
continue;
} else {
/* Child exited or was killed, either way we are done. */
if (WIFEXITED(child_status))
exitcode = WEXITSTATUS(child_status);
else if (WIFSIGNALED(child_status))
exitcode = WTERMSIG(child_status) | 128;
relaysig = suspend_parent(WSTOPSIG(cstat->val), &output);
/* XXX - write relaysig immediately? */
continue;
} else {
/* Child exited or was killed, either way we are done. */
break;
}
} else if (cstat->type == CMD_ERRNO) {
/* Child was unable to execute command. */
break;
}
}
if (FD_ISSET(sv[0], fdsw)) {
/* XXX - we rely on child to be suspended before we suspend us */
n = write(sv[0], &relaysig, sizeof(relaysig));
cstat->type = CMD_SIGNO;
cstat->val = relaysig;
relaysig = 0;
if (n == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN)
break;
} else if (n != sizeof(relaysig)) {
do {
n = send(sv[0], cstat, sizeof(*cstat), 0);
} while (n == -1 && errno == EINTR);
if (n != sizeof(relaysig)) {
break; /* should not happen */
}
}
@@ -717,6 +505,7 @@ script_execv(path, argv)
} else {
if (n == 0)
break; /* got EOF */
log_input(input.buf + input.len, n);
input.len += n;
}
}
@@ -733,7 +522,6 @@ script_execv(path, argv)
}
}
if (FD_ISSET(script_fds[SFD_MASTER], fdsr)) {
gettimeofday(&now, NULL);
n = read(script_fds[SFD_MASTER], output.buf + output.len,
sizeof(output.buf) - output.len);
if (n == -1) {
@@ -744,9 +532,7 @@ script_execv(path, argv)
} else {
if (n == 0)
break; /* got EOF */
/* Update output and timing files. */
log_output(output.buf + output.len, n, &then, &now, ofile, tfile);
log_output(output.buf + output.len, n);
output.len += n;
}
}
@@ -770,26 +556,15 @@ script_execv(path, argv)
n &= ~O_NONBLOCK;
(void) fcntl(STDOUT_FILENO, F_SETFL, n);
}
flush_output(&output, &then, &now, ofile, tfile);
#ifdef HAVE_ZLIB
if (def_compress_transcript) {
gzclose(ofile.g);
gzclose(tfile.g);
} else
#endif
{
fclose(ofile.f);
fclose(tfile.f);
}
flush_output(&output);
#ifdef HAVE_STRSIGNAL
if (WIFSIGNALED(child_status)) {
int signo = WTERMSIG(child_status);
if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
int signo = WTERMSIG(cstat->val);
if (signo && signo != SIGINT && signo != SIGPIPE) {
char *reason = strsignal(signo);
write(STDOUT_FILENO, reason, strlen(reason));
if (WCOREDUMP(child_status))
if (WCOREDUMP(cstat->val))
write(STDOUT_FILENO, " (core dumped)", 14);
write(STDOUT_FILENO, "\n", 1);
}
@@ -800,30 +575,22 @@ script_execv(path, argv)
n = term_restore(script_fds[SFD_USERTTY], 0);
} while (!n && errno == EINTR);
exit(exitcode);
return cstat->type == CMD_ERRNO ? -1 : 0;
}
void
script_child(path, argv, backchannel, rbac_enabled)
char *path;
char *argv[];
int backchannel;
int rbac_enabled;
int
script_child(const char *path, char *argv[], char *envp[], int backchannel, int rbac)
{
struct command_status cstat;
fd_set *fdsr;
sigaction_t sa;
pid_t pid, self = getpid();
int nread, signo, status;
#ifndef TIOCSCTTY
int n;
#endif
int n, signo, status;
recvsig = 0;
/* Close unused fds. */
close(script_fds[SFD_MASTER]);
close(script_fds[SFD_LOG]);
close(script_fds[SFD_OUTPUT]);
close(script_fds[SFD_TIMING]);
close(script_fds[SFD_USERTTY]);
/* Reset signal handlers. */
@@ -840,8 +607,7 @@ script_child(path, argv, backchannel, rbac_enabled)
sigaction(SIGTTIN, &sa, NULL);
sigaction(SIGTTOU, &sa, NULL);
/* We want SIGCHLD to interrupt us. */
sa.sa_flags = 0; /* do not restart syscalls for these signals. */
/* SIGCHLD will interrupt select. */
sa.sa_handler = handler;
sigaction(SIGCHLD, &sa, NULL);
@@ -853,7 +619,7 @@ script_child(path, argv, backchannel, rbac_enabled)
#ifdef HAVE_SETSID
if (setsid() == -1) {
warning("setsid");
_exit(1);
goto bad;
}
#else
# ifdef TIOCNOTTY
@@ -869,7 +635,7 @@ script_child(path, argv, backchannel, rbac_enabled)
#endif
#ifdef TIOCSCTTY
if (ioctl(script_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
log_error(USE_ERRNO, "unable to set controlling tty");
error(1, "unable to set controlling tty");
#else
/* Set controlling tty by reopening slave. */
if ((n = open(slavename, O_RDWR)) >= 0)
@@ -880,10 +646,11 @@ script_child(path, argv, backchannel, rbac_enabled)
foreground = 0;
/* Start command and wait for it to stop or exit */
/* XXX - use details->timeout */
child = fork();
if (child == -1) {
warning("Can't fork");
_exit(1);
goto bad;
}
if (child == 0) {
/* Reset signal handlers. */
@@ -900,9 +667,9 @@ script_child(path, argv, backchannel, rbac_enabled)
sigaction(SIGUSR2, &sa, NULL);
/* setup tty and exec command */
script_run(path, argv, rbac_enabled);
warning("unable to execute %s", path);
_exit(127);
script_run(path, argv, envp, rbac);
warning("unable to execute %s", path); /* XXX - leave this to plugin? */
goto bad;
}
/*
@@ -917,6 +684,9 @@ script_child(path, argv, backchannel, rbac_enabled)
}
/* Wait for signal on backchannel or for SIGCHLD */
fdsr = (fd_set *)emalloc2(howmany(backchannel + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(fdsr, howmany(backchannel + 1, NFDBITS) * sizeof(fd_mask));
FD_SET(backchannel, fdsr);
for (;;) {
/* Read child status, assumes recvsig can only be SIGCHLD */
while (recvsig) {
@@ -934,26 +704,43 @@ script_child(path, argv, backchannel, rbac_enabled)
else
warningx("command exited?");
#endif
if (write(backchannel, &status, sizeof(status)) != sizeof(status))
cstat.type = CMD_WSTATUS;
cstat.val = status;
do {
n = send(backchannel, &cstat, sizeof(cstat), 0);
} while (n == -1 && errno == EINTR);
if (n != sizeof(cstat))
break; /* XXX - error, kill child and exit */
#ifdef SCRIPT_DEBUG
warningx("sent signo to parent");
#endif
if (!WIFSTOPPED(status)) {
/* XXX */
_exit(1); /* child dead */
}
}
}
nread = read(backchannel, &signo, sizeof(signo));
if (nread == -1) {
if (errno != EINTR)
break; /* XXX - error, kill child and exit */
continue;
n = select(backchannel + 1, fdsr, NULL, NULL, NULL);
if (n == -1) {
if (errno == EINTR)
continue;
error(1, "select failed");
}
if (nread != sizeof(signo)) {
/* EOF? */
/* read child status */
n = recv(backchannel, &cstat, sizeof(cstat), 0);
if (n == -1) {
if (errno == EINTR)
continue;
} else if (n != sizeof(cstat)) {
warningx("error reading command status");
break;
}
if (cstat.type != CMD_SIGNO) {
warningx("unexpected reply type on backchannel: %d", cstat.type);
continue;
}
signo = cstat.val;
/* Handle signal from parent. */
#ifdef SCRIPT_DEBUG
@@ -961,7 +748,7 @@ script_child(path, argv, backchannel, rbac_enabled)
#endif
switch (signo) {
case SIGKILL:
_exit(1);
_exit(1); /* XXX */
/* NOTREACHED */
case SIGHUP:
case SIGTERM:
@@ -991,16 +778,14 @@ script_child(path, argv, backchannel, rbac_enabled)
}
}
_exit(1);
_exit(1); /* XXX */
bad:
return errno;
}
static void
flush_output(output, then, now, ofile, tfile)
struct script_buf *output;
struct timeval *then;
struct timeval *now;
union script_fd ofile;
union script_fd tfile;
flush_output(struct script_buf *output)
{
int n;
@@ -1017,7 +802,8 @@ flush_output(output, then, now, ofile, tfile)
n = read(script_fds[SFD_MASTER], output->buf, sizeof(output->buf));
if (n <= 0)
break;
log_output(output->buf, n, &then, &now, ofile, tfile);
/* XXX */
log_output(output->buf, n);
output->off = 0;
output->len = n;
do {
@@ -1031,10 +817,7 @@ flush_output(output, then, now, ofile, tfile)
}
static void
script_run(path, argv, rbac_enabled)
char *path;
char *argv[];
int rbac_enabled;
script_run(const char *path, char *argv[], char *envp[], int rbac_enabled)
{
pid_t self = getpid();
@@ -1058,16 +841,14 @@ script_run(path, argv, rbac_enabled)
#ifdef HAVE_SELINUX
if (rbac_enabled)
selinux_execv(path, argv);
selinux_execve(path, argv, envp);
else
#endif
execv(path, argv);
execve(path, argv, envp);
}
static void
sync_winsize(src, dst)
int src;
int dst;
sync_winsize(int src, int dst)
{
#ifdef TIOCGWINSZ
struct winsize win;
@@ -1087,8 +868,7 @@ sync_winsize(src, dst)
* Handler for SIGCHLD in parent
*/
static void
sigchild(s)
int s;
sigchild(int s)
{
pid_t pid;
int serrno = errno;
@@ -1110,8 +890,7 @@ sigchild(s)
* Generic handler for signals passed from parent -> child
*/
static void
handler(s)
int s;
handler(int s)
{
recvsig = s;
}
@@ -1120,8 +899,7 @@ handler(s)
* Handler for SIGWINCH in parent
*/
static void
sigwinch(s)
int s;
sigwinch(int s)
{
int serrno = errno;

893
src/sudo.c Normal file
View File

@@ -0,0 +1,893 @@
/*
* Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef __TANDEM
# include <floss.h>
#endif
#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
#ifdef HAVE_SETRLIMIT
# include <sys/time.h>
# include <sys/resource.h>
#endif
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
# include <memory.h>
# endif
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* HAVE_STRING_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <pwd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <grp.h>
#if TIME_WITH_SYS_TIME
# include <time.h>
#endif
#ifdef HAVE_SETLOCALE
# include <locale.h>
#endif
#ifdef HAVE_LOGIN_CAP_H
# include <login_cap.h>
#endif
#include <sudo_usage.h>
#include "sudo.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
#ifdef USING_NONUNIX_GROUPS
# include "nonunix.h"
#endif
/*
* Local variables
*/
struct plugin_container policy_plugin;
struct plugin_container_list io_plugins;
/*
* Local functions
*/
static void fix_fds(void);
static void disable_coredumps(void);
static char **get_user_info(struct user_details *);
static void command_info_to_details(char * const info[],
struct command_details *details);
static int run_command(struct command_details *details, char *argv[],
char *envp[]);
/* XXX - header file */
extern const char *list_user, *runas_user, *runas_group;
/* Used by getprogname() unless crt0 supports getting program name. */
int Argc;
char **Argv;
#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
static struct rlimit corelimit;
#endif /* RLIMIT_CORE && !SUDO_DEVEL */
sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp;
int
main(int argc, char *argv[], char *envp[])
{
sigaction_t sa;
int nargc, sudo_mode;
char **nargv, **settings, **env_add;
char **user_info, **command_info, **argv_out, **user_env_out;
struct plugin_container *plugin;
struct user_details user_details;
struct command_details command_details;
int ok;
#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
extern char *malloc_options;
malloc_options = "AFGJPR";
#endif
Argc = argc;
Argv = argv;
#ifdef HAVE_SETLOCALE
setlocale(LC_ALL, "");
#endif
if (geteuid() != 0)
errorx(1, "must be setuid root");
/* XXX - Must be done before shadow file lookups... */
#if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS)
(void) set_auth_parameters(Argc, Argv);
# ifdef HAVE_INITPRIVS
initprivs();
# endif
#endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */
/*
* Signal setup:
* Ignore keyboard-generated signals so the user cannot interrupt
* us at some point and avoid the logging.
* XXX - leave this to the plugin?
*/
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
(void) sigaction(SIGINT, &sa, &saved_sa_int);
(void) sigaction(SIGQUIT, &sa, &saved_sa_quit);
(void) sigaction(SIGTSTP, &sa, &saved_sa_tstp);
/* Turn off core dumps and make sure fds 0-2 are open. */
disable_coredumps();
fix_fds();
/* Parse command line arguments. */
sudo_mode = parse_args(Argc, Argv, &nargc, &nargv, &settings, &env_add);
/* Read sudo.conf and load plugins. */
sudo_load_plugins(_PATH_SUDO_CONF, &policy_plugin, &io_plugins);
/* Fill in user_info with user name, uid, cwd, etc. */
memset(&user_details, 0, sizeof(user_details));
user_info = get_user_info(&user_details);
/* Open each plugin (XXX - check for errors). */
policy_plugin.u.policy->open(SUDO_API_VERSION, sudo_conversation,
settings, user_info, envp);
tq_foreach_fwd(&io_plugins, plugin) {
/* XXX - remove from list if open returns 0 */
plugin->u.io->open(SUDO_API_VERSION, sudo_conversation, settings,
user_info, envp);
}
/* XXX - should not need to check for MODE_INVALIDATE ORed in */
warningx("sudo_mode %d", sudo_mode); /* XXX */
switch (sudo_mode & MODE_MASK) {
case MODE_VERSION:
policy_plugin.u.policy->show_version(!user_details.uid);
tq_foreach_fwd(&io_plugins, plugin) {
plugin->u.io->show_version(!user_details.uid);
}
break;
case MODE_VALIDATE:
case MODE_VALIDATE|MODE_INVALIDATE:
if (policy_plugin.u.policy->validate == NULL) {
warningx("policy plugin %s does not support the -v flag",
policy_plugin.name);
ok = FALSE;
} else {
ok = policy_plugin.u.policy->validate();
}
exit(ok != TRUE);
case MODE_KILL:
case MODE_INVALIDATE:
if (policy_plugin.u.policy->validate == NULL) {
warningx("policy plugin %s does not support the -k/-K flags",
policy_plugin.name);
exit(1);
}
policy_plugin.u.policy->invalidate(sudo_mode == MODE_KILL);
exit(0);
break;
case MODE_CHECK:
case MODE_CHECK|MODE_INVALIDATE:
case MODE_LIST:
case MODE_LIST|MODE_INVALIDATE:
if (policy_plugin.u.policy->list == NULL) {
warningx("policy plugin %s does not support listing privileges",
policy_plugin.name);
ok = FALSE;
} else {
ok = policy_plugin.u.policy->list(nargc, nargv,
ISSET(sudo_mode, MODE_LONG_LIST), list_user);
}
exit(ok != TRUE);
case MODE_RUN:
ok = policy_plugin.u.policy->check_policy(nargc, nargv, env_add,
&command_info, &argv_out, &user_env_out);
warningx("policy plugin returns %d", ok); /* XXX */
if (ok != TRUE)
exit(ok); /* plugin printed error message */
command_info_to_details(command_info, &command_details);
/* Restore coredumpsize resource limit before running. */
#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
(void) setrlimit(RLIMIT_CORE, &corelimit);
#endif /* RLIMIT_CORE && !SUDO_DEVEL */
/* run_command will call close for us */
run_command(&command_details, argv_out, user_env_out);
break;
case MODE_EDIT:
/* XXX - fill in */
break;
default:
errorx(1, "unexpected sudo mode 0x%x", sudo_mode);
}
exit(0);
}
/*
* Ensure that stdin, stdout and stderr are open; set to /dev/null if not.
* Some operating systems do this automatically in the kernel or libc.
*/
static void
fix_fds(void)
{
int miss[3], devnull = -1;
/*
* stdin, stdout and stderr must be open; set them to /dev/null
* if they are closed.
*/
miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1;
miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1;
miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1;
if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) {
if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0644)) != -1) {
if (miss[STDIN_FILENO])
(void) dup2(devnull, STDIN_FILENO);
if (miss[STDOUT_FILENO])
(void) dup2(devnull, STDOUT_FILENO);
if (miss[STDERR_FILENO])
(void) dup2(devnull, STDERR_FILENO);
if (devnull > STDERR_FILENO)
close(devnull);
}
}
}
static char *
get_user_groups(struct user_details *ud)
{
char *gid_list = NULL;
#ifdef HAVE_GETGROUPS
size_t glsize;
char *cp;
int i;
if ((ud->ngroups = getgroups(0, NULL)) <= 0)
return NULL;
ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T));
if (getgroups(ud->ngroups, ud->groups) < 0)
error(1, "can't get group vector");
glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1));
gid_list = emalloc(glsize);
memcpy(gid_list, "groups=", sizeof("groups=") - 1);
cp = gid_list + sizeof("groups=") - 1;
for (i = 0; i < ud->ngroups; i++) {
snprintf(cp, glsize - (cp - gid_list), "%lu%s",
(unsigned long)ud->groups[i], i ? "," : "");
}
#endif
return gid_list;
}
/*
* Return user information as an array of name=value pairs.
* and fill in struct user_details (which shares the same strings).
*/
static char **
get_user_info(struct user_details *ud)
{
char cwd[PATH_MAX];
char host[MAXHOSTNAMELEN];
char **user_info, *cp;
struct passwd *pw;
int i = 0;
/* XXX - bound check number of entries */
user_info = emalloc2(32, sizeof(char *));
ud->uid = getuid();
ud->euid = geteuid();
ud->gid = getgid();
ud->egid = getegid();
pw = getpwuid(ud->uid);
if (pw == NULL)
errorx(1, "unknown uid %lu: who are you?", (unsigned long)ud->uid);
user_info[i] = fmt_string("user", pw->pw_name);
ud->username = user_info[i] + sizeof("user=") - 1;
easprintf(&user_info[++i], "uid=%lu", (unsigned long)ud->uid);
easprintf(&user_info[++i], "euid=%lu", (unsigned long)ud->euid);
easprintf(&user_info[++i], "gid=%lu", (unsigned long)ud->gid);
easprintf(&user_info[++i], "egid=%lu", (unsigned long)ud->egid);
if ((cp = get_user_groups(ud)) != NULL)
user_info[++i] = cp;
if (getcwd(cwd, sizeof(cwd)) != NULL) {
user_info[++i] = fmt_string("cwd", cwd);
ud->cwd = user_info[i] + sizeof("cwd=") - 1;
}
if ((cp = ttyname(STDIN_FILENO)) || (cp = ttyname(STDOUT_FILENO)) ||
(cp = ttyname(STDERR_FILENO))) {
user_info[++i] = fmt_string("tty", cp);
ud->tty = user_info[i] + sizeof("tty=") - 1;
}
if (gethostname(host, sizeof(host)) == 0)
host[sizeof(host) - 1] = '\0';
else
strlcpy(host, "localhost", sizeof(host));
user_info[++i] = fmt_string("host", host);
ud->host = user_info[i] + sizeof("host=") - 1;
user_info[++i] = NULL;
return user_info;
}
/*
* Convert a command_info array into a command_details structure.
*/
static void
command_info_to_details(char * const info[], struct command_details *details)
{
int i;
long lval;
unsigned long ulval;
char *cp, *ep;
memset(details, 0, sizeof(*details));
for (i = 0; info[i] != NULL; i++) {
/* XXX - should ignore empty entries */
switch (info[i][0]) {
case 'c':
if (strncmp("chroot=", info[i], sizeof("chroot=") - 1) == 0) {
details->chroot = info[i] + sizeof("chroot=") - 1;
break;
}
if (strncmp("command=", info[i], sizeof("command=") - 1) == 0) {
details->command = info[i] + sizeof("command=") - 1;
break;
}
if (strncmp("cwd=", info[i], sizeof("cwd=") - 1) == 0) {
details->cwd = info[i] + sizeof("cwd=") - 1;
break;
}
break;
case 'l':
if (strncmp("login_class=", info[i], sizeof("login_class=") - 1) == 0) {
details->login_class = info[i] + sizeof("login_class=") - 1;
break;
}
break;
case 'n':
/* XXX - bounds check -NZERO to NZERO (inclusive). */
if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) {
cp = info[i] + sizeof("nice=") - 1;
errno = 0;
lval = strtol(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
!(errno == ERANGE &&
(lval == LONG_MAX || lval == LONG_MIN)) &&
lval < INT_MAX && lval > INT_MIN) {
details->priority = (int)lval;
SET(details->flags, CD_SET_PRIORITY);
}
break;
}
if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) {
if (atobool(info[i] + sizeof("noexec=") - 1))
SET(details->flags, CD_NOEXEC);
break;
}
break;
case 'p':
if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) {
if (atobool(info[i] + sizeof("preserve_groups=") - 1))
SET(details->flags, CD_PRESERVE_GROUPS);
break;
}
break;
case 'r':
if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) {
cp = info[i] + sizeof("runas_egid=") - 1;
errno = 0;
ulval = strtoul(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->egid = (gid_t)ulval;
SET(details->flags, CD_SET_EGID);
}
break;
}
if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) {
cp = info[i] + sizeof("runas_euid=") - 1;
errno = 0;
ulval = strtoul(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->euid = (uid_t)ulval;
SET(details->flags, CD_SET_EUID);
}
break;
}
if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) {
cp = info[i] + sizeof("runas_gid=") - 1;
errno = 0;
ulval = strtoul(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->gid = (gid_t)ulval;
SET(details->flags, CD_SET_GID);
}
break;
}
if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) {
int j;
/* count groups, alloc and fill in */
cp = info[i] + sizeof("runas_groups=") - 1;
for (;;) {
details->ngroups++;
if ((cp = strchr(cp, ',')) == NULL)
break;
cp++;
}
details->groups = emalloc2(details->ngroups, sizeof(GETGROUPS_T));
cp = info[i] + sizeof("runas_groups=") - 1;
for (j = 0; j < details->ngroups;) {
errno = 0;
ulval = strtoul(cp, &ep, 0);
if (*cp != '\0' && (*ep == ',' || *ep == '\0') &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->groups[j++] = (gid_t)ulval;
}
}
details->ngroups = j;
break;
}
if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) {
cp = info[i] + sizeof("runas_uid=") - 1;
errno = 0;
ulval = strtoul(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->uid = (uid_t)ulval;
SET(details->flags, CD_SET_UID);
}
break;
}
break;
case 's':
if (strncmp("selinux_role=", info[i], sizeof("selinux_role=") - 1) == 0) {
details->selinux_role = info[i] + sizeof("selinux_role=") - 1;
break;
}
if (strncmp("selinux_type=", info[i], sizeof("selinux_type=") - 1) == 0) {
details->selinux_type = info[i] + sizeof("selinux_type=") - 1;
break;
}
break;
case 't':
if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) {
cp = info[i] + sizeof("timeout=") - 1;
errno = 0;
lval = strtol(cp, &ep, 0);
if (*cp != '\0' && *ep == '\0' &&
!(errno == ERANGE &&
(lval == LONG_MAX || lval == LONG_MIN)) &&
lval <= INT_MAX && lval >= 0) {
details->timeout = (int)lval;
SET(details->flags, CD_SET_TIMEOUT);
}
break;
}
break;
case 'u':
if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) {
cp = info[i] + sizeof("umask=") - 1;
errno = 0;
ulval = strtoul(cp, &ep, 8);
if (*cp != '\0' && *ep == '\0' &&
(errno != ERANGE || ulval != ULONG_MAX)) {
details->umask = (uid_t)ulval;
SET(details->flags, CD_SET_UMASK);
}
}
break;
}
}
if (!ISSET(details->flags, CD_SET_EUID))
details->euid = details->uid;
}
/*
* Disable core dumps to avoid dropping a core with user password in it.
* We will reset this limit before executing the command.
* Not all operating systems disable core dumps for setuid processes.
*/
static void
disable_coredumps(void)
{
#if defined(__linux__) || (defined(RLIMIT_CORE) && !defined(SUDO_DEVEL))
struct rlimit rl;
#endif
#if defined(__linux__)
/*
* Unlimit the number of processes since Linux's setuid() will
* apply resource limits when changing uid and return EAGAIN if
* nproc would be violated by the uid switch.
*/
rl.rlim_cur = rl.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_NPROC, &rl)) {
if (getrlimit(RLIMIT_NPROC, &rl) == 0) {
rl.rlim_cur = rl.rlim_max;
(void)setrlimit(RLIMIT_NPROC, &rl);
}
}
#endif /* __linux__ */
#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
/*
* Turn off core dumps.
*/
(void) getrlimit(RLIMIT_CORE, &corelimit);
memcpy(&rl, &corelimit, sizeof(struct rlimit));
rl.rlim_cur = 0;
(void) setrlimit(RLIMIT_CORE, &rl);
#endif /* RLIMIT_CORE && !SUDO_DEVEL */
}
/*
* Cleanup hook for error()/errorx()
*/
void
cleanup(gotsignal)
int gotsignal;
{
#if 0 /* XXX */
struct sudo_nss *nss;
if (!gotsignal) {
if (snl != NULL) {
tq_foreach_fwd(snl, nss)
nss->close(nss);
}
sudo_endpwent();
sudo_endgrent();
}
#ifdef _PATH_SUDO_TRANSCRIPT
if (def_transcript)
term_restore(STDIN_FILENO, 0);
#endif
#endif
}
/*
* Setup the execution environment immediately prior to the call to execve()
*/
int
exec_setup(struct command_details *details)
{
struct passwd *pw;
pw = getpwuid(details->euid);
if (pw != NULL) {
#ifdef HAVE_GETUSERATTR
aix_setlimits(pw->pw_name);
#endif
#ifdef HAVE_LOGIN_CAP_H
if (details->login_class) {
int flags;
login_cap_t *lc;
/*
* We only use setusercontext() to set the nice value and rlimits.
*/
lc = login_getclass((char *)details->login_class);
if (!lc) {
warningx("unknown login class %s", details->login_class);
errno = ENOENT;
goto done;
}
flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY;
if (setusercontext(lc, pw, pw->pw_uid, flags)) {
if (pw->pw_uid != ROOT_UID) {
warning("unable to set user context");
goto done;
} else
warning("unable to set user context");
}
}
#endif /* HAVE_LOGIN_CAP_H */
}
/*
* Set groups, including supplementary group vector.
*/
#ifdef HAVE_SETEUID
if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) {
warning("unable to set egid to runas gid");
goto done;
}
#endif
if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) {
warning("unable to set gid to runas gid");
goto done;
}
if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) {
/* XXX - may need to initgroups anyway--plugin may not have list */
#ifdef HAVE_GETGROUPS
if (details->ngroups >= 0) {
if (setgroups(details->ngroups, details->groups) < 0) {
warning("unable to set supplementary group IDs");
goto done;
}
}
#else
if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) {
warning("unable to set supplementary group IDs");
goto done;
}
#endif
}
if (ISSET(details->flags, CD_SET_PRIORITY)) {
if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) {
warning("unable to set process priority");
goto done;
}
}
if (ISSET(details->flags, CD_SET_UMASK))
(void) umask(details->umask);
if (ISSET(details->flags, CD_SET_TIMEOUT))
alarm(details->timeout);
if (details->chroot) {
if (chroot(details->chroot) != 0 || chdir("/") != 0) {
warning("unable to change root to %s", details->chroot);
goto done;
}
}
if (details->cwd) {
/* cwd is relative to the new root, if any */
if (chdir(details->cwd) != 0) {
warning("unable to change directory to %s", details->cwd);
goto done;
}
}
/* Must set uids last */
#ifdef HAVE_SETRESUID
if (setresuid(details->uid, details->euid, details->euid) != 0) {
warning("unable to change to runas uid");
goto done;
}
#elif HAVE_SETREUID
if (setreuid(details->uid, details->euid) != 0) {
warning("unable to change to runas uid");
goto done;
}
#else
if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) {
warning("unable to change to runas uid");
goto done;
}
#endif /* !HAVE_SETRESUID && !HAVE_SETREUID */
errno = 0;
done:
return errno;
}
static sig_atomic_t sigchld;
static void
sigchild(int s)
{
sigchld = 1;
}
/*
* Run the command and wait for it to complete.
*/
static int
run_command(struct command_details *details, char *argv[], char *envp[])
{
struct plugin_container *plugin;
struct command_status cstat;
int exitcode = 1;
cstat.type = CMD_INVALID;
cstat.val = 0;
/*
* XXX - missing closefrom(), may not be possible in new scheme
* also no background support
* or selinux...
*/
/* If there are I/O plugins, allocate a pty and exec */
if (!tq_empty(&io_plugins)) {
warningx("script mode"); /* XXX */
script_setup(details->euid);
script_execve(details, argv, envp, &cstat);
} else {
pid_t child, pid;
int nready, sv[2];
ssize_t nread;
sigaction_t sa;
fd_set *fdsr;
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
/* Want select() to be interrupted when child dies. */
sa.sa_handler = sigchild;
sigaction(SIGCHLD, &sa, NULL);
/* Ignore SIGPIPE from other end of socketpair. */
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
error(1, "cannot create sockets");
child = fork();
if (child == -1)
error(1, "unable to fork");
if (child == 0) {
/* child */
close(sv[0]);
if (exec_setup(details) == 0) {
/* XXX - fallback via /bin/sh */
execve(details->command, argv, envp);
}
cstat.type = CMD_ERRNO;
cstat.val = errno;
write(sv[1], &cstat, sizeof(cstat));
_exit(1);
}
close(sv[1]);
warningx("waiting for child"); /* XXX */
/* wait for child to complete or for data on sv[0] */
fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(fdsr, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
FD_SET(sv[0], fdsr);
for (;;) {
if (sigchld) {
sigchld = 0;
do {
pid = waitpid(child, &cstat.val, WNOHANG);
if (pid == child)
cstat.type = CMD_WSTATUS;
} while (pid == -1 && errno == EINTR);
if (cstat.type == CMD_WSTATUS) {
/* command terminated, we're done */
break;
}
}
nready = select(sv[0] + 1, fdsr, NULL, NULL, NULL);
if (nready == -1) {
if (errno == EINTR)
continue;
error(1, "select failed");
}
if (FD_ISSET(sv[0], fdsr)) {
/* read child status */
nread = recv(sv[0], &cstat, sizeof(cstat), 0);
if (nread == -1) {
if (errno == EINTR)
continue;
} else if (nread != sizeof(cstat)) {
warningx("error reading command status");
}
break; /* XXX */
}
}
}
switch (cstat.type) {
case CMD_ERRNO:
/* exec_setup() or execve() returned an error. */
policy_plugin.u.policy->close(0, cstat.val);
tq_foreach_fwd(&io_plugins, plugin) {
plugin->u.io->close(0, cstat.val);
}
exitcode = 1;
break;
case CMD_WSTATUS:
/* Command ran, exited or was killed. */
policy_plugin.u.policy->close(cstat.val, 0);
tq_foreach_fwd(&io_plugins, plugin) {
plugin->u.io->close(0, cstat.val);
}
if (WIFEXITED(cstat.val))
exitcode = WEXITSTATUS(cstat.val);
else if (WIFSIGNALED(cstat.val))
exitcode = WTERMSIG(cstat.val) | 128;
break;
default:
warningx("unexpected child termination condition: %d", cstat.type);
break;
}
exit(exitcode);
}
#if 0 /* XXX - convert warning/error to something log this */
/*
* Simple debugging/logging.
* XXX - use askpass if configured?
*/
void
sudo_log(int level, const char *fmt, ...)
{
va_list ap;
switch (level) {
case SUDO_LOG_INFO:
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
break;
case SUDO_LOG_DEBUG1:
case SUDO_LOG_DEBUG2:
case SUDO_LOG_DEBUG3:
case SUDO_LOG_DEBUG4:
case SUDO_LOG_DEBUG5:
case SUDO_LOG_DEBUG6:
case SUDO_LOG_DEBUG7:
case SUDO_LOG_DEBUG8:
case SUDO_LOG_DEBUG9:
if (level > debug_level)
return;
/* FALLTHROUGH */
case SUDO_LOG_WARN:
case SUDO_LOG_ERROR:
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
}
#endif

234
src/sudo.h Normal file
View File

@@ -0,0 +1,234 @@
/*
* Copyright (c) 1993-1996, 1998-2005, 2007-2009
* Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*
* $Sudo: sudo.h,v 1.290 2009/12/12 16:12:26 millert Exp $
*/
#ifndef _SUDO_SUDO_H
#define _SUDO_SUDO_H
#include <pathnames.h>
#include <limits.h>
#include "compat.h"
#include "alloc.h"
#include "error.h"
#include "list.h"
#include "missing.h"
#ifdef __TANDEM
# define ROOT_UID 65535
#else
# define ROOT_UID 0
#endif
/*
* Pseudo-boolean values
*/
#undef TRUE
#define TRUE 1
#undef FALSE
#define FALSE 0
/*
* Various modes sudo can be in (based on arguments) in hex
*/
#define MODE_RUN 0x00000001
#define MODE_EDIT 0x00000002
#define MODE_VALIDATE 0x00000004
#define MODE_INVALIDATE 0x00000008
#define MODE_KILL 0x00000010
#define MODE_VERSION 0x00000020
#define MODE_HELP 0x00000040
#define MODE_LIST 0x00000080
#define MODE_CHECK 0x00000100
#define MODE_MASK 0x0000ffff
/* Mode flags */
/* XXX - prune this */
#define MODE_BACKGROUND 0x00010000
#define MODE_SHELL 0x00020000
#define MODE_LOGIN_SHELL 0x00040000
#define MODE_IMPLIED_SHELL 0x00080000
#define MODE_RESET_HOME 0x00100000
#define MODE_PRESERVE_GROUPS 0x00200000
#define MODE_PRESERVE_ENV 0x00400000
#define MODE_NONINTERACTIVE 0x00800000
#define MODE_LONG_LIST 0x01000000
/*
* Used with set_perms()
*/
#define PERM_ROOT 0x00
#define PERM_USER 0x01
#define PERM_FULL_USER 0x02
#define PERM_SUDOERS 0x03
#define PERM_RUNAS 0x04
#define PERM_FULL_RUNAS 0x05
#define PERM_TIMESTAMP 0x06
#define PERM_NOEXIT 0x10 /* flag */
#define PERM_MASK 0xf0
/*
* We used to use the system definition of PASS_MAX or _PASSWD_LEN,
* but that caused problems with various alternate authentication
* methods. So, we just define our own and assume that it is >= the
* system max.
*/
#define SUDO_PASS_MAX 256
/*
* Flags for lock_file()
*/
#define SUDO_LOCK 1 /* lock a file */
#define SUDO_TLOCK 2 /* test & lock a file (non-blocking) */
#define SUDO_UNLOCK 4 /* unlock a file */
/*
* Flags for tgetpass()
*/
#define TGP_ECHO 0x01 /* leave echo on when reading passwd */
#define TGP_STDIN 0x02 /* read from stdin, not /dev/tty */
#define TGP_ASKPASS 0x04 /* read from askpass helper program */
#define TGP_FEEDBACK 0x08 /* visual feedback during input */
struct user_details {
uid_t uid;
uid_t euid;
uid_t gid;
uid_t egid;
const char *username;
const char *cwd;
const char *tty;
const char *host;
GETGROUPS_T *groups;
int ngroups;
};
#define CD_SET_UID 0x0001
#define CD_SET_EUID 0x0002
#define CD_SET_GID 0x0004
#define CD_SET_EGID 0x0008
#define CD_PRESERVE_GROUPS 0x0010
#define CD_NOEXEC 0x0020
#define CD_SET_PRIORITY 0x0040
#define CD_SET_UMASK 0x0080
#define CD_SET_TIMEOUT 0x0100
struct command_details {
uid_t uid;
uid_t euid;
gid_t gid;
gid_t egid;
mode_t umask;
int flags;
int priority;
int timeout;
int ngroups;
GETGROUPS_T *groups;
const char *command;
const char *cwd;
const char *login_class;
const char *chroot;
const char *selinux_role;
const char *selinux_type;
};
/* Status passed between parent and child via socketpair */
struct command_status {
#define CMD_INVALID 0
#define CMD_ERRNO 1
#define CMD_WSTATUS 2
#define CMD_SIGNO 3
int type;
int val;
};
/* For error() and errorx() (XXX - needed?) */
void cleanup(int);
/* tgetpass.c */
char *tgetpass(const char *, int, int);
int tty_present(void);
/* zero_bytes.c */
void zero_bytes(volatile void *, size_t);
/* fileops.c */
int lock_file(int, int);
char *sudo_parseln(FILE *);
/* script.c */
int script_duplow(int);
int script_execve(struct command_details *details, char *argv[], char *envp[],
struct command_status *cstat);
void script_setup(uid_t);
/* term.c */
int term_cbreak(int);
int term_copy(int, int, int);
int term_noecho(int);
int term_raw(int, int, int);
int term_restore(int, int);
/* fmt_string.h */
char *fmt_string(const char *var, const char *value);
/* atobool.c */
int atobool(const char *str);
/* parse_args.c */
int parse_args(int argc, char **argv, int *nargc, char ***nargv,
char ***settingsp, char ***env_addp);
/* pty.c */
int get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid);
/* sudo.c */
int exec_setup(struct command_details *details);
extern int debug_level;
extern struct plugin_container_list io_plugins;
#ifndef errno
extern int errno;
#endif
#ifdef ntoyet
/*
* Sudo logging/debugging, printf-style.
* XXX - not hooked up yet
* The debug level may be set on the command line via the -D flag.
* A higher debug level yields more verbose debugging.
*/
#define SUDO_LOG_DEBUG1 1
#define SUDO_LOG_DEBUG2 2
#define SUDO_LOG_DEBUG3 3
#define SUDO_LOG_DEBUG4 4
#define SUDO_LOG_DEBUG5 5
#define SUDO_LOG_DEBUG6 6
#define SUDO_LOG_DEBUG7 7
#define SUDO_LOG_DEBUG8 8
#define SUDO_LOG_DEBUG9 9
#define SUDO_LOG_INFO 10
#define SUDO_LOG_WARN 11
#define SUDO_LOG_ERROR 12
void sudo_log(int level, const char *format, ...) __printflike(2, 3);
#endif
#endif /* _SUDO_SUDO_H */

39
src/sudo_plugin_int.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef _SUDO_PLUGIN_INT_H
#define _SUDO_PLUGIN_INT_H
/*
* Sudo plugin internals.
*/
struct plugin_info {
struct plugin_info *prev; /* required */
struct plugin_info *next; /* required */
const char *path;
const char *symbol_name;
};
TQ_DECLARE(plugin_info)
struct plugin_container {
struct plugin_container *prev; /* required */
struct plugin_container *next; /* required */
const char *name;
void *handle;
union {
struct generic_plugin *generic;
struct policy_plugin *policy;
struct io_plugin *io;
} u;
};
TQ_DECLARE(plugin_container)
extern struct plugin_container_list policy_plugins;
extern struct plugin_container_list io_plugins;
int sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[]);
void sudo_load_plugins(const char *conf_file,
struct plugin_container *policy_plugin,
struct plugin_container_list *io_plugins);
#endif /* _SUDO_PLUGIN_INT_H */

32
src/sudo_usage.h.in Normal file
View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2007-2009 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _SUDO_USAGE_H
#define _SUDO_USAGE_H
/*
* Usage strings for sudo. These are here because we
* need to be able to substitute values from configure.
*/
#define SUDO_USAGE1 " -h | -K | -k | -L | -V"
#define SUDO_USAGE2 " -v [-AknS] @BSDAUTH_USAGE@[-g groupname|#gid] [-p prompt] [-u user name|#uid]"
#define SUDO_USAGE3 " -l[l] [-AknS] @BSDAUTH_USAGE@[-g groupname|#gid] [-p prompt] [-U user name] [-u user name|#uid] [-g groupname|#gid] [command]"
#define SUDO_USAGE4 " [-AbEHknPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C fd] @LOGINCAP_USAGE@[-g groupname|#gid] [-p prompt] [-u user name|#uid] [-g groupname|#gid] [VAR=value] [-i|-s] [<command>]"
#define SUDO_USAGE5 " -e [-AknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C fd] @LOGINCAP_USAGE@[-g groupname|#gid] [-p prompt] [-u user name|#uid] file ..."
#endif /* _SUDO_USAGE_H */

View File

@@ -56,6 +56,9 @@
#include "sudo.h"
/* XXX */
char *user_askpass = NULL;
static volatile sig_atomic_t signo;
static void handler __P((int));
@@ -111,7 +114,7 @@ restart:
(void) sigaction(SIGTTIN, &sa, &savettin);
(void) sigaction(SIGTTOU, &sa, &savettou);
if (def_pwfeedback)
if (ISSET(flags, TGP_FEEDBACK))
neednl = term_cbreak(input);
else
neednl = term_noecho(input);
@@ -123,7 +126,7 @@ restart:
if (timeout > 0)
alarm(timeout);
pass = getln(input, buf, sizeof(buf), def_pwfeedback);
pass = getln(input, buf, sizeof(buf), ISSET(flags, TGP_FEEDBACK));
alarm(0);
save_errno = errno;
@@ -184,7 +187,8 @@ sudo_askpass(prompt)
if (pid == 0) {
/* child, point stdout to output side of the pipe and exec askpass */
(void) dup2(pfd[1], STDOUT_FILENO);
set_perms(PERM_FULL_USER);
//XXX - set real and effective uid to user
//set_perms(PERM_FULL_USER);
closefrom(STDERR_FILENO + 1);
execl(user_askpass, user_askpass, prompt, (char *)NULL);
warning("unable to run %s", user_askpass);