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