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