84f87e82 |
1 | /* |
2 | * Encrypting tunnel for userv-ipif tunnels, actual implementation |
3 | * |
4 | * usage: |
5 | * udptunnel-forwarder <public-local-fd> <private-in-fd> <private-out-fd> |
6 | * <mtu> <keepalive> <timeout> |
7 | * <public-remote-addr> [<public-remote-port>] |
8 | * <encdec-keys-fd> <encdec-keys-write> |
9 | * <mech1> [<mech1-params> ...] |
10 | * <mech2> [<mech2-params> ...] |
11 | * '' |
12 | * |
13 | * Remote addr may '' to mean wait to receive a packet and reply to |
14 | * whereever we get a good packet from first, in which case port |
15 | * should not be specified. |
16 | * |
17 | * <enc-keys-write> is '' to mean read, anything else to mean write. |
18 | * |
19 | * Every must be numeric. There is very little argument checking. |
20 | * |
21 | * Exit status: |
22 | * SIGALARM timed out |
23 | * 0 terminated due to outbound packet stream EOF |
24 | * 4 other error |
25 | * 8 system problem |
26 | * 12 usage error |
27 | * 16 bad trouble |
28 | */ |
29 | |
30 | #include <sys/socket.h> |
31 | #include <netinet/in.h> |
32 | #include <arpa/inet.h> |
33 | #include <sys/utsname.h> |
34 | #include <sys/poll.h> |
35 | |
36 | #include <string.h> |
37 | #include <errno.h> |
38 | #include <assert.h> |
39 | #include <stdlib.h> |
40 | |
41 | #include <unistd.h> |
42 | #include <fcntl.h> |
43 | |
44 | #include "mech.h" |
45 | |
46 | #define MAXMECHS 10 |
47 | #define PROGRAM "udptunnel-forwarder" |
48 | char programid[SYS_NMLN+sizeof(PROGRAM)+3]; |
49 | |
50 | static const char *const *argv; |
51 | static size_t buffer_size; |
52 | |
53 | static int public_local_fd, private_in_fd, private_out_fd; |
54 | static int mtu2, keepalive, timeout; |
55 | static int public_remote_specd; |
56 | static struct sockaddr_in public_remote; |
57 | static int encdec_keys_fd, encdec_keys_write; |
58 | static int n_mechs; |
59 | static const struct mechanism *mechs[MAXMECHS]; |
60 | |
61 | static struct mechdata *md_in[MAXMECHS], *md_out[MAXMECHS]; |
62 | static size_t maxprefix, maxsuffix; |
63 | |
64 | static struct buffer buf_in, buf_out; |
65 | static unsigned char *accum_buf; |
66 | static size_t accum_used, accum_avail; |
67 | |
68 | static time_t nextsendka; |
69 | |
70 | |
71 | void arg_assert_fail(const char *msg) { |
72 | fprintf(stderr, PROGRAM ": argument error: %s\n",msg); |
73 | exit(12); |
74 | } |
75 | |
76 | void sysfail(const char *msg) { |
77 | fprintf(stderr, "%s: fatal system error: %s: %s\n", programid, msg, strerror(errno)); |
78 | exit(8); |
79 | } |
80 | |
81 | void fail(const char *msg) { |
82 | fprintf(stderr, "%s: fatal error: %s\n", programid, msg); |
83 | exit(4); |
84 | } |
85 | |
86 | void sysdiag(const char *msg) { |
87 | fprintf(stderr, "%s: system/network error: %s: %s\n", programid, msg, strerror(errno)); |
88 | } |
89 | |
90 | void diag(const char *msg) { |
91 | fprintf(stderr, "%s: %s\n", programid, msg); |
92 | } |
93 | |
94 | time_t now(void) { |
95 | time_t r; |
96 | if (time(&r) == (time_t)-1) sysfail("get time of day"); |
97 | return r; |
98 | } |
99 | |
100 | void *xmalloc(size_t sz) { |
101 | void *r; |
102 | r= malloc(sz); |
103 | if (!r) sysfail("allocate memory"); |
104 | return r; |
105 | } |
106 | |
107 | void get_random(void *ptr, size_t sz) { |
108 | static FILE *randfile; |
109 | |
110 | size_t r; |
111 | |
112 | if (!randfile) { |
113 | randfile= fopen("/dev/urandom","rb"); |
114 | if (!randfile && errno==ENOENT) randfile= fopen("/dev/random","rb"); |
115 | if (!randfile) sysfail("open random number generator"); |
116 | } |
117 | |
118 | r= fread(ptr,1,sz,randfile); |
119 | if (r == sz) return; |
120 | (ferror(randfile) ? sysfail : fail)("cannot read random number generator"); |
121 | } |
122 | |
123 | const char *getarg_string(void) { |
124 | const char *arg; |
125 | |
126 | arg= *++argv; |
127 | arg_assert(arg); |
128 | return arg; |
129 | } |
130 | |
131 | unsigned long getarg_ulong(void) { |
132 | char *ep; |
133 | unsigned long ul; |
134 | |
135 | ul= strtoul(getarg_string(),&ep,0); |
136 | arg_assert(!*ep); |
137 | return ul; |
138 | } |
139 | |
140 | static void setnonblock(int fd, int nonblock) { |
141 | int r; |
142 | |
143 | r= fcntl(fd,F_GETFL); |
144 | if (r==-1) sysfail("fcntl F_GETFL"); |
145 | r= fcntl(fd,F_SETFL, nonblock ? r|O_NONBLOCK : r&~O_NONBLOCK); |
146 | if (r==-1) sysfail("fcntl F_SETFL"); |
147 | } |
148 | |
149 | static const struct mechanism *getarg_mech(void) { |
150 | const char *name; |
151 | const struct mechanism *mech, *const *mechlist; |
152 | |
153 | name= getarg_string(); |
154 | |
155 | for (mechlist= mechanismlists; |
156 | *mechlist; |
157 | mechlist++) |
158 | for (mech= *mechlist; mech->name; mech++) |
159 | if (!strcmp(mech->name,name)) return mech; |
160 | |
161 | fprintf(stderr,"%s: unknown mechanism: %s\n",programid,name); |
162 | exit(4); |
163 | } |
164 | |
165 | static void inbound(void) { |
166 | static int any_recvd; |
167 | |
168 | struct sockaddr_in this_saddr; |
169 | int r, i, different, this_saddrlen; |
170 | const char *emsg; |
171 | |
172 | alarm(timeout); |
173 | |
174 | setnonblock(public_local_fd,1); |
175 | this_saddrlen= sizeof(this_saddr); |
176 | r= recvfrom(public_local_fd, buf_in.base, buffer_size-1, 0, |
177 | &this_saddr, &this_saddrlen); |
178 | if (!r) { diag("empty ciphertext"); return; } |
179 | |
180 | if (r<0) { |
181 | if (errno != EAGAIN && errno != EINTR) sysdiag("receive"); |
182 | return; |
183 | } |
184 | if (this_saddr.sin_family != AF_INET) { |
185 | fprintf(stderr,"%s: received unknown AF %lu", |
186 | programid, (unsigned long)this_saddr.sin_family); |
187 | return; |
188 | } |
189 | assert(this_saddrlen == sizeof(this_saddr)); |
190 | |
191 | buf_in.size= buffer_size; |
192 | buf_in.start= buf_in.base; |
193 | for (i=n_mechs-1; i>=0; i--) { |
194 | emsg= mechs[i]->decode(md_in[i],&buf_in); |
195 | if (emsg) { |
196 | fprintf(stderr, "%s: bad packet: %s: %s\n", programid, mechs[i]->name, emsg); |
197 | return; |
198 | } |
199 | } |
200 | |
201 | different= !public_remote_specd || |
202 | memcmp(&this_saddr,&public_remote,sizeof(this_saddr)); |
203 | |
204 | if (different) { |
205 | |
206 | if (public_remote_specd==2) { |
207 | fprintf(stderr, "%s: packet from unexpected sender %s:%lu", |
208 | programid, inet_ntoa(this_saddr.sin_addr), |
209 | (unsigned long)this_saddr.sin_port); |
210 | return; |
211 | } |
212 | |
213 | fprintf(stderr, "%s: tunnel open with peer %s:%lu", |
214 | programid, inet_ntoa(this_saddr.sin_addr), |
215 | (unsigned long)this_saddr.sin_port); |
216 | nextsendka= now(); |
217 | public_remote_specd= 1; |
218 | memcpy(&public_remote,&this_saddr,sizeof(public_remote)); |
219 | |
220 | } else if (!any_recvd) { |
221 | |
222 | diag("tunnel open"); |
223 | |
224 | } |
225 | |
226 | any_recvd= 1; |
227 | |
228 | buf_in.start[buf_in.size]= 0300; |
229 | *--buf_in.start= 0300; |
230 | buf_in.size+= 2; |
231 | |
232 | setnonblock(private_in_fd,0); |
233 | while (buf_in.size) { |
234 | r= write(private_in_fd, buf_in.start, buf_in.size); |
235 | assert(r && r <= buf_in.size); |
236 | if (r<0) { |
237 | if (errno == EINTR) continue; |
238 | sysfail("write down"); |
239 | } |
240 | buf_in.start += r; |
241 | buf_in.size -= r; |
242 | } |
243 | } |
244 | |
245 | static void sendpacket(const unsigned char *message, size_t size) { |
246 | int i, r; |
247 | |
248 | buf_out.start= buf_out.base+maxprefix; |
249 | buf_out.size= size; |
250 | memcpy(buf_out.start, message, size); |
251 | |
252 | nextsendka= now() + keepalive; |
253 | |
254 | for (i=0; i<n_mechs; i++) mechs[i]->encode(md_out[i],&buf_out); |
255 | assert(public_remote_specd); |
256 | |
257 | setnonblock(public_local_fd,1); |
258 | for (;;) { |
259 | r= sendto(public_local_fd, buf_out.start, buf_out.size, 0, |
260 | &public_remote, sizeof(public_remote)); |
261 | if (r == buf_out.size) break; |
262 | if (r >= 0) { diag("unexpected short send"); return; } |
263 | if (errno != EINTR) { sysdiag("send"); return; } |
264 | } |
265 | } |
266 | |
267 | static void outbound(void) { |
268 | int r; |
269 | unsigned char *after_eaten, *delim; |
270 | size_t this_packet; |
271 | |
272 | setnonblock(private_out_fd,1); |
273 | |
274 | for (;;) { |
275 | r= read(private_out_fd, accum_buf + accum_used, accum_avail - accum_used); |
276 | if (!r) { diag("outbound datastream closed, quitting"); exit(0); } |
277 | if (r<0) { |
278 | if (errno == EAGAIN) return; |
279 | if (errno == EINTR) continue; |
280 | } |
281 | accum_used += r; |
282 | assert(accum_used<=accum_avail); |
283 | |
284 | after_eaten= accum_buf; |
285 | while ((delim= memchr(after_eaten, 0300, accum_used))) { |
286 | this_packet= delim - after_eaten; |
287 | sendpacket(after_eaten, this_packet); |
288 | accum_used -= this_packet+1; |
289 | after_eaten = delim+1; |
290 | } |
291 | memmove(accum_buf, after_eaten, accum_used); |
292 | |
293 | if (accum_used == accum_avail) { |
294 | diag("missing interpacket delimiter in output datastream"); |
295 | accum_used= 0; |
296 | } |
297 | } |
298 | } |
299 | |
300 | int main(int argc, const char *const *const argv_in) { |
301 | const char *arg; |
302 | struct pollfd pollfds[2]; |
303 | struct utsname uname_result; |
304 | int i, polltimeout, r; |
305 | time_t tnow; |
306 | |
307 | argv= argv_in; |
308 | |
309 | if (uname(&uname_result)) { perror(PROGRAM ": uname failed"); exit(16); } |
310 | sprintf(programid, PROGRAM ": %.*s", SYS_NMLN, uname_result.nodename); |
311 | |
312 | public_local_fd= getarg_ulong(); |
313 | mtu2= getarg_ulong() * 2; |
314 | keepalive= getarg_ulong(); |
315 | timeout= getarg_ulong(); |
316 | private_in_fd= getarg_ulong(); |
317 | private_out_fd= getarg_ulong(); |
318 | |
319 | arg= getarg_string(); |
320 | if (*arg) { |
321 | public_remote_specd= 1; |
322 | arg_assert(inet_aton(arg,&public_remote.sin_addr)); |
323 | public_remote.sin_port= getarg_ulong(); |
324 | } |
325 | |
326 | encdec_keys_fd= getarg_ulong(); |
327 | encdec_keys_write= !!*getarg_string(); |
328 | |
329 | maxprefix= 0; |
330 | for (i=0; i<n_mechs; i++) mechs[i]= getarg_mech(); |
331 | for (i=0; i<n_mechs; i++) mechs[i]->encsetup(&md_in[i], &maxprefix, &maxsuffix); |
332 | for (i=0; i<n_mechs; i++) mechs[i]->decsetup(&md_out[i]); |
333 | |
334 | if (maxprefix<1) maxprefix= 1; |
335 | if (maxsuffix<1) maxsuffix= 1; |
336 | buffer_size= mtu2 + maxprefix + maxsuffix; |
337 | buf_in.base= xmalloc(buffer_size); |
338 | buf_out.base= xmalloc(buffer_size); |
339 | accum_avail= mtu2 + 1; |
340 | accum_buf= xmalloc(accum_avail); |
341 | |
342 | alarm(timeout); |
343 | |
344 | pollfds[0].fd= public_local_fd; |
345 | pollfds[0].events= POLLIN; |
346 | pollfds[1].fd= private_out_fd; |
347 | for (;;) { |
348 | pollfds[1].events= public_remote_specd ? POLLIN : 0; |
349 | pollfds[0].revents= 0; |
350 | pollfds[1].revents= 0; |
351 | |
352 | if (keepalive) { |
353 | tnow= now(); |
354 | if (tnow >= nextsendka) sendpacket("\300",1); |
355 | polltimeout= (nextsendka - tnow)*1000; |
356 | } else { |
357 | polltimeout= -1; |
358 | } |
359 | |
360 | r= poll(pollfds,2,polltimeout); |
361 | if (!r) continue; |
362 | if (r==-1 && errno==EINTR) continue; |
363 | if (r==-1) sysfail("poll"); |
364 | |
365 | if (pollfds[0].revents & POLLIN) inbound(); |
366 | if (pollfds[1].revents & POLLOUT) outbound(); |
367 | } |
368 | } |