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 enum ftp_message_type { 63 FTP_PORT_COMMAND, 64 FTP_EPRT_COMMAND, 65 FTP_227_REPLY, 66 FTP_229_REPLY, 67 FTP_UNKNOWN_MESSAGE 68 }; 69 70 static int ParseFtpPortCommand(char *, int); 71 static int ParseFtpEprtCommand(char *, int); 72 static int ParseFtp227Reply(char *, int); 73 static int ParseFtp229Reply(char *, int); 74 static void NewFtpMessage(struct ip *, struct alias_link *, int, int); 75 76 static struct in_addr true_addr; /* in network byte order. */ 77 static u_short true_port; /* in host byte order. */ 78 79 void 80 AliasHandleFtpOut( 81 struct ip *pip, /* IP packet to examine/patch */ 82 struct alias_link *link, /* The link to go through (aliased port) */ 83 int maxpacketsize /* The maximum size this packet can grow to (including headers) */) 84 { 85 int hlen, tlen, dlen; 86 char *sptr; 87 struct tcphdr *tc; 88 int ftp_message_type; 89 90 /* Calculate data length of TCP packet */ 91 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 92 hlen = (pip->ip_hl + tc->th_off) << 2; 93 tlen = ntohs(pip->ip_len); 94 dlen = tlen - hlen; 95 96 /* Place string pointer and beginning of data */ 97 sptr = (char *) pip; 98 sptr += hlen; 99 100 /* 101 * Check that data length is not too long and previous message was 102 * properly terminated with CRLF. 103 */ 104 if (dlen <= MAX_MESSAGE_SIZE && GetLastLineCrlfTermed(link)) { 105 ftp_message_type = FTP_UNKNOWN_MESSAGE; 106 107 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) { 108 /* 109 * When aliasing a client, check for the PORT/EPRT command. 110 */ 111 if (ParseFtpPortCommand(sptr, dlen)) 112 ftp_message_type = FTP_PORT_COMMAND; 113 else if (ParseFtpEprtCommand(sptr, dlen)) 114 ftp_message_type = FTP_EPRT_COMMAND; 115 } else { 116 /* 117 * When aliasing a server, check for the 227/229 reply. 118 */ 119 if (ParseFtp227Reply(sptr, dlen)) 120 ftp_message_type = FTP_227_REPLY; 121 else if (ParseFtp229Reply(sptr, dlen)) 122 ftp_message_type = FTP_229_REPLY; 123 } 124 125 if (ftp_message_type != FTP_UNKNOWN_MESSAGE) 126 NewFtpMessage(pip, link, maxpacketsize, ftp_message_type); 127 } 128 129 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */ 130 131 if (dlen) { /* only if there's data */ 132 sptr = (char *) pip; /* start over at beginning */ 133 tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */ 134 SetLastLineCrlfTermed(link, 135 (sptr[tlen-2] == '\r') && (sptr[tlen-1] == '\n')); 136 } 137 } 138 139 static int 140 ParseFtpPortCommand(char *sptr, int dlen) 141 { 142 char ch; 143 int i, state; 144 u_int32_t addr; 145 u_short port; 146 u_int8_t octet; 147 148 /* Format: "PORT A,D,D,R,PO,RT". */ 149 150 /* Return if data length is too short. */ 151 if (dlen < 18) 152 return 0; 153 154 addr = port = octet = 0; 155 state = -4; 156 for (i = 0; i < dlen; i++) { 157 ch = sptr[i]; 158 switch (state) { 159 case -4: if (ch == 'P') state++; else return 0; break; 160 case -3: if (ch == 'O') state++; else return 0; break; 161 case -2: if (ch == 'R') state++; else return 0; break; 162 case -1: if (ch == 'T') state++; else return 0; break; 163 164 case 0: 165 if (isspace(ch)) 166 break; 167 else 168 state++; 169 case 1: case 3: case 5: case 7: case 9: case 11: 170 if (isdigit(ch)) { 171 octet = ch - '0'; 172 state++; 173 } else 174 return 0; 175 break; 176 case 2: case 4: case 6: case 8: 177 if (isdigit(ch)) 178 octet = 10 * octet + ch - '0'; 179 else if (ch == ',') { 180 addr = (addr << 8) + octet; 181 state++; 182 } else 183 return 0; 184 break; 185 case 10: case 12: 186 if (isdigit(ch)) 187 octet = 10 * octet + ch - '0'; 188 else if (ch == ',' || state == 12) { 189 port = (port << 8) + octet; 190 state++; 191 } else 192 return 0; 193 break; 194 } 195 } 196 197 if (state == 13) { 198 true_addr.s_addr = htonl(addr); 199 true_port = port; 200 return 1; 201 } else 202 return 0; 203 } 204 205 static int 206 ParseFtpEprtCommand(char *sptr, int dlen) 207 { 208 char ch, delim; 209 int i, state; 210 u_int32_t addr; 211 u_short port; 212 u_int8_t octet; 213 214 /* Format: "EPRT |1|A.D.D.R|PORT|". */ 215 216 /* Return if data length is too short. */ 217 if (dlen < 18) 218 return 0; 219 220 addr = port = octet = 0; 221 delim = '|'; /* XXX gcc -Wuninitialized */ 222 state = -4; 223 for (i = 0; i < dlen; i++) { 224 ch = sptr[i]; 225 switch (state) 226 { 227 case -4: if (ch == 'E') state++; else return 0; break; 228 case -3: if (ch == 'P') state++; else return 0; break; 229 case -2: if (ch == 'R') state++; else return 0; break; 230 case -1: if (ch == 'T') state++; else return 0; break; 231 232 case 0: 233 if (!isspace(ch)) { 234 delim = ch; 235 state++; 236 } 237 break; 238 case 1: 239 if (ch == '1') /* IPv4 address */ 240 state++; 241 else 242 return 0; 243 break; 244 case 2: 245 if (ch == delim) 246 state++; 247 else 248 return 0; 249 break; 250 case 3: case 5: case 7: case 9: 251 if (isdigit(ch)) { 252 octet = ch - '0'; 253 state++; 254 } else 255 return 0; 256 break; 257 case 4: case 6: case 8: case 10: 258 if (isdigit(ch)) 259 octet = 10 * octet + ch - '0'; 260 else if (ch == '.' || state == 10) { 261 addr = (addr << 8) + octet; 262 state++; 263 } else 264 return 0; 265 break; 266 case 11: 267 if (isdigit(ch)) { 268 port = ch - '0'; 269 state++; 270 } else 271 return 0; 272 break; 273 case 12: 274 if (isdigit(ch)) 275 port = 10 * port + ch - '0'; 276 else if (ch == delim) 277 state++; 278 else 279 return 0; 280 break; 281 } 282 } 283 284 if (state == 13) { 285 true_addr.s_addr = htonl(addr); 286 true_port = port; 287 return 1; 288 } else 289 return 0; 290 } 291 292 static int 293 ParseFtp227Reply(char *sptr, int dlen) 294 { 295 char ch; 296 int i, state; 297 u_int32_t addr; 298 u_short port; 299 u_int8_t octet; 300 301 /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */ 302 303 /* Return if data length is too short. */ 304 if (dlen < 17) 305 return 0; 306 307 addr = port = octet = 0; 308 309 state = -3; 310 for (i = 0; i < dlen; i++) { 311 ch = sptr[i]; 312 switch (state) 313 { 314 case -3: if (ch == '2') state++; else return 0; break; 315 case -2: if (ch == '2') state++; else return 0; break; 316 case -1: if (ch == '7') state++; else return 0; break; 317 318 case 0: 319 if (ch == '(') 320 state++; 321 break; 322 case 1: case 3: case 5: case 7: case 9: case 11: 323 if (isdigit(ch)) { 324 octet = ch - '0'; 325 state++; 326 } else 327 return 0; 328 break; 329 case 2: case 4: case 6: case 8: 330 if (isdigit(ch)) 331 octet = 10 * octet + ch - '0'; 332 else if (ch == ',') { 333 addr = (addr << 8) + octet; 334 state++; 335 } else 336 return 0; 337 break; 338 case 10: case 12: 339 if (isdigit(ch)) 340 octet = 10 * octet + ch - '0'; 341 else if (ch == ',' || (state == 12 && ch == ')')) { 342 port = (port << 8) + octet; 343 state++; 344 } else 345 return 0; 346 break; 347 } 348 } 349 350 if (state == 13) { 351 true_port = port; 352 true_addr.s_addr = htonl(addr); 353 return 1; 354 } else 355 return 0; 356 } 357 358 static int 359 ParseFtp229Reply(char *sptr, int dlen) 360 { 361 char ch, delim; 362 int i, state; 363 u_short port; 364 365 /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */ 366 367 /* Return if data length is too short. */ 368 if (dlen < 11) 369 return 0; 370 371 port = 0; 372 delim = '|'; /* XXX gcc -Wuninitialized */ 373 374 state = -3; 375 for (i = 0; i < dlen; i++) { 376 ch = sptr[i]; 377 switch (state) 378 { 379 case -3: if (ch == '2') state++; else return 0; break; 380 case -2: if (ch == '2') state++; else return 0; break; 381 case -1: if (ch == '9') state++; else return 0; break; 382 383 case 0: 384 if (ch == '(') 385 state++; 386 break; 387 case 1: 388 delim = ch; 389 state++; 390 break; 391 case 2: case 3: 392 if (ch == delim) 393 state++; 394 else 395 return 0; 396 break; 397 case 4: 398 if (isdigit(ch)) { 399 port = ch - '0'; 400 state++; 401 } else 402 return 0; 403 break; 404 case 5: 405 if (isdigit(ch)) 406 port = 10 * port + ch - '0'; 407 else if (ch == delim) 408 state++; 409 else 410 return 0; 411 break; 412 case 6: 413 if (ch == ')') 414 state++; 415 else 416 return 0; 417 break; 418 } 419 } 420 421 if (state == 7) { 422 true_port = port; 423 return 1; 424 } else 425 return 0; 426 } 427 428 static void 429 NewFtpMessage(struct ip *pip, 430 struct alias_link *link, 431 int maxpacketsize, 432 int ftp_message_type) 433 { 434 struct alias_link *ftp_link; 435 436 /* Security checks. */ 437 if (ftp_message_type != FTP_229_REPLY && 438 pip->ip_src.s_addr != true_addr.s_addr) 439 return; 440 441 if (true_port < IPPORT_RESERVED) 442 return; 443 444 /* Establish link to address and port found in FTP control message. */ 445 ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link), 446 htons(true_port), 0, IPPROTO_TCP, 1); 447 448 if (ftp_link != NULL) 449 { 450 int slen, hlen, tlen, dlen; 451 struct tcphdr *tc; 452 453 #ifndef NO_FW_PUNCH 454 if (ftp_message_type == FTP_PORT_COMMAND || 455 ftp_message_type == FTP_EPRT_COMMAND) { 456 /* Punch hole in firewall */ 457 PunchFWHole(ftp_link); 458 } 459 #endif 460 461 /* Calculate data length of TCP packet */ 462 tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2)); 463 hlen = (pip->ip_hl + tc->th_off) << 2; 464 tlen = ntohs(pip->ip_len); 465 dlen = tlen - hlen; 466 467 /* Create new FTP message. */ 468 { 469 char stemp[MAX_MESSAGE_SIZE + 1]; 470 char *sptr; 471 u_short alias_port; 472 u_char *ptr; 473 int a1, a2, a3, a4, p1, p2; 474 struct in_addr alias_address; 475 476 /* Decompose alias address into quad format */ 477 alias_address = GetAliasAddress(link); 478 ptr = (u_char *) &alias_address.s_addr; 479 a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr; 480 481 alias_port = GetAliasPort(ftp_link); 482 483 switch (ftp_message_type) 484 { 485 case FTP_PORT_COMMAND: 486 case FTP_227_REPLY: 487 /* Decompose alias port into pair format. */ 488 ptr = (char *) &alias_port; 489 p1 = *ptr++; p2=*ptr; 490 491 if (ftp_message_type == FTP_PORT_COMMAND) { 492 /* Generate PORT command string. */ 493 sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n", 494 a1,a2,a3,a4,p1,p2); 495 } else { 496 /* Generate 227 reply string. */ 497 sprintf(stemp, 498 "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", 499 a1,a2,a3,a4,p1,p2); 500 } 501 break; 502 case FTP_EPRT_COMMAND: 503 /* Generate EPRT command string. */ 504 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n", 505 a1,a2,a3,a4,ntohs(alias_port)); 506 break; 507 case FTP_229_REPLY: 508 /* Generate 229 reply string. */ 509 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n", 510 ntohs(alias_port)); 511 break; 512 } 513 514 /* Save string length for IP header modification */ 515 slen = strlen(stemp); 516 517 /* Copy modified buffer into IP packet. */ 518 sptr = (char *) pip; sptr += hlen; 519 strncpy(sptr, stemp, maxpacketsize-hlen); 520 } 521 522 /* Save information regarding modified seq and ack numbers */ 523 { 524 int delta; 525 526 SetAckModified(link); 527 delta = GetDeltaSeqOut(pip, link); 528 AddSeq(pip, link, delta+slen-dlen); 529 } 530 531 /* Revise IP header */ 532 { 533 u_short new_len; 534 535 new_len = htons(hlen + slen); 536 DifferentialChecksum(&pip->ip_sum, 537 &new_len, 538 &pip->ip_len, 539 1); 540 pip->ip_len = new_len; 541 } 542 543 /* Compute TCP checksum for revised packet */ 544 tc->th_sum = 0; 545 tc->th_sum = TcpChecksum(pip); 546 } 547 else 548 { 549 #ifdef DEBUG 550 fprintf(stderr, 551 "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n"); 552 #endif 553 } 554 } 555