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 #ifdef _KERNEL 73 #include <sys/param.h> 74 #include <sys/ctype.h> 75 #include <sys/systm.h> 76 #include <sys/kernel.h> 77 #include <sys/module.h> 78 #else 79 #include <ctype.h> 80 #include <errno.h> 81 #include <sys/types.h> 82 #include <stdio.h> 83 #include <string.h> 84 #endif 85 86 #include <netinet/in_systm.h> 87 #include <netinet/in.h> 88 #include <netinet/ip.h> 89 #include <netinet/tcp.h> 90 91 #ifdef _KERNEL 92 #include <netinet/libalias/alias.h> 93 #include <netinet/libalias/alias_local.h> 94 #include <netinet/libalias/alias_mod.h> 95 #else 96 #include "alias_local.h" 97 #include "alias_mod.h" 98 #endif 99 100 #define FTP_CONTROL_PORT_NUMBER 21 101 102 static void 103 AliasHandleFtpOut(struct libalias *, struct ip *, struct alias_link *, 104 int maxpacketsize); 105 106 static int 107 fingerprint(struct libalias *la, struct alias_data *ah) 108 { 109 110 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL || 111 ah->maxpktsize == 0) 112 return (-1); 113 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER 114 || ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER) 115 return (0); 116 return (-1); 117 } 118 119 static int 120 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah) 121 { 122 123 AliasHandleFtpOut(la, pip, ah->lnk, ah->maxpktsize); 124 return (0); 125 } 126 127 struct proto_handler handlers[] = { 128 { 129 .pri = 80, 130 .dir = OUT, 131 .proto = TCP, 132 .fingerprint = &fingerprint, 133 .protohandler = &protohandler 134 }, 135 { EOH } 136 }; 137 138 static int 139 mod_handler(module_t mod, int type, void *data) 140 { 141 int error; 142 143 switch (type) { 144 case MOD_LOAD: 145 error = 0; 146 LibAliasAttachHandlers(handlers); 147 break; 148 case MOD_UNLOAD: 149 error = 0; 150 LibAliasDetachHandlers(handlers); 151 break; 152 default: 153 error = EINVAL; 154 } 155 return (error); 156 } 157 158 #ifdef _KERNEL 159 static 160 #endif 161 moduledata_t alias_mod = { 162 "alias_ftp", mod_handler, NULL 163 }; 164 165 #ifdef _KERNEL 166 DECLARE_MODULE(alias_ftp, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND); 167 MODULE_VERSION(alias_ftp, 1); 168 MODULE_DEPEND(alias_ftp, libalias, 1, 1, 1); 169 #endif 170 171 #define FTP_CONTROL_PORT_NUMBER 21 172 #define MAX_MESSAGE_SIZE 128 173 174 /* FTP protocol flags. */ 175 #define WAIT_CRLF 0x01 176 177 enum ftp_message_type { 178 FTP_PORT_COMMAND, 179 FTP_EPRT_COMMAND, 180 FTP_227_REPLY, 181 FTP_229_REPLY, 182 FTP_UNKNOWN_MESSAGE 183 }; 184 185 static int ParseFtpPortCommand(struct libalias *la, char *, int); 186 static int ParseFtpEprtCommand(struct libalias *la, char *, int); 187 static int ParseFtp227Reply(struct libalias *la, char *, int); 188 static int ParseFtp229Reply(struct libalias *la, char *, int); 189 static void NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int); 190 191 static void 192 AliasHandleFtpOut( 193 struct libalias *la, 194 struct ip *pip, /* IP packet to examine/patch */ 195 struct alias_link *lnk, /* The link to go through (aliased port) */ 196 int maxpacketsize /* The maximum size this packet can grow to 197 (including headers) */ ) 198 { 199 int hlen, tlen, dlen, pflags; 200 char *sptr; 201 struct tcphdr *tc; 202 int ftp_message_type; 203 204 /* Calculate data length of TCP packet */ 205 tc = (struct tcphdr *)ip_next(pip); 206 hlen = (pip->ip_hl + tc->th_off) << 2; 207 tlen = ntohs(pip->ip_len); 208 dlen = tlen - hlen; 209 210 /* Place string pointer and beginning of data */ 211 sptr = (char *)pip; 212 sptr += hlen; 213 214 /* 215 * Check that data length is not too long and previous message was 216 * properly terminated with CRLF. 217 */ 218 pflags = GetProtocolFlags(lnk); 219 if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) { 220 ftp_message_type = FTP_UNKNOWN_MESSAGE; 221 222 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) { 223 /* 224 * When aliasing a client, check for the PORT/EPRT command. 225 */ 226 if (ParseFtpPortCommand(la, sptr, dlen)) 227 ftp_message_type = FTP_PORT_COMMAND; 228 else if (ParseFtpEprtCommand(la, sptr, dlen)) 229 ftp_message_type = FTP_EPRT_COMMAND; 230 } else { 231 /* 232 * When aliasing a server, check for the 227/229 reply. 233 */ 234 if (ParseFtp227Reply(la, sptr, dlen)) 235 ftp_message_type = FTP_227_REPLY; 236 else if (ParseFtp229Reply(la, sptr, dlen)) { 237 ftp_message_type = FTP_229_REPLY; 238 la->true_addr.s_addr = pip->ip_src.s_addr; 239 } 240 } 241 242 if (ftp_message_type != FTP_UNKNOWN_MESSAGE) 243 NewFtpMessage(la, pip, lnk, maxpacketsize, ftp_message_type); 244 } 245 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */ 246 247 if (dlen) { /* only if there's data */ 248 sptr = (char *)pip; /* start over at beginning */ 249 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may 250 * have grown */ 251 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n') 252 pflags &= ~WAIT_CRLF; 253 else 254 pflags |= WAIT_CRLF; 255 SetProtocolFlags(lnk, pflags); 256 } 257 } 258 259 static int 260 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen) 261 { 262 char ch; 263 int i, state; 264 u_int32_t addr; 265 u_short port; 266 u_int8_t octet; 267 268 /* Format: "PORT A,D,D,R,PO,RT". */ 269 270 /* Return if data length is too short. */ 271 if (dlen < 18) 272 return (0); 273 274 if (strncasecmp("PORT ", sptr, 5)) 275 return (0); 276 277 addr = port = octet = 0; 278 state = 0; 279 for (i = 5; i < dlen; i++) { 280 ch = sptr[i]; 281 switch (state) { 282 case 0: 283 if (isspace(ch)) 284 break; 285 else 286 state++; 287 case 1: 288 case 3: 289 case 5: 290 case 7: 291 case 9: 292 case 11: 293 if (isdigit(ch)) { 294 octet = ch - '0'; 295 state++; 296 } else 297 return (0); 298 break; 299 case 2: 300 case 4: 301 case 6: 302 case 8: 303 if (isdigit(ch)) 304 octet = 10 * octet + ch - '0'; 305 else if (ch == ',') { 306 addr = (addr << 8) + octet; 307 state++; 308 } else 309 return (0); 310 break; 311 case 10: 312 case 12: 313 if (isdigit(ch)) 314 octet = 10 * octet + ch - '0'; 315 else if (ch == ',' || state == 12) { 316 port = (port << 8) + octet; 317 state++; 318 } else 319 return (0); 320 break; 321 } 322 } 323 324 if (state == 13) { 325 la->true_addr.s_addr = htonl(addr); 326 la->true_port = port; 327 return (1); 328 } else 329 return (0); 330 } 331 332 static int 333 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen) 334 { 335 char ch, delim; 336 int i, state; 337 u_int32_t addr; 338 u_short port; 339 u_int8_t octet; 340 341 /* Format: "EPRT |1|A.D.D.R|PORT|". */ 342 343 /* Return if data length is too short. */ 344 if (dlen < 18) 345 return (0); 346 347 if (strncasecmp("EPRT ", sptr, 5)) 348 return (0); 349 350 addr = port = octet = 0; 351 delim = '|'; /* XXX gcc -Wuninitialized */ 352 state = 0; 353 for (i = 5; i < dlen; i++) { 354 ch = sptr[i]; 355 switch (state) { 356 case 0: 357 if (!isspace(ch)) { 358 delim = ch; 359 state++; 360 } 361 break; 362 case 1: 363 if (ch == '1') /* IPv4 address */ 364 state++; 365 else 366 return (0); 367 break; 368 case 2: 369 if (ch == delim) 370 state++; 371 else 372 return (0); 373 break; 374 case 3: 375 case 5: 376 case 7: 377 case 9: 378 if (isdigit(ch)) { 379 octet = ch - '0'; 380 state++; 381 } else 382 return (0); 383 break; 384 case 4: 385 case 6: 386 case 8: 387 case 10: 388 if (isdigit(ch)) 389 octet = 10 * octet + ch - '0'; 390 else if (ch == '.' || state == 10) { 391 addr = (addr << 8) + octet; 392 state++; 393 } else 394 return (0); 395 break; 396 case 11: 397 if (isdigit(ch)) { 398 port = ch - '0'; 399 state++; 400 } else 401 return (0); 402 break; 403 case 12: 404 if (isdigit(ch)) 405 port = 10 * port + ch - '0'; 406 else if (ch == delim) 407 state++; 408 else 409 return (0); 410 break; 411 } 412 } 413 414 if (state == 13) { 415 la->true_addr.s_addr = htonl(addr); 416 la->true_port = port; 417 return (1); 418 } else 419 return (0); 420 } 421 422 static int 423 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen) 424 { 425 char ch; 426 int i, state; 427 u_int32_t addr; 428 u_short port; 429 u_int8_t octet; 430 431 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */ 432 433 /* Return if data length is too short. */ 434 if (dlen < 17) 435 return (0); 436 437 if (strncmp("227 ", sptr, 4)) 438 return (0); 439 440 addr = port = octet = 0; 441 442 state = 0; 443 for (i = 4; i < dlen; i++) { 444 ch = sptr[i]; 445 switch (state) { 446 case 0: 447 if (ch == '(') 448 state++; 449 break; 450 case 1: 451 case 3: 452 case 5: 453 case 7: 454 case 9: 455 case 11: 456 if (isdigit(ch)) { 457 octet = ch - '0'; 458 state++; 459 } else 460 return (0); 461 break; 462 case 2: 463 case 4: 464 case 6: 465 case 8: 466 if (isdigit(ch)) 467 octet = 10 * octet + ch - '0'; 468 else if (ch == ',') { 469 addr = (addr << 8) + octet; 470 state++; 471 } else 472 return (0); 473 break; 474 case 10: 475 case 12: 476 if (isdigit(ch)) 477 octet = 10 * octet + ch - '0'; 478 else if (ch == ',' || (state == 12 && ch == ')')) { 479 port = (port << 8) + octet; 480 state++; 481 } else 482 return (0); 483 break; 484 } 485 } 486 487 if (state == 13) { 488 la->true_port = port; 489 la->true_addr.s_addr = htonl(addr); 490 return (1); 491 } else 492 return (0); 493 } 494 495 static int 496 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen) 497 { 498 char ch, delim; 499 int i, state; 500 u_short port; 501 502 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */ 503 504 /* Return if data length is too short. */ 505 if (dlen < 11) 506 return (0); 507 508 if (strncmp("229 ", sptr, 4)) 509 return (0); 510 511 port = 0; 512 delim = '|'; /* XXX gcc -Wuninitialized */ 513 514 state = 0; 515 for (i = 4; i < dlen; i++) { 516 ch = sptr[i]; 517 switch (state) { 518 case 0: 519 if (ch == '(') 520 state++; 521 break; 522 case 1: 523 delim = ch; 524 state++; 525 break; 526 case 2: 527 case 3: 528 if (ch == delim) 529 state++; 530 else 531 return (0); 532 break; 533 case 4: 534 if (isdigit(ch)) { 535 port = ch - '0'; 536 state++; 537 } else 538 return (0); 539 break; 540 case 5: 541 if (isdigit(ch)) 542 port = 10 * port + ch - '0'; 543 else if (ch == delim) 544 state++; 545 else 546 return (0); 547 break; 548 case 6: 549 if (ch == ')') 550 state++; 551 else 552 return (0); 553 break; 554 } 555 } 556 557 if (state == 7) { 558 la->true_port = port; 559 return (1); 560 } else 561 return (0); 562 } 563 564 static void 565 NewFtpMessage(struct libalias *la, struct ip *pip, 566 struct alias_link *lnk, 567 int maxpacketsize, 568 int ftp_message_type) 569 { 570 struct alias_link *ftp_lnk; 571 572 /* Security checks. */ 573 if (pip->ip_src.s_addr != la->true_addr.s_addr) 574 return; 575 576 if (la->true_port < IPPORT_RESERVED) 577 return; 578 579 /* Establish link to address and port found in FTP control message. */ 580 ftp_lnk = FindUdpTcpOut(la, la->true_addr, GetDestAddress(lnk), 581 htons(la->true_port), 0, IPPROTO_TCP, 1); 582 583 if (ftp_lnk != NULL) { 584 int slen, hlen, tlen, dlen; 585 struct tcphdr *tc; 586 587 #ifndef NO_FW_PUNCH 588 /* Punch hole in firewall */ 589 PunchFWHole(ftp_lnk); 590 #endif 591 592 /* Calculate data length of TCP packet */ 593 tc = (struct tcphdr *)ip_next(pip); 594 hlen = (pip->ip_hl + tc->th_off) << 2; 595 tlen = ntohs(pip->ip_len); 596 dlen = tlen - hlen; 597 598 /* Create new FTP message. */ 599 { 600 char stemp[MAX_MESSAGE_SIZE + 1]; 601 char *sptr; 602 u_short alias_port; 603 u_char *ptr; 604 int a1, a2, a3, a4, p1, p2; 605 struct in_addr alias_address; 606 607 /* Decompose alias address into quad format */ 608 alias_address = GetAliasAddress(lnk); 609 ptr = (u_char *) & alias_address.s_addr; 610 a1 = *ptr++; 611 a2 = *ptr++; 612 a3 = *ptr++; 613 a4 = *ptr; 614 615 alias_port = GetAliasPort(ftp_lnk); 616 617 /* Prepare new command */ 618 switch (ftp_message_type) { 619 case FTP_PORT_COMMAND: 620 case FTP_227_REPLY: 621 /* Decompose alias port into pair format. */ 622 ptr = (char *)&alias_port; 623 p1 = *ptr++; 624 p2 = *ptr; 625 626 if (ftp_message_type == FTP_PORT_COMMAND) { 627 /* Generate PORT command string. */ 628 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n", 629 a1, a2, a3, a4, p1, p2); 630 } else { 631 /* Generate 227 reply string. */ 632 sprintf(stemp, 633 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", 634 a1, a2, a3, a4, p1, p2); 635 } 636 break; 637 case FTP_EPRT_COMMAND: 638 /* Generate EPRT command string. */ 639 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n", 640 a1, a2, a3, a4, ntohs(alias_port)); 641 break; 642 case FTP_229_REPLY: 643 /* Generate 229 reply string. */ 644 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n", 645 ntohs(alias_port)); 646 break; 647 } 648 649 /* Save string length for IP header modification */ 650 slen = strlen(stemp); 651 652 /* Copy modified buffer into IP packet. */ 653 sptr = (char *)pip; 654 sptr += hlen; 655 strncpy(sptr, stemp, maxpacketsize - hlen); 656 } 657 658 /* Save information regarding modified seq and ack numbers */ 659 { 660 int delta; 661 662 SetAckModified(lnk); 663 tc = (struct tcphdr *)ip_next(pip); 664 delta = GetDeltaSeqOut(tc->th_seq, lnk); 665 AddSeq(lnk, delta + slen - dlen, pip->ip_hl, 666 pip->ip_len, tc->th_seq, tc->th_off); 667 } 668 669 /* Revise IP header */ 670 { 671 u_short new_len; 672 673 new_len = htons(hlen + slen); 674 DifferentialChecksum(&pip->ip_sum, 675 &new_len, 676 &pip->ip_len, 677 1); 678 pip->ip_len = new_len; 679 } 680 681 /* Compute TCP checksum for revised packet */ 682 tc->th_sum = 0; 683 #ifdef _KERNEL 684 tc->th_x2 = 1; 685 #else 686 tc->th_sum = TcpChecksum(pip); 687 #endif 688 } else { 689 #ifdef LIBALIAS_DEBUG 690 fprintf(stderr, 691 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n"); 692 #endif 693 } 694 } 695