xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision 9849949cae0603df0485a0be8a3f80fb8f68f304)
1 /*-
2  * Copyright (c) 2001 Charles Mott <cmott@scientech.com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 /*
30     Alias_ftp.c performs special processing for FTP sessions under
31     TCP.  Specifically, when a PORT/EPRT command from the client
32     side or 227/229 reply from the server is sent, it is intercepted
33     and modified.  The address is changed to the gateway machine
34     and an aliasing port is used.
35 
36     For this routine to work, the message must fit entirely into a
37     single TCP packet.  This is typically the case, but exceptions
38     can easily be envisioned under the actual specifications.
39 
40     Probably the most troubling aspect of the approach taken here is
41     that the new message will typically be a different length, and
42     this causes a certain amount of bookkeeping to keep track of the
43     changes of sequence and acknowledgment numbers, since the client
44     machine is totally unaware of the modification to the TCP stream.
45 
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 #include <ctype.h>
72 #include <stdio.h>
73 #include <string.h>
74 #include <sys/types.h>
75 #include <netinet/in_systm.h>
76 #include <netinet/in.h>
77 #include <netinet/ip.h>
78 #include <netinet/tcp.h>
79 
80 #include "alias_local.h"
81 
82 #define FTP_CONTROL_PORT_NUMBER 21
83 #define MAX_MESSAGE_SIZE	128
84 
85 /* FTP protocol flags. */
86 #define WAIT_CRLF		0x01
87 
88 enum ftp_message_type {
89     FTP_PORT_COMMAND,
90     FTP_EPRT_COMMAND,
91     FTP_227_REPLY,
92     FTP_229_REPLY,
93     FTP_UNKNOWN_MESSAGE
94 };
95 
96 static int ParseFtpPortCommand(char *, int);
97 static int ParseFtpEprtCommand(char *, int);
98 static int ParseFtp227Reply(char *, int);
99 static int ParseFtp229Reply(char *, int);
100 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
101 
102 static struct in_addr true_addr;	/* in network byte order. */
103 static u_short true_port;		/* in host byte order. */
104 
105 void
106 AliasHandleFtpOut(
107 struct ip *pip,	  /* IP packet to examine/patch */
108 struct alias_link *link, /* The link to go through (aliased port) */
109 int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
110 {
111     int hlen, tlen, dlen, pflags;
112     char *sptr;
113     struct tcphdr *tc;
114     int ftp_message_type;
115 
116 /* Calculate data length of TCP packet */
117     tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
118     hlen = (pip->ip_hl + tc->th_off) << 2;
119     tlen = ntohs(pip->ip_len);
120     dlen = tlen - hlen;
121 
122 /* Place string pointer and beginning of data */
123     sptr = (char *) pip;
124     sptr += hlen;
125 
126 /*
127  * Check that data length is not too long and previous message was
128  * properly terminated with CRLF.
129  */
130     pflags = GetProtocolFlags(link);
131     if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
132 	ftp_message_type = FTP_UNKNOWN_MESSAGE;
133 
134 	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
135 /*
136  * When aliasing a client, check for the PORT/EPRT command.
137  */
138 	    if (ParseFtpPortCommand(sptr, dlen))
139 		ftp_message_type = FTP_PORT_COMMAND;
140 	    else if (ParseFtpEprtCommand(sptr, dlen))
141 		ftp_message_type = FTP_EPRT_COMMAND;
142 	} else {
143 /*
144  * When aliasing a server, check for the 227/229 reply.
145  */
146 	    if (ParseFtp227Reply(sptr, dlen))
147 		ftp_message_type = FTP_227_REPLY;
148 	    else if (ParseFtp229Reply(sptr, dlen))
149 		ftp_message_type = FTP_229_REPLY;
150 	}
151 
152 	if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
153 	    NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
154     }
155 
156 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
157 
158     if (dlen) {                  /* only if there's data */
159       sptr = (char *) pip; 	 /* start over at beginning */
160       tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
161       if (sptr[tlen-2] == '\r' && sptr[tlen-1] == '\n')
162 	pflags &= ~WAIT_CRLF;
163       else
164 	pflags |= WAIT_CRLF;
165       SetProtocolFlags(link, pflags);
166     }
167 }
168 
169 static int
170 ParseFtpPortCommand(char *sptr, int dlen)
171 {
172     char ch;
173     int i, state;
174     u_int32_t addr;
175     u_short port;
176     u_int8_t octet;
177 
178     /* Format: "PORT A,D,D,R,PO,RT". */
179 
180     /* Return if data length is too short. */
181     if (dlen < 18)
182 	return 0;
183 
184     addr = port = octet = 0;
185     state = -4;
186     for (i = 0; i < dlen; i++) {
187 	ch = sptr[i];
188 	switch (state) {
189 	case -4: if (ch == 'P') state++; else return 0; break;
190 	case -3: if (ch == 'O') state++; else return 0; break;
191 	case -2: if (ch == 'R') state++; else return 0; break;
192 	case -1: if (ch == 'T') state++; else return 0; break;
193 
194 	case 0:
195 	    if (isspace(ch))
196 		break;
197 	    else
198 		state++;
199 	case 1: case 3: case 5: case 7: case 9: case 11:
200 	    if (isdigit(ch)) {
201 		octet = ch - '0';
202 		state++;
203 	    } else
204 		return 0;
205 	    break;
206 	case 2: case 4: case 6: case 8:
207 	    if (isdigit(ch))
208 		octet = 10 * octet + ch - '0';
209             else if (ch == ',') {
210 		addr = (addr << 8) + octet;
211 		state++;
212 	    } else
213 		return 0;
214 	    break;
215 	case 10: case 12:
216 	    if (isdigit(ch))
217 		octet = 10 * octet + ch - '0';
218 	    else if (ch == ',' || state == 12) {
219 		port = (port << 8) + octet;
220 		state++;
221 	    } else
222 		return 0;
223 	    break;
224 	}
225     }
226 
227     if (state == 13) {
228 	true_addr.s_addr = htonl(addr);
229 	true_port = port;
230 	return 1;
231     } else
232 	return 0;
233 }
234 
235 static int
236 ParseFtpEprtCommand(char *sptr, int dlen)
237 {
238     char ch, delim;
239     int i, state;
240     u_int32_t addr;
241     u_short port;
242     u_int8_t octet;
243 
244     /* Format: "EPRT |1|A.D.D.R|PORT|". */
245 
246     /* Return if data length is too short. */
247     if (dlen < 18)
248 	return 0;
249 
250     addr = port = octet = 0;
251     delim = '|';			/* XXX gcc -Wuninitialized */
252     state = -4;
253     for (i = 0; i < dlen; i++) {
254 	ch = sptr[i];
255 	switch (state)
256 	{
257 	case -4: if (ch == 'E') state++; else return 0; break;
258 	case -3: if (ch == 'P') state++; else return 0; break;
259 	case -2: if (ch == 'R') state++; else return 0; break;
260 	case -1: if (ch == 'T') state++; else return 0; break;
261 
262 	case 0:
263 	    if (!isspace(ch)) {
264 		delim = ch;
265 		state++;
266 	    }
267 	    break;
268 	case 1:
269 	    if (ch == '1')	/* IPv4 address */
270 		state++;
271 	    else
272 		return 0;
273 	    break;
274 	case 2:
275 	    if (ch == delim)
276 		state++;
277 	    else
278 		return 0;
279 	    break;
280 	case 3: case 5: case 7: case 9:
281 	    if (isdigit(ch)) {
282 		octet = ch - '0';
283 		state++;
284 	    } else
285 		return 0;
286 	    break;
287 	case 4: case 6: case 8: case 10:
288 	    if (isdigit(ch))
289 		octet = 10 * octet + ch - '0';
290             else if (ch == '.' || state == 10) {
291 		addr = (addr << 8) + octet;
292 		state++;
293 	    } else
294 		return 0;
295 	    break;
296 	case 11:
297 	    if (isdigit(ch)) {
298 		port = ch - '0';
299 		state++;
300 	    } else
301 		return 0;
302 	    break;
303 	case 12:
304 	    if (isdigit(ch))
305 		port = 10 * port + ch - '0';
306 	    else if (ch == delim)
307 		state++;
308 	    else
309 		return 0;
310 	    break;
311 	}
312     }
313 
314     if (state == 13) {
315 	true_addr.s_addr = htonl(addr);
316 	true_port = port;
317 	return 1;
318     } else
319 	return 0;
320 }
321 
322 static int
323 ParseFtp227Reply(char *sptr, int dlen)
324 {
325     char ch;
326     int i, state;
327     u_int32_t addr;
328     u_short port;
329     u_int8_t octet;
330 
331     /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
332 
333     /* Return if data length is too short. */
334     if (dlen < 17)
335 	return 0;
336 
337     addr = port = octet = 0;
338 
339     state = -3;
340     for (i = 0; i < dlen; i++) {
341         ch = sptr[i];
342         switch (state)
343         {
344         case -3: if (ch == '2') state++; else return 0; break;
345         case -2: if (ch == '2') state++; else return 0; break;
346         case -1: if (ch == '7') state++; else return 0; break;
347 
348 	case 0:
349 	    if (ch == '(')
350 		state++;
351 	    break;
352 	case 1: case 3: case 5: case 7: case 9: case 11:
353 	    if (isdigit(ch)) {
354 		octet = ch - '0';
355 		state++;
356 	    } else
357 		return 0;
358 	    break;
359 	case 2: case 4: case 6: case 8:
360 	    if (isdigit(ch))
361 		octet = 10 * octet + ch - '0';
362             else if (ch == ',') {
363 		addr = (addr << 8) + octet;
364 		state++;
365 	    } else
366 		return 0;
367 	    break;
368 	case 10: case 12:
369 	    if (isdigit(ch))
370 		octet = 10 * octet + ch - '0';
371 	    else if (ch == ',' || (state == 12 && ch == ')')) {
372 		port = (port << 8) + octet;
373 		state++;
374 	    } else
375 		return 0;
376 	    break;
377 	}
378     }
379 
380     if (state == 13) {
381         true_port = port;
382         true_addr.s_addr = htonl(addr);
383 	return 1;
384     } else
385 	return 0;
386 }
387 
388 static int
389 ParseFtp229Reply(char *sptr, int dlen)
390 {
391     char ch, delim;
392     int i, state;
393     u_short port;
394 
395     /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
396 
397     /* Return if data length is too short. */
398     if (dlen < 11)
399 	return 0;
400 
401     port = 0;
402     delim = '|';			/* XXX gcc -Wuninitialized */
403 
404     state = -3;
405     for (i = 0; i < dlen; i++) {
406 	ch = sptr[i];
407 	switch (state)
408 	{
409 	case -3: if (ch == '2') state++; else return 0; break;
410 	case -2: if (ch == '2') state++; else return 0; break;
411 	case -1: if (ch == '9') state++; else return 0; break;
412 
413 	case 0:
414 	    if (ch == '(')
415 		state++;
416 	    break;
417 	case 1:
418 	    delim = ch;
419 	    state++;
420 	    break;
421 	case 2: case 3:
422 	    if (ch == delim)
423 		state++;
424 	    else
425 		return 0;
426 	    break;
427 	case 4:
428 	    if (isdigit(ch)) {
429 		port = ch - '0';
430 		state++;
431 	    } else
432 		return 0;
433 	    break;
434 	case 5:
435 	    if (isdigit(ch))
436 		port = 10 * port + ch - '0';
437 	    else if (ch == delim)
438 		state++;
439 	    else
440 		return 0;
441 	    break;
442 	case 6:
443 	    if (ch == ')')
444 		state++;
445 	    else
446 		return 0;
447 	    break;
448 	}
449     }
450 
451     if (state == 7) {
452 	true_port = port;
453 	return 1;
454     } else
455 	return 0;
456 }
457 
458 static void
459 NewFtpMessage(struct ip *pip,
460               struct alias_link *link,
461               int maxpacketsize,
462               int ftp_message_type)
463 {
464     struct alias_link *ftp_link;
465 
466 /* Security checks. */
467     if (ftp_message_type != FTP_229_REPLY &&
468 	pip->ip_src.s_addr != true_addr.s_addr)
469 	return;
470 
471     if (true_port < IPPORT_RESERVED)
472 	return;
473 
474 /* Establish link to address and port found in FTP control message. */
475     ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
476                              htons(true_port), 0, IPPROTO_TCP, 1);
477 
478     if (ftp_link != NULL)
479     {
480         int slen, hlen, tlen, dlen;
481         struct tcphdr *tc;
482 
483 #ifndef NO_FW_PUNCH
484 	if (ftp_message_type == FTP_PORT_COMMAND ||
485 	    ftp_message_type == FTP_EPRT_COMMAND) {
486 	    /* Punch hole in firewall */
487 	    PunchFWHole(ftp_link);
488 	}
489 #endif
490 
491 /* Calculate data length of TCP packet */
492         tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
493         hlen = (pip->ip_hl + tc->th_off) << 2;
494         tlen = ntohs(pip->ip_len);
495         dlen = tlen - hlen;
496 
497 /* Create new FTP message. */
498         {
499             char stemp[MAX_MESSAGE_SIZE + 1];
500             char *sptr;
501             u_short alias_port;
502             u_char *ptr;
503             int a1, a2, a3, a4, p1, p2;
504             struct in_addr alias_address;
505 
506 /* Decompose alias address into quad format */
507             alias_address = GetAliasAddress(link);
508             ptr = (u_char *) &alias_address.s_addr;
509             a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
510 
511 	    alias_port = GetAliasPort(ftp_link);
512 
513 	    switch (ftp_message_type)
514 	    {
515 	    case FTP_PORT_COMMAND:
516 	    case FTP_227_REPLY:
517 		/* Decompose alias port into pair format. */
518 		ptr = (char *) &alias_port;
519 		p1 = *ptr++; p2=*ptr;
520 
521 		if (ftp_message_type == FTP_PORT_COMMAND) {
522 		    /* Generate PORT command string. */
523 		    sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
524 			    a1,a2,a3,a4,p1,p2);
525 		} else {
526 		    /* Generate 227 reply string. */
527 		    sprintf(stemp,
528 			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
529 			    a1,a2,a3,a4,p1,p2);
530 		}
531 		break;
532 	    case FTP_EPRT_COMMAND:
533 		/* Generate EPRT command string. */
534 		sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
535 			a1,a2,a3,a4,ntohs(alias_port));
536 		break;
537 	    case FTP_229_REPLY:
538 		/* Generate 229 reply string. */
539 		sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
540 			ntohs(alias_port));
541 		break;
542 	    }
543 
544 /* Save string length for IP header modification */
545             slen = strlen(stemp);
546 
547 /* Copy modified buffer into IP packet. */
548             sptr = (char *) pip; sptr += hlen;
549             strncpy(sptr, stemp, maxpacketsize-hlen);
550         }
551 
552 /* Save information regarding modified seq and ack numbers */
553         {
554             int delta;
555 
556             SetAckModified(link);
557             delta = GetDeltaSeqOut(pip, link);
558             AddSeq(pip, link, delta+slen-dlen);
559         }
560 
561 /* Revise IP header */
562         {
563             u_short new_len;
564 
565             new_len = htons(hlen + slen);
566             DifferentialChecksum(&pip->ip_sum,
567                                  &new_len,
568                                  &pip->ip_len,
569                                  1);
570             pip->ip_len = new_len;
571         }
572 
573 /* Compute TCP checksum for revised packet */
574         tc->th_sum = 0;
575         tc->th_sum = TcpChecksum(pip);
576     }
577     else
578     {
579 #ifdef DEBUG
580         fprintf(stderr,
581         "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
582 #endif
583     }
584 }
585