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 114enum 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 122static int ParseFtpPortCommand(char *, int); 123static int ParseFtpEprtCommand(char *, int); 124static int ParseFtp227Reply(char *, int); 125static int ParseFtp229Reply(char *, int); 126static void NewFtpMessage(struct ip *, struct alias_link *, int, int); 127 128static struct in_addr true_addr; /* in network byte order. */ 129static u_short true_port; /* in host byte order. */ 130 131void 132AliasHandleFtpOut( 133struct ip *pip, /* IP packet to examine/patch */ 134struct alias_link *link, /* The link to go through (aliased port) */ 135int 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 191static int 192ParseFtpPortCommand(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 257static int 258ParseFtpEprtCommand(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 344static int 345ParseFtp227Reply(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 410static int 411ParseFtp229Reply(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 480static void 481NewFtpMessage(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} 607