blob: df9821652e9b441a907a0c2a07560b8fa0401888 [file] [log] [blame]
Denis Vlasenkoba2fb712007-04-01 09:39:03 +00001/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
2 * which are released into public domain by the author.
3 * Homepage: http://smarden.sunsite.dk/ipsvd/
4 *
5 * Copyright (C) 2007 by Denis Vlasenko.
6 *
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 */
9
10/* Based on ipsvd ipsvd-0.12.1. This tcpsvd accepts all options
11 * which are supported by one from ipsvd-0.12.1, but not all are
12 * functional. See help text at the end of this file for details.
13 *
14 * Code inside "#ifdef SSLSVD" is for sslsvd and is currently unused.
15 * Code inside #if 0" is parts of original tcpsvd which are not implemented
16 * for busyboxed version.
17 */
Denis Vlasenko2856dab2007-04-01 01:18:20 +000018
19#include "busybox.h"
20#include "ipsvd_perhost.h"
21
22#ifdef SSLSVD
23#include "matrixSsl.h"
24#include "ssl_io.h"
25#endif
26
27
28static unsigned max_per_host; /* originally in ipsvd_check.c */
29static unsigned cur_per_host;
30static unsigned verbose;
31static unsigned cnum;
32static unsigned cmax = 30;
33
34/* Must match getopt32 in main! */
35enum {
36 OPT_c = (1 << 0),
37 OPT_C = (1 << 1),
38 OPT_i = (1 << 2),
39 OPT_x = (1 << 3),
40 OPT_u = (1 << 4),
41 OPT_l = (1 << 5),
42 OPT_E = (1 << 6),
43 OPT_b = (1 << 7),
44 OPT_h = (1 << 8),
45 OPT_p = (1 << 9),
46 OPT_t = (1 << 10),
47 OPT_v = (1 << 11),
48 OPT_V = (1 << 12),
49 OPT_U = (1 << 13),
50 OPT_slash = (1 << 14),
51 OPT_Z = (1 << 15),
52 OPT_K = (1 << 16),
53};
54
55static void connection_status(void)
56{
57 printf("%s: info: status %u/%u\n", applet_name, cnum, cmax);
58}
59
60static void sig_term_handler(int sig)
61{
62 if (verbose)
63 printf("%s: info: sigterm received, exit\n", applet_name);
64 exit(0);
65}
66
67static void sig_child_handler(int sig)
68{
69 int wstat;
70 int pid;
71
72 while ((pid = wait_nohang(&wstat)) > 0) {
73 if (max_per_host)
74 ipsvd_perhost_remove(pid);
75 if (cnum)
76 cnum--;
77 if (verbose) {
78 /* Little bloated, but tries to give accurate info
79 * how child exited. Makes easier to spot segfaulting
80 * children etc... */
81 unsigned e = 0;
82 const char *cause = "?exit";
83 if (WIFEXITED(wstat)) {
84 cause++;
85 e = WEXITSTATUS(wstat);
86 } else if (WIFSIGNALED(wstat)) {
87 cause = "signal";
88 e = WTERMSIG(wstat);
89 }
90 printf("%s: info: end %d %s %d\n",
91 applet_name, pid, cause, e);
92 }
93 }
94 if (verbose)
95 connection_status();
96}
97
98int tcpsvd_main(int argc, char **argv);
99int tcpsvd_main(int argc, char **argv)
100{
101 char *str_c, *str_C, *str_b, *str_t;
102 char *user;
103 struct hcc *hccp;
104 const char *instructs;
105 char *msg_per_host = NULL;
106 unsigned len_per_host = len_per_host; /* gcc */
107 int need_addresses;
108 int pid;
109 int sock;
110 int conn;
111 unsigned backlog = 20;
112 union {
113 struct sockaddr sa;
114 struct sockaddr_in sin;
115 USE_FEATURE_IPV6(struct sockaddr_in6 sin6;)
116 } sock_adr;
117 socklen_t sockadr_size;
118 uint16_t local_port = local_port;
119 uint16_t remote_port;
120 unsigned port;
121 char *local_hostname = NULL;
122 char *remote_hostname = (char*)""; /* "" used if no -h */
123 char *local_ip = local_ip;
124 char *remote_ip = NULL;
125 //unsigned iscdb = 0; /* = option_mask32 & OPT_x (TODO) */
126 //unsigned long timeout = 0;
127#ifndef SSLSVD
128 struct bb_uidgid_t ugid;
129#endif
130
131 /* 3+ args, -i at most once, -p implies -h, -v is counter */
132 opt_complementary = "-3:?:i--i:ph:vv";
133#ifdef SSLSVD
134 getopt32(argc, argv, "c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:",
135 &str_c, &str_C, &instructs, &instructs, &user, &local_hostname,
136 &str_b, &str_t, &ssluser, &root, &cert, &key, &verbose
137 );
138#else
139 getopt32(argc, argv, "c:C:i:x:u:l:Eb:hpt:v",
140 &str_c, &str_C, &instructs, &instructs, &user, &local_hostname,
141 &str_b, &str_t, &verbose
142 );
143#endif
144 if (option_mask32 & OPT_c)
145 cmax = xatou_range(str_c, 1, INT_MAX);
146 if (option_mask32 & OPT_C) { /* -C n[:message] */
147 max_per_host = bb_strtou(str_C, &str_C, 10);
148 if (str_C[0]) {
149 if (str_C[0] != ':')
150 bb_show_usage();
151 msg_per_host = str_C + 1;
152 len_per_host = strlen(msg_per_host);
153 }
154 }
155 if (max_per_host > cmax)
156 max_per_host = cmax;
157 if (option_mask32 & OPT_u) {
158 if (!get_uidgid(&ugid, user, 1))
159 bb_error_msg_and_die("unknown user/group: %s", user);
160 }
161 if (option_mask32 & OPT_b)
162 backlog = xatou(str_b);
163// if (option_mask32 & OPT_t) timeout = xatou(str_t);
164#ifdef SSLSVD
165 if (option_mask32 & OPT_U) ssluser = (char*)optarg; break;
166 if (option_mask32 & OPT_slash) root = (char*)optarg; break;
167 if (option_mask32 & OPT_Z) cert = (char*)optarg; break;
168 if (option_mask32 & OPT_K) key = (char*)optarg; break;
169#endif
170 argv += optind;
171 if (!argv[0][0] || LONE_CHAR(argv[0], '0'))
172 argv[0] = (char*)"0.0.0.0";
173
174 need_addresses = verbose || !(option_mask32 & OPT_E);
175
176#ifdef SSLSVD
177 sslser = user;
178 client = 0;
179 if ((getuid() == 0) && !(option_mask32 & OPT_u)) {
180 xfunc_exitcode = 100;
181 bb_error_msg_and_die("fatal: -U ssluser must be set when running as root");
182 }
183 if (option_mask32 & OPT_u)
184 if (!uidgid_get(&sslugid, ssluser, 1)) {
185 if (errno) {
186 xfunc_exitcode = 100;
187 bb_perror_msg_and_die("fatal: cannot get user/group: %s", ssluser);
188 }
189 xfunc_exitcode = 111;
190 bb_error_msg_and_die("fatal: unknown user/group '%s'", ssluser);
191 }
192 if (!cert) cert = "./cert.pem";
193 if (!key) key = cert;
194 if (matrixSslOpen() < 0)
195 fatal("cannot initialize ssl");
196 if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) {
197 if (client)
198 fatal("cannot read cert, key, or ca file");
199 fatal("cannot read cert or key file");
200 }
201 if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0)
202 fatal("cannot create ssl session");
203#endif
204
205 sig_block(SIGCHLD);
206 signal(SIGCHLD, sig_child_handler);
207 signal(SIGTERM, sig_term_handler);
208 signal(SIGPIPE, SIG_IGN);
209
210 if (max_per_host)
211 ipsvd_perhost_init(cmax);
212
213 port = bb_lookup_port(argv[1], "tcp", 0);
214 sock = create_and_bind_stream_or_die(argv[0], port);
215 xlisten(sock, backlog);
216 /* ndelay_off(sock); - it is the default I think? */
217
218#ifndef SSLSVD
219 if (option_mask32 & OPT_u) {
220 /* drop permissions */
221 xsetgid(ugid.gid);
222 xsetuid(ugid.uid);
223 }
224#endif
225 bb_sanitize_stdio(); /* fd# 1,2 must be opened */
226 close(0);
227
228 if (verbose) {
229 /* we do it only for ":port" cosmetics... oh well */
230 len_and_sockaddr *lsa = xhost2sockaddr(argv[0], port);
231 char *addr = xmalloc_sockaddr2dotted(&lsa->sa, lsa->len);
232
233 printf("%s: info: listening on %s", applet_name, addr);
234 free(addr);
235#ifndef SSLSVD
236 if (option_mask32 & OPT_u)
237 printf(", uid %u, gid %u",
238 (unsigned)ugid.uid, (unsigned)ugid.uid);
239#endif
240 puts(", starting");
241 }
242
243 /* The rest is a main accept() loop */
244
245 again:
246 hccp = NULL;
247
248 while (cnum >= cmax)
249 sig_pause(); /* wait for any signal (expecting SIGCHLD) */
250
251 sockadr_size = sizeof(sock_adr);
252 sig_unblock(SIGCHLD);
253 conn = accept(sock, &sock_adr.sa, &sockadr_size);
254 sig_block(SIGCHLD);
255 if (conn == -1) {
256 if (errno != EINTR)
257 bb_perror_msg("accept");
258 goto again;
259 }
260
261 if (max_per_host) {
262 /* we drop connection immediately if cur_per_host > max_per_host
263 * (minimizing load under SYN flood) */
264 free(remote_ip);
265 remote_ip = xmalloc_sockaddr2dotted_noport(&sock_adr.sa, sockadr_size);
266 cur_per_host = ipsvd_perhost_add(remote_ip, max_per_host, &hccp);
267 if (cur_per_host > max_per_host) {
268 /* ipsvd_perhost_add detected that max is exceeded
269 * (and did not store us in connection table) */
270 if (msg_per_host) {
271 ndelay_on(conn);
272 /* don't test for errors */
273 write(conn, msg_per_host, len_per_host);
274 }
275 close(conn);
276 goto again;
277 }
278 }
279
280 cnum++;
281 if (verbose)
282 connection_status();
283
284 pid = fork();
285 if (pid == -1) {
286 bb_perror_msg("fork");
287 close(conn);
288 goto again;
289 }
290 if (pid != 0) {
291 /* parent */
292 close(conn);
293 if (hccp)
294 hccp->pid = pid;
295 goto again;
296 }
297
298 /* Child: prepare env, log, and exec prog */
299
300 close(sock);
301
302 if (!max_per_host)
303 remote_ip = xmalloc_sockaddr2dotted_noport(&sock_adr.sa, sizeof(sock_adr));
304 /* else it is already done */
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000305
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000306 remote_port = get_nport(&sock_adr.sa);
307 remote_port = ntohs(remote_port);
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000308
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000309 if (verbose) {
310 pid = getpid();
311 printf("%s: info: pid %d from %s\n", applet_name, pid, remote_ip);
312 }
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000313
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000314 if (need_addresses && (option_mask32 & OPT_h)) {
315 remote_hostname = xmalloc_sockaddr2host(&sock_adr.sa, sizeof(sock_adr));
316 if (!remote_hostname) {
317 bb_error_msg("warning: cannot look up hostname for %s", remote_ip);
318 remote_hostname = (char*)"";
319 }
320 }
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000321
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000322 sockadr_size = sizeof(sock_adr);
323 /* Errors ignored (I'm not paranoid enough to imagine kernel
324 * which doesn't know local ip) */
325 getsockname(conn, &sock_adr.sa, &sockadr_size);
326
327 if (need_addresses) {
328 local_ip = xmalloc_sockaddr2dotted_noport(&sock_adr.sa, sockadr_size);
329 local_port = get_nport(&sock_adr.sa);
330 local_port = ntohs(local_port);
331 if (!local_hostname) {
332 local_hostname = xmalloc_sockaddr2host_noport(&sock_adr.sa, sockadr_size);
333 if (!local_hostname)
334 bb_error_msg_and_die("cannot look up local hostname for %s", local_ip);
335 }
336 }
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000337
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000338 if (!(option_mask32 & OPT_E)) {
339 /* setup ucspi env */
340 xsetenv("PROTO", "TCP");
341 xsetenv("TCPLOCALIP", local_ip);
342 xsetenv("TCPLOCALPORT", utoa(local_port));
343 xsetenv("TCPLOCALHOST", local_hostname);
344 xsetenv("TCPREMOTEIP", remote_ip);
345 xsetenv("TCPREMOTEPORT", utoa(remote_port));
346 if (option_mask32 & OPT_h) {
347 xsetenv("TCPREMOTEHOST", remote_hostname);
348 }
349 xsetenv("TCPREMOTEINFO", "");
350 /* additional */
351 if (cur_per_host > 0)
352 xsetenv("TCPCONCURRENCY", utoa(cur_per_host));
353 }
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000354
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000355#if 0
356 if (instructs) {
357 ac = ipsvd_check(iscdb, &inst, &match, (char*)instructs,
358 remote_ip, remote_hostname, timeout);
359 if (ac == -1) drop2("cannot check inst", remote_ip);
360 if (ac == IPSVD_ERR) drop2("cannot read", (char*)instructs);
361 } else
362 ac = IPSVD_DEFAULT;
363#endif
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000364
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000365 if (max_per_host && verbose)
366 printf("%s: info: concurrency %u %s %u/%u\n",
367 applet_name, pid, remote_ip, cur_per_host, max_per_host);
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000368
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000369 if (verbose) {
370 printf("%s: info: start %u %s:%s :%s:%s:%u\n",
371 applet_name, pid,
372 local_hostname, local_ip,
373 remote_hostname, remote_ip, (unsigned)remote_port);
374#if 0
375 switch(ac) {
376 case IPSVD_DENY:
377 printf("deny "); break;
378 case IPSVD_DEFAULT:
379 case IPSVD_INSTRUCT:
380 printf("start "); break;
381 case IPSVD_EXEC:
382 printf("exec "); break;
383 }
384 ...
385 if (instructs) {
386 printf(" ");
387 if (iscdb) {
388 printf((char*)instructs);
389 printf("/");
390 }
391 outfix(match.s);
392 if(inst.s && inst.len && (verbose > 1)) {
393 printf(": ");
394 printf(&inst);
395 }
396 }
397 printf("\n");
398#endif
399 }
400
401#if 0
402 if (ac == IPSVD_DENY) {
403 close(conn);
404 _exit(100);
405 }
406 if (ac == IPSVD_EXEC) {
407 args[0] = "/bin/sh";
408 args[1] = "-c";
409 args[2] = inst.s;
410 args[3] = 0;
411 run = args;
412 } else
413 run = argv + 2; /* below: we use argv+2 (was using run) */
414#endif
415
416 xmove_fd(conn, 0);
417 dup2(0, 1);
418 signal(SIGTERM, SIG_DFL);
419 signal(SIGPIPE, SIG_DFL);
420 signal(SIGCHLD, SIG_DFL);
421 sig_unblock(SIGCHLD);
422
423 argv += 2;
424#ifdef SSLSVD
425 strcpy(id, utoa(pid);
426 ssl_io(0, argv);
427#else
428 BB_EXECVP(argv[0], argv);
429#endif
430 bb_perror_msg_and_die("exec '%s'", argv[0]);
431}
432
433/*
434tcpsvd [-hpEvv] [-c n] [-C n:msg] [-b n] [-u user] [-l name] [-i dir|-x cdb] [ -t sec] host port prog
435
436tcpsvd creates a TCP/IP socket, binds it to the address host:port,
437and listens on the socket for incoming connections.
438
439On each incoming connection, tcpsvd conditionally runs a program,
440with standard input reading from the socket, and standard output
441writing to the socket, to handle this connection. tcpsvd keeps
442listening on the socket for new connections, and can handle
443multiple connections simultaneously.
444
445tcpsvd optionally checks for special instructions depending
446on the IP address or hostname of the client that initiated
447the connection, see ipsvd-instruct(5).
448
449host
450 host either is a hostname, or a dotted-decimal IP address,
451 or 0. If host is 0, tcpsvd accepts connections to any local
452 IP address.
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000453 * busybox accepts IPv6 addresses and host:port pairs too
454 In this case second parameter is ignored
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000455port
456 tcpsvd accepts connections to host:port. port may be a name
457 from /etc/services or a number.
458prog
459 prog consists of one or more arguments. For each connection,
460 tcpsvd normally runs prog, with file descriptor 0 reading from
461 the network, and file descriptor 1 writing to the network.
462 By default it also sets up TCP-related environment variables,
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000463 see tcp-environ(5)
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000464-i dir
465 read instructions for handling new connections from the instructions
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000466 directory dir. See ipsvd-instruct(5) for details.
467 * ignored by busyboxed version
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000468-x cdb
469 read instructions for handling new connections from the constant database
470 cdb. The constant database normally is created from an instructions
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000471 directory by running ipsvd-cdb(8).
472 * ignored by busyboxed version
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000473-t sec
474 timeout. This option only takes effect if the -i option is given.
475 While checking the instructions directory, check the time of last access
476 of the file that matches the clients address or hostname if any, discard
477 and remove the file if it wasn't accessed within the last sec seconds;
478 tcpsvd does not discard or remove a file if the user's write permission
479 is not set, for those files the timeout is disabled. Default is 0,
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000480 which means that the timeout is disabled.
481 * ignored by busyboxed version
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000482-l name
483 local hostname. Do not look up the local hostname in DNS, but use name
484 as hostname. This option must be set if tcpsvd listens on port 53
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000485 to avoid loops.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000486-u user[:group]
487 drop permissions. Switch user ID to user's UID, and group ID to user's
488 primary GID after creating and binding to the socket. If user is followed
489 by a colon and a group name, the group ID is switched to the GID of group
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000490 instead. All supplementary groups are removed.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000491-c n
492 concurrency. Handle up to n connections simultaneously. Default is 30.
493 If there are n connections active, tcpsvd defers acceptance of a new
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000494 connection until an active connection is closed.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000495-C n[:msg]
496 per host concurrency. Allow only up to n connections from the same IP
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000497 address simultaneously. If there are n active connections from one IP
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000498 address, new incoming connections from this IP address are closed
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000499 immediately. If n is followed by :msg, the message msg is written
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000500 to the client if possible, before closing the connection. By default
501 msg is empty. See ipsvd-instruct(5) for supported escape sequences in msg.
502
503 For each accepted connection, the current per host concurrency is
504 available through the environment variable TCPCONCURRENCY. n and msg
505 can be overwritten by ipsvd(7) instructions, see ipsvd-instruct(5).
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000506 By default tcpsvd doesn't keep track of connections.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000507-h
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000508 Look up the client's hostname in DNS.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000509-p
510 paranoid. After looking up the client's hostname in DNS, look up the IP
511 addresses in DNS for that hostname, and forget about the hostname
512 if none of the addresses match the client's IP address. You should
513 set this option if you use hostname based instructions. The -p option
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000514 implies the -h option.
515 * ignored by busyboxed version
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000516-b n
517 backlog. Allow a backlog of approximately n TCP SYNs. On some systems n
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000518 is silently limited. Default is 20.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000519-E
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000520 no special environment. Do not set up TCP-related environment variables.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000521-v
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000522 verbose. Print verbose messsages to standard output.
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000523-vv
Denis Vlasenkoba2fb712007-04-01 09:39:03 +0000524 more verbose. Print more verbose messages to standard output.
525 * no difference between -v and -vv in busyboxed version
Denis Vlasenko2856dab2007-04-01 01:18:20 +0000526*/