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