chiark / gitweb /
Privileged outgoing connections.
[fwd] / privconn.c
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
62 typedef struct connrec {
63   struct in_addr peer;
64   unsigned port;
65 } connrec;
66
67 typedef struct connrq {
68   int i;
69   struct in_addr bind;
70 } connrq;
71
72 DA_DECL(connrec_v, connrec);
73
74 /*----- Static variables --------------------------------------------------*/
75
76 static connrec_v cv = DA_INIT;
77 static conn *qhead = 0, **qtail = &qhead;
78 static int kidfd = -1;
79 static 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
92 static 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
133 bound:
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
146 fail_1:
147   close(fd);
148 fail_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
162 static 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
233 void 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
255 fail:
256   c->func(-1, c->p);
257   return;
258
259 close:
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
283 void 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
317 int 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
351 int 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 -------------------------------------------------*/