diff --git a/README.md b/README.md index f15610c..f967eca 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,12 @@ similar to sudo. See the comment block in `timestamp.c` for an in-depth description on how timestamps are created and checked to be as safe as possible. + +### `--with-doas-confdir` + +An optional feature can be enabled which will result in `doas` reading configuration +snippets from `/etc/doas.d`. These configuration snippets have the same requirements +as `/etc/doas.conf` (owned by root, not world-writable). + +If this feature is enabled, only the `/etc/doas.d` directory is read, and the historical +`/etc/doas.conf` file is ignored. \ No newline at end of file diff --git a/configure b/configure index 1f92f01..a3078dc 100755 --- a/configure +++ b/configure @@ -27,6 +27,7 @@ usage: configure [options] --without-shadow disable shadow support --with-timestamp enable timestamp support + --with-doas-confdir enable configuration directory support --uid-max=NUM set UID_MAX (default 65535) --gid-max=NUM set GID_MAX (default 65535) @@ -38,6 +39,7 @@ EOF # defaults WITHOUT_TIMESTAMP=yes +WITHOUT_CONFDIR=yes UID_MAX=65535 GID_MAX=65535 @@ -56,6 +58,8 @@ for x; do --target) TARGET=$var ;; --enable-debug) DEBUG=yes ;; --enable-static) BUILD_STATIC=yes ;; + --with-doas-confdir) WITHOUT_CONFDIR= ;; + --without-doas-confdir) WITHOUT_CONFDIR=yes ;; --with-pam) WITHOUT_PAM=; WITHOUT_SHADOW=yes ;; --with-shadow) WITHOUT_SHADOW=; WITHOUT_PAM=yes ;; --without-pam) WITHOUT_PAM=yes ;; @@ -558,4 +562,8 @@ fi printf '#define DOAS_CONF "%s/doas.conf"\n' "${SYSCONFDIR}" >>$CONFIG_H +if [ -z "$WITHOUT_CONFDIR" ]; then + printf '#define DOAS_CONFDIR "%s/doas.d"\n' "${SYSCONFDIR}" >>$CONFIG_H +fi + printf '\n#endif /* CONFIG_H */\n' >>$CONFIG_H diff --git a/doas.c b/doas.c index ac3a42a..a2f2105 100644 --- a/doas.c +++ b/doas.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "openbsd.h" #include "doas.h" @@ -155,8 +156,6 @@ permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr, static void parseconfig(const char *filename, int checkperms) { - extern FILE *yyfp; - extern int yyparse(void); struct stat sb; yyfp = fopen(filename, "r"); @@ -164,6 +163,8 @@ parseconfig(const char *filename, int checkperms) err(1, checkperms ? "doas is not enabled, %s" : "could not open config file %s", filename); + yyfn = filename; + if (checkperms) { if (fstat(fileno(yyfp), &sb) != 0) err(1, "fstat(\"%s\")", filename); @@ -174,11 +175,67 @@ parseconfig(const char *filename, int checkperms) } yyparse(); + yyfn = NULL; + fclose(yyfp); if (parse_errors) exit(1); } +#ifdef DOAS_CONFDIR +static int +isconfdir(const char *dirpath) +{ + struct stat sb; + + if (lstat(dirpath, &sb) != 0) + err(1, "lstat(\"%s\")", dirpath); + + if ((sb.st_mode & (S_IFMT)) == S_IFDIR) + return 1; + + errno = ENOTDIR; + return 0; +} + +static void +parseconfdir(const char *dirpath, int checkperms) +{ + struct dirent **dirent_table; + size_t i, dirent_count; + char pathbuf[PATH_MAX]; + + if (!isconfdir(dirpath)) + err(1, checkperms ? "doas is not enabled, %s" : + "could not open config directory %s", dirpath); + + dirent_count = scandir(dirpath, &dirent_table, NULL, alphasort); + + for (i = 0; i < dirent_count; i++) + { + struct stat sb; + size_t pathlen; + + pathlen = snprintf(pathbuf, sizeof pathbuf, "%s/%s", dirpath, dirent_table[i]->d_name); + free(dirent_table[i]); + + /* make sure path ends in .conf */ + if (strcmp(pathbuf + (pathlen - 5), ".conf")) + continue; + + if (stat(pathbuf, &sb) != 0) + err(1, "stat(\"%s\")", pathbuf); + + if ((sb.st_mode & (S_IFMT)) != S_IFREG) + continue; + + parseconfig(pathbuf, checkperms); + } + + free(dirent_table); +} +#endif + static void __dead checkconfig(const char *confpath, int argc, char **argv, uid_t uid, gid_t *groups, int ngroups, uid_t target) @@ -188,7 +245,13 @@ checkconfig(const char *confpath, int argc, char **argv, if (setresuid(uid, uid, uid) != 0) err(1, "setresuid"); +#ifdef DOAS_CONFDIR + if (isconfdir(confpath)) + parseconfdir(confpath, 0); + else +#else parseconfig(confpath, 0); +#endif if (!argc) exit(0); @@ -330,7 +393,13 @@ main(int argc, char **argv) if (geteuid()) errx(1, "not installed setuid"); +#ifdef DOAS_CONFDIR + if (isconfdir(DOAS_CONFDIR)) + parseconfdir(DOAS_CONFDIR, 1); + else +#else parseconfig(DOAS_CONF, 1); +#endif /* cmdline is used only for logging, no need to abort on truncate */ (void)strlcpy(cmdline, argv[0], sizeof(cmdline)); diff --git a/doas.h b/doas.h index a8aa41b..cd26bc4 100644 --- a/doas.h +++ b/doas.h @@ -25,6 +25,9 @@ struct rule { const char **envlist; }; +extern const char *yyfn; +extern FILE *yyfp; + extern struct rule **rules; extern size_t nrules; extern int parse_errors; @@ -33,6 +36,8 @@ extern const char *formerpath; struct passwd; +extern int yyparse(void); + char **prepenv(const struct rule *, const struct passwd *, const struct passwd *); diff --git a/parse.y b/parse.y index 388c2a5..c6d7ebf 100644 --- a/parse.y +++ b/parse.y @@ -49,6 +49,7 @@ typedef struct { } yystype; #define YYSTYPE yystype +const char *yyfn; FILE *yyfp; struct rule **rules; @@ -203,7 +204,7 @@ yyerror(const char *fmt, ...) va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); - fprintf(stderr, " at line %d\n", yylval.lineno + 1); + fprintf(stderr, " at %s, line %d\n", yyfn, yylval.lineno + 1); parse_errors++; }