Privileged outgoing connections.
[fwd] / privconn.c
CommitLineData
ee599f55 1/* -*-c-*-
2 *
3 * $Id: privconn.c,v 1.1 2003/11/29 20:36:07 mdw Exp $
4 *
5 * Making privileged connections
6 *
7 * (c) 2003 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: privconn.c,v $
32 * Revision 1.1 2003/11/29 20:36:07 mdw
33 * Privileged outgoing connections.
34 *
35 */
36
37/*----- Header files ------------------------------------------------------*/
38
39#include <assert.h>
40#include <errno.h>
41#include <signal.h>
42#include <string.h>
43
44#include <sys/types.h>
45#include <unistd.h>
46
47#include <sys/socket.h>
48#include <arpa/inet.h>
49#include <netinet/in.h>
50
51#include <mLib/conn.h>
52#include <mLib/darray.h>
53#include <mLib/fdflags.h>
54#include <mLib/fdpass.h>
55#include <mLib/report.h>
56#include <mLib/sel.h>
57
58#include "privconn.h"
59
60/*----- Data structures ---------------------------------------------------*/
61
62typedef struct connrec {
63 struct in_addr peer;
64 unsigned port;
65} connrec;
66
67typedef struct connrq {
68 int i;
69 struct in_addr bind;
70} connrq;
71
72DA_DECL(connrec_v, connrec);
73
74/*----- Static variables --------------------------------------------------*/
75
76static connrec_v cv = DA_INIT;
77static conn *qhead = 0, **qtail = &qhead;
78static int kidfd = -1;
79static sel_file sf;
80
81/*----- Main code ---------------------------------------------------------*/
82
83/* --- @doconn@ --- *
84 *
85 * Arguments: @const connrq *rq@ = index of connection record
86 *
87 * Returns: Connected file descriptor, or @-1@.
88 *
89 * Use: Main privileged connection thing.
90 */
91
92static int doconn(const connrq *rq)
93{
94 struct sockaddr_in sin_bind;
95 struct sockaddr_in sin_peer;
96 int fd;
97 int i;
98 connrec *c;
99
100 /* --- Check the argument --- */
101
102 if (rq->i < 0 || rq->i >= DA_LEN(&cv)) {
103 errno = EINVAL;
104 goto fail_0;
105 }
106 c = &DA(&cv)[rq->i];
107
108 /* --- Make a new socket --- */
109
110 if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
111 goto fail_0;
112
113 /* --- Bind it to a low-numbered port --- */
114
115 memset(&sin_bind, 0, sizeof(sin_bind));
116 sin_bind.sin_family = AF_INET;
117 sin_bind.sin_addr = rq->bind;
118 for (i = 1023; i >= 512; i--) {
119 sin_bind.sin_port = htons(i);
120 if (!bind(fd, (struct sockaddr *)&sin_bind, sizeof(sin_bind)))
121 goto bound;
122 if (errno != EADDRINUSE)
123 goto fail_1;
124 }
125 goto fail_1;
126
127 /* --- Connect to the peer --- *
128 *
129 * We can find out whether it's connected later, so there's no need to
130 * distinguish these cases.
131 */
132
133bound:
134 memset(&sin_peer, 0, sizeof(sin_peer));
135 sin_peer.sin_family = AF_INET;
136 sin_peer.sin_addr = c->peer;
137 sin_peer.sin_port = c->port;
138 fdflags(fd, O_NONBLOCK, O_NONBLOCK, 0, 0);
139 if (connect(fd, (struct sockaddr *)&sin_peer, sizeof(sin_peer)) < 0 &&
140 errno != EINPROGRESS)
141 goto fail_1;
142 return (fd);
143
144 /* --- Tidy up on errors --- */
145
146fail_1:
147 close(fd);
148fail_0:
149 return (-1);
150}
151
152/* --- @dochild@ --- *
153 *
154 * Arguments: @int fd@ = my file descriptor
155 *
156 * Returns: Never.
157 *
158 * Use: Child process for making privileged connections, separated
159 * from main process after initialization.
160 */
161
162static void dochild(int fd)
163{
164 int i;
165 connrq rq;
166 int nfd;
167 ssize_t sz;
168#if defined(_SC_OPEN_MAX)
169 int maxfd = sysconf(_SC_OPEN_MAX);
170#elif defined(OPEN_MAX)
171 int maxfd = OPEN_MAX;
172#else
173 int maxfd = -1;
174#endif
175 struct sigaction sa;
176 struct sigaction sa_dfl;
177
178 /* --- Clear out unnecessary file descriptors --- */
179
180 if (maxfd < 0)
181 maxfd = 256;
182 for (i = 3; i < maxfd; i++)
183 if (i != fd) close(i);
184
185 /* --- Close off signal handlers --- */
186
187 sa_dfl.sa_handler = SIG_DFL;
188 sigemptyset(&sa_dfl.sa_mask);
189 sa_dfl.sa_flags = 0;
190 for (i = 0; i < 256; i++) {
191 if (sigaction(i, 0, &sa))
192 break;
193 if (sa.sa_handler != SIG_DFL && sa.sa_handler != SIG_IGN)
194 sigaction(i, &sa_dfl, 0);
195 }
196
197 /* --- Main loop --- */
198
199 for (;;) {
200 sz = read(fd, &rq, sizeof(rq));
201 if (!sz)
202 break;
203 if (sz < 0)
204 die(1, "read error in privconn child: %s", strerror(errno));
205 if ((nfd = doconn(&rq)) < 0)
206 goto err;
207 i = 0;
208 sz = fdpass_send(fd, nfd, &i, sizeof(i));
209 if (sz < 0)
210 goto err;
211 if (sz < sizeof(i))
212 die(1, "short write in privconn child");
213 continue;
214
215 err:
216 if (write(fd, &errno, sizeof(errno)) < 0)
217 die(1, "write error in privconn child: %s", strerror(errno));
218 }
219 _exit(0);
220}
221
222/* --- @dorecvfd@ --- *
223 *
224 * Arguments: @int fd@ = file descriptor (@== kidfd@)
225 * @unsigned mode@ = what's happening (@== SEL_READ@)
226 * @void *p@ = uninteresting (@== 0@)
227 *
228 * Returns: ---
229 *
230 * Use: Receives a file descriptor from the privileged part.
231 */
232
233void dorecvfd(int fd, unsigned mode, void *p)
234{
235 conn *c, *cc;
236 ssize_t n;
237 int e;
238
239 n = fdpass_recv(kidfd, &fd, &e, sizeof(e));
240 if (!n)
241 goto close;
242 assert(qhead);
243 c = qhead;
244 qhead = (conn *)c->writer.next;
245 if (!qhead) qtail = &qhead;
246 if (n < 0 || (errno = e) != 0)
247 goto fail;
248 if (fd == -1) {
249 errno = EIO;
250 goto fail;
251 }
252 conn_fd(c, c->writer.s, fd, c->func, c->p);
253 return;
254
255fail:
256 c->func(-1, c->p);
257 return;
258
259close:
260 close(kidfd);
261 kidfd = 0;
262 errno = EIO;
263 sel_rmfile(&sf);
264 for (c = qhead; c; c = cc) {
265 cc = (conn *)c->writer.next;
266 c->func(-1, c->p);
267 }
268 qhead = 0;
269 qtail = &qhead;
270 return;
271}
272
273/* --- @privconn_split@ --- *
274 *
275 * Arguments: @sel_state *s@ = select state
276 *
277 * Returns: ---
278 *
279 * Use: Splits off the privileged binding code into a separate
280 * process.
281 */
282
283void privconn_split(sel_state *s)
284{
285 pid_t kid;
286 int fd[2];
287
288 if (kidfd != -1)
289 return;
290 if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0)
291 die(1, "couldn't create privconn socketpair: %s", strerror(errno));
292 kidfd = fd[0];
293 if ((kid = fork()) < 0)
294 die(1, "couldn't fork privconn child: %s", strerror(errno));
295 if (!kid) {
296 close(kidfd);
297 dochild(fd[1]);
298 _exit(127);
299 }
300 close(fd[1]);
301 fdflags(kidfd, 0, 0, FD_CLOEXEC, FD_CLOEXEC);
302 sel_initfile(s, &sf, kidfd, SEL_READ, dorecvfd, 0);
303 sel_addfile(&sf);
304}
305
306/* --- @privconn_adddest@ --- *
307 *
308 * Arguments: @struct in_addr peer@ = address to connect to
309 * @unsigned port@ = port to connect to
310 *
311 * Returns: Index for this destination address, or @-1@ if not
312 * available.
313 *
314 * Use: Adds a valid destination for a privileged connection.
315 */
316
317int privconn_adddest(struct in_addr peer, unsigned port)
318{
319 int i;
320 struct connrec *c;
321
322 if (kidfd != -1)
323 return (-1);
324 for (i = 0; i < DA_LEN(&cv); i++) {
325 c = &DA(&cv)[i];
326 if (peer.s_addr == c->peer.s_addr && port == c->port)
327 return (i);
328 }
329 DA_ENSURE(&cv, 1);
330 DA_EXTEND(&cv, 1);
331 c = &DA(&cv)[i];
332 c->peer = peer;
333 c->port = port;
334 return (i);
335}
336
337/* --- @privconn_connect@ --- *
338 *
339 * Arguments: @conn *c@ = connection structure to fill in
340 * @sel_state *s@ = pointer to select state to attach to
341 * @int i@ = address index to connect to
342 * @struct in_addr bind@ = address to bind to
343 * @void (*func)(int, void *)@ = function to call on connect
344 * @void *p@ = argument for the function
345 *
346 * Returns: Zero on success, @-1@ on failure.
347 *
348 * Use: Sets up a privileged connection job.
349 */
350
351int privconn_connect(conn *c, sel_state *s, int i, struct in_addr bind,
352 void (*func)(int, void *), void *p)
353{
354 int fd;
355 connrq rq;
356 ssize_t n;
357
358 rq.i = i;
359 rq.bind = bind;
360 if (kidfd == -1) {
361 if ((fd = doconn(&rq)) < 0)
362 return (-1);
363 conn_fd(c, s, fd, func, p);
364 return (0);
365 }
366
367 n = write(kidfd, &rq, sizeof(rq));
368 if (n < 0)
369 return (-1);
370 c->writer.fd = -1;
371 c->writer.s = s;
372 c->writer.next = 0;
373 c->func = func;
374 c->p = p;
375 *qtail = c;
376 qtail = (conn **)&c->writer.next;
377 return (0);
378}
379
380/*----- That's all, folks -------------------------------------------------*/