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