aa1f699e |
1 | /* -*-c-*- |
2 | * |
ee599f55 |
3 | * $Id: inet.c,v 1.6 2003/11/29 20:36:07 mdw Exp $ |
aa1f699e |
4 | * |
5 | * Protocol specific definitions for IPv4 sockets |
6 | * |
7 | * (c) 1999 Straylight/Edgeware |
8 | */ |
9 | |
10 | /*----- Licensing notice --------------------------------------------------* |
11 | * |
12 | * This file is part of the `fw' port forwarder. |
13 | * |
14 | * `fw' is free software; you can redistribute it and/or modify |
15 | * it under the terms of the GNU General Public License as published by |
16 | * the Free Software Foundation; either version 2 of the License, or |
17 | * (at your option) any later version. |
18 | * |
19 | * `fw' is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU General Public License for more details. |
23 | * |
24 | * You should have received a copy of the GNU General Public License |
25 | * along with `fw'; if not, write to the Free Software Foundation, |
26 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
27 | */ |
28 | |
29 | /*----- Revision history --------------------------------------------------* |
30 | * |
31 | * $Log: inet.c,v $ |
ee599f55 |
32 | * Revision 1.6 2003/11/29 20:36:07 mdw |
33 | * Privileged outgoing connections. |
34 | * |
0ac54f22 |
35 | * Revision 1.5 2003/11/25 14:08:23 mdw |
36 | * Debianization. Socket target options. Internet binding. |
37 | * |
1a039139 |
38 | * Revision 1.4 2002/01/13 14:49:56 mdw |
39 | * Conditional compilation for @getnetbyname@, since Cygwin doesn't have |
40 | * it. |
41 | * |
bc241e98 |
42 | * Revision 1.3 2000/08/01 17:59:56 mdw |
43 | * Switch over to using `size_t' for socket address lengths. |
44 | * |
e0ce9d38 |
45 | * Revision 1.2 1999/07/27 18:30:53 mdw |
46 | * Various minor portability fixes. |
47 | * |
aa1f699e |
48 | * Revision 1.1 1999/07/26 23:34:11 mdw |
49 | * New socket address types. |
50 | * |
51 | */ |
52 | |
53 | /*----- Header files ------------------------------------------------------*/ |
54 | |
55 | #include "config.h" |
56 | |
57 | #include <ctype.h> |
58 | #include <errno.h> |
59 | #include <stdio.h> |
60 | #include <stdlib.h> |
61 | #include <string.h> |
62 | |
63 | #include <sys/types.h> |
64 | #include <unistd.h> |
65 | |
66 | #include <sys/socket.h> |
67 | #include <netinet/in.h> |
68 | #include <arpa/inet.h> |
69 | #include <netdb.h> |
70 | |
71 | #include <mLib/alloc.h> |
72 | #include <mLib/dstr.h> |
0ac54f22 |
73 | #include <mLib/fdflags.h> |
aa1f699e |
74 | #include <mLib/report.h> |
75 | #include <mLib/sub.h> |
76 | |
77 | #include "acl.h" |
78 | #include "addr.h" |
79 | #include "conf.h" |
0ac54f22 |
80 | #include "fw.h" |
aa1f699e |
81 | #include "identify.h" |
82 | #include "inet.h" |
ee599f55 |
83 | #include "privconn.h" |
aa1f699e |
84 | #include "reffd.h" |
85 | #include "scan.h" |
0ac54f22 |
86 | #include "socket.h" |
aa1f699e |
87 | |
88 | /*----- Data structures ---------------------------------------------------*/ |
89 | |
90 | typedef struct inet_addrx { |
91 | addr a; |
92 | struct sockaddr_in sin; |
93 | } inet_addrx; |
94 | |
95 | typedef struct inet_opts { |
96 | addr_opts ao; |
0ac54f22 |
97 | struct in_addr bind; |
98 | } inet_opts; |
99 | |
100 | typedef struct inet_srcopts { |
101 | inet_opts io; |
aa1f699e |
102 | acl_entry *acl; |
103 | acl_entry **acltail; |
0ac54f22 |
104 | } inet_srcopts; |
105 | |
106 | typedef struct inet_targopts { |
107 | inet_opts io; |
ee599f55 |
108 | int ipriv; |
0ac54f22 |
109 | } inet_targopts; |
110 | |
ee599f55 |
111 | #define ADDRF_PRIVCONN 16u |
112 | |
0ac54f22 |
113 | static inet_srcopts inet_globalsrc = |
114 | { { { 0 }, { INADDR_ANY } }, 0, &inet_globalsrc.acl }; |
115 | static inet_targopts inet_globaltarg = |
116 | { { { 0 }, { INADDR_ANY } } }; |
aa1f699e |
117 | |
118 | /*----- Protocol operations -----------------------------------------------*/ |
119 | |
120 | /* --- @read@ --- */ |
121 | |
122 | static addr *inet_read(scanner *sc, unsigned type) |
123 | { |
124 | inet_addrx *ia = xmalloc(sizeof(*ia)); |
125 | |
126 | ia->a.ops = &inet_ops; |
127 | ia->a.sz = sizeof(struct sockaddr_in); |
e0ce9d38 |
128 | memset(&ia->sin, 0, sizeof(ia->sin)); |
aa1f699e |
129 | ia->sin.sin_family = AF_INET; |
130 | |
131 | /* --- Read the host address part --- */ |
132 | |
133 | switch (type) { |
134 | case ADDR_SRC: |
135 | if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "port") == 0) |
136 | token(sc); |
137 | ia->sin.sin_addr.s_addr = htonl(INADDR_ANY); |
138 | break; |
139 | case ADDR_DEST: { |
140 | struct hostent *h; |
141 | dstr d = DSTR_INIT; |
142 | conf_name(sc, '.', &d); |
143 | if ((h = gethostbyname(d.buf)) == 0) |
144 | error(sc, "couldn't resolve Internet address `%s'", d.buf); |
145 | memcpy(&ia->sin.sin_addr, h->h_addr, sizeof(struct in_addr)); |
146 | dstr_destroy(&d); |
147 | if (sc->t == ':') |
148 | token(sc); |
149 | } break; |
150 | } |
151 | |
152 | /* --- Read the port number --- */ |
153 | |
154 | { |
155 | struct servent *s; |
156 | |
157 | if (sc->t != CTOK_WORD) |
158 | error(sc, "parse error, TCP port expected"); |
159 | if (isdigit((unsigned char)sc->d.buf[0])) |
160 | ia->sin.sin_port = htons(atoi(sc->d.buf)); |
161 | else if ((s = getservbyname(sc->d.buf, "tcp")) == 0) |
162 | error(sc, "unknown tcp service `%s'", sc->d.buf); |
163 | else |
164 | ia->sin.sin_port = s->s_port; |
165 | token(sc); |
166 | } |
167 | |
168 | return (&ia->a); |
169 | } |
170 | |
171 | /* --- @destroy@ --- */ |
172 | |
173 | static void inet_destroy(addr *a) |
174 | { |
175 | inet_addrx *ia = (inet_addrx *)a; |
176 | DESTROY(ia); |
177 | } |
178 | |
179 | /* --- @print@ --- */ |
180 | |
181 | static void inet_print(addr *a, unsigned type, dstr *d) |
182 | { |
183 | inet_addrx *ia = (inet_addrx *)a; |
184 | switch (type) { |
185 | case ADDR_SRC: |
186 | dstr_putf(d, "inet:%u", (unsigned)ntohs(ia->sin.sin_port)); |
187 | break; |
188 | case ADDR_DEST: |
189 | dstr_putf(d, "inet:%s:%u", |
190 | inet_ntoa(ia->sin.sin_addr), |
191 | (unsigned)ntohs(ia->sin.sin_port)); |
192 | break; |
193 | } |
194 | } |
195 | |
196 | /* --- @initopts@ --- */ |
197 | |
0ac54f22 |
198 | static addr_opts *inet_initsrcopts(void) |
aa1f699e |
199 | { |
0ac54f22 |
200 | inet_srcopts *io = CREATE(inet_srcopts); |
201 | *io = inet_globalsrc; |
aa1f699e |
202 | io->acl = 0; |
203 | io->acltail = &io->acl; |
0ac54f22 |
204 | return (&io->io.ao); |
205 | } |
206 | |
207 | static addr_opts *inet_inittargopts(void) |
208 | { |
209 | inet_targopts *io = CREATE(inet_targopts); |
210 | *io = inet_globaltarg; |
ee599f55 |
211 | io->ipriv = -1; |
0ac54f22 |
212 | return (&io->io.ao); |
aa1f699e |
213 | } |
214 | |
215 | /* --- @option@ --- */ |
216 | |
0ac54f22 |
217 | static void addropt(scanner *sc, inet_opts *io) |
aa1f699e |
218 | { |
0ac54f22 |
219 | dstr d = DSTR_INIT; |
220 | struct hostent *h; |
aa1f699e |
221 | |
0ac54f22 |
222 | token(sc); |
223 | if (sc->t == '=') |
224 | token(sc); |
225 | if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "any") == 0) |
226 | io->bind.s_addr = INADDR_ANY; |
227 | else { |
228 | conf_name(sc, '.', &d); |
229 | if ((h = gethostbyname(d.buf)) == 0) |
230 | error(sc, "couldn't resolve address `%s'", d.buf); |
231 | memcpy(&io->bind, h->h_addr, sizeof(struct in_addr)); |
232 | } |
233 | } |
aa1f699e |
234 | |
0ac54f22 |
235 | static int srcopt(scanner *sc, addr_opts *ao) |
236 | { |
237 | inet_srcopts *io = (inet_srcopts *)ao; |
aa1f699e |
238 | unsigned act; |
239 | |
0ac54f22 |
240 | CONF_BEGIN(sc, "source", "Internet socket source") |
241 | |
242 | /* --- Initialization --- */ |
243 | |
244 | if (!io) { |
245 | if (!inet_globalsrc.acltail) |
246 | inet_globalsrc.acltail = &inet_globalsrc.acl; |
247 | io = &inet_globalsrc; |
248 | } |
249 | |
250 | /* --- Source address configuration --- */ |
251 | |
252 | if (strcmp(sc->d.buf, "addr") == 0) { |
253 | addropt(sc, &io->io); |
254 | CONF_ACCEPT; |
255 | } |
256 | |
aa1f699e |
257 | /* --- Access control limitations --- */ |
258 | |
259 | if ((strcmp(sc->d.buf, "allow") == 0 && (act = ACL_ALLOW, 1)) || |
260 | (strcmp(sc->d.buf, "deny") == 0 && (act = ACL_DENY, 1))) { |
261 | struct hostent *h; |
262 | struct netent *n; |
263 | struct in_addr a, m; |
264 | dstr d = DSTR_INIT; |
265 | |
0ac54f22 |
266 | /* --- Find out what's going on --- */ |
aa1f699e |
267 | |
268 | token(sc); |
269 | if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "from") == 0) |
270 | token(sc); |
aa1f699e |
271 | |
ee599f55 |
272 | if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "priv") == 0 || |
273 | strcmp(sc->d.buf, "priv-port") == 0)) { |
0ac54f22 |
274 | acl_addpriv(&io->acltail, act); |
aa1f699e |
275 | token(sc); |
0ac54f22 |
276 | } else { |
277 | if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "host") == 0) |
278 | token(sc); |
279 | |
280 | /* --- Find the host or network address --- */ |
281 | |
aa1f699e |
282 | conf_name(sc, '.', &d); |
0ac54f22 |
283 | #ifdef HAVE_GETNETBYNAME |
284 | if ((n = getnetbyname(d.buf)) != 0) |
285 | a.s_addr = htonl(n->n_net); |
286 | else |
287 | #endif |
288 | if ((h = gethostbyname(d.buf)) == 0) |
289 | error(sc, "couldn't resolve address `%s'", d.buf); |
aa1f699e |
290 | else |
0ac54f22 |
291 | memcpy(&a, h->h_addr, sizeof(struct in_addr)); |
292 | |
293 | /* --- Find the netmask, if any --- */ |
294 | |
295 | if (sc->t != '/') |
296 | m.s_addr = ~0ul; |
297 | else { |
298 | token(sc); |
299 | DRESET(&d); |
300 | conf_name(sc, '.', &d); |
301 | if (strchr(d.buf, '.') == 0) { |
302 | int n = atoi(d.buf); |
303 | if (n == 0) |
304 | m.s_addr = 0; |
305 | else |
306 | m.s_addr = htonl((~0ul << (32 - n)) & 0xffffffff); |
307 | } else { |
aa1f699e |
308 | #ifdef HAVE_INET_ATON |
0ac54f22 |
309 | if (!inet_aton(d.buf, &m)) |
310 | error(sc, "bad netmask `%s'", d.buf); |
aa1f699e |
311 | #else |
0ac54f22 |
312 | m.s_addr = inet_addr(d.buf); |
aa1f699e |
313 | #endif |
0ac54f22 |
314 | } |
aa1f699e |
315 | } |
0ac54f22 |
316 | dstr_destroy(&d); |
aa1f699e |
317 | |
0ac54f22 |
318 | /* --- Add the access control entry --- */ |
aa1f699e |
319 | |
0ac54f22 |
320 | acl_addhost(&io->acltail, act, a, m); |
321 | } |
aa1f699e |
322 | CONF_ACCEPT; |
323 | } |
324 | |
325 | /* --- Anything unrecognized --- */ |
326 | |
327 | CONF_END; |
328 | } |
329 | |
0ac54f22 |
330 | static int targopt(scanner *sc, addr_opts *ao) |
331 | { |
332 | inet_targopts *io = (inet_targopts *)ao; |
333 | |
334 | CONF_BEGIN(sc, "dest", "Internet socket target"); |
335 | if (strcmp(sc->d.buf, "addr") == 0) { |
336 | addropt(sc, &io->io); |
337 | CONF_ACCEPT; |
338 | } |
ee599f55 |
339 | if (strcmp(sc->d.buf, "priv") == 0 || |
340 | strcmp(sc->d.buf, "priv-port") == 0) { |
341 | token(sc); |
342 | if (sc->t == '=') token(sc); |
343 | if (conf_enum(sc, "no,yes", ENUM_ABBREV, "privileged connection status")) |
344 | io->io.ao.f |= ADDRF_PRIVCONN; |
345 | else |
346 | io->io.ao.f &= ~ADDRF_PRIVCONN; |
347 | CONF_ACCEPT; |
348 | } |
0ac54f22 |
349 | CONF_END; |
350 | } |
351 | |
352 | static int inet_option(scanner *sc, addr_opts *ao, unsigned type) |
353 | { |
354 | CONF_BEGIN(sc, "inet", "Internet socket"); |
355 | if (type != ADDR_DEST && srcopt(sc, ao)) |
356 | CONF_ACCEPT; |
357 | if (type != ADDR_SRC && targopt(sc, ao)) |
358 | CONF_ACCEPT; |
359 | CONF_END; |
360 | } |
361 | |
ee599f55 |
362 | /* --- @confirm@ --- */ |
363 | |
364 | static void inet_confirm(addr *a, unsigned type, addr_opts *ao) |
365 | { |
366 | inet_addrx *ia = (inet_addrx *)a; |
367 | |
368 | switch (type) { |
369 | case ADDR_DEST: { |
370 | inet_targopts *io = (inet_targopts *)ao; |
371 | if ((io->io.ao.f & ADDRF_PRIVCONN) && |
372 | (io->ipriv = privconn_adddest(ia->sin.sin_addr, |
373 | ia->sin.sin_port)) < 0) |
374 | die(1, "couldn't add privileged connection target (too late)"); |
375 | } break; |
376 | } |
377 | } |
378 | |
0ac54f22 |
379 | /* --- @freeopts@ --- */ |
380 | |
381 | static void inet_freesrcopts(addr_opts *ao) |
382 | { |
383 | inet_srcopts *io = (inet_srcopts *)ao; |
384 | acl_free(io->acl); |
385 | DESTROY(io); |
386 | } |
387 | |
388 | static void inet_freetargopts(addr_opts *ao) |
389 | { |
390 | inet_targopts *io = (inet_targopts *)ao; |
391 | DESTROY(io); |
392 | } |
393 | |
394 | /* --- @bind@ --- */ |
395 | |
396 | static int inet_bind(addr *a, addr_opts *ao) |
397 | { |
398 | inet_addrx *ia = (inet_addrx *)a; |
399 | inet_srcopts *io = (inet_srcopts *)ao; |
400 | struct sockaddr_in sin; |
401 | int opt = 1; |
402 | int fd; |
403 | |
404 | if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) |
405 | goto fail_0; |
406 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
407 | fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); |
408 | sin = ia->sin; |
409 | sin.sin_addr = io->io.bind; |
410 | if (bind(fd, (struct sockaddr *)&sin, sizeof(ia->sin))) |
411 | goto fail_1; |
412 | return (fd); |
413 | |
414 | fail_1: |
415 | close(fd); |
416 | fail_0: |
417 | return (-1); |
418 | } |
419 | |
aa1f699e |
420 | /* --- @accept@ --- */ |
421 | |
422 | static reffd *inet_accept(int fd, addr_opts *ao, const char *desc) |
423 | { |
0ac54f22 |
424 | inet_srcopts *io = (inet_srcopts *)ao; |
aa1f699e |
425 | int nfd; |
426 | id_req q; |
bc241e98 |
427 | size_t lsinsz = sizeof(q.lsin), rsinsz = sizeof(q.rsin); |
0ac54f22 |
428 | int act = ACL_ALLOW; |
aa1f699e |
429 | |
430 | /* --- Accept the new connection --- */ |
431 | |
432 | if ((nfd = accept(fd, (struct sockaddr *)&q.rsin, &rsinsz)) < 0) |
433 | return (0); |
434 | if (getsockname(nfd, (struct sockaddr *)&q.lsin, &lsinsz)) { |
435 | close(nfd); |
436 | return (0); |
437 | } |
438 | q.desc = desc; |
439 | q.r = reffd_init(nfd); |
440 | |
441 | /* --- Find out whether this connection is allowed --- */ |
442 | |
0ac54f22 |
443 | if (!acl_check(io->acl, q.rsin.sin_addr, ntohs(q.rsin.sin_port), &act)) |
444 | acl_check(inet_globalsrc.acl, q.rsin.sin_addr, |
445 | ntohs(q.rsin.sin_port), &act); |
446 | if (act != ACL_ALLOW) { |
aa1f699e |
447 | q.act = "refused"; |
0ac54f22 |
448 | if (!(io->io.ao.f & ADDRF_NOLOG)) |
aa1f699e |
449 | identify(&q); |
450 | REFFD_DEC(q.r); |
451 | return (0); |
452 | } |
453 | |
454 | /* --- Everything seems to be OK --- */ |
455 | |
456 | q.act = "accepted"; |
0ac54f22 |
457 | if (!(io->io.ao.f & ADDRF_NOLOG)) |
aa1f699e |
458 | identify(&q); |
459 | return (q.r); |
460 | } |
461 | |
0ac54f22 |
462 | /* --- @connect@ --- */ |
aa1f699e |
463 | |
0ac54f22 |
464 | static int inet_connect(addr *a, addr_opts *ao, conn *c, endpt *e) |
aa1f699e |
465 | { |
0ac54f22 |
466 | inet_addrx *ia = (inet_addrx *)a; |
467 | inet_targopts *io = (inet_targopts *)ao; |
468 | int fd; |
469 | |
ee599f55 |
470 | if (io->ipriv >= 0) { |
471 | return (privconn_connect(c, sel, io->ipriv, io->io.bind, |
472 | starget_connected, e)); |
473 | } |
0ac54f22 |
474 | if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) |
475 | goto fail_0; |
476 | if (io->io.bind.s_addr != INADDR_ANY) { |
477 | struct sockaddr_in sin; |
478 | memset(&sin, 0, sizeof(sin)); |
479 | sin.sin_family = AF_INET; |
480 | sin.sin_addr = io->io.bind; |
481 | sin.sin_port = 0; |
482 | if (bind(fd, (struct sockaddr *)&sin, sizeof(sin))) |
483 | goto fail_1; |
484 | } |
485 | fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); |
486 | return (conn_init(c, sel, fd, (struct sockaddr *)&ia->sin, sizeof(ia->sin), |
487 | starget_connected, e)); |
488 | fail_1: |
489 | close(fd); |
490 | fail_0: |
491 | return (-1); |
aa1f699e |
492 | } |
493 | |
494 | /* --- Ops table --- */ |
495 | |
496 | addr_ops inet_ops = { |
0ac54f22 |
497 | "inet", |
aa1f699e |
498 | inet_read, inet_destroy, inet_print, |
ee599f55 |
499 | inet_initsrcopts, inet_option, inet_confirm, inet_freesrcopts, |
0ac54f22 |
500 | inet_bind, 0, inet_accept, |
501 | inet_inittargopts, inet_freetargopts, |
502 | inet_connect |
aa1f699e |
503 | }; |
504 | |
505 | /*----- That's all, folks -------------------------------------------------*/ |