commit 09a12e37c36bb108ccb3e4556c96ea992d7695b3 from: Martijn van Duren date: Thu Sep 08 11:39:58 2016 UTC Import vias commit - /dev/null commit + 09a12e37c36bb108ccb3e4556c96ea992d7695b3 blob - /dev/null blob + fb3216d6cf4e60bc3738ca310dfae4d291ad477a (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,14 @@ +# $OpenBSD: Makefile,v 1.1 2015/07/16 20:44:21 tedu Exp $ + +SRCS= parse.y vias.c + +PROG= vias +MAN= vias.1 vias.conf.5 + +BINDIR= /usr/bin +BINOWN= root +BINMODE=4555 + +COPTS+= -Wall + +.include blob - /dev/null blob + c0e7dc2f71581125e6a029c74e322e4322a91a0b (mode 644) --- /dev/null +++ parse.y @@ -0,0 +1,330 @@ +/* $OpenBSD: parse.y,v 1.19 2016/06/27 15:41:17 tedu Exp $ */ +/* + * Copyright (c) 2016 Martijn van Duren + * Copyright (c) 2015 Ted Unangst + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vias.h" + +typedef struct { + union { + struct { + int action; + int options; + const char **files; + }; + const char *str; + }; + int lineno; + int colno; +} yystype; +#define YYSTYPE yystype + +FILE *yyfp; + +struct rule **rules; +int nrules, maxrules; +int parse_errors = 0; +int obsolete_warned = 0; + +void yyerror(const char *, ...); +int yylex(void); +int yyparse(void); + +%} + +%token TPERMIT TDENY TOWNER TEDIT +%token TNOPASS TPERSIST +%token TSTRING + +%% + +grammar: /* empty */ + | grammar '\n' + | grammar rule '\n' + | error '\n' + ; + +rule: action ident target edit { + struct rule *r; + if ((r = calloc(1, sizeof(*r))) == NULL) + err(1, NULL); + r->action = $1.action; + r->options = $1.options; + r->ident = $2.str; + r->target = $3.str; + r->files = $4.files; + if (nrules == maxrules) { + if (maxrules == 0) + maxrules = 63; + else + maxrules *= 2; + if ((rules = reallocarray(rules, maxrules, + sizeof(*rules))) == NULL) + err(1, NULL); + } + rules[nrules++] = r; + } ; + +action: TPERMIT options { + $$.action = PERMIT; + $$.options = $2.options; + } | TDENY { + $$.action = DENY; + $$.options = 0; + } ; + +options: /* none */ { + $$.options = 0; + } | options option { + $$.options = $1.options | $2.options; + if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) { + yyerror("can't combine nopass and persist"); + YYERROR; + } + } ; +option: TNOPASS { + $$.options = NOPASS; + } | TPERSIST { + $$.options = PERSIST; + }; + +ident: TSTRING { + $$.str = $1.str; + } ; + +target: /* optional */ { + $$.str = NULL; + } | TAS TSTRING { + $$.str = $2.str; + } ; + +edit: /* none */ { + $$.files = NULL; + } | TEDIT files { + if (arraylen($2.files)) + $$.files = $2.files; + else { + free($2.files); + $$.files = NULL; + } + } ; + +files: /* empty */ { + if (($$.files = calloc(1, sizeof(char *))) == NULL) + err(1, NULL); + } | files TSTRING { + int nargs = arraylen($1.files); + char *str; + int strl, skip = 0; + if ($2.str[0] != '/') { + warnx("File %s can't be relative", $2.str); + skip = 1; + } + if ((str = realpath($2.str, NULL)) == NULL && + errno != ENOENT) { + warn("Problem verifying %s", $2.str); + skip = 1; + } + if (str != NULL && strcmp($2.str, str)) { + strl = strlen(str); + if ((str = reallocarray(str, strl + 2, + sizeof(*str))) == NULL) + err(1, NULL); + str[strl] = '/'; + str[strl+1] = '\0'; + if (strcmp($2.str, str)) { + warnx("file %s needs to be a resolved " + "path", $2.str); + skip = 1; + } + } + free(str); + if (!skip) { + if (($$.files = reallocarray($1.files, nargs + 2, + sizeof(char *))) == NULL) + err(1, NULL); + $$.files[nargs] = $2.str; + $$.files[nargs + 1] = NULL; + } + } ; + +%% + +void +yyerror(const char *fmt, ...) +{ + va_list va; + + fprintf(stderr, "vias: "); + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + fprintf(stderr, " at line %d\n", yylval.lineno + 1); + parse_errors++; +} + +struct keyword { + const char *word; + int token; +} keywords[] = { + { "deny", TDENY }, + { "permit", TPERMIT }, + { "as", TAS }, + { "nopass", TNOPASS }, + { "persist", TPERSIST }, + { "edit", TEDIT }, +}; + +int +yylex(void) +{ + char buf[1024], *ebuf, *p, *str; + int i, c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; + + p = buf; + ebuf = buf + sizeof(buf); + +repeat: + /* skip whitespace first */ + for (c = getc(yyfp); c == ' ' || c == '\t'; c = getc(yyfp)) + yylval.colno++; + + /* check for special one-character constructions */ + switch (c) { + case '\n': + yylval.colno = 0; + yylval.lineno++; + /* FALLTHROUGH */ + case '{': + case '}': + return c; + case '#': + /* skip comments; NUL is allowed; no continuation */ + while ((c = getc(yyfp)) != '\n') + if (c == EOF) + goto eof; + yylval.colno = 0; + yylval.lineno++; + return c; + case EOF: + goto eof; + } + + /* parsing next word */ + for (;; c = getc(yyfp), yylval.colno++) { + switch (c) { + case '\0': + yyerror("unallowed character NUL in column %d", + yylval.colno + 1); + escape = 0; + continue; + case '\\': + escape = !escape; + if (escape) + continue; + break; + case '\n': + if (quotes) + yyerror("unterminated quotes in column %d", + qpos + 1); + if (escape) { + nonkw = 1; + escape = 0; + yylval.colno = 0; + yylval.lineno++; + continue; + } + goto eow; + case EOF: + if (escape) + yyerror("unterminated escape in column %d", + yylval.colno); + if (quotes) + yyerror("unterminated quotes in column %d", + qpos + 1); + goto eow; + /* FALLTHROUGH */ + case '{': + case '}': + case '#': + case ' ': + case '\t': + if (!escape && !quotes) + goto eow; + break; + case '"': + if (!escape) { + quotes = !quotes; + if (quotes) { + nonkw = 1; + qpos = yylval.colno; + } + continue; + } + } + *p++ = c; + if (p == ebuf) { + yyerror("too long line"); + p = buf; + } + escape = 0; + } + +eow: + *p = 0; + if (c != EOF) + ungetc(c, yyfp); + if (p == buf) { + /* + * There could be a number of reasons for empty buffer, + * and we handle all of them here, to avoid cluttering + * the main loop. + */ + if (c == EOF) + goto eof; + else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ + goto repeat; + } + if (!nonkw) { + for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { + if (strcmp(buf, keywords[i].word) == 0) + return keywords[i].token; + } + } + if ((str = strdup(buf)) == NULL) + err(1, "strdup"); + yylval.str = str; + return TSTRING; + +eof: + if (ferror(yyfp)) + yyerror("input error reading config"); + return 0; +} blob - /dev/null blob + 1d08973fbf995e8a70855eb6a68969e2408786d7 (mode 644) --- /dev/null +++ vias.1 @@ -0,0 +1,102 @@ +.\" $OpenBSD: $ +.\" +.\"Copyright (c) 2016 Martijn van Duren +.\"Copyright (c) 2015 Ted Unangst +.\" +.\"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. +.Dd $Mdocdate: September 2 2016 $ +.Dt VIAS 1 +.Os +.Sh NAME +.Nm vias +.Nd edit a file owned by another user +.Sh SYNOPSIS +.Nm vias +.Op Fl a Ar style +.Op Fl C Ar config +.Ar file +.Op Ar editor flags +.Sh DESCRIPTION +The +.Nm +utility makes a temporary copy of +.Ar file +normaly not accessible by the user and allows the user to edit the temporary +.Ar file +with their own editor. +Upon exit the temporary file is copied back to the original file. +.Pp +The options are as follows: +.Bl -tag -width tenletters +.It Fl a Ar style +Use the specified authentication style when validating the user, +as allowed by +.Pa /etc/login.conf . +A list of doas-specific authentication methods may be configured by adding an +.Sq auth-doas +entry in +.Xr login.conf 5 . +.It Fl C Ar config +Parse and check the configuration file +.Ar config , +then exit. +If +.Ar file +is supplied, +.Nm +will also try to open the +.Ar file . +In the latter case +either +.Sq permit , +.Sq permit nopass +or +.Sq deny +will be printed on standard output, depending on file +matching results. +No copy will be made. +.El +.Sh EXIT STATUS +.Ex -std doas +It may fail for one of the following reasons: +.Pp +.Bl -bullet -compact +.It +The config file +.Pa /etc/vias.conf +could not be parsed. +.It +The user attempted to open a file which is not permitted. +.It +The password was incorrect. +.It +The +.Xr open 2 +command failed. +.El +.Sh ENVIRONMENT +If the following environment variable exists it will be utilized by +.Nm : +.Bl -tag -width EDITOR +.It Ev EDITOR +The editor specified by the string +.Ev EDITOR +will be invoked instead of the default editor +.Xr vi 1 . +.El + +.Sh SEE ALSO +.Xr doas 1 , +.Xr vias.conf 5 +.Sh AUTHORS +.An Martijn van Duren Aq Mt martijn@openbsd.org blob - /dev/null blob + 244baeac496196d5b096928b125f4e0abbdd42dd (mode 644) --- /dev/null +++ vias.c @@ -0,0 +1,500 @@ +/* $OpenBSD: vias.c,v 1.57 2016/06/19 19:29:43 martijn Exp $ */ +/* + * Copyright (c) 2016 Martijn van Duren + * Copyright (c) 2015 Ted Unangst + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vias.h" + +static void __dead +usage(void) +{ + fprintf(stderr, "usage: vias [-a style] [-C config] " + "file [editor args]\n"); + exit(1); +} + +size_t +arraylen(const char **arr) +{ + size_t cnt = 0; + + while (*arr) { + cnt++; + arr++; + } + return cnt; +} + +static int +parseuid(const char *s, uid_t *uid) +{ + struct passwd *pw; + const char *errstr; + + if ((pw = getpwnam(s)) != NULL) { + *uid = pw->pw_uid; + return 0; + } + *uid = strtonum(s, 0, UID_MAX, &errstr); + if (errstr) + return -1; + return 0; +} + +static int +uidcheck(const char *s, uid_t desired) +{ + uid_t uid; + + if (parseuid(s, &uid) != 0) + return -1; + if (uid != desired) + return -1; + return 0; +} + +static int +parsegid(const char *s, gid_t *gid) +{ + struct group *gr; + const char *errstr; + + if ((gr = getgrnam(s)) != NULL) { + *gid = gr->gr_gid; + return 0; + } + *gid = strtonum(s, 0, GID_MAX, &errstr); + if (errstr) + return -1; + return 0; +} + +static int +open_nosym(const char *file) +{ + static int dep = 0; + struct stat sb; + char dir[PATH_MAX]; + int fd, pfd; + + dep++; + (void) strlcpy(dir, dirname(file), sizeof(dir)); + + if (dir[1] == '\0') { + dep--; + if ((pfd = open("/", O_CLOEXEC)) == -1) + return -1; + } else { + pfd = open_nosym(dir); + dep--; + if (pfd == -1) + return -1; + } + + do { + if (dep) { + fd = openat(pfd, basename(file), + O_NOFOLLOW | O_CLOEXEC); + } else { + fd = openat(pfd, basename(file), + O_RDWR | O_NOFOLLOW | O_CLOEXEC | O_CREAT, 0777); + } + } while (fd == -1 && errno == EINTR); + close(pfd); + if (fd == -1) + return -1; + + if (fstat(fd, &sb) == -1) + err(1, "fstat"); +/* + * This should already have been checked in parse.y. + * It's only here to test for race-conditions + */ + if (S_ISLNK(sb.st_mode)) + errx(1, "Symbolic links not allowed"); + if (dep && !S_ISDIR(sb.st_mode)) + errc(1, ENOTDIR, NULL); + if (!dep && !S_ISREG(sb.st_mode)) + errx(1, "File is not a regular file"); + + return fd; +} + +static int +match(uid_t uid, gid_t *groups, int ngroups, const char *file, struct rule *r) +{ + int i; + int flen; + + if (r->ident[0] == ':') { + gid_t rgid; + if (parsegid(r->ident + 1, &rgid) == -1) + return 0; + for (i = 0; i < ngroups; i++) { + if (rgid == groups[i]) + break; + } + if (i == ngroups) + return 0; + } else { + if (uidcheck(r->ident, uid) != 0) + return 0; + } + if (r->files != NULL) { + for (i = 0; r->files[i] != NULL; i++) { + flen = strlen(r->files[i]); +/* Allow access to the entire directory tree */ + if (r->files[i][flen-1] == '/') { + if (!strncmp(r->files[i], file, flen)) + break; + } else { + if (!strcmp(r->files[i], file)) + break; + } + } + if (r->files[i] == NULL) + return 0; + } + return 1; +} + +static int +permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr, + const char *file) +{ + int i; + int fd = -1, pfd = -1; + uid_t suid = -1; + struct rule *r; + char *rfile; + + if ((rfile = realpath(file, NULL)) == NULL) + err(1, "Unable to open %s", file); + + *lastr = NULL; + for (i = 0; i < nrules; i++) { + r = rules[i]; + if (match(uid, groups, ngroups, rfile, r)) { +/* Try to open the file, so we know the rule is really permitted */ + if (r->target) { + if (parseuid(r->target, &suid) == -1) + err(1, "getpwnam"); + if (seteuid(suid) == -1) + err(1, "seteuid"); + } + if ((fd = open_nosym(rfile)) != -1) { + if (pfd != -1) + if (close(pfd) == -1) + err(1, "close"); + pfd = fd; + *lastr = rules[i]; + } else if (errno != EPERM) { + err(1, "open %s", file); + } + if (r->target && seteuid(0)) + err(1, "seteuid"); + } + } + if (*lastr == NULL || (*lastr)->action != PERMIT) { + close(fd); + return -1; + } + + return fd; +} + +static void +parseconfig(const char *filename, int checkperms) +{ + extern FILE *yyfp; + extern int yyparse(void); + struct stat sb; + + yyfp = fopen(filename, "r"); + if (!yyfp) + err(1, checkperms ? "vias is not enabled, %s" : + "could not open config file %s", filename); + + if (checkperms) { + if (fstat(fileno(yyfp), &sb) != 0) + err(1, "fstat(\"%s\")", filename); + if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) + errx(1, "%s is writable by group or other", filename); + if (sb.st_uid != 0) + errx(1, "%s is not owned by root", filename); + } + + yyparse(); + fclose(yyfp); + if (parse_errors) + exit(1); +} + +static void __dead +checkconfig(const char *confpath, uid_t uid, gid_t *groups, int ngroups, + const char *file) +{ + struct rule *rule; + + parseconfig(confpath, 0); + if (!file) + exit(0); + + if (permit(uid, groups, ngroups, &rule, file) != -1) { + printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : ""); + exit(0); + } + printf("deny\n"); + exit(1); +} + +static void +authuser(char *myname, char *login_style, int persist) +{ + char *challenge = NULL, *response, rbuf[1024], cbuf[128]; + auth_session_t *as; + int fd = -1; + + if (persist) + fd = open("/dev/tty", O_RDWR); + if (fd != -1) { + if (ioctl(fd, TIOCCHKVERAUTH) == 0) + goto good; + } + + if (!(as = auth_userchallenge(myname, login_style, "auth-doas", + &challenge))) + errx(1, "Authorization failed"); + if (!challenge) { + char host[HOST_NAME_MAX + 1]; + if (gethostname(host, sizeof(host))) + snprintf(host, sizeof(host), "?"); + snprintf(cbuf, sizeof(cbuf), + "\rvias (%.32s@%.32s) password: ", myname, host); + challenge = cbuf; + } + response = readpassphrase(challenge, rbuf, sizeof(rbuf), + RPP_REQUIRE_TTY); + if (response == NULL && errno == ENOTTY) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "tty required for %s", myname); + errx(1, "a tty is required"); + } + if (!auth_userresponse(as, response, 0)) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "failed auth for %s", myname); + errc(1, EPERM, NULL); + } + explicit_bzero(rbuf, sizeof(rbuf)); +good: + if (fd != -1) { + int secs = 5 * 60; + ioctl(fd, TIOCSETVERAUTH, &secs); + close(fd); + } +} +static int +fcpy(int dfd, int sfd) +{ + unsigned char buf[4096]; + int r; + + do { + while ((r = read(sfd, buf, sizeof(buf))) > 0) { + if (write(dfd, buf, r) != r) + return 0; + } + } while (r == -1 && errno == EINTR); + if (r == -1) + return 0; + return 1; +} + +int +main(int argc, char **argv) +{ + const char *confpath = NULL; + char tmpfile[] = "/tmp/vias.XXXXXX"; + char myname[_PW_NAME_LEN + 1]; + struct passwd *pw; + struct rule *rule; + uid_t uid; + gid_t groups[NGROUPS_MAX + 1]; + int ngroups; + int i, ch; + int ofd, tfd; + char cwdpath[PATH_MAX]; + const char *cwd; + char *login_style = NULL; + char *file; + char **eargv; + int status; + pid_t ret; + + setprogname("vias"); + + closefrom(STDERR_FILENO + 1); + + uid = getuid(); + if (setuid(0) == -1) + err(1, "setuid"); + + while ((ch = getopt(argc, argv, "a:C:")) != -1) { + switch (ch) { + case 'a': + login_style = optarg; + break; + case 'C': + confpath = optarg; + break; + default: + usage(); + } + } + argv += optind; + argc -= optind; + + if (argc == 0) + usage(); + file = argv[0]; + argv++; + argc--; + + pw = getpwuid(uid); + if (!pw) + err(1, "getpwuid failed"); + if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname)) + errx(1, "pw_name too long"); + ngroups = getgroups(NGROUPS_MAX, groups); + if (ngroups == -1) + err(1, "can't get groups"); + groups[ngroups++] = getgid(); + + if (confpath) + checkconfig(confpath, uid, groups, ngroups, file); + + parseconfig("/etc/vias.conf", 1); + + if ((ofd = permit(uid, groups, ngroups, &rule, file)) == -1) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "failed edit for %s: %s", myname, file); + err(1, "%s", file); + } + + if (!(rule->options & NOPASS)) + authuser(myname, login_style, rule->options & PERSIST); + + if (pledge("stdio rpath wpath cpath exec proc id", NULL) == -1) + err(1, "pledge"); + + if ((setuid(uid)) == -1) + err(1, "setuid failed"); + + if (pledge("stdio rpath wpath cpath exec proc", NULL) == -1) + err(1, "pledge"); + + if ((tfd = mkstemp(tmpfile)) == -1) + err(1, "mkstemp failed"); + + if (pledge("stdio rpath cpath exec proc", NULL) == -1) + err(1, "pledge"); + + if (!fcpy(tfd, ofd)) { + unlink(tmpfile); + err(1, "temp copy failed"); + } + + if (getcwd(cwdpath, sizeof(cwdpath)) == NULL) + cwd = "(failed)"; + else + cwd = cwdpath; + + if (pledge("stdio cpath exec proc", NULL) == -1) + err(1, "pledge"); + + syslog(LOG_AUTHPRIV | LOG_INFO, "%s edited %s as %s from %s", + myname, file, pw->pw_name, cwd); + + if ((eargv = reallocarray(NULL, arraylen((const char **) argv) + 2, + sizeof(*eargv))) == NULL) { + unlink(tmpfile); + err(1, NULL); + } + eargv[0] = getenv("EDITOR"); + if (eargv[0] == NULL || *(eargv[0]) == '\0') + eargv[0] = "vi"; + + switch (fork()) { + case -1: + unlink(tmpfile); + err(1, "fork failed"); + case 0: + if (pledge("stdio exec", NULL) == -1) + err(1, "pledge"); + + for (i = 0; argv[i] != NULL; i++) + eargv[i+1] = argv[i]; + eargv[i+1] = tmpfile; + eargv[i+2] = NULL; + execvp(eargv[0], eargv); + err(1, "execvp failed"); + default: + if (pledge("stdio cpath", NULL) == -1) + err(1, "pledge"); + + while ((ret = wait(&status)) == -1 && errno == EINTR) + ; + if (ret == -1) + err(1, "wait failed: Temporary file saved at %s", + tmpfile); + } + + if (WEXITSTATUS(status) != 0) { + errx(1, "%s exited with status %d: Temporary file saved at %s", + eargv[0], WEXITSTATUS(status), tmpfile); + } + + (void) lseek(tfd, 0, SEEK_SET); + if (ftruncate(ofd, 0) == -1) + err(1, "ftruncate failed: Temporary file saved at %s", tmpfile); + (void) lseek(ofd, 0, SEEK_SET); + if (!fcpy(ofd, tfd)) + err(1, "restoring failed: Temporary file saved at %s", tmpfile); + if (unlink(tmpfile) == -1) + err(1, "unlink %s", tmpfile); + return 0; +} blob - /dev/null blob + 4493c3af54ed59239f89c3f4c86bc221fd38c3b7 (mode 644) --- /dev/null +++ vias.conf.5 @@ -0,0 +1,98 @@ +.\" $OpenBSD: $ +.\" +.\"Copyright (c) 2016 Martijn van Duren +.\"Copyright (c) 2015 Ted Unangst +.\" +.\"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. +.Dd $Mdocdate: September 2 2016 $ +.Dt VIAS.CONF 5 +.Os +.Sh NAME +.Nm vias.conf +.Nd vias configuration file +.Sh SYNOPSIS +.Nm /etc/vias.conf +.Sh DESCRIPTION +The +.Xr vias 1 +utility allows a user to edit any file as their own user according to the rules +in the +.Nm +configuration file. +.Pp +The rules have the following format: +.Bd -ragged -offset indent +.Ic permit Ns | Ns Ic deny +.Op Ar options +.Ar identity +.Op Ic as Ar target +.Op Ic edit Op ... +.Ed +.Pp +Rules consist of the following parts: +.Bl -tag -width 11n +.It Ic permit Ns | Ns Ic deny +The action to be taken if this rule matches. +.It Ar options +Options are: +.Bl -tag -width keepenv +.It Ic nopass +The user is not required to enter a password. +.El +.It Ar identity +The username to match. +Groups may be specified by prepending a colon +.Pq Sq \&: . +Numeric IDs are also accepted. +.It Ic as Ar target +The +.Ar target +user who opens the file. +This can be used as an extra restriction on opening certain files. +The system will try to open the file as that user if all other checks match. +The default is root. +.It Ic edit Op ... +A space separated list of files to be matched. +A file needs to be the full pathname without symlinks as produced by +.Xr realpath 3 . +If the filename ends in a slash it allows access on that entire subtree. +When using the directory syntax it is advised to set +.Ar target . +.El +.Pp +The last matching rule determines the action taken. +If no rule matches, the action is denied. +.Pp +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Pp +The following quoting rules apply: +.Bl -dash +.It +The text between a pair of double quotes +.Pq Sq \&" +is taken as is. +.It +The backslash character +.Pq Sq \e +escapes the next character, including new line characters, outside comments; +as a result, comments may not be extended over multiple lines. +.It +If quotes or backslashes are used in a word, +it is not considered a keyword. +.El +.Sh SEE ALSO +.Xr vias 1 +.Sh AUTHORS +.An Martijn van Duren Aq Mt martijn@openbsd.org blob - /dev/null blob + 48d88dd7713cf28247a91d7c72781ed611365a32 (mode 644) --- /dev/null +++ vias.h @@ -0,0 +1,20 @@ +/* $OpenBSD: doas.h,v 1.8 2016/06/19 19:29:43 martijn Exp $ */ +struct rule { + int action; + int options; + const char *ident; + const char *target; + const char **files; +}; + +extern struct rule **rules; +extern int nrules, maxrules; +extern int parse_errors; + +size_t arraylen(const char **); + +#define PERMIT 1 +#define DENY 2 + +#define NOPASS 0x1 +#define PERSIST 0x4