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