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 <t...@openbsd.org>
+.\"Copyright (c) 2015 Martijn van Duren <v...@imperialat.at>
 .\"
 .\"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 t...@openbsd.org
+.An Martijn van Duren Aq Mt v...@imperialat.at
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 <t...@openbsd.org>
+ * Copyright (c) 2015 Martijn van Duren <v...@imperialat.at>
  *
  * 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 <t...@openbsd.org>
+.\"Copyright (c) 2015 Martijn van Duren <v...@imperialat.at>
+.\"
+.\"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 t...@openbsd.org
+.An Martijn van Duren Aq Mt v...@imperialat.at
+.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 <t...@openbsd.org>
+ * Copyright (c) 2015 Martijn van Duren <v...@imperialat.at>
+ *
+ * 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