1 | /*
|
---|
2 | tcpd.c
|
---|
3 | */
|
---|
4 |
|
---|
5 | #include <sys/types.h>
|
---|
6 | #include <errno.h>
|
---|
7 | #include <fcntl.h>
|
---|
8 | #include <limits.h>
|
---|
9 | #include <stdio.h>
|
---|
10 | #include <stdlib.h>
|
---|
11 | #include <string.h>
|
---|
12 | #include <unistd.h>
|
---|
13 | #include <signal.h>
|
---|
14 | #include <minix/config.h>
|
---|
15 | #include <sys/ioctl.h>
|
---|
16 | #include <sys/wait.h>
|
---|
17 | #include <net/hton.h>
|
---|
18 | #include <net/netlib.h>
|
---|
19 | #include <net/gen/in.h>
|
---|
20 | #include <net/gen/inet.h>
|
---|
21 | #include <net/gen/netdb.h>
|
---|
22 | #include <net/gen/tcp.h>
|
---|
23 | #include <net/gen/tcp_io.h>
|
---|
24 |
|
---|
25 | /* This program can be compiled to be paranoid, i.e. check incoming connection
|
---|
26 | * according to an access file, or to trust anyone. The much smaller "trust
|
---|
27 | * 'em" binary will call the paranoid version if the access file exists.
|
---|
28 | */
|
---|
29 |
|
---|
30 | static char *arg0, *service;
|
---|
31 | static unsigned nchildren;
|
---|
32 |
|
---|
33 | static void report(const char *label)
|
---|
34 | {
|
---|
35 | int err= errno;
|
---|
36 |
|
---|
37 | fprintf(stderr, "%s %s: %s: %s\n", arg0, service, label, strerror(err));
|
---|
38 | errno= err;
|
---|
39 | }
|
---|
40 |
|
---|
41 | static void sigchld(int sig)
|
---|
42 | {
|
---|
43 | while (waitpid(0, NULL, WNOHANG) > 0) {
|
---|
44 | if (nchildren > 0) nchildren--;
|
---|
45 | }
|
---|
46 | }
|
---|
47 |
|
---|
48 | static void release(int *fd)
|
---|
49 | {
|
---|
50 | if (*fd != -1) {
|
---|
51 | close(*fd);
|
---|
52 | *fd= -1;
|
---|
53 | }
|
---|
54 | }
|
---|
55 |
|
---|
56 | static void usage(void)
|
---|
57 | {
|
---|
58 | fprintf(stderr,
|
---|
59 | "Usage: %s [-d] [-m maxclients] service program [arg ...]\n",
|
---|
60 | arg0);
|
---|
61 | exit(1);
|
---|
62 | }
|
---|
63 |
|
---|
64 | int main(int argc, char **argv)
|
---|
65 | {
|
---|
66 | tcpport_t port;
|
---|
67 | int last_failed = 0;
|
---|
68 | struct nwio_tcpcl tcplistenopt;
|
---|
69 | struct nwio_tcpconf tcpconf;
|
---|
70 | struct nwio_tcpopt tcpopt;
|
---|
71 | char *tcp_device;
|
---|
72 | struct servent *servent;
|
---|
73 | int tcp_fd, client_fd, count, r;
|
---|
74 | int pfd[2];
|
---|
75 | unsigned stall= 0;
|
---|
76 | struct sigaction sa;
|
---|
77 | sigset_t chldmask, chldunmask, oldmask;
|
---|
78 | char **progv;
|
---|
79 |
|
---|
80 | #if !PARANOID
|
---|
81 | # define debug 0
|
---|
82 | # define max_children ((unsigned) -1)
|
---|
83 | arg0= argv[0];
|
---|
84 |
|
---|
85 | /* Switch to the paranoid version of me if there are flags, or if
|
---|
86 | * there is an access file.
|
---|
87 | */
|
---|
88 | if (argv[1][0] == '-' || access(_PATH_SERVACCES, F_OK) == 0) {
|
---|
89 | execv("/usr/bin/tcpdp", argv);
|
---|
90 | report("tcpdp");
|
---|
91 | exit(1);
|
---|
92 | }
|
---|
93 | if (argc < 3) usage();
|
---|
94 | service= argv[1];
|
---|
95 | progv= argv+2;
|
---|
96 |
|
---|
97 | #else /* PARANOID */
|
---|
98 | int debug, i;
|
---|
99 | unsigned max_children;
|
---|
100 |
|
---|
101 | arg0= argv[0];
|
---|
102 | debug= 0;
|
---|
103 | max_children= -1;
|
---|
104 | i= 1;
|
---|
105 | while (i < argc && argv[i][0] == '-') {
|
---|
106 | char *opt= argv[i++] + 1;
|
---|
107 | unsigned long m;
|
---|
108 | char *end;
|
---|
109 |
|
---|
110 | if (*opt == '-' && opt[1] == 0) break; /* -- */
|
---|
111 |
|
---|
112 | while (*opt != 0) switch (*opt++) {
|
---|
113 | case 'd':
|
---|
114 | debug= 1;
|
---|
115 | break;
|
---|
116 | case 'm':
|
---|
117 | if (*opt == 0) {
|
---|
118 | if (i == argc) usage();
|
---|
119 | opt= argv[i++];
|
---|
120 | }
|
---|
121 | m= strtoul(opt, &end, 10);
|
---|
122 | if (m <= 0 || m > UINT_MAX || *end != 0) usage();
|
---|
123 | max_children= m;
|
---|
124 | opt= "";
|
---|
125 | break;
|
---|
126 | default:
|
---|
127 | usage();
|
---|
128 | }
|
---|
129 | }
|
---|
130 | service= argv[i++];
|
---|
131 | progv= argv+i;
|
---|
132 | if (i >= argc) usage();
|
---|
133 | #endif
|
---|
134 |
|
---|
135 | /* The interface to start the service on. */
|
---|
136 | if ((tcp_device= getenv("TCP_DEVICE")) == NULL) tcp_device= TCP_DEVICE;
|
---|
137 |
|
---|
138 | /* Let SIGCHLD interrupt whatever I'm doing. */
|
---|
139 | sigemptyset(&chldmask);
|
---|
140 | sigaddset(&chldmask, SIGCHLD);
|
---|
141 | sigprocmask(SIG_BLOCK, &chldmask, &oldmask);
|
---|
142 | chldunmask= oldmask;
|
---|
143 | sigdelset(&chldunmask, SIGCHLD);
|
---|
144 | sigemptyset(&sa.sa_mask);
|
---|
145 | sa.sa_flags = 0;
|
---|
146 | sa.sa_handler = sigchld;
|
---|
147 | sigaction(SIGCHLD, &sa, NULL);
|
---|
148 |
|
---|
149 | /* Open a socket to the service I'm to serve. */
|
---|
150 | if ((servent= getservbyname(service, "tcp")) == NULL) {
|
---|
151 | unsigned long p;
|
---|
152 | char *end;
|
---|
153 |
|
---|
154 | p= strtoul(service, &end, 0);
|
---|
155 | if (p <= 0 || p > 0xFFFF || *end != 0) {
|
---|
156 | fprintf(stderr, "%s: %s: Unknown service\n",
|
---|
157 | arg0, service);
|
---|
158 | exit(1);
|
---|
159 | }
|
---|
160 | port= htons((tcpport_t) p);
|
---|
161 | } else {
|
---|
162 | port= servent->s_port;
|
---|
163 |
|
---|
164 | if (debug)
|
---|
165 | {
|
---|
166 | fprintf(stderr, "%s %s: listening to port %u\n",
|
---|
167 | arg0, service, ntohs(port));
|
---|
168 | }
|
---|
169 | }
|
---|
170 |
|
---|
171 | /* No client yet. */
|
---|
172 | client_fd= -1;
|
---|
173 |
|
---|
174 | while (1) {
|
---|
175 | if ((tcp_fd= open(tcp_device, O_RDWR)) < 0) {
|
---|
176 | report(tcp_device);
|
---|
177 | #if 0
|
---|
178 | if (errno == ENOENT || errno == ENODEV
|
---|
179 | || errno == ENXIO) {
|
---|
180 | exit(1);
|
---|
181 | }
|
---|
182 | #endif
|
---|
183 | last_failed = 1;
|
---|
184 | goto bad;
|
---|
185 | }
|
---|
186 | if(last_failed)
|
---|
187 | fprintf(stderr, "%s %s: %s: Ok\n",
|
---|
188 | arg0, service, tcp_device);
|
---|
189 | last_failed = 0;
|
---|
190 |
|
---|
191 | tcpconf.nwtc_flags= NWTC_LP_SET | NWTC_UNSET_RA | NWTC_UNSET_RP;
|
---|
192 | tcpconf.nwtc_locport= port;
|
---|
193 |
|
---|
194 | if (ioctl(tcp_fd, NWIOSTCPCONF, &tcpconf) < 0) {
|
---|
195 | report("Can't configure TCP channel");
|
---|
196 | exit(1);
|
---|
197 | }
|
---|
198 |
|
---|
199 | tcpopt.nwto_flags= NWTO_DEL_RST;
|
---|
200 |
|
---|
201 | if (ioctl(tcp_fd, NWIOSTCPOPT, &tcpopt) < 0) {
|
---|
202 | report("Can't set TCP options");
|
---|
203 | exit(1);
|
---|
204 | }
|
---|
205 |
|
---|
206 | if (client_fd != -1) {
|
---|
207 | /* We have a client, so start a server for it. */
|
---|
208 |
|
---|
209 | tcpopt.nwto_flags= 0;
|
---|
210 | (void) ioctl(client_fd, NWIOSTCPOPT, &tcpopt);
|
---|
211 |
|
---|
212 | fflush(NULL);
|
---|
213 |
|
---|
214 | /* Create a pipe to serve as an error indicator. */
|
---|
215 | if (pipe(pfd) < 0) {
|
---|
216 | report("pipe");
|
---|
217 | goto bad;
|
---|
218 | }
|
---|
219 | (void) fcntl(pfd[1], F_SETFD,
|
---|
220 | fcntl(pfd[1], F_GETFD) | FD_CLOEXEC);
|
---|
221 |
|
---|
222 | /* Fork and exec. */
|
---|
223 | switch (fork()) {
|
---|
224 | case -1:
|
---|
225 | report("fork");
|
---|
226 | close(pfd[0]);
|
---|
227 | close(pfd[1]);
|
---|
228 | goto bad;
|
---|
229 | case 0:
|
---|
230 | close(tcp_fd);
|
---|
231 | close(pfd[0]);
|
---|
232 | #if PARANOID
|
---|
233 | /* Check if access to this service allowed. */
|
---|
234 | if (ioctl(client_fd, NWIOGTCPCONF, &tcpconf) == 0
|
---|
235 | && tcpconf.nwtc_remaddr != tcpconf.nwtc_locaddr
|
---|
236 | && !servxcheck(tcpconf.nwtc_remaddr, argv[1], NULL)
|
---|
237 | ) {
|
---|
238 | exit(1);
|
---|
239 | }
|
---|
240 | #endif
|
---|
241 | sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
---|
242 | dup2(client_fd, 0);
|
---|
243 | dup2(client_fd, 1);
|
---|
244 | close(client_fd);
|
---|
245 | execvp(progv[0], progv);
|
---|
246 | report(progv[0]);
|
---|
247 | write(pfd[1], &errno, sizeof(errno));
|
---|
248 | exit(1);
|
---|
249 | default:
|
---|
250 | nchildren++;
|
---|
251 | release(&client_fd);
|
---|
252 | close(pfd[1]);
|
---|
253 | r= read(pfd[0], &errno, sizeof(errno));
|
---|
254 | close(pfd[0]);
|
---|
255 | if (r != 0) goto bad;
|
---|
256 | break;
|
---|
257 | }
|
---|
258 | }
|
---|
259 |
|
---|
260 | while (nchildren >= max_children) {
|
---|
261 | /* Too many clients, wait for one to die off. */
|
---|
262 | sigsuspend(&chldunmask);
|
---|
263 | }
|
---|
264 |
|
---|
265 | /* Wait for a new connection. */
|
---|
266 | sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
|
---|
267 |
|
---|
268 | tcplistenopt.nwtcl_flags= 0;
|
---|
269 | while (ioctl(tcp_fd, NWIOTCPLISTEN, &tcplistenopt) < 0) {
|
---|
270 | if (errno != EINTR) {
|
---|
271 | if (errno != EAGAIN || debug) {
|
---|
272 | report("Unable to listen");
|
---|
273 | }
|
---|
274 | goto bad;
|
---|
275 | }
|
---|
276 | }
|
---|
277 | sigprocmask(SIG_BLOCK, &chldmask, NULL);
|
---|
278 |
|
---|
279 | /* We got a connection. */
|
---|
280 | client_fd= tcp_fd;
|
---|
281 | tcp_fd= -1;
|
---|
282 |
|
---|
283 | if (debug && ioctl(client_fd, NWIOGTCPCONF, &tcpconf) == 0) {
|
---|
284 | fprintf(stderr, "%s %s: Connection from %s:%u\n",
|
---|
285 | arg0, service,
|
---|
286 | inet_ntoa(tcpconf.nwtc_remaddr),
|
---|
287 | ntohs(tcpconf.nwtc_remport));
|
---|
288 | }
|
---|
289 | /* All is well, no need to stall. */
|
---|
290 | stall= 0;
|
---|
291 | continue;
|
---|
292 |
|
---|
293 | bad:
|
---|
294 | /* All is not well, release resources. */
|
---|
295 | release(&tcp_fd);
|
---|
296 | release(&client_fd);
|
---|
297 |
|
---|
298 | /* Wait a bit if this happens more than once. */
|
---|
299 | if (stall != 0) {
|
---|
300 | if (debug) {
|
---|
301 | fprintf(stderr, "%s %s: stalling %u second%s\n",
|
---|
302 | arg0, service,
|
---|
303 | stall, stall == 1 ? "" : "s");
|
---|
304 | }
|
---|
305 | sleep(stall);
|
---|
306 | stall <<= 1;
|
---|
307 | } else {
|
---|
308 | stall= 1;
|
---|
309 | }
|
---|
310 | }
|
---|
311 | }
|
---|