Somehow my patches seem to get mangled. I put the diff file here: ftp://imperialat.at/vias.diff

On 08/12/15 14:22, Martijn van Duren wrote:
Hello tech@,

I really like the new doas(1) utility, but after a while I started
missing the sudoedit(8) command and it didn't felt right to execute my
editor as <other user>.

I know I could just install sudo(8) from ports, but I felt valiant and
created vias(1) as a sibling to doas(1).

- It walks through the rules and tries to open the file directly after a
sete[gu]id(2). Last matching rule still wins.
- If there are files specified in the config file: No symlinks in the
path are allowed.
- The editor is opened as the user, without lingering file descriptors
- New files will be created with ownership specified by owner. If owner
is a group, both group are set and group rights will be set to rw.
- The default editor is vi, but can be overwritten via the EDITOR env.
- flags will be passed to the editor without further parsing
- The file editing will be done on a copy under /tmp

Shortcomings are:
- Only one file at a time can be edited, even if the editor supports
multiple files. If multiple files are specified only the last file will
be done on the copy.
- The copy actions are non-atomic. This is done on purpose. rename(2)
only works if the files are on the same filesystem and I don't want to
leave mkstemp(3) clutter on unexpected locations on crash. If the
application would crash during copy it leaves the original file under
/tmp. So the only risk should be when the system goes into a full hang
during the final copy. You do have backups right?

I already showed a very early concept to tedu@, who reckons it might be
a bit overkill to include in tree, but I personally reckon that only
doas(1) motivates people to open their editor as root.

Feedback is welcome.

Sincerely,

Martijn van Duren

Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/doas/Makefile,v
retrieving revision 1.1
diff -u -p -r1.1 Makefile
--- Makefile    16 Jul 2015 20:44:21 -0000    1.1
+++ Makefile    12 Aug 2015 11:44:53 -0000
@@ -1,14 +1,8 @@
-#    $OpenBSD: Makefile,v 1.1 2015/07/16 20:44:21 tedu Exp $
+#    $OpenBSD: Makefile,v 1.12 2013/07/21 09:38:51 eric Exp $

-SRCS=    parse.y doas.c
+.include <bsd.own.mk>

-PROG=    doas
-MAN=    doas.1 doas.conf.5
+SUBDIR = doas
+SUBDIR+= vias

