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