1 /* 2 Alias_ftp.c performs special processing for FTP sessions under 3 TCP. Specifically, when a PORT/EPRT command from the client 4 side or 227/229 reply from the server is sent, it is intercepted 5 and modified. The address is changed to the gateway machine 6 and an aliasing port is used. 7 8 For this routine to work, the message must fit entirely into a 9 single TCP packet. This is typically the case, but exceptions 10 can easily be envisioned under the actual specifications. 11 12 Probably the most troubling aspect of the approach taken here is 13 that the new message will typically be a different length, and 14 this causes a certain amount of bookkeeping to keep track of the 15 changes of sequence and acknowledgment numbers, since the client 16 machine is totally unaware of the modification to the TCP stream. 17 18 19 This software is placed into the public domain with no restrictions 20 on its distribution. 21 22 References: RFC 959, RFC 2428. 23 24 Initial version: August, 1996 (cjm) 25 26 Version 1.6 27 Brian Somers and Martin Renters identified an IP checksum 28 error for modified IP packets. 29 30 Version 1.7: January 9, 1996 (cjm) 31 Differential checksum computation for change 32 in IP packet length. 33 34 Version 2.1: May, 1997 (cjm) 35 Very minor changes to conform with 36 local/global/function naming conventions 37 within the packet aliasing module. 38 39 Version 3.1: May, 2000 (eds) 40 Add support for passive mode, alias the 227 replies. 41 42 See HISTORY file for record of revisions. 43 44 $FreeBSD$ 45 */ 46 47 /* Includes */ 48 #include <ctype.h> 49 #include <stdio.h> 50 #include <string.h> 51 #include <sys/types.h> 52 #include <netinet/in_systm.h> 53 #include <netinet/in.h> 54 #include <netinet/ip.h> 55 #include <netinet/tcp.h> 56 57 #include "alias_local.h" 58 59 #define FTP_CONTROL_PORT_NUMBER 21 60 #define MAX_MESSAGE_SIZE 128 61 62 /* FTP protocol flags. */ 63 #define WAIT_CRLF 0x01 64 65 enum ftp_message_type { 66 FTP_PORT_COMMAND, 67 FTP_EPRT_COMMAND, 68 FTP_227_REPLY, 69 FTP_229_REPLY, 70 FTP_UNKNOWN_MESSAGE 71 }; 72 73 static int ParseFtpPortCommand(char *, int); 74 static int ParseFtpEprtCommand(char *, int); 75 static int ParseFtp227Reply(char *, int); 76 static int ParseFtp229Reply(char *, int); 77 static void NewFtpMessage(struct ip *, struct alias_link *, int, int); 78 79 static struct in_addr true_addr; /* in network byte order. */ 80 static u_short true_port; /* in host byte order. */ 81 82 void 83 AliasHandleFtpOut( 84 struct ip *pip, /* IP packet to examine/patch */ 85 struct alias_link *link, /* The link to go through (aliased port) */ 86 int maxpacketsize /* The maximum size this packet can grow to (including headers) */) 87 { 88 int hlen, tlen, dlen, pflags; 89 char *sptr; 90 struct tcphdr *tc; 91 int ftp_message_type; 92 93 /* Calculate data length of TCP packet */ 94 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 95 hlen = (pip->ip_hl + tc->th_off) << 2; 96 tlen = ntohs(pip->ip_len); 97 dlen = tlen - hlen; 98 99 /* Place string pointer and beginning of data */ 100 sptr = (char *) pip; 101 sptr += hlen; 102 103 /* 104 * Check that data length is not too long and previous message was 105 * properly terminated with CRLF. 106 */ 107 pflags = GetProtocolFlags(link); 108 if (dlen <= MAX_MESSAGE_SIZE && (pflags & WAIT_CRLF)) { 109 ftp_message_type = FTP_UNKNOWN_MESSAGE; 110 111 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) { 112 /* 113 * When aliasing a client, check for the PORT/EPRT command. 114 */ 115 if (ParseFtpPortCommand(sptr, dlen)) 116 ftp_message_type = FTP_PORT_COMMAND; 117 else if (ParseFtpEprtCommand(sptr, dlen)) 118 ftp_message_type = FTP_EPRT_COMMAND; 119 } else { 120 /* 121 * When aliasing a server, check for the 227/229 reply. 122 */ 123 if (ParseFtp227Reply(sptr, dlen)) 124 ftp_message_type = FTP_227_REPLY; 125 else if (ParseFtp229Reply(sptr, dlen)) 126 ftp_message_type = FTP_229_REPLY; 127 } 128 129 if (ftp_message_type != FTP_UNKNOWN_MESSAGE) 130 NewFtpMessage(pip, link, maxpacketsize, ftp_message_type); 131 } 132 133 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */ 134 135 if (dlen) { /* only if there's data */ 136 sptr = (char *) pip; /* start over at beginning */ 137 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */ 138 if (sptr[tlen-2] == '\r' && sptr[tlen-1] == '\n') 139 pflags &= ~WAIT_CRLF; 140 else 141 pflags |= WAIT_CRLF; 142 SetProtocolFlags(link, pflags); 143 } 144 } 145 146 static int 147 ParseFtpPortCommand(char *sptr, int dlen) 148 { 149 char ch; 150 int i, state; 151 u_int32_t addr; 152 u_short port; 153 u_int8_t octet; 154 155 /* Format: "PORT A,D,D,R,PO,RT". */ 156 157 /* Return if data length is too short. */ 158 if (dlen < 18) 159 return 0; 160 161 addr = port = octet = 0; 162 state = -4; 163 for (i = 0; i < dlen; i++) { 164 ch = sptr[i]; 165 switch (state) { 166 case -4: if (ch == 'P') state++; else return 0; break; 167 case -3: if (ch == 'O') state++; else return 0; break; 168 case -2: if (ch == 'R') state++; else return 0; break; 169 case -1: if (ch == 'T') state++; else return 0; break; 170 171 case 0: 172 if (isspace(ch)) 173 break; 174 else 175 state++; 176 case 1: case 3: case 5: case 7: case 9: case 11: 177 if (isdigit(ch)) { 178 octet = ch - '0'; 179 state++; 180 } else 181 return 0; 182 break; 183 case 2: case 4: case 6: case 8: 184 if (isdigit(ch)) 185 octet = 10 * octet + ch - '0'; 186 else if (ch == ',') { 187 addr = (addr << 8) + octet; 188 state++; 189 } else 190 return 0; 191 break; 192 case 10: case 12: 193 if (isdigit(ch)) 194 octet = 10 * octet + ch - '0'; 195 else if (ch == ',' || state == 12) { 196 port = (port << 8) + octet; 197 state++; 198 } else 199 return 0; 200 break; 201 } 202 } 203 204 if (state == 13) { 205 true_addr.s_addr = htonl(addr); 206 true_port = port; 207 return 1; 208 } else 209 return 0; 210 } 211 212 static int 213 ParseFtpEprtCommand(char *sptr, int dlen) 214 { 215 char ch, delim; 216 int i, state; 217 u_int32_t addr; 218 u_short port; 219 u_int8_t octet; 220 221 /* Format: "EPRT |1|A.D.D.R|PORT|". */ 222 223 /* Return if data length is too short. */ 224 if (dlen < 18) 225 return 0; 226 227 addr = port = octet = 0; 228 delim = '|'; /* XXX gcc -Wuninitialized */ 229 state = -4; 230 for (i = 0; i < dlen; i++) { 231 ch = sptr[i]; 232 switch (state) 233 { 234 case -4: if (ch == 'E') state++; else return 0; break; 235 case -3: if (ch == 'P') state++; else return 0; break; 236 case -2: if (ch == 'R') state++; else return 0; break; 237 case -1: if (ch == 'T') state++; else return 0; break; 238 239 case 0: 240 if (!isspace(ch)) { 241 delim = ch; 242 state++; 243 } 244 break; 245 case 1: 246 if (ch == '1') /* IPv4 address */ 247 state++; 248 else 249 return 0; 250 break; 251 case 2: 252 if (ch == delim) 253 state++; 254 else 255 return 0; 256 break; 257 case 3: case 5: case 7: case 9: 258 if (isdigit(ch)) { 259 octet = ch - '0'; 260 state++; 261 } else 262 return 0; 263 break; 264 case 4: case 6: case 8: case 10: 265 if (isdigit(ch)) 266 octet = 10 * octet + ch - '0'; 267 else if (ch == '.' || state == 10) { 268 addr = (addr << 8) + octet; 269 state++; 270 } else 271 return 0; 272 break; 273 case 11: 274 if (isdigit(ch)) { 275 port = ch - '0'; 276 state++; 277 } else 278 return 0; 279 break; 280 case 12: 281 if (isdigit(ch)) 282 port = 10 * port + ch - '0'; 283 else if (ch == delim) 284 state++; 285 else 286 return 0; 287 break; 288 } 289 } 290 291 if (state == 13) { 292 true_addr.s_addr = htonl(addr); 293 true_port = port; 294 return 1; 295 } else 296 return 0; 297 } 298 299 static int 300 ParseFtp227Reply(char *sptr, int dlen) 301 { 302 char ch; 303 int i, state; 304 u_int32_t addr; 305 u_short port; 306 u_int8_t octet; 307 308 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */ 309 310 /* Return if data length is too short. */ 311 if (dlen < 17) 312 return 0; 313 314 addr = port = octet = 0; 315 316 state = -3; 317 for (i = 0; i < dlen; i++) { 318 ch = sptr[i]; 319 switch (state) 320 { 321 case -3: if (ch == '2') state++; else return 0; break; 322 case -2: if (ch == '2') state++; else return 0; break; 323 case -1: if (ch == '7') state++; else return 0; break; 324 325 case 0: 326 if (ch == '(') 327 state++; 328 break; 329 case 1: case 3: case 5: case 7: case 9: case 11: 330 if (isdigit(ch)) { 331 octet = ch - '0'; 332 state++; 333 } else 334 return 0; 335 break; 336 case 2: case 4: case 6: case 8: 337 if (isdigit(ch)) 338 octet = 10 * octet + ch - '0'; 339 else if (ch == ',') { 340 addr = (addr << 8) + octet; 341 state++; 342 } else 343 return 0; 344 break; 345 case 10: case 12: 346 if (isdigit(ch)) 347 octet = 10 * octet + ch - '0'; 348 else if (ch == ',' || (state == 12 && ch == ')')) { 349 port = (port << 8) + octet; 350 state++; 351 } else 352 return 0; 353 break; 354 } 355 } 356 357 if (state == 13) { 358 true_port = port; 359 true_addr.s_addr = htonl(addr); 360 return 1; 361 } else 362 return 0; 363 } 364 365 static int 366 ParseFtp229Reply(char *sptr, int dlen) 367 { 368 char ch, delim; 369 int i, state; 370 u_short port; 371 372 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */ 373 374 /* Return if data length is too short. */ 375 if (dlen < 11) 376 return 0; 377 378 port = 0; 379 delim = '|'; /* XXX gcc -Wuninitialized */ 380 381 state = -3; 382 for (i = 0; i < dlen; i++) { 383 ch = sptr[i]; 384 switch (state) 385 { 386 case -3: if (ch == '2') state++; else return 0; break; 387 case -2: if (ch == '2') state++; else return 0; break; 388 case -1: if (ch == '9') state++; else return 0; break; 389 390 case 0: 391 if (ch == '(') 392 state++; 393 break; 394 case 1: 395 delim = ch; 396 state++; 397 break; 398 case 2: case 3: 399 if (ch == delim) 400 state++; 401 else 402 return 0; 403 break; 404 case 4: 405 if (isdigit(ch)) { 406 port = ch - '0'; 407 state++; 408 } else 409 return 0; 410 break; 411 case 5: 412 if (isdigit(ch)) 413 port = 10 * port + ch - '0'; 414 else if (ch == delim) 415 state++; 416 else 417 return 0; 418 break; 419 case 6: 420 if (ch == ')') 421 state++; 422 else 423 return 0; 424 break; 425 } 426 } 427 428 if (state == 7) { 429 true_port = port; 430 return 1; 431 } else 432 return 0; 433 } 434 435 static void 436 NewFtpMessage(struct ip *pip, 437 struct alias_link *link, 438 int maxpacketsize, 439 int ftp_message_type) 440 { 441 struct alias_link *ftp_link; 442 443 /* Security checks. */ 444 if (ftp_message_type != FTP_229_REPLY && 445 pip->ip_src.s_addr != true_addr.s_addr) 446 return; 447 448 if (true_port < IPPORT_RESERVED) 449 return; 450 451 /* Establish link to address and port found in FTP control message. */ 452 ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link), 453 htons(true_port), 0, IPPROTO_TCP, 1); 454 455 if (ftp_link != NULL) 456 { 457 int slen, hlen, tlen, dlen; 458 struct tcphdr *tc; 459 460 #ifndef NO_FW_PUNCH 461 if (ftp_message_type == FTP_PORT_COMMAND || 462 ftp_message_type == FTP_EPRT_COMMAND) { 463 /* Punch hole in firewall */ 464 PunchFWHole(ftp_link); 465 } 466 #endif 467 468 /* Calculate data length of TCP packet */ 469 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 470 hlen = (pip->ip_hl + tc->th_off) << 2; 471 tlen = ntohs(pip->ip_len); 472 dlen = tlen - hlen; 473 474 /* Create new FTP message. */ 475 { 476 char stemp[MAX_MESSAGE_SIZE + 1]; 477 char *sptr; 478 u_short alias_port; 479 u_char *ptr; 480 int a1, a2, a3, a4, p1, p2; 481 struct in_addr alias_address; 482 483 /* Decompose alias address into quad format */ 484 alias_address = GetAliasAddress(link); 485 ptr = (u_char *) &alias_address.s_addr; 486 a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr; 487 488 alias_port = GetAliasPort(ftp_link); 489 490 switch (ftp_message_type) 491 { 492 case FTP_PORT_COMMAND: 493 case FTP_227_REPLY: 494 /* Decompose alias port into pair format. */ 495 ptr = (char *) &alias_port; 496 p1 = *ptr++; p2=*ptr; 497 498 if (ftp_message_type == FTP_PORT_COMMAND) { 499 /* Generate PORT command string. */ 500 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n", 501 a1,a2,a3,a4,p1,p2); 502 } else { 503 /* Generate 227 reply string. */ 504 sprintf(stemp, 505 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", 506 a1,a2,a3,a4,p1,p2); 507 } 508 break; 509 case FTP_EPRT_COMMAND: 510 /* Generate EPRT command string. */ 511 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n", 512 a1,a2,a3,a4,ntohs(alias_port)); 513 break; 514 case FTP_229_REPLY: 515 /* Generate 229 reply string. */ 516 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n", 517 ntohs(alias_port)); 518 break; 519 } 520 521 /* Save string length for IP header modification */ 522 slen = strlen(stemp); 523 524 /* Copy modified buffer into IP packet. */ 525 sptr = (char *) pip; sptr += hlen; 526 strncpy(sptr, stemp, maxpacketsize-hlen); 527 } 528 529 /* Save information regarding modified seq and ack numbers */ 530 { 531 int delta; 532 533 SetAckModified(link); 534 delta = GetDeltaSeqOut(pip, link); 535 AddSeq(pip, link, delta+slen-dlen); 536 } 537 538 /* Revise IP header */ 539 { 540 u_short new_len; 541 542 new_len = htons(hlen + slen); 543 DifferentialChecksum(&pip->ip_sum, 544 &new_len, 545 &pip->ip_len, 546 1); 547 pip->ip_len = new_len; 548 } 549 550 /* Compute TCP checksum for revised packet */ 551 tc->th_sum = 0; 552 tc->th_sum = TcpChecksum(pip); 553 } 554 else 555 { 556 #ifdef DEBUG 557 fprintf(stderr, 558 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n"); 559 #endif 560 } 561 } 562