getline() instead of fgets() internally.
common/fmt_string.c
common/lbuf.c
common/list.c
+common/regress/sudo_parseln/parseln_test.c
+common/regress/sudo_parseln/test1.in
+common/regress/sudo_parseln/test1.out.ok
+common/regress/sudo_parseln/test2.in
+common/regress/sudo_parseln/test2.out.ok
+common/regress/sudo_parseln/test3.in
+common/regress/sudo_parseln/test3.out.ok
+common/regress/sudo_parseln/test4.in
+common/regress/sudo_parseln/test4.out.ok
+common/regress/sudo_parseln/test5.in
+common/regress/sudo_parseln/test5.out.ok
+common/regress/sudo_parseln/test6.in
+common/regress/sudo_parseln/test6.out.ok
common/secure_path.c
common/setgroups.c
common/sudo_conf.c
#
-# Copyright (c) 2011 Todd C. Miller <Todd.Miller@courtesan.com>
+# Copyright (c) 2011-2013 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
SSP_CFLAGS = @SSP_CFLAGS@
SSP_LDFLAGS = @SSP_LDFLAGS@
+# Regression tests
+TEST_PROGS = parseln_test
+TEST_LIBS = @LIBS@ @LIBINTL@
+TEST_LDFLAGS = @LDFLAGS@
+
# OS dependent defines
DEFS = @OSDEFS@ -D_PATH_SUDO_CONF=\"$(sysconfdir)/sudo.conf\"
secure_path.lo setgroups.lo sudo_conf.lo sudo_debug.lo sudo_printf.lo \
term.lo ttysize.lo zero_bytes.lo @COMMON_OBJS@
+PARSELN_TEST_OBJS = parseln_test.lo
+
all: libcommon.la
Makefile: $(srcdir)/Makefile.in
libcommon.la: $(LTOBJS)
$(LIBTOOL) --mode=link $(CC) -o $@ $(LTOBJS) -no-install
+parseln_test: $(PARSELN_TEST_OBJS) libcommon.la
+ $(LIBTOOL) --mode=link $(CC) -o $@ $(PARSELN_TEST_OBJS) libcommon.la $(TEST_LDFLAGS) $(TEST_LIBS)
+
pre-install:
install:
uninstall:
-check:
+check: $(TEST_PROGS)
+ @if test X"$(cross_compiling)" != X"yes"; then \
+ passed=0; failed=0; total=0; \
+ dir=sudo_parseln; \
+ mkdir -p regress/$$dir; \
+ for t in $(srcdir)/regress/$$dir/*.in; do \
+ base=`basename $$t .in`; \
+ out="regress/$$dir/$$base.out"; \
+ ./parseln_test <$$t >$$out; \
+ if cmp $$out $(srcdir)/$$out.ok >/dev/null; then \
+ passed=`expr $$passed + 1`; \
+ echo "$$dir/$$base: OK"; \
+ else \
+ failed=`expr $$failed + 1`; \
+ echo "$$dir/$$base: FAIL"; \
+ diff $$out $(srcdir)/$$out.ok; \
+ fi; \
+ total=`expr $$total + 1`; \
+ done; \
+ echo "$$dir: $$passed/$$total tests passed; $$failed/$$total tests failed"; \
+ exit $$failed; \
+ fi
clean:
-$(LIBTOOL) --mode=clean rm -f *.lo *.o *.la *.a stamp-* core *.core core.*
list.lo: $(srcdir)/list.c $(top_builddir)/config.h $(incdir)/missing.h \
$(incdir)/list.h $(incdir)/error.h
$(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/list.c
+parseln_test.lo: $(srcdir)/regress/sudo_parseln/parseln_test.c \
+ $(top_builddir)/config.h $(top_srcdir)/compat/stdbool.h \
+ $(incdir)/missing.h $(incdir)/fileops.h
+ $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/regress/sudo_parseln/parseln_test.c
secure_path.lo: $(srcdir)/secure_path.c $(top_builddir)/config.h \
$(incdir)/missing.h $(incdir)/sudo_debug.h \
$(incdir)/secure_path.h
/*
- * Copyright (c) 1999-2005, 2007, 2009-2011
+ * Copyright (c) 1999-2005, 2007, 2009-2013
* Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
#ifdef HAVE_FLOCK
# include <sys/file.h>
#endif /* HAVE_FLOCK */
-#include <stdio.h>
-#ifdef HAVE_STDBOOL_H
-# include <stdbool.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
#else
-# include "compat/stdbool.h"
-#endif
+# 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>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
-#endif /* HAVE_STRINGS_H */
+#endif /* HAVE_STRING_H */
+#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
+# include <malloc.h>
+#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
#include <ctype.h>
-#include <limits.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <fcntl.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
#if TIME_WITH_SYS_TIME
# include <time.h>
#endif
#include "fileops.h"
#include "sudo_debug.h"
-#ifndef LINE_MAX
-# define LINE_MAX 2048
-#endif
-
/*
* Update the access and modify times on an fd or file.
*/
#endif
/*
- * Read a line of input, remove comments and strip off leading
- * and trailing spaces. Returns static storage that is reused.
+ * Read a line of input, honoring line continuation chars.
+ * Remove comments and strips off leading and trailing spaces.
+ * Returns the line length and updates the buf and bufsize pointers.
+ * XXX - just use a struct w/ state, including getline buffer?
+ * could also make comment char and line continuation configurable
*/
-char *
-sudo_parseln(FILE *fp)
+ssize_t
+sudo_parseln(char **bufp, size_t *bufsizep, unsigned int *lineno, FILE *fp)
{
- size_t len;
- char *cp = NULL;
- static char buf[LINE_MAX];
+ size_t len, linesize = 0, total = 0;
+ char *cp, *line = NULL;
+ bool continued;
debug_decl(sudo_parseln, SUDO_DEBUG_UTIL)
- if (fgets(buf, sizeof(buf), fp) != NULL) {
- /* Remove comments */
- if ((cp = strchr(buf, '#')) != NULL)
+ do {
+ continued = false;
+ len = getline(&line, &linesize, fp);
+ if (len == -1)
+ break;
+ if (lineno != NULL)
+ (*lineno)++;
+
+ /* Remove trailing newline(s) if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ /* Remove comments or check for line continuation (but not both) */
+ if ((cp = strchr(line, '#')) != NULL) {
*cp = '\0';
+ len = (size_t)(cp - line);
+ } else if (len > 0 && line[len - 1] == '\\' && (len == 1 || line[len - 2] != '\\')) {
+ line[--len] = '\0';
+ continued = true;
+ }
- /* Trim leading and trailing whitespace/newline */
- len = strlen(buf);
- while (len > 0 && isspace((unsigned char)buf[len - 1]))
- buf[--len] = '\0';
- for (cp = buf; isblank((unsigned char)*cp); cp++)
- continue;
- }
- debug_return_str(cp);
+ /* Trim leading and trailing whitespace */
+ if (!continued) {
+ while (len > 0 && isblank((unsigned char)line[len - 1]))
+ line[--len] = '\0';
+ }
+ for (cp = line; isblank((unsigned char)*cp); cp++)
+ len--;
+
+ if (*bufp == NULL || total + len >= *bufsizep) {
+ void *tmp;
+ unsigned int size = total + len + 1;
+
+ if (size < 64) {
+ size = 64;
+ } else {
+ /* Round up to next highest power of two. */
+ size--;
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size |= size >> 8;
+ size |= size >> 16;
+ size++;
+ }
+ if ((tmp = realloc(*bufp, size)) == NULL)
+ break;
+ *bufp = tmp;
+ *bufsizep = size;
+ }
+ memcpy(*bufp + total, cp, len + 1);
+ total += len;
+ } while (continued);
+ free(line);
+ if (len == -1 && total == 0)
+ debug_return_size_t((size_t)-1);
+ debug_return_size_t(total);
}
--- /dev/null
+/*
+ * Copyright (c) 2013 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 <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>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "missing.h"
+#include "fileops.h"
+
+/*
+ * Simple test driver for sudo_parseln().
+ * Behaves similarly to "cat -n" but with comment removal
+ * and line continuation.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+
+ while (sudo_parseln(&line, &linesize, &lineno, stdin) != -1)
+ printf("%6u\t%s\n", lineno, line);
+ free(line);
+ exit(0);
+}
+
+/* STUB */
+void
+warning_set_locale(void)
+{
+ return;
+}
+
+/* STUB */
+void
+warning_restore_locale(void)
+{
+ return;
+}
--- /dev/null
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+#Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing dummy versions of the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support C<LD_PRELOAD> or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+#Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+#Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+#Set group_source static
--- /dev/null
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19 Plugin sudoers_policy sudoers.so
+ 20 Plugin sudoers_io sudoers.so
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
--- /dev/null
+this \
+is all \
+one line
+# this is a comment, and does not get continued\
+trim the \
+ leading \
+ white \
+space
--- /dev/null
+ 3 this is all one line
+ 4
+ 8 trim the leading white space
--- /dev/null
+line continuation at EOF \
--- /dev/null
+ 1 line continuation at EOF
--- /dev/null
+line contin\
+uation raw
+line contin\
+ uation indented
--- /dev/null
+ 2 line continuation raw
+ 4 line continuation indented
--- /dev/null
+ leading and trailing white space
+ # a comment
+\
--- /dev/null
+ 1 leading and trailing white space
+ 2
struct sudo_conf_table *cur;
struct stat sb;
FILE *fp;
- char *cp, *prev_locale = estrdup(setlocale(LC_ALL, NULL));
+ char *cp, *line = NULL;
+ char *prev_locale = estrdup(setlocale(LC_ALL, NULL));
+ size_t linesize = 0;
/* Parse sudo.conf in the "C" locale. */
if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
}
lineno = 0;
- while ((cp = sudo_parseln(fp)) != NULL) {
- lineno++;
- if (*cp == '\0')
+ while (sudo_parseln(&line, &linesize, &lineno, fp) != -1) {
+ if (*(cp = line) == '\0')
continue; /* empty line or comment */
for (cur = sudo_conf_table; cur->name != NULL; cur++) {
}
}
fclose(fp);
+ free(line);
done:
/* Restore locale if needed. */
if (prev_locale[0] != 'C' || prev_locale[1] != '\0')
bool lock_file(int, int);
int touch(int, char *, struct timeval *);
-char *sudo_parseln(FILE *);
+ssize_t sudo_parseln(char **buf, size_t *bufsize, unsigned int *lineno, FILE *fp);
#endif /* _SUDO_FILEOPS_H */
read_env_file(const char *path, int overwrite)
{
FILE *fp;
- char *cp, *var, *val;
- size_t var_len, val_len;
+ char *cp, *var, *val, *line = NULL;
+ size_t var_len, val_len, linesize = 0;
if ((fp = fopen(path, "r")) == NULL)
return;
- while ((var = sudo_parseln(fp)) != NULL) {
+ while (sudo_parseln(&line, &linesize, NULL, fp) != -1) {
/* Skip blank or comment lines */
- if (*var == '\0')
+ if (*(var = line) == '\0')
continue;
/* Skip optional "export " */
sudo_putenv(cp, true, overwrite);
}
+ free(line);
fclose(fp);
}
sudo_ldap_read_config(void)
{
FILE *fp;
- char *cp, *keyword, *value;
+ char *cp, *keyword, *value, *line = NULL;
+ size_t linesize = 0;
debug_decl(sudo_ldap_read_config, SUDO_DEBUG_LDAP)
/* defaults */
if ((fp = fopen(_PATH_LDAP_CONF, "r")) == NULL)
debug_return_bool(false);
- while ((cp = sudo_parseln(fp)) != NULL) {
- if (*cp == '\0')
+ while (sudo_parseln(&line, &linesize, NULL, fp) != -1) {
+ if (*line == '\0')
continue; /* skip empty line */
/* split into keyword and value */
- keyword = cp;
+ keyword = cp = line;
while (*cp && !isblank((unsigned char) *cp))
cp++;
if (*cp)
if (!sudo_ldap_parse_keyword(keyword, value, ldap_conf_global))
sudo_ldap_parse_keyword(keyword, value, ldap_conf_conn);
}
+ free(line);
fclose(fp);
if (!ldap_conf.host)
sudo_read_nss(void)
{
FILE *fp;
- char *cp;
+ char *cp, *line = NULL;
+ size_t linesize = 0;
#ifdef HAVE_SSSD
bool saw_sss = false;
#endif
if ((fp = fopen(_PATH_NSSWITCH_CONF, "r")) == NULL)
goto nomatch;
- while ((cp = sudo_parseln(fp)) != NULL) {
+ while (sudo_parseln(&line, &linesize, NULL, fp) != -1) {
/* Skip blank or comment lines */
- if (*cp == '\0')
+ if (*line == '\0')
continue;
/* Look for a line starting with "sudoers:" */
- if (strncasecmp(cp, "sudoers:", 8) != 0)
+ if (strncasecmp(line, "sudoers:", 8) != 0)
continue;
/* Parse line */
- for ((cp = strtok(cp + 8, " \t")); cp != NULL; (cp = strtok(NULL, " \t"))) {
+ for ((cp = strtok(line + 8, " \t")); cp != NULL; (cp = strtok(NULL, " \t"))) {
if (strcasecmp(cp, "files") == 0 && !saw_files) {
tq_append(&snl, &sudo_nss_file);
got_match = true;
/* Only parse the first "sudoers:" line */
break;
}
+ free(line);
fclose(fp);
nomatch:
sudo_read_nss(void)
{
FILE *fp;
- char *cp, *ep;
+ char *cp, *ep, *line = NULL;
+ ssize_t linesize = 0;
#ifdef HAVE_SSSD
bool saw_sss = false;
#endif
if ((fp = fopen(_PATH_NETSVC_CONF, "r")) == NULL)
goto nomatch;
- while ((cp = sudo_parseln(fp)) != NULL) {
+ while (sudo_parseln(&line, &linesize, NULL, fp) != -1) {
/* Skip blank or comment lines */
- if (*cp == '\0')
+ if (*(cp = line) == '\0')
continue;
/* Look for a line starting with "sudoers = " */