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