1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2001 Charles Mott <cm@linktel.net> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 /* 33 Alias_ftp.c performs special processing for FTP sessions under 34 TCP. Specifically, when a PORT/EPRT command from the client 35 side or 227/229 reply from the server is sent, it is intercepted 36 and modified. The address is changed to the gateway machine 37 and an aliasing port is used. 38 39 For this routine to work, the message must fit entirely into a 40 single TCP packet. This is typically the case, but exceptions 41 can easily be envisioned under the actual specifications. 42 43 Probably the most troubling aspect of the approach taken here is 44 that the new message will typically be a different length, and 45 this causes a certain amount of bookkeeping to keep track of the 46 changes of sequence and acknowledgment numbers, since the client 47 machine is totally unaware of the modification to the TCP stream. 48 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 #ifdef _KERNEL 75 #include <sys/param.h> 76 #include <sys/ctype.h> 77 #include <sys/systm.h> 78 #include <sys/kernel.h> 79 #include <sys/module.h> 80 #else 81 #include <ctype.h> 82 #include <errno.h> 83 #include <sys/types.h> 84 #include <stdio.h> 85 #include <string.h> 86 #endif 87 88 #include <netinet/in_systm.h> 89 #include <netinet/in.h> 90 #include <netinet/ip.h> 91 #include <netinet/tcp.h> 92 93 #ifdef _KERNEL 94 #include <netinet/libalias/alias.h> 95 #include <netinet/libalias/alias_local.h> 96 #include <netinet/libalias/alias_mod.h> 97 #else 98 #include "alias_local.h" 99 #include "alias_mod.h" 100 #endif 101 102 #define FTP_CONTROL_PORT_NUMBER 21 103 104 static void 105 AliasHandleFtpOut(struct libalias *, struct ip *, struct alias_link *, 106 int maxpacketsize); 107 static void 108 AliasHandleFtpIn(struct libalias *, struct ip *, struct alias_link *); 109 110 static int 111 fingerprint_out(struct libalias *la, struct alias_data *ah) 112 { 113 114 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL || 115 ah->maxpktsize == 0) 116 return (-1); 117 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER || 118 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER) 119 return (0); 120 return (-1); 121 } 122 123 static int 124 fingerprint_in(struct libalias *la, struct alias_data *ah) 125 { 126 127 if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL) 128 return (-1); 129 if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER || 130 ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER) 131 return (0); 132 return (-1); 133 } 134 135 static int 136 protohandler_out(struct libalias *la, struct ip *pip, struct alias_data *ah) 137 { 138 139 AliasHandleFtpOut(la, pip, ah->lnk, ah->maxpktsize); 140 return (0); 141 } 142 143 144 static int 145 protohandler_in(struct libalias *la, struct ip *pip, struct alias_data *ah) 146 { 147 148 AliasHandleFtpIn(la, pip, ah->lnk); 149 return (0); 150 } 151 152 struct proto_handler handlers[] = { 153 { 154 .pri = 80, 155 .dir = OUT, 156 .proto = TCP, 157 .fingerprint = &fingerprint_out, 158 .protohandler = &protohandler_out 159 }, 160 { 161 .pri = 80, 162 .dir = IN, 163 .proto = TCP, 164 .fingerprint = &fingerprint_in, 165 .protohandler = &protohandler_in 166 }, 167 { EOH } 168 }; 169 170 static int 171 mod_handler(module_t mod, int type, void *data) 172 { 173 int error; 174 175 switch (type) { 176 case MOD_LOAD: 177 error = 0; 178 LibAliasAttachHandlers(handlers); 179 break; 180 case MOD_UNLOAD: 181 error = 0; 182 LibAliasDetachHandlers(handlers); 183 break; 184 default: 185 error = EINVAL; 186 } 187 return (error); 188 } 189 190 #ifdef _KERNEL 191 static 192 #endif 193 moduledata_t alias_mod = { 194 "alias_ftp", mod_handler, NULL 195 }; 196 197 #ifdef _KERNEL 198 DECLARE_MODULE(alias_ftp, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND); 199 MODULE_VERSION(alias_ftp, 1); 200 MODULE_DEPEND(alias_ftp, libalias, 1, 1, 1); 201 #endif 202 203 #define FTP_CONTROL_PORT_NUMBER 21 204 #define MAX_MESSAGE_SIZE 128 205 206 /* FTP protocol flags. */ 207 #define WAIT_CRLF 0x01 208 209 enum ftp_message_type { 210 FTP_PORT_COMMAND, 211 FTP_EPRT_COMMAND, 212 FTP_227_REPLY, 213 FTP_229_REPLY, 214 FTP_UNKNOWN_MESSAGE 215 }; 216 217 static int ParseFtpPortCommand(struct libalias *la, char *, int); 218 static int ParseFtpEprtCommand(struct libalias *la, char *, int); 219 static int ParseFtp227Reply(struct libalias *la, char *, int); 220 static int ParseFtp229Reply(struct libalias *la, char *, int); 221 static void NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int); 222 223 static void 224 AliasHandleFtpOut( 225 struct libalias *la, 226 struct ip *pip, /* IP packet to examine/patch */ 227 struct alias_link *lnk, /* The link to go through (aliased port) */ 228 int maxpacketsize /* The maximum size this packet can grow to 229 (including headers) */ ) 230 { 231 int hlen, tlen, dlen, pflags; 232 char *sptr; 233 struct tcphdr *tc; 234 int ftp_message_type; 235 236 /* Calculate data length of TCP packet */ 237 tc = (struct tcphdr *)ip_next(pip); 238 hlen = (pip->ip_hl + tc->th_off) << 2; 239 tlen = ntohs(pip->ip_len); 240 dlen = tlen - hlen; 241 242 /* Place string pointer and beginning of data */ 243 sptr = (char *)pip; 244 sptr += hlen; 245 246 /* 247 * Check that data length is not too long and previous message was 248 * properly terminated with CRLF. 249 */ 250 pflags = GetProtocolFlags(lnk); 251 if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) { 252 ftp_message_type = FTP_UNKNOWN_MESSAGE; 253 254 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) { 255 /* 256 * When aliasing a client, check for the PORT/EPRT command. 257 */ 258 if (ParseFtpPortCommand(la, sptr, dlen)) 259 ftp_message_type = FTP_PORT_COMMAND; 260 else if (ParseFtpEprtCommand(la, sptr, dlen)) 261 ftp_message_type = FTP_EPRT_COMMAND; 262 } else { 263 /* 264 * When aliasing a server, check for the 227/229 reply. 265 */ 266 if (ParseFtp227Reply(la, sptr, dlen)) 267 ftp_message_type = FTP_227_REPLY; 268 else if (ParseFtp229Reply(la, sptr, dlen)) { 269 ftp_message_type = FTP_229_REPLY; 270 la->true_addr.s_addr = pip->ip_src.s_addr; 271 } 272 } 273 274 if (ftp_message_type != FTP_UNKNOWN_MESSAGE) 275 NewFtpMessage(la, pip, lnk, maxpacketsize, ftp_message_type); 276 } 277 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */ 278 279 if (dlen) { /* only if there's data */ 280 sptr = (char *)pip; /* start over at beginning */ 281 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may 282 * have grown */ 283 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n') 284 pflags &= ~WAIT_CRLF; 285 else 286 pflags |= WAIT_CRLF; 287 SetProtocolFlags(lnk, pflags); 288 } 289 } 290 291 static void 292 AliasHandleFtpIn(struct libalias *la, 293 struct ip *pip, /* IP packet to examine/patch */ 294 struct alias_link *lnk) /* The link to go through (aliased port) */ 295 { 296 int hlen, tlen, dlen, pflags; 297 char *sptr; 298 struct tcphdr *tc; 299 300 /* Calculate data length of TCP packet */ 301 tc = (struct tcphdr *)ip_next(pip); 302 hlen = (pip->ip_hl + tc->th_off) << 2; 303 tlen = ntohs(pip->ip_len); 304 dlen = tlen - hlen; 305 306 /* Place string pointer and beginning of data */ 307 sptr = (char *)pip; 308 sptr += hlen; 309 310 /* 311 * Check that data length is not too long and previous message was 312 * properly terminated with CRLF. 313 */ 314 pflags = GetProtocolFlags(lnk); 315 if (dlen <= MAX_MESSAGE_SIZE && (pflags & WAIT_CRLF) == 0 && 316 ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER && 317 (ParseFtpPortCommand(la, sptr, dlen) != 0 || 318 ParseFtpEprtCommand(la, sptr, dlen) != 0)) { 319 /* 320 * Alias active mode client requesting data from server 321 * behind NAT. We need to alias server->client connection 322 * to external address client is connecting to. 323 */ 324 AddLink(la, GetOriginalAddress(lnk), la->true_addr, 325 GetAliasAddress(lnk), htons(FTP_CONTROL_PORT_NUMBER - 1), 326 htons(la->true_port), GET_ALIAS_PORT, IPPROTO_TCP); 327 } 328 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */ 329 if (dlen) { 330 sptr = (char *)pip; /* start over at beginning */ 331 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may 332 * have grown. 333 */ 334 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n') 335 pflags &= ~WAIT_CRLF; 336 else 337 pflags |= WAIT_CRLF; 338 SetProtocolFlags(lnk, pflags); 339 } 340 } 341 342 static int 343 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen) 344 { 345 char ch; 346 int i, state; 347 u_int32_t addr; 348 u_short port; 349 u_int8_t octet; 350 351 /* Format: "PORT A,D,D,R,PO,RT". */ 352 353 /* Return if data length is too short. */ 354 if (dlen < 18) 355 return (0); 356 357 if (strncasecmp("PORT ", sptr, 5)) 358 return (0); 359 360 addr = port = octet = 0; 361 state = 0; 362 for (i = 5; i < dlen; i++) { 363 ch = sptr[i]; 364 switch (state) { 365 case 0: 366 if (isspace(ch)) 367 break; 368 else 369 state++; 370 case 1: 371 case 3: 372 case 5: 373 case 7: 374 case 9: 375 case 11: 376 if (isdigit(ch)) { 377 octet = ch - '0'; 378 state++; 379 } else 380 return (0); 381 break; 382 case 2: 383 case 4: 384 case 6: 385 case 8: 386 if (isdigit(ch)) 387 octet = 10 * octet + ch - '0'; 388 else if (ch == ',') { 389 addr = (addr << 8) + octet; 390 state++; 391 } else 392 return (0); 393 break; 394 case 10: 395 case 12: 396 if (isdigit(ch)) 397 octet = 10 * octet + ch - '0'; 398 else if (ch == ',' || state == 12) { 399 port = (port << 8) + octet; 400 state++; 401 } else 402 return (0); 403 break; 404 } 405 } 406 407 if (state == 13) { 408 la->true_addr.s_addr = htonl(addr); 409 la->true_port = port; 410 return (1); 411 } else 412 return (0); 413 } 414 415 static int 416 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen) 417 { 418 char ch, delim; 419 int i, state; 420 u_int32_t addr; 421 u_short port; 422 u_int8_t octet; 423 424 /* Format: "EPRT |1|A.D.D.R|PORT|". */ 425 426 /* Return if data length is too short. */ 427 if (dlen < 18) 428 return (0); 429 430 if (strncasecmp("EPRT ", sptr, 5)) 431 return (0); 432 433 addr = port = octet = 0; 434 delim = '|'; /* XXX gcc -Wuninitialized */ 435 state = 0; 436 for (i = 5; i < dlen; i++) { 437 ch = sptr[i]; 438 switch (state) { 439 case 0: 440 if (!isspace(ch)) { 441 delim = ch; 442 state++; 443 } 444 break; 445 case 1: 446 if (ch == '1') /* IPv4 address */ 447 state++; 448 else 449 return (0); 450 break; 451 case 2: 452 if (ch == delim) 453 state++; 454 else 455 return (0); 456 break; 457 case 3: 458 case 5: 459 case 7: 460 case 9: 461 if (isdigit(ch)) { 462 octet = ch - '0'; 463 state++; 464 } else 465 return (0); 466 break; 467 case 4: 468 case 6: 469 case 8: 470 case 10: 471 if (isdigit(ch)) 472 octet = 10 * octet + ch - '0'; 473 else if (ch == '.' || state == 10) { 474 addr = (addr << 8) + octet; 475 state++; 476 } else 477 return (0); 478 break; 479 case 11: 480 if (isdigit(ch)) { 481 port = ch - '0'; 482 state++; 483 } else 484 return (0); 485 break; 486 case 12: 487 if (isdigit(ch)) 488 port = 10 * port + ch - '0'; 489 else if (ch == delim) 490 state++; 491 else 492 return (0); 493 break; 494 } 495 } 496 497 if (state == 13) { 498 la->true_addr.s_addr = htonl(addr); 499 la->true_port = port; 500 return (1); 501 } else 502 return (0); 503 } 504 505 static int 506 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen) 507 { 508 char ch; 509 int i, state; 510 u_int32_t addr; 511 u_short port; 512 u_int8_t octet; 513 514 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */ 515 516 /* Return if data length is too short. */ 517 if (dlen < 17) 518 return (0); 519 520 if (strncmp("227 ", sptr, 4)) 521 return (0); 522 523 addr = port = octet = 0; 524 525 state = 0; 526 for (i = 4; i < dlen; i++) { 527 ch = sptr[i]; 528 switch (state) { 529 case 0: 530 if (ch == '(') 531 state++; 532 break; 533 case 1: 534 case 3: 535 case 5: 536 case 7: 537 case 9: 538 case 11: 539 if (isdigit(ch)) { 540 octet = ch - '0'; 541 state++; 542 } else 543 return (0); 544 break; 545 case 2: 546 case 4: 547 case 6: 548 case 8: 549 if (isdigit(ch)) 550 octet = 10 * octet + ch - '0'; 551 else if (ch == ',') { 552 addr = (addr << 8) + octet; 553 state++; 554 } else 555 return (0); 556 break; 557 case 10: 558 case 12: 559 if (isdigit(ch)) 560 octet = 10 * octet + ch - '0'; 561 else if (ch == ',' || (state == 12 && ch == ')')) { 562 port = (port << 8) + octet; 563 state++; 564 } else 565 return (0); 566 break; 567 } 568 } 569 570 if (state == 13) { 571 la->true_port = port; 572 la->true_addr.s_addr = htonl(addr); 573 return (1); 574 } else 575 return (0); 576 } 577 578 static int 579 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen) 580 { 581 char ch, delim; 582 int i, state; 583 u_short port; 584 585 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */ 586 587 /* Return if data length is too short. */ 588 if (dlen < 11) 589 return (0); 590 591 if (strncmp("229 ", sptr, 4)) 592 return (0); 593 594 port = 0; 595 delim = '|'; /* XXX gcc -Wuninitialized */ 596 597 state = 0; 598 for (i = 4; i < dlen; i++) { 599 ch = sptr[i]; 600 switch (state) { 601 case 0: 602 if (ch == '(') 603 state++; 604 break; 605 case 1: 606 delim = ch; 607 state++; 608 break; 609 case 2: 610 case 3: 611 if (ch == delim) 612 state++; 613 else 614 return (0); 615 break; 616 case 4: 617 if (isdigit(ch)) { 618 port = ch - '0'; 619 state++; 620 } else 621 return (0); 622 break; 623 case 5: 624 if (isdigit(ch)) 625 port = 10 * port + ch - '0'; 626 else if (ch == delim) 627 state++; 628 else 629 return (0); 630 break; 631 case 6: 632 if (ch == ')') 633 state++; 634 else 635 return (0); 636 break; 637 } 638 } 639 640 if (state == 7) { 641 la->true_port = port; 642 return (1); 643 } else 644 return (0); 645 } 646 647 static void 648 NewFtpMessage(struct libalias *la, struct ip *pip, 649 struct alias_link *lnk, 650 int maxpacketsize, 651 int ftp_message_type) 652 { 653 struct alias_link *ftp_lnk; 654 655 /* Security checks. */ 656 if (pip->ip_src.s_addr != la->true_addr.s_addr) 657 return; 658 659 if (la->true_port < IPPORT_RESERVED) 660 return; 661 662 /* Establish link to address and port found in FTP control message. */ 663 ftp_lnk = AddLink(la, la->true_addr, GetDestAddress(lnk), 664 GetAliasAddress(lnk), htons(la->true_port), 0, GET_ALIAS_PORT, 665 IPPROTO_TCP); 666 667 if (ftp_lnk != NULL) { 668 int slen, hlen, tlen, dlen; 669 struct tcphdr *tc; 670 671 #ifndef NO_FW_PUNCH 672 /* Punch hole in firewall */ 673 PunchFWHole(ftp_lnk); 674 #endif 675 676 /* Calculate data length of TCP packet */ 677 tc = (struct tcphdr *)ip_next(pip); 678 hlen = (pip->ip_hl + tc->th_off) << 2; 679 tlen = ntohs(pip->ip_len); 680 dlen = tlen - hlen; 681 682 /* Create new FTP message. */ 683 { 684 char stemp[MAX_MESSAGE_SIZE + 1]; 685 char *sptr; 686 u_short alias_port; 687 u_char *ptr; 688 int a1, a2, a3, a4, p1, p2; 689 struct in_addr alias_address; 690 691 /* Decompose alias address into quad format */ 692 alias_address = GetAliasAddress(lnk); 693 ptr = (u_char *) & alias_address.s_addr; 694 a1 = *ptr++; 695 a2 = *ptr++; 696 a3 = *ptr++; 697 a4 = *ptr; 698 699 alias_port = GetAliasPort(ftp_lnk); 700 701 /* Prepare new command */ 702 switch (ftp_message_type) { 703 case FTP_PORT_COMMAND: 704 case FTP_227_REPLY: 705 /* Decompose alias port into pair format. */ 706 ptr = (char *)&alias_port; 707 p1 = *ptr++; 708 p2 = *ptr; 709 710 if (ftp_message_type == FTP_PORT_COMMAND) { 711 /* Generate PORT command string. */ 712 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n", 713 a1, a2, a3, a4, p1, p2); 714 } else { 715 /* Generate 227 reply string. */ 716 sprintf(stemp, 717 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", 718 a1, a2, a3, a4, p1, p2); 719 } 720 break; 721 case FTP_EPRT_COMMAND: 722 /* Generate EPRT command string. */ 723 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n", 724 a1, a2, a3, a4, ntohs(alias_port)); 725 break; 726 case FTP_229_REPLY: 727 /* Generate 229 reply string. */ 728 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n", 729 ntohs(alias_port)); 730 break; 731 } 732 733 /* Save string length for IP header modification */ 734 slen = strlen(stemp); 735 736 /* Copy modified buffer into IP packet. */ 737 sptr = (char *)pip; 738 sptr += hlen; 739 strncpy(sptr, stemp, maxpacketsize - hlen); 740 } 741 742 /* Save information regarding modified seq and ack numbers */ 743 { 744 int delta; 745 746 SetAckModified(lnk); 747 tc = (struct tcphdr *)ip_next(pip); 748 delta = GetDeltaSeqOut(tc->th_seq, lnk); 749 AddSeq(lnk, delta + slen - dlen, pip->ip_hl, 750 pip->ip_len, tc->th_seq, tc->th_off); 751 } 752 753 /* Revise IP header */ 754 { 755 u_short new_len; 756 757 new_len = htons(hlen + 758 MIN(slen, maxpacketsize - hlen)); 759 DifferentialChecksum(&pip->ip_sum, 760 &new_len, 761 &pip->ip_len, 762 1); 763 pip->ip_len = new_len; 764 } 765 766 /* Compute TCP checksum for revised packet */ 767 tc->th_sum = 0; 768 #ifdef _KERNEL 769 tc->th_x2 = 1; 770 #else 771 tc->th_sum = TcpChecksum(pip); 772 #endif 773 } else { 774 #ifdef LIBALIAS_DEBUG 775 fprintf(stderr, 776 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n"); 777 #endif 778 } 779 } 780