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