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