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