1 /*- 2 * Copyright (c) 2001 Charles Mott <cm@linktel.net> 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 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 /* 31 Alias_ftp.c performs special processing for FTP sessions under 32 TCP. Specifically, when a PORT/EPRT command from the client 33 side or 227/229 reply from the server is sent, it is intercepted 34 and modified. The address is changed to the gateway machine 35 and an aliasing port is used. 36 37 For this routine to work, the message must fit entirely into a 38 single TCP packet. This is typically the case, but exceptions 39 can easily be envisioned under the actual specifications. 40 41 Probably the most troubling aspect of the approach taken here is 42 that the new message will typically be a different length, and 43 this causes a certain amount of bookkeeping to keep track of the 44 changes of sequence and acknowledgment numbers, since the client 45 machine is totally unaware of the modification to the TCP stream. 46 47 48 References: RFC 959, RFC 2428. 49 50 Initial version: August, 1996 (cjm) 51 52 Version 1.6 53 Brian Somers and Martin Renters identified an IP checksum 54 error for modified IP packets. 55 56 Version 1.7: January 9, 1996 (cjm) 57 Differential checksum computation for change 58 in IP packet length. 59 60 Version 2.1: May, 1997 (cjm) 61 Very minor changes to conform with 62 local/global/function naming conventions 63 within the packet aliasing module. 64 65 Version 3.1: May, 2000 (eds) 66 Add support for passive mode, alias the 227 replies. 67 68 See HISTORY file for record of revisions. 69 */ 70 71 /* Includes */ 72 #include <ctype.h> 73 #include <stdio.h> 74 #include <string.h> 75 #include <sys/types.h> 76 #include <netinet/in_systm.h> 77 #include <netinet/in.h> 78 #include <netinet/ip.h> 79 #include <netinet/tcp.h> 80 81 #include "alias_local.h" 82 83 #define FTP_CONTROL_PORT_NUMBER 21 84 #define MAX_MESSAGE_SIZE 128 85 86 /* FTP protocol flags. */ 87 #define WAIT_CRLF 0x01 88 89 enum ftp_message_type { 90 FTP_PORT_COMMAND, 91 FTP_EPRT_COMMAND, 92 FTP_227_REPLY, 93 FTP_229_REPLY, 94 FTP_UNKNOWN_MESSAGE 95 }; 96 97 static int ParseFtpPortCommand(char *, int); 98 static int ParseFtpEprtCommand(char *, int); 99 static int ParseFtp227Reply(char *, int); 100 static int ParseFtp229Reply(char *, int); 101 static void NewFtpMessage(struct ip *, struct alias_link *, int, int); 102 103 static struct in_addr true_addr; /* in network byte order. */ 104 static u_short true_port; /* in host byte order. */ 105 106 void 107 AliasHandleFtpOut( 108 struct ip *pip, /* IP packet to examine/patch */ 109 struct alias_link *link, /* The link to go through (aliased port) */ 110 int maxpacketsize /* The maximum size this packet can grow to (including headers) */) 111 { 112 int hlen, tlen, dlen, pflags; 113 char *sptr; 114 struct tcphdr *tc; 115 int ftp_message_type; 116 117 /* Calculate data length of TCP packet */ 118 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 119 hlen = (pip->ip_hl + tc->th_off) << 2; 120 tlen = ntohs(pip->ip_len); 121 dlen = tlen - hlen; 122 123 /* Place string pointer and beginning of data */ 124 sptr = (char *) pip; 125 sptr += hlen; 126 127 /* 128 * Check that data length is not too long and previous message was 129 * properly terminated with CRLF. 130 */ 131 pflags = GetProtocolFlags(link); 132 if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) { 133 ftp_message_type = FTP_UNKNOWN_MESSAGE; 134 135 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) { 136 /* 137 * When aliasing a client, check for the PORT/EPRT command. 138 */ 139 if (ParseFtpPortCommand(sptr, dlen)) 140 ftp_message_type = FTP_PORT_COMMAND; 141 else if (ParseFtpEprtCommand(sptr, dlen)) 142 ftp_message_type = FTP_EPRT_COMMAND; 143 } else { 144 /* 145 * When aliasing a server, check for the 227/229 reply. 146 */ 147 if (ParseFtp227Reply(sptr, dlen)) 148 ftp_message_type = FTP_227_REPLY; 149 else if (ParseFtp229Reply(sptr, dlen)) { 150 ftp_message_type = FTP_229_REPLY; 151 true_addr.s_addr = pip->ip_src.s_addr; 152 } 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 (pip->ip_src.s_addr != true_addr.s_addr) 471 return; 472 473 if (true_port < IPPORT_RESERVED) 474 return; 475 476 /* Establish link to address and port found in FTP control message. */ 477 ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link), 478 htons(true_port), 0, IPPROTO_TCP, 1); 479 480 if (ftp_link != NULL) 481 { 482 int slen, hlen, tlen, dlen; 483 struct tcphdr *tc; 484 485 #ifndef NO_FW_PUNCH 486 /* Punch hole in firewall */ 487 PunchFWHole(ftp_link); 488 #endif 489 490 /* Calculate data length of TCP packet */ 491 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 492 hlen = (pip->ip_hl + tc->th_off) << 2; 493 tlen = ntohs(pip->ip_len); 494 dlen = tlen - hlen; 495 496 /* Create new FTP message. */ 497 { 498 char stemp[MAX_MESSAGE_SIZE + 1]; 499 char *sptr; 500 u_short alias_port; 501 u_char *ptr; 502 int a1, a2, a3, a4, p1, p2; 503 struct in_addr alias_address; 504 505 /* Decompose alias address into quad format */ 506 alias_address = GetAliasAddress(link); 507 ptr = (u_char *) &alias_address.s_addr; 508 a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr; 509 510 alias_port = GetAliasPort(ftp_link); 511 512 switch (ftp_message_type) 513 { 514 case FTP_PORT_COMMAND: 515 case FTP_227_REPLY: 516 /* Decompose alias port into pair format. */ 517 ptr = (char *) &alias_port; 518 p1 = *ptr++; p2=*ptr; 519 520 if (ftp_message_type == FTP_PORT_COMMAND) { 521 /* Generate PORT command string. */ 522 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n", 523 a1,a2,a3,a4,p1,p2); 524 } else { 525 /* Generate 227 reply string. */ 526 sprintf(stemp, 527 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", 528 a1,a2,a3,a4,p1,p2); 529 } 530 break; 531 case FTP_EPRT_COMMAND: 532 /* Generate EPRT command string. */ 533 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n", 534 a1,a2,a3,a4,ntohs(alias_port)); 535 break; 536 case FTP_229_REPLY: 537 /* Generate 229 reply string. */ 538 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n", 539 ntohs(alias_port)); 540 break; 541 } 542 543 /* Save string length for IP header modification */ 544 slen = strlen(stemp); 545 546 /* Copy modified buffer into IP packet. */ 547 sptr = (char *) pip; sptr += hlen; 548 strncpy(sptr, stemp, maxpacketsize-hlen); 549 } 550 551 /* Save information regarding modified seq and ack numbers */ 552 { 553 int delta; 554 555 SetAckModified(link); 556 delta = GetDeltaSeqOut(pip, link); 557 AddSeq(pip, link, delta+slen-dlen); 558 } 559 560 /* Revise IP header */ 561 { 562 u_short new_len; 563 564 new_len = htons(hlen + slen); 565 DifferentialChecksum(&pip->ip_sum, 566 &new_len, 567 &pip->ip_len, 568 1); 569 pip->ip_len = new_len; 570 } 571 572 /* Compute TCP checksum for revised packet */ 573 tc->th_sum = 0; 574 tc->th_sum = TcpChecksum(pip); 575 } 576 else 577 { 578 #ifdef DEBUG 579 fprintf(stderr, 580 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n"); 581 #endif 582 } 583 } 584