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