commit 36e80b7

Emilia Smólska  ·  2026-07-25 23:59:11 +0000 UTC
parent 36e80b7
import
5 files changed,  +385, -0
A arg.h
A config
+38, -0
 1@@ -0,0 +1,38 @@
 2+.POSIX:
 3+.SUFFIXES:
 4+
 5+CC=	cc
 6+CFLAGS=	-g -Wall -Wextra -Wpedantic
 7+
 8+CPP=	cpp
 9+SED=	sed
10+
11+PREFIX=		/usr/local
12+SBINDIR=	${DESTDIR}${PREFIX}/sbin
13+
14+PROG=	verily
15+OBJS=	verily.o
16+
17+MAN=	verily.8
18+MANIN=	verily.8.in
19+
20+all: ${PROG} ${MAN}
21+
22+${PROG}: ${OBJS}
23+	${CC} ${LDFLAGS} -o $@ ${OBJS}
24+
25+${OBJS}: arg.h config
26+
27+.SUFFIXES: .c .o
28+.c.o:
29+	${CC} ${CFLAGS} -c -o $@ $<
30+
31+${MAN}: ${MANIN} config
32+	${CPP} ${CPPFLAGS} ${MANIN} | ${SED} -e '/^#/d' -e '/^$$/d' >$@
33+
34+clean:
35+	rm -f *.core *.o ${PROG} ${MAN}
36+
37+install:
38+	cp ${PROG} ${SBINDIR}/${PROG}
39+	chmod u+s ${SBINDIR}/${PROG}
A arg.h
+33, -0
 1@@ -0,0 +1,33 @@
 2+#define nil	((void *)0)
 3+
 4+extern char	*argv0;
 5+
 6+#define ARGBEGIN	for((argv0 || (argv0 = argv[0])), argc--, argv++;\
 7+			    argv[0] && argv[0][0] == '-' && argv[0][1];\
 8+			    argc--, argv++){\
 9+				char *_args;\
10+				char *_argt;\
11+				char _argc;\
12+				_args = &argv[0][1];\
13+				if(_args[0] == '-' && _args[1] == '\0'){\
14+					argc--, argv++;\
15+					break;\
16+				}\
17+				_argc = '\0';\
18+				while(_args[0] && (_argc = *_args++))\
19+					switch(_argc)
20+
21+#define ARGEND			(void)_args, _argt, _argc;\
22+			}\
23+			(void)argc, argv;
24+
25+#define ARGF()		(_argt = _args, _args = "",\
26+			     (_argt[0] ? _argt :\
27+			     argv[1] ? (argc--, *++argv) : nil))
28+
29+#define EARGF(x)	(_argt = _args, _args = "",\
30+			     (_argt[0] ? _argt :\
31+			     argv[1] ? (argc--, *++argv) :\
32+			     ((x), abort(), nil)))
33+
34+#define ARGC()		_argc
A config
+25, -0
 1@@ -0,0 +1,25 @@
 2+/* file to check for write access */
 3+#ifndef CHECKFILE
 4+#define CHECKFILE	/etc/verily
 5+#endif
 6+
 7+/* group to check for membership */
 8+#ifndef CHECKGRP
 9+#define CHECKGRP	wheel
