1 |
|
|
/* $OpenBSD: privsep.c,v 1.47 2017/09/08 19:30:13 brynet Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2003 Can Erkin Acar |
5 |
|
|
* Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org> |
6 |
|
|
* |
7 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
8 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
9 |
|
|
* copyright notice and this permission notice appear in all copies. |
10 |
|
|
* |
11 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
12 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
14 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
16 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
17 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 |
|
|
*/ |
19 |
|
|
|
20 |
|
|
#include <sys/types.h> |
21 |
|
|
#include <sys/socket.h> |
22 |
|
|
#include <sys/wait.h> |
23 |
|
|
#include <sys/ioctl.h> |
24 |
|
|
|
25 |
|
|
#include <netinet/in.h> |
26 |
|
|
#include <net/if.h> |
27 |
|
|
#include <netinet/if_ether.h> |
28 |
|
|
#include <net/bpf.h> |
29 |
|
|
#include <net/pfvar.h> |
30 |
|
|
|
31 |
|
|
#include <rpc/rpc.h> |
32 |
|
|
|
33 |
|
|
#include <err.h> |
34 |
|
|
#include <errno.h> |
35 |
|
|
#include <fcntl.h> |
36 |
|
|
#include <netdb.h> |
37 |
|
|
#include <paths.h> |
38 |
|
|
#include <pwd.h> |
39 |
|
|
#include <signal.h> |
40 |
|
|
#include <stdarg.h> |
41 |
|
|
#include <stdio.h> |
42 |
|
|
#include <stdlib.h> |
43 |
|
|
#include <string.h> |
44 |
|
|
#include <syslog.h> |
45 |
|
|
#include <unistd.h> |
46 |
|
|
|
47 |
|
|
#include "interface.h" |
48 |
|
|
#include "privsep.h" |
49 |
|
|
#include "pfctl_parser.h" |
50 |
|
|
|
51 |
|
|
/* |
52 |
|
|
* tcpdump goes through four states: STATE_INIT is where the |
53 |
|
|
* bpf device and the input file is opened. In STATE_BPF, the |
54 |
|
|
* pcap filter gets set. STATE_FILTER is used for parsing |
55 |
|
|
* /etc/services and /etc/protocols and opening the output |
56 |
|
|
* file. STATE_RUN is the packet processing part. |
57 |
|
|
*/ |
58 |
|
|
|
59 |
|
|
enum priv_state { |
60 |
|
|
STATE_INIT, /* initial state */ |
61 |
|
|
STATE_BPF, /* input file/device opened */ |
62 |
|
|
STATE_FILTER, /* filter applied */ |
63 |
|
|
STATE_RUN, /* running and accepting network traffic */ |
64 |
|
|
STATE_EXIT /* in the process of dying */ |
65 |
|
|
}; |
66 |
|
|
|
67 |
|
|
#define ALLOW(action) (1 << (action)) |
68 |
|
|
|
69 |
|
|
/* |
70 |
|
|
* Set of maximum allowed actions. |
71 |
|
|
*/ |
72 |
|
|
static const int allowed_max[] = { |
73 |
|
|
/* INIT */ ALLOW(PRIV_OPEN_BPF) | ALLOW(PRIV_OPEN_DUMP) | |
74 |
|
|
ALLOW(PRIV_SETFILTER), |
75 |
|
|
/* BPF */ ALLOW(PRIV_SETFILTER), |
76 |
|
|
/* FILTER */ ALLOW(PRIV_OPEN_OUTPUT) | ALLOW(PRIV_GETSERVENTRIES) | |
77 |
|
|
ALLOW(PRIV_GETPROTOENTRIES) | |
78 |
|
|
ALLOW(PRIV_ETHER_NTOHOST) | ALLOW(PRIV_INIT_DONE), |
79 |
|
|
/* RUN */ ALLOW(PRIV_GETHOSTBYADDR) | ALLOW(PRIV_ETHER_NTOHOST) | |
80 |
|
|
ALLOW(PRIV_GETRPCBYNUMBER) | ALLOW(PRIV_GETLINES) | |
81 |
|
|
ALLOW(PRIV_LOCALTIME) | ALLOW(PRIV_PCAP_STATS), |
82 |
|
|
/* EXIT */ 0 |
83 |
|
|
}; |
84 |
|
|
|
85 |
|
|
/* |
86 |
|
|
* Default set of allowed actions. More actions get added |
87 |
|
|
* later depending on the supplied parameters. |
88 |
|
|
*/ |
89 |
|
|
static int allowed_ext[] = { |
90 |
|
|
/* INIT */ ALLOW(PRIV_SETFILTER), |
91 |
|
|
/* BPF */ ALLOW(PRIV_SETFILTER), |
92 |
|
|
/* FILTER */ ALLOW(PRIV_GETSERVENTRIES), |
93 |
|
|
/* RUN */ ALLOW(PRIV_GETLINES) | ALLOW(PRIV_LOCALTIME) | |
94 |
|
|
ALLOW(PRIV_PCAP_STATS), |
95 |
|
|
/* EXIT */ 0 |
96 |
|
|
}; |
97 |
|
|
|
98 |
|
|
struct ftab { |
99 |
|
|
char *name; |
100 |
|
|
int max; |
101 |
|
|
int count; |
102 |
|
|
}; |
103 |
|
|
|
104 |
|
|
static struct ftab file_table[] = {{PF_OSFP_FILE, 1, 0}}; |
105 |
|
|
|
106 |
|
|
#define NUM_FILETAB (sizeof(file_table) / sizeof(struct ftab)) |
107 |
|
|
|
108 |
|
|
int debug_level = LOG_INFO; |
109 |
|
|
int priv_fd = -1; |
110 |
|
|
volatile pid_t child_pid = -1; |
111 |
|
|
static volatile sig_atomic_t cur_state = STATE_INIT; |
112 |
|
|
|
113 |
|
|
extern void set_slave_signals(void); |
114 |
|
|
|
115 |
|
|
static void impl_open_bpf(int, int *); |
116 |
|
|
static void impl_open_dump(int, const char *); |
117 |
|
|
static void impl_open_output(int, const char *); |
118 |
|
|
static void impl_setfilter(int, char *, int *); |
119 |
|
|
static void impl_init_done(int, int *); |
120 |
|
|
static void impl_gethostbyaddr(int); |
121 |
|
|
static void impl_ether_ntohost(int); |
122 |
|
|
static void impl_getrpcbynumber(int); |
123 |
|
|
static void impl_getserventries(int); |
124 |
|
|
static void impl_getprotoentries(int); |
125 |
|
|
static void impl_localtime(int fd); |
126 |
|
|
static void impl_getlines(int); |
127 |
|
|
static void impl_pcap_stats(int, int *); |
128 |
|
|
|
129 |
|
|
static void test_state(int, int); |
130 |
|
|
static void logmsg(int, const char *, ...); |
131 |
|
|
|
132 |
|
|
int |
133 |
|
|
priv_init(int argc, char **argv) |
134 |
|
|
{ |
135 |
|
|
int i, nargc, socks[2]; |
136 |
|
|
struct passwd *pw; |
137 |
|
|
sigset_t allsigs, oset; |
138 |
|
|
char **privargv; |
139 |
|
|
|
140 |
|
|
closefrom(STDERR_FILENO + 1); |
141 |
|
|
for (i = 1; i < _NSIG; i++) |
142 |
|
|
signal(i, SIG_DFL); |
143 |
|
|
|
144 |
|
|
/* Create sockets */ |
145 |
|
|
if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) |
146 |
|
|
err(1, "socketpair() failed"); |
147 |
|
|
|
148 |
|
|
sigfillset(&allsigs); |
149 |
|
|
sigprocmask(SIG_BLOCK, &allsigs, &oset); |
150 |
|
|
|
151 |
|
|
child_pid = fork(); |
152 |
|
|
if (child_pid < 0) |
153 |
|
|
err(1, "fork() failed"); |
154 |
|
|
|
155 |
|
|
if (child_pid) { |
156 |
|
|
close(socks[0]); |
157 |
|
|
priv_fd = socks[1]; |
158 |
|
|
|
159 |
|
|
set_slave_signals(); |
160 |
|
|
sigprocmask(SIG_SETMASK, &oset, NULL); |
161 |
|
|
|
162 |
|
|
/* |
163 |
|
|
* If run as regular user, packet parser will rely on |
164 |
|
|
* pledge(2). If we are root, we want to chroot also.. |
165 |
|
|
*/ |
166 |
|
|
if (getuid() != 0) |
167 |
|
|
return (0); |
168 |
|
|
|
169 |
|
|
pw = getpwnam("_tcpdump"); |
170 |
|
|
if (pw == NULL) |
171 |
|
|
errx(1, "unknown user _tcpdump"); |
172 |
|
|
|
173 |
|
|
if (chroot(pw->pw_dir) == -1) |
174 |
|
|
err(1, "unable to chroot"); |
175 |
|
|
if (chdir("/") == -1) |
176 |
|
|
err(1, "unable to chdir"); |
177 |
|
|
|
178 |
|
|
/* drop to _tcpdump */ |
179 |
|
|
if (setgroups(1, &pw->pw_gid) == -1) |
180 |
|
|
err(1, "setgroups() failed"); |
181 |
|
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) |
182 |
|
|
err(1, "setresgid() failed"); |
183 |
|
|
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) |
184 |
|
|
err(1, "setresuid() failed"); |
185 |
|
|
|
186 |
|
|
return (0); |
187 |
|
|
} |
188 |
|
|
close(socks[1]); |
189 |
|
|
|
190 |
|
|
if (dup2(socks[0], 3) == -1) |
191 |
|
|
err(1, "dup2 priv sock failed"); |
192 |
|
|
closefrom(4); |
193 |
|
|
|
194 |
|
|
if ((privargv = reallocarray(NULL, argc + 2, sizeof(char *))) == NULL) |
195 |
|
|
err(1, "alloc priv argv failed"); |
196 |
|
|
nargc = 0; |
197 |
|
|
privargv[nargc++] = argv[0]; |
198 |
|
|
privargv[nargc++] = "-P"; |
199 |
|
|
for (i = 1; i < argc; i++) |
200 |
|
|
privargv[nargc++] = argv[i]; |
201 |
|
|
privargv[nargc] = NULL; |
202 |
|
|
execvp(privargv[0], privargv); |
203 |
|
|
err(1, "exec priv '%s' failed", privargv[0]); |
204 |
|
|
} |
205 |
|
|
|
206 |
|
|
__dead void |
207 |
|
|
priv_exec(int argc, char *argv[]) |
208 |
|
|
{ |
209 |
|
|
int bpfd = -1; |
210 |
|
|
int i, sock, cmd, nflag = 0, Pflag = 0; |
211 |
|
|
char *cmdbuf, *infile = NULL; |
212 |
|
|
char *RFileName = NULL; |
213 |
|
|
char *WFileName = NULL; |
214 |
|
|
|
215 |
|
|
sock = 3; |
216 |
|
|
|
217 |
|
|
closefrom(4); |
218 |
|
|
for (i = 1; i < _NSIG; i++) |
219 |
|
|
signal(i, SIG_DFL); |
220 |
|
|
|
221 |
|
|
signal(SIGINT, SIG_IGN); |
222 |
|
|
|
223 |
|
|
/* parse the arguments for required options */ |
224 |
|
|
opterr = 0; |
225 |
|
|
while ((i = getopt(argc, argv, |
226 |
|
|
"ac:D:deE:fF:i:lLnNOopPqr:s:StT:vw:xXy:Y")) != -1) { |
227 |
|
|
switch (i) { |
228 |
|
|
case 'n': |
229 |
|
|
nflag++; |
230 |
|
|
break; |
231 |
|
|
|
232 |
|
|
case 'r': |
233 |
|
|
RFileName = optarg; |
234 |
|
|
break; |
235 |
|
|
|
236 |
|
|
case 'w': |
237 |
|
|
WFileName = optarg; |
238 |
|
|
break; |
239 |
|
|
|
240 |
|
|
case 'F': |
241 |
|
|
infile = optarg; |
242 |
|
|
break; |
243 |
|
|
|
244 |
|
|
case 'P': |
245 |
|
|
Pflag = 1; |
246 |
|
|
break; |
247 |
|
|
|
248 |
|
|
default: |
249 |
|
|
/* nothing */ |
250 |
|
|
break; |
251 |
|
|
} |
252 |
|
|
} |
253 |
|
|
|
254 |
|
|
if (!Pflag) |
255 |
|
|
errx(1, "exec without priv"); |
256 |
|
|
|
257 |
|
|
if (RFileName != NULL) { |
258 |
|
|
if (strcmp(RFileName, "-") != 0) |
259 |
|
|
allowed_ext[STATE_INIT] |= ALLOW(PRIV_OPEN_DUMP); |
260 |
|
|
} else |
261 |
|
|
allowed_ext[STATE_INIT] |= ALLOW(PRIV_OPEN_BPF); |
262 |
|
|
if (WFileName != NULL) { |
263 |
|
|
if (strcmp(WFileName, "-") != 0) |
264 |
|
|
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_OPEN_OUTPUT); |
265 |
|
|
else |
266 |
|
|
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_INIT_DONE); |
267 |
|
|
} else |
268 |
|
|
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_INIT_DONE); |
269 |
|
|
if (!nflag) { |
270 |
|
|
allowed_ext[STATE_RUN] |= ALLOW(PRIV_GETHOSTBYADDR); |
271 |
|
|
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_ETHER_NTOHOST); |
272 |
|
|
allowed_ext[STATE_RUN] |= ALLOW(PRIV_ETHER_NTOHOST); |
273 |
|
|
allowed_ext[STATE_RUN] |= ALLOW(PRIV_GETRPCBYNUMBER); |
274 |
|
|
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_GETPROTOENTRIES); |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
if (infile) |
278 |
|
|
cmdbuf = read_infile(infile); |
279 |
|
|
else |
280 |
|
|
cmdbuf = copy_argv(&argv[optind]); |
281 |
|
|
|
282 |
|
|
setproctitle("[priv]"); |
283 |
|
|
|
284 |
|
|
for (;;) { |
285 |
|
|
if (may_read(sock, &cmd, sizeof(int))) |
286 |
|
|
break; |
287 |
|
|
switch (cmd) { |
288 |
|
|
case PRIV_OPEN_BPF: |
289 |
|
|
test_state(cmd, STATE_BPF); |
290 |
|
|
impl_open_bpf(sock, &bpfd); |
291 |
|
|
break; |
292 |
|
|
case PRIV_OPEN_DUMP: |
293 |
|
|
test_state(cmd, STATE_BPF); |
294 |
|
|
impl_open_dump(sock, RFileName); |
295 |
|
|
break; |
296 |
|
|
case PRIV_OPEN_OUTPUT: |
297 |
|
|
test_state(cmd, STATE_RUN); |
298 |
|
|
impl_open_output(sock, WFileName); |
299 |
|
|
break; |
300 |
|
|
case PRIV_SETFILTER: |
301 |
|
|
test_state(cmd, STATE_FILTER); |
302 |
|
|
impl_setfilter(sock, cmdbuf, &bpfd); |
303 |
|
|
break; |
304 |
|
|
case PRIV_INIT_DONE: |
305 |
|
|
test_state(cmd, STATE_RUN); |
306 |
|
|
impl_init_done(sock, &bpfd); |
307 |
|
|
|
308 |
|
|
if (pledge("stdio rpath inet unix dns recvfd bpf flock cpath wpath", NULL) == -1) |
309 |
|
|
err(1, "pledge"); |
310 |
|
|
|
311 |
|
|
break; |
312 |
|
|
case PRIV_GETHOSTBYADDR: |
313 |
|
|
test_state(cmd, STATE_RUN); |
314 |
|
|
impl_gethostbyaddr(sock); |
315 |
|
|
break; |
316 |
|
|
case PRIV_ETHER_NTOHOST: |
317 |
|
|
test_state(cmd, cur_state); |
318 |
|
|
impl_ether_ntohost(sock); |
319 |
|
|
break; |
320 |
|
|
case PRIV_GETRPCBYNUMBER: |
321 |
|
|
test_state(cmd, STATE_RUN); |
322 |
|
|
impl_getrpcbynumber(sock); |
323 |
|
|
break; |
324 |
|
|
case PRIV_GETSERVENTRIES: |
325 |
|
|
test_state(cmd, STATE_FILTER); |
326 |
|
|
impl_getserventries(sock); |
327 |
|
|
break; |
328 |
|
|
case PRIV_GETPROTOENTRIES: |
329 |
|
|
test_state(cmd, STATE_FILTER); |
330 |
|
|
impl_getprotoentries(sock); |
331 |
|
|
break; |
332 |
|
|
case PRIV_LOCALTIME: |
333 |
|
|
test_state(cmd, STATE_RUN); |
334 |
|
|
impl_localtime(sock); |
335 |
|
|
break; |
336 |
|
|
case PRIV_GETLINES: |
337 |
|
|
test_state(cmd, STATE_RUN); |
338 |
|
|
impl_getlines(sock); |
339 |
|
|
break; |
340 |
|
|
case PRIV_PCAP_STATS: |
341 |
|
|
test_state(cmd, STATE_RUN); |
342 |
|
|
impl_pcap_stats(sock, &bpfd); |
343 |
|
|
break; |
344 |
|
|
default: |
345 |
|
|
logmsg(LOG_ERR, "[priv]: unknown command %d", cmd); |
346 |
|
|
exit(1); |
347 |
|
|
/* NOTREACHED */ |
348 |
|
|
} |
349 |
|
|
} |
350 |
|
|
|
351 |
|
|
/* NOTREACHED */ |
352 |
|
|
exit(0); |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
static void |
356 |
|
|
impl_open_bpf(int fd, int *bpfd) |
357 |
|
|
{ |
358 |
|
|
int snaplen, promisc, err; |
359 |
|
|
u_int dlt, dirfilt; |
360 |
|
|
char device[IFNAMSIZ]; |
361 |
|
|
size_t iflen; |
362 |
|
|
|
363 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_OPEN_BPF received"); |
364 |
|
|
|
365 |
|
|
must_read(fd, &snaplen, sizeof(int)); |
366 |
|
|
must_read(fd, &promisc, sizeof(int)); |
367 |
|
|
must_read(fd, &dlt, sizeof(u_int)); |
368 |
|
|
must_read(fd, &dirfilt, sizeof(u_int)); |
369 |
|
|
iflen = read_string(fd, device, sizeof(device), __func__); |
370 |
|
|
if (iflen == 0) |
371 |
|
|
errx(1, "Invalid interface size specified"); |
372 |
|
|
*bpfd = pcap_live(device, snaplen, promisc, dlt, dirfilt); |
373 |
|
|
err = errno; |
374 |
|
|
if (*bpfd < 0) |
375 |
|
|
logmsg(LOG_DEBUG, |
376 |
|
|
"[priv]: failed to open bpf device for %s: %s", |
377 |
|
|
device, strerror(errno)); |
378 |
|
|
send_fd(fd, *bpfd); |
379 |
|
|
must_write(fd, &err, sizeof(int)); |
380 |
|
|
/* do not close bpfd until filter is set */ |
381 |
|
|
} |
382 |
|
|
|
383 |
|
|
static void |
384 |
|
|
impl_open_dump(int fd, const char *RFileName) |
385 |
|
|
{ |
386 |
|
|
int file, err = 0; |
387 |
|
|
|
388 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_OPEN_DUMP received"); |
389 |
|
|
|
390 |
|
|
if (RFileName == NULL) { |
391 |
|
|
file = -1; |
392 |
|
|
logmsg(LOG_ERR, "[priv]: No offline file specified"); |
393 |
|
|
} else { |
394 |
|
|
file = open(RFileName, O_RDONLY, 0); |
395 |
|
|
err = errno; |
396 |
|
|
if (file < 0) |
397 |
|
|
logmsg(LOG_DEBUG, "[priv]: failed to open %s: %s", |
398 |
|
|
RFileName, strerror(errno)); |
399 |
|
|
} |
400 |
|
|
send_fd(fd, file); |
401 |
|
|
must_write(fd, &err, sizeof(int)); |
402 |
|
|
if (file >= 0) |
403 |
|
|
close(file); |
404 |
|
|
} |
405 |
|
|
|
406 |
|
|
static void |
407 |
|
|
impl_open_output(int fd, const char *WFileName) |
408 |
|
|
{ |
409 |
|
|
int file, err; |
410 |
|
|
|
411 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_OPEN_OUTPUT received"); |
412 |
|
|
|
413 |
|
|
file = open(WFileName, O_WRONLY|O_CREAT|O_TRUNC, 0666); |
414 |
|
|
err = errno; |
415 |
|
|
send_fd(fd, file); |
416 |
|
|
must_write(fd, &err, sizeof(int)); |
417 |
|
|
if (file < 0) |
418 |
|
|
logmsg(LOG_DEBUG, "[priv]: failed to open %s: %s", |
419 |
|
|
WFileName, strerror(err)); |
420 |
|
|
else |
421 |
|
|
close(file); |
422 |
|
|
} |
423 |
|
|
|
424 |
|
|
static void |
425 |
|
|
impl_setfilter(int fd, char *cmdbuf, int *bpfd) |
426 |
|
|
{ |
427 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_SETFILTER received"); |
428 |
|
|
|
429 |
|
|
if (setfilter(*bpfd, fd, cmdbuf)) |
430 |
|
|
logmsg(LOG_DEBUG, "[priv]: setfilter() failed"); |
431 |
|
|
} |
432 |
|
|
|
433 |
|
|
static void |
434 |
|
|
impl_init_done(int fd, int *bpfd) |
435 |
|
|
{ |
436 |
|
|
int ret; |
437 |
|
|
|
438 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_INIT_DONE received"); |
439 |
|
|
|
440 |
|
|
ret = 0; |
441 |
|
|
must_write(fd, &ret, sizeof(ret)); |
442 |
|
|
} |
443 |
|
|
|
444 |
|
|
static void |
445 |
|
|
impl_gethostbyaddr(int fd) |
446 |
|
|
{ |
447 |
|
|
char hostname[HOST_NAME_MAX+1]; |
448 |
|
|
size_t hostname_len; |
449 |
|
|
int addr_af; |
450 |
|
|
struct hostent *hp; |
451 |
|
|
|
452 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETHOSTBYADDR received"); |
453 |
|
|
|
454 |
|
|
/* Expecting: address block, address family */ |
455 |
|
|
hostname_len = read_block(fd, hostname, sizeof(hostname), __func__); |
456 |
|
|
if (hostname_len == 0) |
457 |
|
|
_exit(1); |
458 |
|
|
must_read(fd, &addr_af, sizeof(int)); |
459 |
|
|
hp = gethostbyaddr(hostname, hostname_len, addr_af); |
460 |
|
|
if (hp == NULL) |
461 |
|
|
write_zero(fd); |
462 |
|
|
else |
463 |
|
|
write_string(fd, hp->h_name); |
464 |
|
|
} |
465 |
|
|
|
466 |
|
|
static void |
467 |
|
|
impl_ether_ntohost(int fd) |
468 |
|
|
{ |
469 |
|
|
struct ether_addr ether; |
470 |
|
|
char hostname[HOST_NAME_MAX+1]; |
471 |
|
|
|
472 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_ETHER_NTOHOST received"); |
473 |
|
|
|
474 |
|
|
/* Expecting: ethernet address */ |
475 |
|
|
must_read(fd, ðer, sizeof(ether)); |
476 |
|
|
if (ether_ntohost(hostname, ðer) == -1) |
477 |
|
|
write_zero(fd); |
478 |
|
|
else |
479 |
|
|
write_string(fd, hostname); |
480 |
|
|
} |
481 |
|
|
|
482 |
|
|
static void |
483 |
|
|
impl_getrpcbynumber(int fd) |
484 |
|
|
{ |
485 |
|
|
int rpc; |
486 |
|
|
struct rpcent *rpce; |
487 |
|
|
|
488 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETRPCBYNUMBER received"); |
489 |
|
|
|
490 |
|
|
must_read(fd, &rpc, sizeof(int)); |
491 |
|
|
rpce = getrpcbynumber(rpc); |
492 |
|
|
if (rpce == NULL) |
493 |
|
|
write_zero(fd); |
494 |
|
|
else |
495 |
|
|
write_string(fd, rpce->r_name); |
496 |
|
|
} |
497 |
|
|
|
498 |
|
|
static void |
499 |
|
|
impl_getserventries(int fd) |
500 |
|
|
{ |
501 |
|
|
struct servent *sp; |
502 |
|
|
|
503 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETSERVENTRIES received"); |
504 |
|
|
|
505 |
|
|
for (;;) { |
506 |
|
|
sp = getservent(); |
507 |
|
|
if (sp == NULL) { |
508 |
|
|
write_zero(fd); |
509 |
|
|
break; |
510 |
|
|
} else { |
511 |
|
|
write_string(fd, sp->s_name); |
512 |
|
|
must_write(fd, &sp->s_port, sizeof(int)); |
513 |
|
|
write_string(fd, sp->s_proto); |
514 |
|
|
} |
515 |
|
|
} |
516 |
|
|
endservent(); |
517 |
|
|
} |
518 |
|
|
|
519 |
|
|
static void |
520 |
|
|
impl_getprotoentries(int fd) |
521 |
|
|
{ |
522 |
|
|
struct protoent *pe; |
523 |
|
|
|
524 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETPROTOENTRIES received"); |
525 |
|
|
|
526 |
|
|
for (;;) { |
527 |
|
|
pe = getprotoent(); |
528 |
|
|
if (pe == NULL) { |
529 |
|
|
write_zero(fd); |
530 |
|
|
break; |
531 |
|
|
} else { |
532 |
|
|
write_string(fd, pe->p_name); |
533 |
|
|
must_write(fd, &pe->p_proto, sizeof(int)); |
534 |
|
|
} |
535 |
|
|
} |
536 |
|
|
endprotoent(); |
537 |
|
|
} |
538 |
|
|
|
539 |
|
|
/* read the time and send the corresponding localtime and gmtime |
540 |
|
|
* results back to the unprivileged process */ |
541 |
|
|
static void |
542 |
|
|
impl_localtime(int fd) |
543 |
|
|
{ |
544 |
|
|
struct tm *lt, *gt; |
545 |
|
|
time_t t; |
546 |
|
|
|
547 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_LOCALTIME received"); |
548 |
|
|
|
549 |
|
|
must_read(fd, &t, sizeof(time_t)); |
550 |
|
|
|
551 |
|
|
/* this must be done separately, since they apparently use the |
552 |
|
|
* same local buffer */ |
553 |
|
|
if ((lt = localtime(&t)) == NULL) |
554 |
|
|
errx(1, "localtime()"); |
555 |
|
|
must_write(fd, lt, sizeof(*lt)); |
556 |
|
|
|
557 |
|
|
if ((gt = gmtime(&t)) == NULL) |
558 |
|
|
errx(1, "gmtime()"); |
559 |
|
|
must_write(fd, gt, sizeof(*gt)); |
560 |
|
|
|
561 |
|
|
if (lt->tm_zone == NULL) |
562 |
|
|
write_zero(fd); |
563 |
|
|
else |
564 |
|
|
write_string(fd, lt->tm_zone); |
565 |
|
|
} |
566 |
|
|
|
567 |
|
|
static void |
568 |
|
|
impl_getlines(int fd) |
569 |
|
|
{ |
570 |
|
|
FILE *fp; |
571 |
|
|
char *buf, *lbuf, *file; |
572 |
|
|
size_t len, fid; |
573 |
|
|
|
574 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETLINES received"); |
575 |
|
|
|
576 |
|
|
must_read(fd, &fid, sizeof(size_t)); |
577 |
|
|
if (fid >= NUM_FILETAB) |
578 |
|
|
errx(1, "invalid file id"); |
579 |
|
|
|
580 |
|
|
file = file_table[fid].name; |
581 |
|
|
|
582 |
|
|
if (file == NULL) |
583 |
|
|
errx(1, "invalid file referenced"); |
584 |
|
|
|
585 |
|
|
if (file_table[fid].count >= file_table[fid].max) |
586 |
|
|
errx(1, "maximum open count exceeded for %s", file); |
587 |
|
|
|
588 |
|
|
file_table[fid].count++; |
589 |
|
|
|
590 |
|
|
if ((fp = fopen(file, "r")) == NULL) { |
591 |
|
|
write_zero(fd); |
592 |
|
|
return; |
593 |
|
|
} |
594 |
|
|
|
595 |
|
|
lbuf = NULL; |
596 |
|
|
while ((buf = fgetln(fp, &len))) { |
597 |
|
|
if (buf[len - 1] == '\n') |
598 |
|
|
buf[len - 1] = '\0'; |
599 |
|
|
else { |
600 |
|
|
if ((lbuf = malloc(len + 1)) == NULL) |
601 |
|
|
err(1, NULL); |
602 |
|
|
memcpy(lbuf, buf, len); |
603 |
|
|
lbuf[len] = '\0'; |
604 |
|
|
buf = lbuf; |
605 |
|
|
} |
606 |
|
|
|
607 |
|
|
write_string(fd, buf); |
608 |
|
|
|
609 |
|
|
free(lbuf); |
610 |
|
|
lbuf = NULL; |
611 |
|
|
} |
612 |
|
|
write_zero(fd); |
613 |
|
|
fclose(fp); |
614 |
|
|
} |
615 |
|
|
|
616 |
|
|
static void |
617 |
|
|
impl_pcap_stats(int fd, int *bpfd) |
618 |
|
|
{ |
619 |
|
|
struct pcap_stat stats; |
620 |
|
|
|
621 |
|
|
logmsg(LOG_DEBUG, "[priv]: msg PRIV_PCAP_STATS received"); |
622 |
|
|
|
623 |
|
|
if (ioctl(*bpfd, BIOCGSTATS, &stats) == -1) |
624 |
|
|
write_zero(fd); |
625 |
|
|
else |
626 |
|
|
must_write(fd, &stats, sizeof(stats)); |
627 |
|
|
} |
628 |
|
|
|
629 |
|
|
void |
630 |
|
|
priv_init_done(void) |
631 |
|
|
{ |
632 |
|
|
int ret; |
633 |
|
|
|
634 |
|
|
if (priv_fd < 0) |
635 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
636 |
|
|
|
637 |
|
|
write_command(priv_fd, PRIV_INIT_DONE); |
638 |
|
|
must_read(priv_fd, &ret, sizeof(int)); |
639 |
|
|
} |
640 |
|
|
|
641 |
|
|
/* Reverse address resolution; response is placed into res, and length of |
642 |
|
|
* response is returned (zero on error) */ |
643 |
|
|
size_t |
644 |
|
|
priv_gethostbyaddr(char *addr, size_t addr_len, int af, char *res, size_t res_len) |
645 |
|
|
{ |
646 |
|
|
if (priv_fd < 0) |
647 |
|
|
errx(1, "%s called from privileged portion", __func__); |
648 |
|
|
|
649 |
|
|
write_command(priv_fd, PRIV_GETHOSTBYADDR); |
650 |
|
|
write_block(priv_fd, addr_len, addr); |
651 |
|
|
must_write(priv_fd, &af, sizeof(int)); |
652 |
|
|
|
653 |
|
|
return (read_string(priv_fd, res, res_len, __func__)); |
654 |
|
|
} |
655 |
|
|
|
656 |
|
|
size_t |
657 |
|
|
priv_ether_ntohost(char *name, size_t name_len, struct ether_addr *e) |
658 |
|
|
{ |
659 |
|
|
if (priv_fd < 0) |
660 |
|
|
errx(1, "%s called from privileged portion", __func__); |
661 |
|
|
|
662 |
|
|
write_command(priv_fd, PRIV_ETHER_NTOHOST); |
663 |
|
|
must_write(priv_fd, e, sizeof(*e)); |
664 |
|
|
|
665 |
|
|
/* Read the host name */ |
666 |
|
|
return (read_string(priv_fd, name, name_len, __func__)); |
667 |
|
|
} |
668 |
|
|
|
669 |
|
|
size_t |
670 |
|
|
priv_getrpcbynumber(int rpc, char *progname, size_t progname_len) |
671 |
|
|
{ |
672 |
|
|
if (priv_fd < 0) |
673 |
|
|
errx(1, "%s called from privileged portion", __func__); |
674 |
|
|
|
675 |
|
|
write_command(priv_fd, PRIV_GETRPCBYNUMBER); |
676 |
|
|
must_write(priv_fd, &rpc, sizeof(int)); |
677 |
|
|
|
678 |
|
|
return read_string(priv_fd, progname, progname_len, __func__); |
679 |
|
|
} |
680 |
|
|
|
681 |
|
|
/* start getting service entries */ |
682 |
|
|
void |
683 |
|
|
priv_getserventries(void) |
684 |
|
|
{ |
685 |
|
|
if (priv_fd < 0) |
686 |
|
|
errx(1, "%s called from privileged portion", __func__); |
687 |
|
|
|
688 |
|
|
write_command(priv_fd, PRIV_GETSERVENTRIES); |
689 |
|
|
} |
690 |
|
|
|
691 |
|
|
/* retrieve a service entry, should be called repeatedly after calling |
692 |
|
|
priv_getserventries(), until it returns zero. */ |
693 |
|
|
size_t |
694 |
|
|
priv_getserventry(char *name, size_t name_len, int *port, char *prot, |
695 |
|
|
size_t prot_len) |
696 |
|
|
{ |
697 |
|
|
if (priv_fd < 0) |
698 |
|
|
errx(1, "%s called from privileged portion", __func__); |
699 |
|
|
|
700 |
|
|
/* read the service name */ |
701 |
|
|
if (read_string(priv_fd, name, name_len, __func__) == 0) |
702 |
|
|
return 0; |
703 |
|
|
|
704 |
|
|
/* read the port */ |
705 |
|
|
must_read(priv_fd, port, sizeof(int)); |
706 |
|
|
|
707 |
|
|
/* read the protocol */ |
708 |
|
|
return (read_string(priv_fd, prot, prot_len, __func__)); |
709 |
|
|
} |
710 |
|
|
|
711 |
|
|
/* start getting ip protocol entries */ |
712 |
|
|
void |
713 |
|
|
priv_getprotoentries(void) |
714 |
|
|
{ |
715 |
|
|
if (priv_fd < 0) |
716 |
|
|
errx(1, "%s called from privileged portion", __func__); |
717 |
|
|
|
718 |
|
|
write_command(priv_fd, PRIV_GETPROTOENTRIES); |
719 |
|
|
} |
720 |
|
|
|
721 |
|
|
/* retrieve a ip protocol entry, should be called repeatedly after calling |
722 |
|
|
priv_getprotoentries(), until it returns zero. */ |
723 |
|
|
size_t |
724 |
|
|
priv_getprotoentry(char *name, size_t name_len, int *num) |
725 |
|
|
{ |
726 |
|
|
if (priv_fd < 0) |
727 |
|
|
errx(1, "%s called from privileged portion", __func__); |
728 |
|
|
|
729 |
|
|
/* read the proto name */ |
730 |
|
|
if (read_string(priv_fd, name, name_len, __func__) == 0) |
731 |
|
|
return 0; |
732 |
|
|
|
733 |
|
|
/* read the num */ |
734 |
|
|
must_read(priv_fd, num, sizeof(int)); |
735 |
|
|
|
736 |
|
|
return (1); |
737 |
|
|
} |
738 |
|
|
|
739 |
|
|
/* localtime() replacement: ask the privileged process for localtime and |
740 |
|
|
* gmtime, cache the localtime for about one minute i.e. until one of the |
741 |
|
|
* fields other than seconds changes. The check is done using gmtime |
742 |
|
|
* values since they are the same in parent and child. */ |
743 |
|
|
struct tm * |
744 |
|
|
priv_localtime(const time_t *t) |
745 |
|
|
{ |
746 |
|
|
static struct tm lt, gt0; |
747 |
|
|
static struct tm *gt = NULL; |
748 |
|
|
static char zone[PATH_MAX]; |
749 |
|
|
|
750 |
|
|
if (gt != NULL) { |
751 |
|
|
gt = gmtime(t); |
752 |
|
|
gt0.tm_sec = gt->tm_sec; |
753 |
|
|
gt0.tm_zone = gt->tm_zone; |
754 |
|
|
|
755 |
|
|
if (memcmp(gt, >0, sizeof(struct tm)) == 0) { |
756 |
|
|
lt.tm_sec = gt0.tm_sec; |
757 |
|
|
return < |
758 |
|
|
} |
759 |
|
|
} |
760 |
|
|
|
761 |
|
|
write_command(priv_fd, PRIV_LOCALTIME); |
762 |
|
|
must_write(priv_fd, t, sizeof(time_t)); |
763 |
|
|
must_read(priv_fd, <, sizeof(lt)); |
764 |
|
|
must_read(priv_fd, >0, sizeof(gt0)); |
765 |
|
|
|
766 |
|
|
if (read_string(priv_fd, zone, sizeof(zone), __func__)) |
767 |
|
|
lt.tm_zone = zone; |
768 |
|
|
else |
769 |
|
|
lt.tm_zone = NULL; |
770 |
|
|
|
771 |
|
|
gt0.tm_zone = NULL; |
772 |
|
|
gt = >0; |
773 |
|
|
|
774 |
|
|
return < |
775 |
|
|
} |
776 |
|
|
|
777 |
|
|
/* start getting lines from a file */ |
778 |
|
|
void |
779 |
|
|
priv_getlines(size_t sz) |
780 |
|
|
{ |
781 |
|
|
if (priv_fd < 0) |
782 |
|
|
errx(1, "%s called from privileged portion", __func__); |
783 |
|
|
|
784 |
|
|
write_command(priv_fd, PRIV_GETLINES); |
785 |
|
|
must_write(priv_fd, &sz, sizeof(size_t)); |
786 |
|
|
} |
787 |
|
|
|
788 |
|
|
int |
789 |
|
|
priv_pcap_stats(struct pcap_stat *ps) |
790 |
|
|
{ |
791 |
|
|
if (priv_fd < 0) |
792 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
793 |
|
|
|
794 |
|
|
write_command(priv_fd, PRIV_PCAP_STATS); |
795 |
|
|
must_read(priv_fd, ps, sizeof(*ps)); |
796 |
|
|
return (0); |
797 |
|
|
} |
798 |
|
|
|
799 |
|
|
/* retrieve a line from a file, should be called repeatedly after calling |
800 |
|
|
priv_getlines(), until it returns zero. */ |
801 |
|
|
size_t |
802 |
|
|
priv_getline(char *line, size_t line_len) |
803 |
|
|
{ |
804 |
|
|
if (priv_fd < 0) |
805 |
|
|
errx(1, "%s called from privileged portion", __func__); |
806 |
|
|
|
807 |
|
|
/* read the line */ |
808 |
|
|
return (read_string(priv_fd, line, line_len, __func__)); |
809 |
|
|
} |
810 |
|
|
|
811 |
|
|
/* Read all data or return 1 for error. */ |
812 |
|
|
int |
813 |
|
|
may_read(int fd, void *buf, size_t n) |
814 |
|
|
{ |
815 |
|
|
char *s = buf; |
816 |
|
|
ssize_t res, pos = 0; |
817 |
|
|
|
818 |
|
|
while (n > pos) { |
819 |
|
|
res = read(fd, s + pos, n - pos); |
820 |
|
|
switch (res) { |
821 |
|
|
case -1: |
822 |
|
|
if (errno == EINTR || errno == EAGAIN) |
823 |
|
|
continue; |
824 |
|
|
/* FALLTHROUGH */ |
825 |
|
|
case 0: |
826 |
|
|
return (1); |
827 |
|
|
default: |
828 |
|
|
pos += res; |
829 |
|
|
} |
830 |
|
|
} |
831 |
|
|
return (0); |
832 |
|
|
} |
833 |
|
|
|
834 |
|
|
/* Read data with the assertion that it all must come through, or |
835 |
|
|
* else abort the process. Based on atomicio() from openssh. */ |
836 |
|
|
void |
837 |
|
|
must_read(int fd, void *buf, size_t n) |
838 |
|
|
{ |
839 |
|
|
char *s = buf; |
840 |
|
|
ssize_t res, pos = 0; |
841 |
|
|
|
842 |
|
|
while (n > pos) { |
843 |
|
|
res = read(fd, s + pos, n - pos); |
844 |
|
|
switch (res) { |
845 |
|
|
case -1: |
846 |
|
|
if (errno == EINTR || errno == EAGAIN) |
847 |
|
|
continue; |
848 |
|
|
/* FALLTHROUGH */ |
849 |
|
|
case 0: |
850 |
|
|
_exit(0); |
851 |
|
|
default: |
852 |
|
|
pos += res; |
853 |
|
|
} |
854 |
|
|
} |
855 |
|
|
} |
856 |
|
|
|
857 |
|
|
/* Write data with the assertion that it all has to be written, or |
858 |
|
|
* else abort the process. Based on atomicio() from openssh. */ |
859 |
|
|
void |
860 |
|
|
must_write(int fd, const void *buf, size_t n) |
861 |
|
|
{ |
862 |
|
|
const char *s = buf; |
863 |
|
|
ssize_t res, pos = 0; |
864 |
|
|
|
865 |
|
|
while (n > pos) { |
866 |
|
|
res = write(fd, s + pos, n - pos); |
867 |
|
|
switch (res) { |
868 |
|
|
case -1: |
869 |
|
|
if (errno == EINTR || errno == EAGAIN) |
870 |
|
|
continue; |
871 |
|
|
/* FALLTHROUGH */ |
872 |
|
|
case 0: |
873 |
|
|
_exit(0); |
874 |
|
|
default: |
875 |
|
|
pos += res; |
876 |
|
|
} |
877 |
|
|
} |
878 |
|
|
} |
879 |
|
|
|
880 |
|
|
/* test for a given state, and possibly increase state */ |
881 |
|
|
static void |
882 |
|
|
test_state(int action, int next) |
883 |
|
|
{ |
884 |
|
|
if (cur_state < 0 || cur_state > STATE_RUN) { |
885 |
|
|
logmsg(LOG_ERR, "[priv] Invalid state: %d", cur_state); |
886 |
|
|
_exit(1); |
887 |
|
|
} |
888 |
|
|
if ((allowed_max[cur_state] & allowed_ext[cur_state] |
889 |
|
|
& ALLOW(action)) == 0) { |
890 |
|
|
logmsg(LOG_ERR, "[priv] Invalid action %d in state %d", |
891 |
|
|
action, cur_state); |
892 |
|
|
_exit(1); |
893 |
|
|
} |
894 |
|
|
if (next < cur_state) { |
895 |
|
|
logmsg(LOG_ERR, "[priv] Invalid next state: %d < %d", |
896 |
|
|
next, cur_state); |
897 |
|
|
_exit(1); |
898 |
|
|
} |
899 |
|
|
|
900 |
|
|
cur_state = next; |
901 |
|
|
} |
902 |
|
|
|
903 |
|
|
static void |
904 |
|
|
logmsg(int pri, const char *message, ...) |
905 |
|
|
{ |
906 |
|
|
va_list ap; |
907 |
|
|
if (pri > debug_level) |
908 |
|
|
return; |
909 |
|
|
va_start(ap, message); |
910 |
|
|
|
911 |
|
|
vfprintf(stderr, message, ap); |
912 |
|
|
fprintf(stderr, "\n"); |
913 |
|
|
va_end(ap); |
914 |
|
|
} |
915 |
|
|
|
916 |
|
|
/* write a command to the peer */ |
917 |
|
|
void |
918 |
|
|
write_command(int fd, int cmd) |
919 |
|
|
{ |
920 |
|
|
must_write(fd, &cmd, sizeof(cmd)); |
921 |
|
|
} |
922 |
|
|
|
923 |
|
|
/* write a zero 'length' to signal an error to read_{string|block} */ |
924 |
|
|
void |
925 |
|
|
write_zero(int fd) |
926 |
|
|
{ |
927 |
|
|
size_t len = 0; |
928 |
|
|
must_write(fd, &len, sizeof(size_t)); |
929 |
|
|
} |
930 |
|
|
|
931 |
|
|
/* send a string */ |
932 |
|
|
void |
933 |
|
|
write_string(int fd, const char *str) |
934 |
|
|
{ |
935 |
|
|
size_t len; |
936 |
|
|
|
937 |
|
|
len = strlen(str) + 1; |
938 |
|
|
must_write(fd, &len, sizeof(size_t)); |
939 |
|
|
must_write(fd, str, len); |
940 |
|
|
} |
941 |
|
|
|
942 |
|
|
/* send a block of data of given size */ |
943 |
|
|
void |
944 |
|
|
write_block(int fd, size_t size, const char *str) |
945 |
|
|
{ |
946 |
|
|
must_write(fd, &size, sizeof(size_t)); |
947 |
|
|
must_write(fd, str, size); |
948 |
|
|
} |
949 |
|
|
|
950 |
|
|
/* read a string from the channel, return 0 if error, or total size of |
951 |
|
|
* the buffer, including the terminating '\0' */ |
952 |
|
|
size_t |
953 |
|
|
read_string(int fd, char *buf, size_t size, const char *func) |
954 |
|
|
{ |
955 |
|
|
size_t len; |
956 |
|
|
|
957 |
|
|
len = read_block(fd, buf, size, func); |
958 |
|
|
if (len == 0) |
959 |
|
|
return (0); |
960 |
|
|
|
961 |
|
|
if (buf[len - 1] != '\0') |
962 |
|
|
errx(1, "%s: received invalid string", func); |
963 |
|
|
|
964 |
|
|
return (len); |
965 |
|
|
} |
966 |
|
|
|
967 |
|
|
/* read a block of data from the channel, return length of data, or 0 |
968 |
|
|
* if error */ |
969 |
|
|
size_t |
970 |
|
|
read_block(int fd, char *buf, size_t size, const char *func) |
971 |
|
|
{ |
972 |
|
|
size_t len; |
973 |
|
|
/* Expect back an integer size, and then a string of that length */ |
974 |
|
|
must_read(fd, &len, sizeof(size_t)); |
975 |
|
|
|
976 |
|
|
/* Check there was no error (indicated by a return of 0) */ |
977 |
|
|
if (len == 0) |
978 |
|
|
return (0); |
979 |
|
|
|
980 |
|
|
/* Make sure we aren't overflowing the passed in buffer */ |
981 |
|
|
if (size < len) |
982 |
|
|
errx(1, "%s: overflow attempt in return", func); |
983 |
|
|
|
984 |
|
|
/* Read the string and make sure we got all of it */ |
985 |
|
|
must_read(fd, buf, len); |
986 |
|
|
return (len); |
987 |
|
|
} |