mistress verily.c
  1#include <errno.h>
  2#include <grp.h>
  3#include <limits.h>
  4#include <pwd.h>
  5#include <stdarg.h>
  6#include <stdbool.h>
  7#include <stddef.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_LOGIN_CAP
 18#include <login_cap.h>
 19#endif
 20
 21typedef ptrdiff_t	isz;
 22
 23#define countof(x)	(isz)(sizeof(x) / sizeof((x)[0]))
 24
 25#define STRINGIFY1(x)	#x
 26#define STRINGIFY(x)	STRINGIFY1(x)
 27#define CHECKFILESTR	STRINGIFY(CHECKFILE)
 28#define CHECKGRPSTR	STRINGIFY(CHECKGRP)
 29
 30char *argv0;
 31
 32__attribute((format(printf, 1, 2)))
 33static noreturn void	die(char *, ...);
 34static noreturn void	usage(void);
 35static void		dosetuid(uid_t, struct passwd *);
 36
 37int
 38main(int argc, char **argv)
 39{
 40	argv0 = argv[0];
 41
 42#if !defined(WITH_CHROOT) && defined(HAVE_PLEDGE)
 43	if (pledge("stdio rpath getpw exec id", 0) < 0)
 44		die("pledge: %s", strerror(errno));
 45#endif
 46
 47	struct group *wheel = getgrnam(CHECKGRPSTR);
 48	if (!wheel)
 49		die(CHECKGRPSTR ": No such group");
 50	gid_t groups[NGROUPS_MAX];
 51	int ngroups = getgroups(countof(groups), groups);
 52	if (ngroups < 0)
 53		die("getgroups: %s", strerror(errno));
 54	bool iswheel = false;
 55	for (int i = 0; i < countof(groups); i++)
 56		if (groups[i] == wheel->gr_gid) {
 57			iswheel = true;
 58			break;
 59		}
 60	if (!iswheel)
 61		die("Not in the " CHECKGRPSTR " group");
 62
 63	if (access(CHECKFILESTR, W_OK) < 0)
 64		die(CHECKFILESTR ": %s", strerror(errno));
 65
 66	if (argc <= 1 || argv[1][0] != '-')
 67		dosetuid(0, 0);
 68
 69	ARGBEGIN {
 70#ifdef HAVE_LOGIN_CAP 
 71	case 'c': {
 72		char *arg = EARGF(usage());
 73		if (setclasscontext(arg, LOGIN_SETALL) < 0)
 74			die("setclasscontext %s: %s", arg, strerror(errno));
 75		break;
 76	}
 77#endif
 78	case 'G': {
 79		char *arg = EARGF(usage());
 80		char *end;
 81		gid_t gid = strtol(arg, &end, 10);
 82		if (arg[0] == '\0' || end[0] != '\0')
 83			usage();
 84		if (setgid(gid) < 0)
 85			die("setgid %u: %s", gid, strerror(errno));
 86		break;
 87	}
 88	case 'g': {
 89		char *arg = EARGF(usage());
 90		struct group *group = getgrnam(arg);
 91		if (!group)
 92			die("%s: No such group", arg);
 93		if (setgid(group->gr_gid) < 0)
 94			die("setgid %u: %s", group->gr_gid, strerror(errno));
 95		break;
 96	}
 97	case 'n':
 98		exit(0);
 99#ifdef WITH_CHROOT
100	case 'r': {
101		char *arg = EARGF(usage());
102		if (chroot(arg) < 0)
103			die("chroot %s: %s", arg, strerror(errno));
104		if (chdir("/") < 0)
105			die("chdir /: %s", strerror(errno));
106		break;
107	}
108#endif
109	case 'U': {
110		char *arg = EARGF(usage());
111		char *end;
112		uid_t uid = strtol(arg, &end, 10);
113		if (arg[0] == '\0' || end[0] != '\0')
114			usage();
115		dosetuid(uid, 0);
116		break;
117	}
118	case 'u': {
119		char *arg = EARGF(usage());
120		struct passwd *passwd = getpwnam(arg);
121		if (!passwd)
122			die("%s: No such user", arg);
123		dosetuid(passwd->pw_uid, passwd);
124		break;
125	}
126	default:
127		usage();
128	}ARGEND
129
130	char **cmd;
131	char *shcmd[3] = {0, "-i", 0};
132	if (argc == 0) {
133		shcmd[0] = getenv("SHELL");
134		if (!shcmd[0])
135			die("$SHELL not set");
136		cmd = shcmd;
137	} else
138		cmd = argv;
139
140	(void)execvp(cmd[0], cmd);
141	die("execvp %s: %s", cmd[0], strerror(errno));
142}
143
144__attribute((format(printf, 1, 2)))
145static noreturn void
146die(char *fmt, ...)
147{
148	va_list ap;
149
150	fprintf(stderr, "%s: ", argv0);
151
152	va_start(ap, fmt);
153	vfprintf(stderr, fmt, ap);
154	va_end(ap);
155
156	fputc('\n', stderr);
157
158	exit(1);
159}
160
161static noreturn void
162usage(void)
163{
164	fprintf(stderr, "usage: %s [-n] "
165#ifdef HAVE_LOGIN_CAP
166	    "[-c class] "
167#endif
168	    "[-G gid] [-g group] "
169#ifdef WITH_CHROOT
170	    "[-r dir] "
171#endif
172	    "[-U uid] [-u user]\n", argv0);
173	exit(1);
174}
175
176static void
177dosetuid(uid_t uid, struct passwd *passwd)
178{
179#ifdef HAVE_LOGIN_CAP
180	if (!passwd)
181		passwd = getpwuid(uid);
182	if (!passwd)
183		die("%u: No such user id", uid);
184	if (setusercontext(0, passwd, uid, LOGIN_SETALL) < 0)
185		die("setusercontext: %s", strerror(errno));
186#else
187	(void)passwd;
188	if(setuid(uid) < 0)
189		die("setuid %u: %s", uid, strerror(errno));
190#endif
191}