2 * Copyright (c) 2016 Martijn van Duren <vias@imperialat.at>
3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/types.h>
21 #include <sys/ioctl.h>
26 #include <login_cap.h>
28 #include <readpassphrase.h>
45 fprintf(stderr, "usage: vias [-a style] [-C config] "
46 "file [editor args]\n");
51 arraylen(const char **arr)
63 parseuid(const char *s, uid_t *uid)
68 if ((pw = getpwnam(s)) != NULL) {
72 *uid = strtonum(s, 0, UID_MAX, &errstr);
79 uidcheck(const char *s, uid_t desired)
83 if (parseuid(s, &uid) != 0)
91 parsegid(const char *s, gid_t *gid)
96 if ((gr = getgrnam(s)) != NULL) {
100 *gid = strtonum(s, 0, GID_MAX, &errstr);
107 open_nosym(const char *file)
115 (void) strlcpy(dir, dirname(file), sizeof(dir));
117 if (dir[1] == '\0') {
119 if ((pfd = open("/", O_CLOEXEC)) == -1)
122 pfd = open_nosym(dir);
130 fd = openat(pfd, basename(file),
131 O_NOFOLLOW | O_CLOEXEC);
133 fd = openat(pfd, basename(file),
134 O_RDWR | O_NOFOLLOW | O_CLOEXEC | O_CREAT, 0777);
136 } while (fd == -1 && errno == EINTR);
141 if (fstat(fd, &sb) == -1)
144 * This should already have been checked in parse.y.
145 * It's only here to test for race-conditions
147 if (S_ISLNK(sb.st_mode))
148 errx(1, "Symbolic links not allowed");
149 if (dep && !S_ISDIR(sb.st_mode))
150 errc(1, ENOTDIR, NULL);
151 if (!dep && !S_ISREG(sb.st_mode))
152 errx(1, "File is not a regular file");
158 match(uid_t uid, gid_t *groups, int ngroups, const char *file, struct rule *r)
163 if (r->ident[0] == ':') {
165 if (parsegid(r->ident + 1, &rgid) == -1)
167 for (i = 0; i < ngroups; i++) {
168 if (rgid == groups[i])
174 if (uidcheck(r->ident, uid) != 0)
177 if (r->files != NULL) {
178 for (i = 0; r->files[i] != NULL; i++) {
179 flen = strlen(r->files[i]);
180 /* Allow access to the entire directory tree */
181 if (r->files[i][flen-1] == '/') {
182 if (!strncmp(r->files[i], file, flen))
185 if (!strcmp(r->files[i], file))
189 if (r->files[i] == NULL)
196 permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
200 int fd = -1, pfd = -1;
205 if ((rfile = realpath(file, NULL)) == NULL)
206 err(1, "Unable to open %s", file);
209 for (i = 0; i < nrules; i++) {
211 if (match(uid, groups, ngroups, rfile, r)) {
212 /* Try to open the file, so we know the rule is really permitted */
214 if (parseuid(r->target, &suid) == -1)
216 if (seteuid(suid) == -1)
219 if ((fd = open_nosym(rfile)) != -1) {
221 if (close(pfd) == -1)
225 } else if (errno != EPERM) {
226 err(1, "open %s", file);
228 if (r->target && seteuid(0))
232 if (*lastr == NULL || (*lastr)->action != PERMIT) {
233 if (fd != -1 && close(fd) == -1)
242 parseconfig(const char *filename, int checkperms)
245 extern int yyparse(void);
248 yyfp = fopen(filename, "r");
250 err(1, checkperms ? "vias is not enabled, %s" :
251 "could not open config file %s", filename);
254 if (fstat(fileno(yyfp), &sb) != 0)
255 err(1, "fstat(\"%s\")", filename);
256 if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
257 errx(1, "%s is writable by group or other", filename);
259 errx(1, "%s is not owned by root", filename);
269 checkconfig(const char *confpath, uid_t uid, gid_t *groups, int ngroups,
274 parseconfig(confpath, 0);
278 if (permit(uid, groups, ngroups, &rule, file) != -1) {
279 printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
287 authuser(char *myname, char *login_style, int persist)
289 char *challenge = NULL, *response, rbuf[1024], cbuf[128];
294 fd = open("/dev/tty", O_RDWR);
296 if (ioctl(fd, TIOCCHKVERAUTH) == 0)
300 if (!(as = auth_userchallenge(myname, login_style, "auth-doas",
302 errx(1, "Authorization failed");
304 char host[HOST_NAME_MAX + 1];
305 if (gethostname(host, sizeof(host)))
306 snprintf(host, sizeof(host), "?");
307 snprintf(cbuf, sizeof(cbuf),
308 "\rvias (%.32s@%.32s) password: ", myname, host);
311 response = readpassphrase(challenge, rbuf, sizeof(rbuf),
313 if (response == NULL && errno == ENOTTY) {
314 syslog(LOG_AUTHPRIV | LOG_NOTICE,
315 "tty required for %s", myname);
316 errx(1, "a tty is required");
318 if (!auth_userresponse(as, response, 0)) {
319 explicit_bzero(rbuf, sizeof(rbuf));
320 syslog(LOG_AUTHPRIV | LOG_NOTICE,
321 "failed auth for %s", myname);
322 errc(1, EPERM, NULL);
324 explicit_bzero(rbuf, sizeof(rbuf));
328 ioctl(fd, TIOCSETVERAUTH, &secs);
333 fcpy(int dfd, int sfd)
335 unsigned char buf[4096];
339 while ((r = read(sfd, buf, sizeof(buf))) > 0) {
340 if (write(dfd, buf, r) != r)
343 } while (r == -1 && errno == EINTR);
350 main(int argc, char **argv)
352 const char *confpath = NULL;
353 char tmpfile[] = "/tmp/vias.XXXXXX";
354 char myname[_PW_NAME_LEN + 1];
358 gid_t groups[NGROUPS_MAX + 1];
362 char cwdpath[PATH_MAX];
364 char *login_style = NULL;
372 closefrom(STDERR_FILENO + 1);
376 while ((ch = getopt(argc, argv, "a:C:")) != -1) {
379 login_style = optarg;
391 if (argc == 0 && confpath == NULL)
399 err(1, "getpwuid failed");
400 if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
401 errx(1, "pw_name too long");
402 ngroups = getgroups(NGROUPS_MAX, groups);
404 err(1, "can't get groups");
405 groups[ngroups++] = getgid();
408 checkconfig(confpath, uid, groups, ngroups, file);
411 errx(1, "not installed setuid");
413 parseconfig("/etc/vias.conf", 1);
417 if ((ofd = permit(uid, groups, ngroups, &rule, file)) == -1) {
418 syslog(LOG_AUTHPRIV | LOG_NOTICE,
419 "failed edit for %s: %s", myname, file);
420 errc(1, EPERM, "%s", file);
423 if (setreuid(uid, 0) == -1)
424 err(1, "setreuid failed");
426 if (!(rule->options & NOPASS))
427 authuser(myname, login_style, rule->options & PERSIST);
429 if (pledge("stdio rpath wpath cpath exec proc id", NULL) == -1)
432 if ((setuid(uid)) == -1)
433 err(1, "setuid failed");
435 if (pledge("stdio rpath wpath cpath exec proc", NULL) == -1)
438 if ((tfd = mkstemp(tmpfile)) == -1)
439 err(1, "mkstemp failed");
441 if (pledge("stdio rpath cpath exec proc", NULL) == -1)
444 if (!fcpy(tfd, ofd)) {
446 err(1, "temp copy failed");
449 if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
454 if (pledge("stdio cpath exec proc", NULL) == -1)
457 syslog(LOG_AUTHPRIV | LOG_INFO, "%s edited %s from %s",
460 if ((eargv = reallocarray(NULL, arraylen((const char **) argv) + 2,
461 sizeof(*eargv))) == NULL) {
465 eargv[0] = getenv("EDITOR");
466 if (eargv[0] == NULL || *(eargv[0]) == '\0')
469 switch ((vipid = fork())) {
472 err(1, "fork failed");
474 if (pledge("stdio exec", NULL) == -1)
477 for (i = 0; argv[i] != NULL; i++)
478 eargv[i+1] = argv[i];
479 eargv[i+1] = tmpfile;
481 execvp(eargv[0], eargv);
482 err(1, "execvp failed");
484 if (pledge("stdio cpath", NULL) == -1)
487 (void) signal(SIGINT, SIG_IGN);
488 while ((ret = waitpid(vipid, &status, 0)) == -1 &&
492 err(1, "wait failed: Temporary file saved at %s",
496 if (WEXITSTATUS(status) != 0) {
497 errx(1, "%s exited with status %d: Temporary file saved at %s",
498 eargv[0], WEXITSTATUS(status), tmpfile);
501 (void) lseek(tfd, 0, SEEK_SET);
502 if (ftruncate(ofd, 0) == -1)
503 err(1, "ftruncate failed: Temporary file saved at %s", tmpfile);
504 (void) lseek(ofd, 0, SEEK_SET);
506 err(1, "restoring failed: Temporary file saved at %s", tmpfile);
507 if (unlink(tmpfile) == -1)
508 err(1, "unlink %s", tmpfile);