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}