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