-BINOWN= root
-BINMODE=4555
-
-CFLAGS+= -I${.CURDIR}
-COPTS+=    -Wall
-
-.include <bsd.prog.mk>
+.include <bsd.subdir.mk>
Index: doas.c
===================================================================
RCS file: /cvs/src/usr.bin/doas/doas.c,v
retrieving revision 1.34
diff -u -p -r1.34 doas.c
--- doas.c    3 Aug 2015 15:31:05 -0000    1.34
+++ doas.c    12 Aug 2015 11:44:53 -0000
@@ -102,6 +102,9 @@ match(uid_t uid, gid_t *groups, int ngro
  {
      int i;

+    if (!(r->mode & CMD))
+        return 0;
+
      if (r->ident[0] == ':') {
          gid_t rgid;
          if (parsegid(r->ident + 1, &rgid) == -1)
Index: doas.conf.5
===================================================================
RCS file: /cvs/src/usr.bin/doas/doas.conf.5,v
retrieving revision 1.14
diff -u -p -r1.14 doas.conf.5
--- doas.conf.5    30 Jul 2015 14:02:04 -0000    1.14
+++ doas.conf.5    12 Aug 2015 11:44:53 -0000
@@ -1,6 +1,7 @@
  .\" $OpenBSD: doas.conf.5,v 1.14 2015/07/30 14:02:04 zhuk Exp $
  .\"
  .\"Copyright (c) 2015 Ted Unangst <[email protected]>
+.\"Copyright (c) 2015 Martijn van Duren <[email protected]>
  .\"
  .\"Permission to use, copy, modify, and distribute this software for any
  .\"purpose with or without fee is hereby granted, provided that the above
@@ -18,11 +19,13 @@
  .Os
  .Sh NAME
  .Nm doas.conf
-.Nd doas configuration file
+.Nd doas and vias configuration file
  .Sh DESCRIPTION
  The
  .Xr doas 1
-utility executes commands as other users according to the rules
+and
+.Xr vias 1
+utilities execute according to the rules
  in the
  .Nm
  configuration file.
@@ -32,8 +35,22 @@ The rules have the following format:
  .Ic permit Ns | Ns Ic deny
  .Op Ar options
  .Ar identity
+.Ed
+.Bd -ragged -offset indent
+.Ic permit Ns | Ns Ic deny
+.Op Ar options
+.Ar identity
  .Op Ic as Ar target
-.Op Ic cmd Ar command Op Ic args ...
+.Ic cmd
+.Op Ar command Op Ic args ...
+.Ed
+.Bd -ragged -offset indent
+.Ic permit Ns | Ns Ic deny
+.Op Ar options
+.Ar identity
+.Op Ic owner Ar target
+.Ic edit
+.Op Ar file ...
  .Ed
  .Pp
  Rules consist of the following parts:
@@ -59,7 +76,9 @@ and
  .Ev USERNAME .
  .It Ic keepenv { Oo Ar variable ... Oc Ic }
  In addition to the variables mentioned above, keep the space-separated
-specified variables.
+specified variables. The
+.Ic keepenv
+keyword is ignored with the edit keyword.
  .El
  .It Ar identity
  The username to match.
@@ -69,6 +88,15 @@ Numeric IDs are also accepted.
  .It Ic as Ar target
  The target user the running user is allowed to run the command as.
  The default is all users.
+.It Ic owner Ar target
+The
+.Ar target
+as whom the file is opened. If
+.Ar target
+is prepended with a colon
+.Pq Sq \&:
+then the file is opened with the uid of the user and the gid of
+.Ar target .
  .It Ic cmd Ar command
  The command the user is allowed or denied to run.
  The default is all commands.
@@ -80,8 +108,23 @@ need to match for the command to be succ
  Specifying
  .Ic args
  alone means that command should be run without any arguments.
+.It Ic file
+One or more filenames that may be opened by this statement. Only one
+.Ic file
+at a time may be opened by
+.Xr vias 1 .
  .El
  .Pp
+When no
+.Ic cmd
+or
+.Ic edit
+keywords are present, the rule permits unlimited
+.Xr doas 1
+and
+.Xr vias 1
+usage.
+.Pp
  The last matching rule determines the action taken.
  .Pp
  Comments can be put anywhere in the file using a hash mark
@@ -123,6 +166,7 @@ permit nopass keepenv { \e
          SUBPACKAGE WRKOBJDIR SUDO_PORT_V1 } :wsrc
  permit nopass keepenv { ENV PS1 SSH_AUTH_SOCK } :wheel
  permit nopass tedu as root cmd /usr/sbin/procmap
+permit nopass tedu owner root edit /etc/hosts
  .Ed
  .Sh SEE ALSO
  .Xr doas 1
@@ -133,3 +177,4 @@ configuration file first appeared in
  .Ox 5.8 .
  .Sh AUTHORS
  .An Ted Unangst Aq Mt [email protected]
+.An Martijn van Duren Aq Mt [email protected]
Index: doas.h
===================================================================
RCS file: /cvs/src/usr.bin/doas/doas.h,v
retrieving revision 1.4
diff -u -p -r1.4 doas.h
--- doas.h    24 Jul 2015 06:36:42 -0000    1.4
+++ doas.h    12 Aug 2015 11:44:53 -0000
@@ -4,10 +4,19 @@ struct rule {
      int action;
      int options;
      const char *ident;
-    const char *target;
-    const char *cmd;
-    const char **cmdargs;
-    const char **envlist;
+    int mode;
+    union {
+        const char *target;
+        const char *owner;
+    };
+    union {
+        struct {
+            const char *cmd;
+            const char **cmdargs;
+            const char **envlist;
+        };
+        const char **files;
+    };
  };

  extern struct rule **rules;
@@ -21,3 +30,6 @@ size_t arraylen(const char **);

  #define NOPASS        0x1
  #define KEEPENV        0x2
+
+#define CMD        0x1
+#define EDIT        0x2
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.bin/doas/parse.y,v
retrieving revision 1.11
diff -u -p -r1.11 parse.y
--- parse.y    28 Jul 2015 21:36:03 -0000    1.11
+++ parse.y    12 Aug 2015 11:44:53 -0000
@@ -1,6 +1,7 @@
  /* $OpenBSD: parse.y,v 1.11 2015/07/28 21:36:03 deraadt Exp $ */
  /*
   * Copyright (c) 2015 Ted Unangst <[email protected]>
+ * Copyright (c) 2015 Martijn van Duren <[email protected]>
   *
   * Permission to use, copy, modify, and distribute this software for any
   * purpose with or without fee is hereby granted, provided that the above
@@ -16,8 +17,10 @@
   */

  %{
+#include <sys/stat.h>
  #include <sys/types.h>
  #include <ctype.h>
+#include <libgen.h>
  #include <unistd.h>
  #include <stdint.h>
  #include <stdarg.h>
@@ -32,8 +35,14 @@ typedef struct {
          struct {
              int action;
              int options;
-            const char *cmd;
-            const char **cmdargs;
+            int mode;
+            union {
+                struct {
+                    const char *cmd;
+                    const char **cmdargs;
+                };
+                const char **files;
+            };
              const char **envlist;
          };
          const char *str;
@@ -55,7 +64,7 @@ int yyparse(void);

  %}

-%token TPERMIT TDENY TAS TCMD TARGS
+%token TPERMIT TDENY TAS TOWNER TCMD TEDIT TARGS
  %token TNOPASS TKEEPENV
  %token TSTRING

@@ -79,6 +88,29 @@ rule:        action ident target cmd {
              r->target = $3.str;
              r->cmd = $4.cmd;
              r->cmdargs = $4.cmdargs;
+            r->mode = $4.mode;
+            if (nrules == maxrules) {
+                if (maxrules == 0)
+                    maxrules = 63;
+                else
+                    maxrules *= 2;
+                if (!(rules = reallocarray(rules, maxrules,
+                    sizeof(*rules))))
+                    errx(1, "can't allocate rules");
+            }
+            rules[nrules++] = r;
+        } | action ident owner edit {
+            struct rule *r;
+            r = calloc(1, sizeof(*r));
+            if (!r)
+                errx(1, "can't allocate rule");
+            r->action = $1.action;
+            r->options = $1.options;
+/* envlist is ignore */
+            r->ident = $2.str;
+            r->owner = $3.str;
+            r->files = $4.files;
+            r->mode = $4.mode;
              if (nrules == maxrules) {
                  if (maxrules == 0)
                      maxrules = 63;
@@ -143,12 +175,26 @@ target:        /* optional */ {
              $$.str = $2.str;
          } ;

-cmd:        /* optional */ {
+owner:        /* optional */ {
+            $$.str = NULL;
+        } | TOWNER TSTRING {
+            $$.str = $2.str;
+        } ;
+
+cmd:        TCMD cmdval {
+            $$.mode = $2.mode;
+            $$.cmd = $2.str;
+            $$.cmdargs = $2.cmdargs;
+        } ;
+
+cmdval:        /* optional */ {
              $$.cmd = NULL;
              $$.cmdargs = NULL;
-        } | TCMD TSTRING args {
+            $$.mode = CMD|EDIT;
+        } | TSTRING args {
              $$.cmd = $2.str;
              $$.cmdargs = $3.cmdargs;
+            $$.mode = CMD;
          } ;

  args:        /* empty */ {
@@ -169,6 +215,30 @@ argslist:    /* empty */ {
              $$.cmdargs[nargs + 1] = NULL;
          } ;

+edit:        TEDIT files {
+            $$.mode = EDIT;
+            if (arraylen($2.files))
+                $$.files = $2.files;
+            else {
+                free($2.files);
+                $$.files = NULL;
+            }
+        } ;
+
+files:        /* empty */ {
+            if (!($$.files = calloc(1, sizeof(char *))))
+                errx(1, "can't allocate files");
+        } | files TSTRING {
+            int nargs = arraylen($1.files);
+            if ($2.str[0] != '/')
+                yyerror("file %s can't be relative", $2.str);
+            if (!($$.files = reallocarray($1.files, nargs + 2,
+                sizeof(char *))))
+                errx(1, "can't allocate args");
+            $$.files[nargs] = $2.str;
+            $$.files[nargs + 1] = NULL;
+        } ;
+
  %%

  void
@@ -190,9 +260,11 @@ struct keyword {
      { "deny", TDENY },
      { "permit", TPERMIT },
      { "as", TAS },
+    { "owner", TOWNER },
      { "cmd", TCMD },
      { "args", TARGS },
      { "nopass", TNOPASS },
+    { "edit", TEDIT },
      { "keepenv", TKEEPENV },
  };

Index: vias.1
===================================================================
RCS file: vias.1
diff -N vias.1
--- /dev/null    1 Jan 1970 00:00:00 -0000
+++ vias.1    12 Aug 2015 11:44:53 -0000
@@ -0,0 +1,86 @@
+.\" $OpenBSD: doas.1,v 1.14 2015/07/27 17:57:06 jmc Exp $
+.\"
+.\"Copyright (c) 2015 Ted Unangst <[email protected]>
+.\"Copyright (c) 2015 Martijn van Duren <[email protected]>
+.\"
+.\"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: July 27 2015 $
+.Dt VIAS 1
+.Os
+.Sh NAME
+.Nm vias
+.Nd write a file owner by another user
+.Sh SYNOPSIS
+.Nm vias
+.Op "editor options"
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a copy of the specified file as another user and opens
it in the
+favorite editor as the user executing the command.
+The
+.Ar "editor options"
+are passed to the editor without modification.
+.Pp
+If the path to
+.Ar file
+contains a symlink in one of it's components, it will result in a
permission denied.
+.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 EXIT STATUS
+.Ex -std
+It may fail for one of the following reasons:
+.Pp
+.Bl -bullet -compact
+.It
+The config file
+.Pa /etc/doas.conf
+could not be parsed.
+.It
+The user specified file could not be opened.
+.It
+The password was incorrect.
+.It
+The editor exited with a error status.
+.El
+.Sh SEE ALSO
+.Xr vi 1 ,
+.Xr doas.conf 5
+.Sh AUTHORS
+.An Ted Unangst Aq Mt [email protected]
+.An Martijn van Duren Aq Mt [email protected]
+.Sh BUGS
+.Bl -bullet -compact
+.It
+Only one
+.Ar file
+can be edited at a time, extra files will be opened with the rights of
the user.
+.It
+.Ar file
+isn't copied back atomically. This means that if the application crashes
+.Ar file
+could become corrupted. In those cases a copy of
+.Ar file
+should be available under
+.Pa /tmp ,
+as pointed out in the error message.
+.El
Index: vias.c
===================================================================
RCS file: vias.c
diff -N vias.c
--- /dev/null    1 Jan 1970 00:00:00 -0000
+++ vias.c    12 Aug 2015 11:44:53 -0000
@@ -0,0 +1,424 @@
+/* $OpenBSD: doas.c,v 1.34 2015/08/03 15:31:05 tedu Exp $ */
+/*
+ * Copyright (c) 2015 Ted Unangst <[email protected]>
+ * Copyright (c) 2015 Martijn van Duren <[email protected]>
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <login_cap.h>
+#include <bsd_auth.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <syslog.h>
+#include <errno.h>
+
+#include "doas.h"
+
+static void __dead
+usage(void)
+{
+    fprintf(stderr, "usage: vias [editor options] file\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 inline int
+containssym(const char *path)
+{
+    struct stat sb;
+
+    if (path[1] == '\0')
+        return 0;
+
+    if (lstat(path, &sb) == -1) {
+        if (errno != ENOENT)
+            return 1;
+        else
+            return containssym(dirname(path));
+    }
+
+    return (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) ?
+        containssym(dirname(path)) : 1;
+}
+
+#define BUFSIZE 2048
+static int
+fcpy(int from, int to)
+{
+    int nread;
+    char buf[BUFSIZE];
+
+/* file descriptors should be validated before copying */
+    assert(lseek(from, 0, SEEK_SET) == 0);
+    if(ftruncate(to, 0) == -1)
+        err(1, "ftruncate");
+    assert(lseek(to, 0, SEEK_SET) == 0);
+    while((nread = read(from, buf, BUFSIZE)) > 0) {
+        if(write(to, buf, nread) != nread)
+            return 0;
+    }
+    return (nread == -1) ? 0 : 1;
+}
+
+static int
+match(uid_t uid, gid_t *groups, int ngroups, const char *file, int *fd,
+      const struct stat *sb, struct rule *r)
+{
+    struct stat rsb, tsb;
+    int i;
+    int fexists = 1;
+    char rrfile[PATH_MAX];
+    char rfile[PATH_MAX];
+    uid_t ruid = 0;
+    gid_t rgid, gid = 0;
+
+    if (!(r->mode & EDIT))
+        return 0;
+
+    if (!sb)
+        fexists = 0;
+
+    if (r->ident[0] == ':') {
+        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) {
+        for (i = 0; r->files[i]; i++) {
+            if (stat(r->files[i], &rsb) == 0) {
+                if (!fexists)
+                    continue;
+                if (rsb.st_ino == sb->st_ino &&
+                    rsb.st_dev == sb->st_dev)
+                    break;
+            } else {
+                if (errno != ENOENT || fexists)
+                    continue;
+                if (realpath(file, rfile) == NULL ||
+                    realpath(r->files[i], rrfile) == NULL)
+                     continue;
+                if (strncmp(rfile, rrfile, PATH_MAX) == 0)
+                    break;
+            }
+        }
+        if (!r->files[i])
+            return 0;
+        if (containssym(r->files[i]))
+            return 0;
+    }
+
+    if (r->owner) {
+        gid = getegid();
+        if (r->owner[0] == ':') {
+            if (parsegid(r->owner + 1, &rgid) == -1)
+                return 0;
+/* setegid and seteuid should work, since euid should be 0 */
+            assert(setegid(rgid) == 0);
+            assert(seteuid(uid) == 0);
+        } else {
+            if (parseuid(r->owner, &ruid) == -1)
+                return 0;
+            assert(seteuid(ruid) == 0);
+        }
+    }
+
+    *fd = open(file, O_RDWR | O_CREAT, 0666);
+
+    if (r->owner) {
+        assert(seteuid(0) == 0);
+        assert(setegid(gid) == 0);
+        if (!fexists && *fd != -1 && r->owner[0] == ':') {
+/* Make sure that new files remains writable for group owner, if newly
created, based on group rights */
+            if (fchown(*fd, -1, rgid) == -1 ||
+                fstat(*fd, &tsb) == -1 ||
+                fchmod(*fd, ((tsb.st_mode | 060) & 07777)) == -1) {
+                (void)unlink(file);
+                close(*fd);
+                *fd = -1;
+            }
+        }
+    }
+
+    return (*fd == -1) ? 0 : 1;
+}
+
+static int
+permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
+    const char *file, int *fd)
+{
+    struct stat sb, *psb = NULL;
+    int i;
+    int tfd = -1;
+
+    if (stat(file, &sb) == 0) {
+        if(!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode))
+            return 0;
+        psb = &sb;
+    } else if (errno != ENOENT)
+            return 0;
+
+    *lastr = NULL;
+    for (i = 0; i < nrules; i++) {
+        if (match(uid, groups, ngroups, file, fd, psb, rules[i])) {
+            *lastr = rules[i];
+            if (tfd != -1)
+                (void)close(tfd);
+            tfd = *fd;
+        }
+    }
+    if (!*lastr)
+        return 0;
+    return (*lastr)->action == PERMIT;
+}
+
+static void
+parseconfig(const char *filename, int checkperms)
+{
+    extern FILE *yyfp;
+    extern int yyparse(void);
+    struct stat sb;
+
+    yyfp = fopen(filename, "r");
+    if (!yyfp) {
+        if (checkperms)
+            fprintf(stderr, "doas is not enabled.\n");
+        else
+            warn("could not open config file");
+        exit(1);
+    }
+
+    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
+fail(void)
+{
+    fprintf(stderr, "Permission denied\n");
+    exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+    char *editor;
+    char cmdline[LINE_MAX];
+    char tfile[PATH_MAX];
+    char *file, *bfile;
+    char myname[_PW_NAME_LEN + 1];
+    struct passwd *pw;
+    struct rule *rule;
+    struct stat tsb, tsb2;
+    uid_t uid;
+    gid_t groups[NGROUPS_MAX + 1];
+    int ngroups;
+    int i;
+    int tfd, fd = -1;
+    int status;
+
+    if (argc == 1)
+        usage();
+
+    uid = getuid();
+
+    ngroups = getgroups(NGROUPS_MAX, groups);
+    if (ngroups == -1)
+        err(1, "can't get groups");
+    groups[ngroups++] = getgid();
+
+    if(setuid(0) == -1)
+        errx(1, "vias not executed as root");
+
+    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");
+
+    parseconfig("/etc/doas.conf", 1);
+
+    if ((editor = getenv("EDITOR")) == NULL || editor[0] == '\0')
+        editor = "vi";
+
+    /* cmdline is used only for logging, no need to abort on truncate */
+    (void) strlcpy(cmdline, editor, sizeof(cmdline));
+    for (i = 1; i < argc; i++) {
+        if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
+            break;
+        if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
+            break;
+    }
+
+    file = argv[argc-1];
+
+    if (!permit(uid, groups, ngroups, &rule, file, &fd)) {
+        syslog(LOG_AUTHPRIV | LOG_NOTICE,
+            "failed command for %s: %s", myname, cmdline);
+        fail();
+    }
+
+    if (!(rule->options & NOPASS)) {
+        if (!auth_userokay(myname, NULL, NULL, NULL)) {
+            syslog(LOG_AUTHPRIV | LOG_NOTICE,
+                "failed password for %s", myname);
+            fail();
+        }
+    }
+
+    if ((bfile = basename(file)) == NULL)
+        err(1, "basename");
+    if (snprintf(tfile, PATH_MAX, "/tmp/%s.XXXXXX", bfile) >= PATH_MAX)
+        errx(1, "Could not create temporary name");
+
+    if ((tfd = mkstemp(tfile)) == -1)
+        err(1, "mkstemp");
+    if (fchown(tfd, uid, -1) == -1) {
+        (void)unlink(tfile);
+        err(1, "fchown");
+    }
+
+    if (!fcpy(fd, tfd))
+        err(1, "fcpy");
+
+    if (fstat(tfd, &tsb) == -1) {
+        (void)unlink(tfile);
+        err(1, "fstat");
+    }
+
+    switch (fork()) {
+        case -1:
+            (void)unlink(tfile);
+            err(1, "fork");
+        case 0:
+            (void)close(tfd);
+            (void)close(fd);
+            setuid(uid);
+            argv[0] = editor;
+            argv[argc-1] = tfile;
+            execvp(editor, argv);
+            err(1, "execvp");
+    }
+    if (wait(&status) == -1) {
+        (void)unlink(tfile);
+        err(1, "wait");
+    }
+    if (status != 0) {
+        (void)unlink(tfile);
+        errx(1, "Editor %s exited with error code %d",
+             editor, status);
+    }
+
+    if (fstat(tfd, &tsb2) == -1)
+        err(1, "stat (temp file %s preserved)", tfile);
+
+    if (tsb.st_mtime == tsb2.st_mtime && tsb.st_size == tsb2.st_size) {
+        (void)unlink(tfile);
+        warnx("temporary file not modified");
+        exit(0);
+    }
+    if (!fcpy(tfd, fd))
+        err(1, "fcpy (temp file %s preserved)", tfile);
+    (void)close(fd);
+    (void)close(tfd);
+    (void)unlink(tfile);
+    exit(0);
+}
Index: doas/Makefile
===================================================================
RCS file: doas/Makefile
diff -N doas/Makefile
--- /dev/null    1 Jan 1970 00:00:00 -0000
+++ doas/Makefile    12 Aug 2015 11:44:54 -0000
@@ -0,0 +1,17 @@
+#    $OpenBSD: Makefile,v 1.1 2015/07/16 20:44:21 tedu Exp $
+
+.PATH:        ${.CURDIR}/..
+
+SRCS=    parse.y doas.c
+
+PROG=    doas
+MAN=    doas.1 doas.conf.5
+
+BINDIR=    /usr/bin
+BINOWN=    root
+BINMODE=4555
+
+CFLAGS+= -I${.CURDIR}/..
+COPTS+=    -Wall
+
+.include <bsd.prog.mk>
Index: vias/Makefile
===================================================================
RCS file: vias/Makefile
diff -N vias/Makefile
--- /dev/null    1 Jan 1970 00:00:00 -0000
+++ vias/Makefile    12 Aug 2015 11:44:54 -0000
@@ -0,0 +1,17 @@
+#    $OpenBSD: Makefile,v 1.1 2015/07/16 20:44:21 tedu Exp $
+
+.PATH:        ${.CURDIR}/..
+
+SRCS=    parse.y vias.c
+
+PROG=    vias
+MAN=    vias.1 doas.conf.5
+
+BINDIR=    /usr/bin
+BINOWN= root
+BINMODE=4555
+
+CFLAGS+= -I${.CURDIR}/..
+COPTS+=    -Wall
+
+.include <bsd.prog.mk>

Reply via email to