]> git.saurik.com Git - apple/network_cmds.git/blob - alias/alias_ftp.c
77c4d8cb4d840f2d4354260beaf706110ee1748d
[apple/network_cmds.git] / alias / alias_ftp.c
1 /*
2 * Copyright (c) 2000-2002 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23 /*-
24 * Copyright (c) 2001 Charles Mott <cmott@scientech.com>
25 * All rights reserved.
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
37 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
40 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
41 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
42 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
44 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
45 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 * SUCH DAMAGE.
47 *
48 * Based upon:
49 * $FreeBSD: src/lib/libalias/alias_ftp.c,v 1.5.2.4 2001/08/21 03:50:25 brian Exp $
50 */
51
52 /*
53 Alias_ftp.c performs special processing for FTP sessions under
54 TCP. Specifically, when a PORT/EPRT command from the client
55 side or 227/229 reply from the server is sent, it is intercepted
56 and modified. The address is changed to the gateway machine
57 and an aliasing port is used.
58
59 For this routine to work, the message must fit entirely into a
60 single TCP packet. This is typically the case, but exceptions
61 can easily be envisioned under the actual specifications.
62
63 Probably the most troubling aspect of the approach taken here is
64 that the new message will typically be a different length, and
65 this causes a certain amount of bookkeeping to keep track of the
66 changes of sequence and acknowledgment numbers, since the client
67 machine is totally unaware of the modification to the TCP stream.
68
69
70 References: RFC 959, RFC 2428.
71
72 Initial version: August, 1996 (cjm)
73
74 Version 1.6
75 Brian Somers and Martin Renters identified an IP checksum
76 error for modified IP packets.
77
78 Version 1.7: January 9, 1996 (cjm)
79 Differential checksum computation for change
80 in IP packet length.
81
82 Version 2.1: May, 1997 (cjm)
83 Very minor changes to conform with
84 local/global/function naming conventions
85 within the packet aliasing module.
86
87 Version 3.1: May, 2000 (eds)
88 Add support for passive mode, alias the 227 replies.
89
90 See HISTORY file for record of revisions.
91 */
92
93 /* Includes */
94 #include <ctype.h>
95 #include <stdio.h>
96 #include <string.h>
97 #include <sys/types.h>
98 #include <netinet/in_systm.h>
99 #include <netinet/in.h>
100 #include <netinet/ip.h>
101 #include <netinet/tcp.h>
102
103 #include "alias_local.h"
104
105 #define FTP_CONTROL_PORT_NUMBER 21
106 #define MAX_MESSAGE_SIZE 128
107
108 enum ftp_message_type {
109 FTP_PORT_COMMAND,
110 FTP_EPRT_COMMAND,
111 FTP_227_REPLY,
112 FTP_229_REPLY,
113 FTP_UNKNOWN_MESSAGE
114 };
115
116 static int ParseFtpPortCommand(char *, int);
117 static int ParseFtpEprtCommand(char *, int);
118 static int ParseFtp227Reply(char *, int);
119 static int ParseFtp229Reply(char *, int);
120 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
121
122 static struct in_addr true_addr; /* in network byte order. */
123 static u_short true_port; /* in host byte order. */
124
125 void
126 AliasHandleFtpOut(
127 struct ip *pip, /* IP packet to examine/patch */
128 struct alias_link *link, /* The link to go through (aliased port) */
129 int maxpacketsize /* The maximum size this packet can grow to (including headers) */)
130 {
131 int hlen, tlen, dlen;
132 char *sptr;
133 struct tcphdr *tc;
134 int ftp_message_type;
135
136 /* Calculate data length of TCP packet */
137 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
138 hlen = (pip->ip_hl + tc->th_off) << 2;
139 tlen = ntohs(pip->ip_len);
140 dlen = tlen - hlen;
141
142 /* Place string pointer and beginning of data */
143 sptr = (char *) pip;
144 sptr += hlen;
145
146 /*
147 * Check that data length is not too long and previous message was
148 * properly terminated with CRLF.
149 */
150 if (dlen <= MAX_MESSAGE_SIZE && GetLastLineCrlfTermed(link)) {
151 ftp_message_type = FTP_UNKNOWN_MESSAGE;
152
153 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
154 /*
155 * When aliasing a client, check for the PORT/EPRT command.
156 */
157 if (ParseFtpPortCommand(sptr, dlen))
158 ftp_message_type = FTP_PORT_COMMAND;
159 else if (ParseFtpEprtCommand(sptr, dlen))
160 ftp_message_type = FTP_EPRT_COMMAND;
161 } else {
162 /*
163 * When aliasing a server, check for the 227/229 reply.
164 */
165 if (ParseFtp227Reply(sptr, dlen))
166 ftp_message_type = FTP_227_REPLY;
167 else if (ParseFtp229Reply(sptr, dlen))
168 ftp_message_type = FTP_229_REPLY;
169 }
170
171 if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
172 NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
173 }
174
175 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
176
177 if (dlen) { /* only if there's data */
178 sptr = (char *) pip; /* start over at beginning */
179 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
180 SetLastLineCrlfTermed(link,
181 (sptr[tlen-2] == '\r') && (sptr[tlen-1] == '\n'));
182 }
183 }
184
185 static int
186 ParseFtpPortCommand(char *sptr, int dlen)
187 {
188 char ch;
189 int i, state;
190 u_int32_t addr;
191 u_short port;
192 u_int8_t octet;
193
194 /* Format: "PORT A,D,D,R,PO,RT". */
195
196 /* Return if data length is too short. */
197 if (dlen < 18)
198 return 0;
199
200 addr = port = octet = 0;
201 state = -4;
202 for (i = 0; i < dlen; i++) {
203 ch = sptr[i];
204 switch (state) {
205 case -4: if (ch == 'P') state++; else return 0; break;
206 case -3: if (ch == 'O') state++; else return 0; break;
207 case -2: if (ch == 'R') state++; else return 0; break;
208 case -1: if (ch == 'T') state++; else return 0; break;
209
210 case 0:
211 if (isspace(ch))
212 break;
213 else
214 state++;
215 case 1: case 3: case 5: case 7: case 9: case 11:
216 if (isdigit(ch)) {
217 octet = ch - '0';
218 state++;
219 } else
220 return 0;
221 break;
222 case 2: case 4: case 6: case 8:
223 if (isdigit(ch))
224 octet = 10 * octet + ch - '0';
225 else if (ch == ',') {
226 addr = (addr << 8) + octet;
227 state++;
228 } else
229 return 0;
230 break;
231 case 10: case 12:
232 if (isdigit(ch))
233 octet = 10 * octet + ch - '0';
234 else if (ch == ',' || state == 12) {
235 port = (port << 8) + octet;
236 state++;
237 } else
238 return 0;
239 break;
240 }
241 }
242
243 if (state == 13) {
244 true_addr.s_addr = htonl(addr);
245 true_port = port;
246 return 1;
247 } else
248 return 0;
249 }
250
251 static int
252 ParseFtpEprtCommand(char *sptr, int dlen)
253 {
254 char ch, delim;
255 int i, state;
256 u_int32_t addr;
257 u_short port;
258 u_int8_t octet;
259
260 /* Format: "EPRT |1|A.D.D.R|PORT|". */
261
262 /* Return if data length is too short. */
263 if (dlen < 18)
264 return 0;
265
266 addr = port = octet = 0;
267 delim = '|'; /* XXX gcc -Wuninitialized */
268 state = -4;
269 for (i = 0; i < dlen; i++) {
270 ch = sptr[i];
271 switch (state)
272 {
273 case -4: if (ch == 'E') state++; else return 0; break;
274 case -3: if (ch == 'P') state++; else return 0; break;
275 case -2: if (ch == 'R') state++; else return 0; break;
276 case -1: if (ch == 'T') state++; else return 0; break;
277
278 case 0:
279 if (!isspace(ch)) {
280 delim = ch;
281 state++;
282 }
283 break;
284 case 1:
285 if (ch == '1') /* IPv4 address */
286 state++;
287 else
288 return 0;
289 break;
290 case 2:
291 if (ch == delim)
292 state++;
293 else
294 return 0;
295 break;
296 case 3: case 5: case 7: case 9:
297 if (isdigit(ch)) {
298 octet = ch - '0';
299 state++;
300 } else
301 return 0;
302 break;
303 case 4: case 6: case 8: case 10:
304 if (isdigit(ch))
305 octet = 10 * octet + ch - '0';
306 else if (ch == '.' || state == 10) {
307 addr = (addr << 8) + octet;
308 state++;
309 } else
310 return 0;
311 break;
312 case 11:
313 if (isdigit(ch)) {
314 port = ch - '0';
315 state++;
316 } else
317 return 0;
318 break;
319 case 12:
320 if (isdigit(ch))
321 port = 10 * port + ch - '0';
322 else if (ch == delim)
323 state++;
324 else
325 return 0;
326 break;
327 }
328 }
329
330 if (state == 13) {
331 true_addr.s_addr = htonl(addr);
332 true_port = port;
333 return 1;
334 } else
335 return 0;
336 }
337
338 static int
339 ParseFtp227Reply(char *sptr, int dlen)
340 {
341 char ch;
342 int i, state;
343 u_int32_t addr;
344 u_short port;
345 u_int8_t octet;
346
347 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
348
349 /* Return if data length is too short. */
350 if (dlen < 17)
351 return 0;
352
353 addr = port = octet = 0;
354
355 state = -3;
356 for (i = 0; i < dlen; i++) {
357 ch = sptr[i];
358 switch (state)
359 {
360 case -3: if (ch == '2') state++; else return 0; break;
361 case -2: if (ch == '2') state++; else return 0; break;
362 case -1: if (ch == '7') state++; else return 0; break;
363
364 case 0:
365 if (ch == '(')
366 state++;
367 break;
368 case 1: case 3: case 5: case 7: case 9: case 11:
369 if (isdigit(ch)) {
370 octet = ch - '0';
371 state++;
372 } else
373 return 0;
374 break;
375 case 2: case 4: case 6: case 8:
376 if (isdigit(ch))
377 octet = 10 * octet + ch - '0';
378 else if (ch == ',') {
379 addr = (addr << 8) + octet;
380 state++;
381 } else
382 return 0;
383 break;
384 case 10: case 12:
385 if (isdigit(ch))
386 octet = 10 * octet + ch - '0';
387 else if (ch == ',' || (state == 12 && ch == ')')) {
388 port = (port << 8) + octet;
389 state++;
390 } else
391 return 0;
392 break;
393 }
394 }
395
396 if (state == 13) {
397 true_port = port;
398 true_addr.s_addr = htonl(addr);
399 return 1;
400 } else
401 return 0;
402 }
403
404 static int
405 ParseFtp229Reply(char *sptr, int dlen)
406 {
407 char ch, delim;
408 int i, state;
409 u_short port;
410
411 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
412
413 /* Return if data length is too short. */
414 if (dlen < 11)
415 return 0;
416
417 port = 0;
418 delim = '|'; /* XXX gcc -Wuninitialized */
419
420 state = -3;
421 for (i = 0; i < dlen; i++) {
422 ch = sptr[i];
423 switch (state)
424 {
425 case -3: if (ch == '2') state++; else return 0; break;
426 case -2: if (ch == '2') state++; else return 0; break;
427 case -1: if (ch == '9') state++; else return 0; break;
428
429 case 0:
430 if (ch == '(')
431 state++;
432 break;
433 case 1:
434 delim = ch;
435 state++;
436 break;
437 case 2: case 3:
438 if (ch == delim)
439 state++;
440 else
441 return 0;
442 break;
443 case 4:
444 if (isdigit(ch)) {
445 port = ch - '0';
446 state++;
447 } else
448 return 0;
449 break;
450 case 5:
451 if (isdigit(ch))
452 port = 10 * port + ch - '0';
453 else if (ch == delim)
454 state++;
455 else
456 return 0;
457 break;
458 case 6:
459 if (ch == ')')
460 state++;
461 else
462 return 0;
463 break;
464 }
465 }
466
467 if (state == 7) {
468 true_port = port;
469 return 1;
470 } else
471 return 0;
472 }
473
474 static void
475 NewFtpMessage(struct ip *pip,
476 struct alias_link *link,
477 int maxpacketsize,
478 int ftp_message_type)
479 {
480 struct alias_link *ftp_link;
481
482 /* Security checks. */
483 if (ftp_message_type != FTP_229_REPLY &&
484 pip->ip_src.s_addr != true_addr.s_addr)
485 return;
486
487 if (true_port < IPPORT_RESERVED)
488 return;
489
490 /* Establish link to address and port found in FTP control message. */
491 ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
492 htons(true_port), 0, IPPROTO_TCP, 1);
493
494 if (ftp_link != NULL)
495 {
496 int slen, hlen, tlen, dlen;
497 struct tcphdr *tc;
498
499 #ifndef NO_FW_PUNCH
500 if (ftp_message_type == FTP_PORT_COMMAND ||
501 ftp_message_type == FTP_EPRT_COMMAND) {
502 /* Punch hole in firewall */
503 PunchFWHole(ftp_link);
504 }
505 #endif
506
507 /* Calculate data length of TCP packet */
508 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
509 hlen = (pip->ip_hl + tc->th_off) << 2;
510 tlen = ntohs(pip->ip_len);
511 dlen = tlen - hlen;
512
513 /* Create new FTP message. */
514 {
515 char stemp[MAX_MESSAGE_SIZE + 1];
516 char *sptr;
517 u_short alias_port;
518 u_char *ptr;
519 int a1, a2, a3, a4, p1, p2;
520 struct in_addr alias_address;
521
522 /* Decompose alias address into quad format */
523 alias_address = GetAliasAddress(link);
524 ptr = (u_char *) &alias_address.s_addr;
525 a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
526
527 alias_port = GetAliasPort(ftp_link);
528
529 switch (ftp_message_type)
530 {
531 case FTP_PORT_COMMAND:
532 case FTP_227_REPLY:
533 /* Decompose alias port into pair format. */
534 ptr = (char *) &alias_port;
535 p1 = *ptr++; p2=*ptr;
536
537 if (ftp_message_type == FTP_PORT_COMMAND) {
538 /* Generate PORT command string. */
539 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
540 a1,a2,a3,a4,p1,p2);
541 } else {
542 /* Generate 227 reply string. */
543 sprintf(stemp,
544 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
545 a1,a2,a3,a4,p1,p2);
546 }
547 break;
548 case FTP_EPRT_COMMAND:
549 /* Generate EPRT command string. */
550 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
551 a1,a2,a3,a4,ntohs(alias_port));
552 break;
553 case FTP_229_REPLY:
554 /* Generate 229 reply string. */
555 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
556 ntohs(alias_port));
557 break;
558 }
559
560 /* Save string length for IP header modification */
561 slen = strlen(stemp);
562
563 /* Copy modified buffer into IP packet. */
564 sptr = (char *) pip; sptr += hlen;
565 strncpy(sptr, stemp, maxpacketsize-hlen);
566 }
567
568 /* Save information regarding modified seq and ack numbers */
569 {
570 int delta;
571
572 SetAckModified(link);
573 delta = GetDeltaSeqOut(pip, link);
574 AddSeq(pip, link, delta+slen-dlen);
575 }
576
577 /* Revise IP header */
578 {
579 u_short new_len;
580
581 new_len = htons(hlen + slen);
582 DifferentialChecksum(&pip->ip_sum,
583 &new_len,
584 &pip->ip_len,
585 1);
586 pip->ip_len = new_len;
587 }
588
589 /* Compute TCP checksum for revised packet */
590 tc->th_sum = 0;
591 tc->th_sum = TcpChecksum(pip);
592 }
593 else
594 {
595 #ifdef DEBUG
596 fprintf(stderr,
597 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
598 #endif
599 }
600 }