10+#endif
11+
12+/* support for -r flag */
13+/* #define WITH_CHROOT */
14+
15+/* pledge system call present */
16+#ifdef __OpenBSD__
17+#define HAVE_PLEDGE
18+#endif
19+
20+/* setclasscontext library function present */
21+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
22+#define HAVE_SETCLASSCONTEXT
23+#endif
24+
25+/* for compilers without attribute support */
26+/* #define __attribute(_) */
+107, -0
  1@@ -0,0 +1,107 @@
  2+#include "config"
  3+.\" -*- coding: utf-8; -*- "
  4+.Dd $Mdocdate$
  5+.Dt verily 8
  6+.Os
  7+.Sh NAME
  8+.Nm verily
  9+.Nd run commands as different class, group, or user
 10+.Sh SYNOPSIS
 11+.Nm
 12+#ifdef HAVE_SETCLASSCONTEXT
 13+.Op Fl c
 14+.Op Ar class
 15+#endif
 16+.Op Fl G Ar gid
 17+.Op Fl g Ar group
 18+#ifdef CONFIG_CHROOT
 19+.Op Fl r Ar dir
 20+#endif
 21+.Op Fl U Ar uid
 22+.Op Fl u Ar user
 23+.Op Ar cmd
 24+.Op Ar args ...
 25+.Sh DESCRIPTION
 26+The
 27+.Nm
 28+utility allows a user to run a command
 29+.Po Ev $SHELL Fl i
 30+if no command is specified
 31+.Pc
 32+with a different
 33+#ifdef HAVE_SETCLASSCONTEXT
 34+login class,
 35+#endif
 36+group id,
 37+#ifdef CONFIG_CHROOT
 38+root directory,
 39+#endif
 40+or user id, provided that the user is a member of the CHECKGRP group and has
 41+write permission on
 42+.Pa CHECKFILE .
 43+The options are as follows:
 44+.Bl -tag -width 8n
 45+#ifdef HAVE_SETCLASSCONTEXT
 46+.It Fl c Ar class
 47+Specify a login class.
 48+#endif
 49+.It Fl G Ar gid
 50+Specify a group id.
 51+.It Fl g Ar group
 52+Specify a group name.
 53+#ifdef CONFIG_CHROOT
 54+.It Fl r Ar dir
 55+Specify a new root directory to
 56+.Xr chroot 2
 57+into.
 58+#endif
 59+.It Fl U Ar uid
 60+Specify a user id. If the
 61+.Fl s
 62+option is not supplied in an invocation, the
 63+.Nm
 64+utility defaults to user id 0.
 65+.It Fl u Ar user
 66+Specify a user name.
 67+.El
 68+.Sh ENVIRONMENT
 69+.Bl -tag -width Ds
 70+.It Ev SHELL
 71+Used as the default command
 72+.Pq with the Fl i No flag
 73+when invoked without arguments.
 74+.El
 75+.Sh FILES
 76+.Bl -tag -width Ds
 77+.It Pa CHECKFILE
 78+The user must have write permission on this file in order to invoke the
 79+.Nm
 80+utility.
 81+.El
 82+.Sh EXIT STATUS
 83+The
 84+.Nm
 85+utility exits 0 on success, and >0 if an error occurs or if the user is not
 86+authorized.
 87+.Sh SEE ALSO
 88+#ifdef CONFIG_CHROOT
 89+.Xr chroot 1 ,
 90+#endif
 91+.Xr doas 1 ,
 92+#ifdef HAVE_SETCLASSCONTEXT
 93+.Xr login 1 ,
 94+#endif
 95+.Xr su 1 ,
 96+.Xr sudo 1 ,
 97+.Xr really 8 .
 98+.Sh AUTHORS
 99+.An Emilia Smólska Aq Mt emilia@hopeserv.net
100+.Sh SECURITY CONSIDERATIONS
101+Installing
102+.Nm
103+setuid is unlikely to compromise the security of the system. It is intended to
104+serve as a convenient safeguard for administrator accounts that are equivalent
105+in power to root, and not as a comprehensive user-switching tool like
106+.Xr doas 1 .
107+.Sh MAKE ME A SANDWICH
108+.Lk "https://xkcd.com/149"
+182, -0
  1@@ -0,0 +1,182 @@
  2+#include <errno.h>
  3+#include <grp.h>
  4+#include <limits.h>
  5+#include <pwd.h>
  6+#include <stdarg.h>
  7+#include <stdbool.h>
  8+#include <stdnoreturn.h>
  9+#include <stdio.h>
 10+#include <stdlib.h>
 11+#include <string.h>
 12+#include <unistd.h>
 13+
 14+#include "arg.h"
 15+#include "config"
 16+
 17+#ifdef HAVE_SETCLASSCONTEXT
 18+#include <login_cap.h>
 19+#endif
 20+
 21+#define nelem(x)	(long)(sizeof(x) / sizeof((x)[0]))
 22+
 23+#define STRINGIFY1(x)	#x
 24+#define STRINGIFY(x)	STRINGIFY1(x)
 25+#define CHECKFILESTR	STRINGIFY(CHECKFILE)
 26+#define CHECKGRPSTR	STRINGIFY(CHECKGRP)
 27+
 28+char *argv0;
 29+
 30+__attribute((format(printf, 1, 2)))
 31+static noreturn void	die(char *, ...);
 32+static noreturn void	usage(void);
 33+
 34+int
 35+main(int argc, char **argv)
 36+{
 37+	gid_t groups[NGROUPS_MAX];
 38+	char *shcmd[3] = {nil, "-i", nil};
 39+	char **cmd;
 40+	char *arg;
 41+	struct group *wheel;
 42+	int ngroups;
 43+	bool iswheel;
 44+
 45+	argv0 = argv[0];
 46+
 47+#if !defined(WITH_CHROOT) && defined(HAVE_PLEDGE)
 48+	if(pledge("stdio rpath getpw exec id", nil) < 0)
 49+		die("pledge: %s", strerror(errno));
 50+#endif
 51+
 52+	wheel = getgrnam(CHECKGRPSTR);
 53+	if(!wheel)
 54+		die(CHECKGRPSTR ": No such group");
 55+	ngroups = getgroups(nelem(groups), groups);
 56+	if(ngroups < 0)
 57+		die("getgroups: %s", strerror(errno));
 58+	iswheel = false;
 59+	for(int i = 0; i < nelem(groups); i++)
 60+		if(groups[i] == wheel->gr_gid)
 61+			iswheel = true;
 62+	if(!iswheel)
 63+		die("Not in the " CHECKGRPSTR " group");
 64+
 65+	if(access(CHECKFILESTR, W_OK) < 0)
 66+		die(CHECKFILESTR ": %s", strerror(errno));
 67+
 68+	if(argc <= 1 || argv[1][0] != '-')
 69+		if(seteuid(0) < 0)
 70+			die("seteuid 0: %s", strerror(errno));
 71+
 72+	ARGBEGIN{
 73+#ifdef HAVE_SETCLASSCONTEXT 
 74+	case 'c': {
 75+		arg = EARGF(usage());
 76+		if(setclasscontext(arg, LOGIN_SETALL) < 0)
 77+			die("setclasscontext %s: %s", arg, strerror(errno));
 78+		break;
 79+	}
 80+#endif
 81+	case 'G': {
 82+		char *end;
 83+		gid_t gid;
 84+
 85+		arg = EARGF(usage());
 86+		gid = strtol(arg, &end, 10);
 87+		if(arg[0] == '\0' || end[0] != '\0')
 88+			usage();
 89+		if(setegid(gid) < 0)
 90+			die("setegid %u: %s", gid, strerror(errno));
 91+		break;
 92+	}
 93+	case 'g': {
 94+		struct group *group;
 95+
 96+		arg = EARGF(usage());
 97+		group = getgrnam(arg);
 98+		if(!group)
 99+			die("%s: No such group", arg);
100+		if(setegid(group->gr_gid) < 0)
101+			die("setegid %u: %s", group->gr_gid, strerror(errno));
102+		break;
103+	}
104+#ifdef WITH_CHROOT
105+	case 'r': {
106+		arg = EARGF(usage());
107+		if(chroot(arg) < 0)
108+			die("chroot %s: %s", arg, strerror(errno));
109+		if(chdir("/") < 0)
110+			die("chdir /: %s", strerror(errno));
111+		break;
112+	}
113+#endif
114+	case 'U': {
115+		char *end;
116+		uid_t uid;
117+
118+		arg = EARGF(usage());
119+		uid = strtol(arg, &end, 10);
120+		if(arg[0] == '\0' || end[0] != '\0')
121+			usage();
122+		if(seteuid(uid) < 0)
123+			die("seteuid %u: %s", uid, strerror(errno));
124+		break;
125+	}
126+	case 'u': {
127+		struct passwd *passwd;
128+
129+		arg = EARGF(usage());
130+		passwd = getpwnam(arg);
131+		if(!passwd)
132+			die("%s: No such user", arg);
133+		if(seteuid(passwd->pw_uid) < 0)
134+			die("seteuid %u: %s", passwd->pw_uid, strerror(errno));
135+		break;
136+	}
137+	default:
138+		usage();
139+	}ARGEND
140+
141+	if(argc == 0){
142+		shcmd[0] = getenv("SHELL");
143+		if(!shcmd[0])
144+			die("$SHELL not set");
145+		cmd = shcmd;
146+	}else
147+		cmd = argv;
148+
149+	(void)execvp(cmd[0], cmd);
150+	die("execvp %s: %s", cmd[0], strerror(errno));
151+}
152+
153+__attribute((format(printf, 1, 2)))
154+static noreturn void
155+die(char *fmt, ...)
156+{
157+	va_list ap;
158+
159+	fprintf(stderr, "%s: ", argv0);
160+
161+	va_start(ap, fmt);
162+	vfprintf(stderr, fmt, ap);
163+	va_end(ap);
164+
165+	fputc('\n', stderr);
166+
167+	exit(1);
168+}
169+
170+static noreturn void
171+usage(void)
172+{
173+	fprintf(stderr, "usage: %s "
174+#if defined(HAVE_SETCLASSCONTEXT)
175+	    "[-c class] "
176+#endif
177+	    "[-G gid] [-g group] "
178+#if defined(WITH_CHROOT)
179+	    "[-r dir] "
180+#endif
181+	    "[-U uid] [-u user]\n", argv0);
182+	exit(1);
183+}