xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision f311ebb4)
1 /*-
2  * Copyright (c) 2001 Charles Mott <cm@linktel.net>
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 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
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 
48     References: RFC 959, RFC 2428.
49 
50     Initial version:  August, 1996  (cjm)
51 
52     Version 1.6
53 	 Brian Somers and Martin Renters identified an IP checksum
54 	 error for modified IP packets.
55 
56     Version 1.7:  January 9, 1996 (cjm)
57 	 Differential checksum computation for change
58 	 in IP packet length.
59 
60     Version 2.1:  May, 1997 (cjm)
61 	 Very minor changes to conform with
62 	 local/global/function naming conventions
63 	 within the packet aliasing module.
64 
65     Version 3.1:  May, 2000 (eds)
66 	 Add support for passive mode, alias the 227 replies.
67 
68     See HISTORY file for record of revisions.
69 */
70 
71 /* Includes */
72 #include <ctype.h>
73 #include <stdio.h>
74 #include <string.h>
75 #include <sys/types.h>
76 #include <netinet/in_systm.h>
77 #include <netinet/in.h>
78 #include <netinet/ip.h>
79 #include <netinet/tcp.h>
80 
81 #include "alias_local.h"
82 
83 #define FTP_CONTROL_PORT_NUMBER 21
84 #define MAX_MESSAGE_SIZE	128
85 
86 /* FTP protocol flags. */
87 #define WAIT_CRLF		0x01
88 
89 enum ftp_message_type {
90 	FTP_PORT_COMMAND,
91 	FTP_EPRT_COMMAND,
92 	FTP_227_REPLY,
93 	FTP_229_REPLY,
94 	FTP_UNKNOWN_MESSAGE
95 };
96 
97 static int	ParseFtpPortCommand(struct libalias *la, char *, int);
98 static int	ParseFtpEprtCommand(struct libalias *la, char *, int);
99 static int	ParseFtp227Reply(struct libalias *la, char *, int);
100 static int	ParseFtp229Reply(struct libalias *la, char *, int);
101 static void	NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int);
102 
103 void
104 AliasHandleFtpOut(
105     struct libalias *la,
106     struct ip *pip,		/* IP packet to examine/patch */
107     struct alias_link *link,	/* The link to go through (aliased port) */
108     int maxpacketsize		/* The maximum size this packet can grow to
109 	(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(la, sptr, dlen))
139 				ftp_message_type = FTP_PORT_COMMAND;
140 			else if (ParseFtpEprtCommand(la, 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(la, sptr, dlen))
147 				ftp_message_type = FTP_227_REPLY;
148 			else if (ParseFtp229Reply(la, sptr, dlen)) {
149 				ftp_message_type = FTP_229_REPLY;
150 				la->true_addr.s_addr = pip->ip_src.s_addr;
151 			}
152 		}
153 
154 		if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
155 			NewFtpMessage(la, pip, link, maxpacketsize, ftp_message_type);
156 	}
157 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
158 
159 	if (dlen) {		/* only if there's data */
160 		sptr = (char *)pip;	/* start over at beginning */
161 		tlen = ntohs(pip->ip_len);	/* recalc tlen, pkt may
162 						 * have grown */
163 		if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
164 			pflags &= ~WAIT_CRLF;
165 		else
166 			pflags |= WAIT_CRLF;
167 		SetProtocolFlags(link, pflags);
168 	}
169 }
170 
171 static int
172 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen)
173 {
174 	char ch;
175 	int i, state;
176 	u_int32_t addr;
177 	u_short port;
178 	u_int8_t octet;
179 
180 	/* Format: "PORT A,D,D,R,PO,RT". */
181 
182 	/* Return if data length is too short. */
183 	if (dlen < 18)
184 		return 0;
185 
186 	addr = port = octet = 0;
187 	state = -4;
188 	for (i = 0; i < dlen; i++) {
189 		ch = sptr[i];
190 		switch (state) {
191 		case -4:
192 			if (ch == 'P')
193 				state++;
194 			else
195 				return 0;
196 			break;
197 		case -3:
198 			if (ch == 'O')
199 				state++;
200 			else
201 				return 0;
202 			break;
203 		case -2:
204 			if (ch == 'R')
205 				state++;
206 			else
207 				return 0;
208 			break;
209 		case -1:
210 			if (ch == 'T')
211 				state++;
212 			else
213 				return 0;
214 			break;
215 
216 		case 0:
217 			if (isspace(ch))
218 				break;
219 			else
220 				state++;
221 		case 1:
222 		case 3:
223 		case 5:
224 		case 7:
225 		case 9:
226 		case 11:
227 			if (isdigit(ch)) {
228 				octet = ch - '0';
229 				state++;
230 			} else
231 				return 0;
232 			break;
233 		case 2:
234 		case 4:
235 		case 6:
236 		case 8:
237 			if (isdigit(ch))
238 				octet = 10 * octet + ch - '0';
239 			else if (ch == ',') {
240 				addr = (addr << 8) + octet;
241 				state++;
242 			} else
243 				return 0;
244 			break;
245 		case 10:
246 		case 12:
247 			if (isdigit(ch))
248 				octet = 10 * octet + ch - '0';
249 			else if (ch == ',' || state == 12) {
250 				port = (port << 8) + octet;
251 				state++;
252 			} else
253 				return 0;
254 			break;
255 		}
256 	}
257 
258 	if (state == 13) {
259 		la->true_addr.s_addr = htonl(addr);
260 		la->true_port = port;
261 		return 1;
262 	} else
263 		return 0;
264 }
265 
266 static int
267 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen)
268 {
269 	char ch, delim;
270 	int i, state;
271 	u_int32_t addr;
272 	u_short port;
273 	u_int8_t octet;
274 
275 	/* Format: "EPRT |1|A.D.D.R|PORT|". */
276 
277 	/* Return if data length is too short. */
278 	if (dlen < 18)
279 		return 0;
280 
281 	addr = port = octet = 0;
282 	delim = '|';		/* XXX gcc -Wuninitialized */
283 	state = -4;
284 	for (i = 0; i < dlen; i++) {
285 		ch = sptr[i];
286 		switch (state) {
287 		case -4:
288 			if (ch == 'E')
289 				state++;
290 			else
291 				return 0;
292 			break;
293 		case -3:
294 			if (ch == 'P')
295 				state++;
296 			else
297 				return 0;
298 			break;
299 		case -2:
300 			if (ch == 'R')
301 				state++;
302 			else
303 				return 0;
304 			break;
305 		case -1:
306 			if (ch == 'T')
307 				state++;
308 			else
309 				return 0;
310 			break;
311 
312 		case 0:
313 			if (!isspace(ch)) {
314 				delim = ch;
315 				state++;
316 			}
317 			break;
318 		case 1:
319 			if (ch == '1')	/* IPv4 address */
320 				state++;
321 			else
322 				return 0;
323 			break;
324 		case 2:
325 			if (ch == delim)
326 				state++;
327 			else
328 				return 0;
329 			break;
330 		case 3:
331 		case 5:
332 		case 7:
333 		case 9:
334 			if (isdigit(ch)) {
335 				octet = ch - '0';
336 				state++;
337 			} else
338 				return 0;
339 			break;
340 		case 4:
341 		case 6:
342 		case 8:
343 		case 10:
344 			if (isdigit(ch))
345 				octet = 10 * octet + ch - '0';
346 			else if (ch == '.' || state == 10) {
347 				addr = (addr << 8) + octet;
348 				state++;
349 			} else
350 				return 0;
351 			break;
352 		case 11:
353 			if (isdigit(ch)) {
354 				port = ch - '0';
355 				state++;
356 			} else
357 				return 0;
358 			break;
359 		case 12:
360 			if (isdigit(ch))
361 				port = 10 * port + ch - '0';
362 			else if (ch == delim)
363 				state++;
364 			else
365 				return 0;
366 			break;
367 		}
368 	}
369 
370 	if (state == 13) {
371 		la->true_addr.s_addr = htonl(addr);
372 		la->true_port = port;
373 		return 1;
374 	} else
375 		return 0;
376 }
377 
378 static int
379 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen)
380 {
381 	char ch;
382 	int i, state;
383 	u_int32_t addr;
384 	u_short port;
385 	u_int8_t octet;
386 
387 	/* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
388 
389 	/* Return if data length is too short. */
390 	if (dlen < 17)
391 		return 0;
392 
393 	addr = port = octet = 0;
394 
395 	state = -3;
396 	for (i = 0; i < dlen; i++) {
397 		ch = sptr[i];
398 		switch (state) {
399 		case -3:
400 			if (ch == '2')
401 				state++;
402 			else
403 				return 0;
404 			break;
405 		case -2:
406 			if (ch == '2')
407 				state++;
408 			else
409 				return 0;
410 			break;
411 		case -1:
412 			if (ch == '7')
413 				state++;
414 			else
415 				return 0;
416 			break;
417 
418 		case 0:
419 			if (ch == '(')
420 				state++;
421 			break;
422 		case 1:
423 		case 3:
424 		case 5:
425 		case 7:
426 		case 9:
427 		case 11:
428 			if (isdigit(ch)) {
429 				octet = ch - '0';
430 				state++;
431 			} else
432 				return 0;
433 			break;
434 		case 2:
435 		case 4:
436 		case 6:
437 		case 8:
438 			if (isdigit(ch))
439 				octet = 10 * octet + ch - '0';
440 			else if (ch == ',') {
441 				addr = (addr << 8) + octet;
442 				state++;
443 			} else
444 				return 0;
445 			break;
446 		case 10:
447 		case 12:
448 			if (isdigit(ch))
449 				octet = 10 * octet + ch - '0';
450 			else if (ch == ',' || (state == 12 && ch == ')')) {
451 				port = (port << 8) + octet;
452 				state++;
453 			} else
454 				return 0;
455 			break;
456 		}
457 	}
458 
459 	if (state == 13) {
460 		la->true_port = port;
461 		la->true_addr.s_addr = htonl(addr);
462 		return 1;
463 	} else
464 		return 0;
465 }
466 
467 static int
468 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen)
469 {
470 	char ch, delim;
471 	int i, state;
472 	u_short port;
473 
474 	/* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
475 
476 	/* Return if data length is too short. */
477 	if (dlen < 11)
478 		return 0;
479 
480 	port = 0;
481 	delim = '|';		/* XXX gcc -Wuninitialized */
482 
483 	state = -3;
484 	for (i = 0; i < dlen; i++) {
485 		ch = sptr[i];
486 		switch (state) {
487 		case -3:
488 			if (ch == '2')
489 				state++;
490 			else
491 				return 0;
492 			break;
493 		case -2:
494 			if (ch == '2')
495 				state++;
496 			else
497 				return 0;
498 			break;
499 		case -1:
500 			if (ch == '9')
501 				state++;
502 			else
503 				return 0;
504 			break;
505 
506 		case 0:
507 			if (ch == '(')
508 				state++;
509 			break;
510 		case 1:
511 			delim = ch;
512 			state++;
513 			break;
514 		case 2:
515 		case 3:
516 			if (ch == delim)
517 				state++;
518 			else
519 				return 0;
520 			break;
521 		case 4:
522 			if (isdigit(ch)) {
523 				port = ch - '0';
524 				state++;
525 			} else
526 				return 0;
527 			break;
528 		case 5:
529 			if (isdigit(ch))
530 				port = 10 * port + ch - '0';
531 			else if (ch == delim)
532 				state++;
533 			else
534 				return 0;
535 			break;
536 		case 6:
537 			if (ch == ')')
538 				state++;
539 			else
540 				return 0;
541 			break;
542 		}
543 	}
544 
545 	if (state == 7) {
546 		la->true_port = port;
547 		return 1;
548 	} else
549 		return 0;
550 }
551 
552 static void
553 NewFtpMessage(struct libalias *la, struct ip *pip,
554     struct alias_link *link,
555     int maxpacketsize,
556     int ftp_message_type)
557 {
558 	struct alias_link *ftp_link;
559 
560 /* Security checks. */
561 	if (pip->ip_src.s_addr != la->true_addr.s_addr)
562 		return;
563 
564 	if (la->true_port < IPPORT_RESERVED)
565 		return;
566 
567 /* Establish link to address and port found in FTP control message. */
568 	ftp_link = FindUdpTcpOut(la, la->true_addr, GetDestAddress(link),
569 	    htons(la->true_port), 0, IPPROTO_TCP, 1);
570 
571 	if (ftp_link != NULL) {
572 		int slen, hlen, tlen, dlen;
573 		struct tcphdr *tc;
574 
575 #ifndef NO_FW_PUNCH
576 		/* Punch hole in firewall */
577 		PunchFWHole(ftp_link);
578 #endif
579 
580 /* Calculate data length of TCP packet */
581 		tc = (struct tcphdr *)((char *)pip + (pip->ip_hl << 2));
582 		hlen = (pip->ip_hl + tc->th_off) << 2;
583 		tlen = ntohs(pip->ip_len);
584 		dlen = tlen - hlen;
585 
586 /* Create new FTP message. */
587 		{
588 			char stemp[MAX_MESSAGE_SIZE + 1];
589 			char *sptr;
590 			u_short alias_port;
591 			u_char *ptr;
592 			int a1, a2, a3, a4, p1, p2;
593 			struct in_addr alias_address;
594 
595 /* Decompose alias address into quad format */
596 			alias_address = GetAliasAddress(link);
597 			ptr = (u_char *) & alias_address.s_addr;
598 			a1 = *ptr++;
599 			a2 = *ptr++;
600 			a3 = *ptr++;
601 			a4 = *ptr;
602 
603 			alias_port = GetAliasPort(ftp_link);
604 
605 			switch (ftp_message_type) {
606 			case FTP_PORT_COMMAND:
607 			case FTP_227_REPLY:
608 				/* Decompose alias port into pair format. */
609 				ptr = (char *)&alias_port;
610 				p1 = *ptr++;
611 				p2 = *ptr;
612 
613 				if (ftp_message_type == FTP_PORT_COMMAND) {
614 					/* Generate PORT command string. */
615 					sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
616 					    a1, a2, a3, a4, p1, p2);
617 				} else {
618 					/* Generate 227 reply string. */
619 					sprintf(stemp,
620 					    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
621 					    a1, a2, a3, a4, p1, p2);
622 				}
623 				break;
624 			case FTP_EPRT_COMMAND:
625 				/* Generate EPRT command string. */
626 				sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
627 				    a1, a2, a3, a4, ntohs(alias_port));
628 				break;
629 			case FTP_229_REPLY:
630 				/* Generate 229 reply string. */
631 				sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
632 				    ntohs(alias_port));
633 				break;
634 			}
635 
636 /* Save string length for IP header modification */
637 			slen = strlen(stemp);
638 
639 /* Copy modified buffer into IP packet. */
640 			sptr = (char *)pip;
641 			sptr += hlen;
642 			strncpy(sptr, stemp, maxpacketsize - hlen);
643 		}
644 
645 /* Save information regarding modified seq and ack numbers */
646 		{
647 			int delta;
648 
649 			SetAckModified(link);
650 			delta = GetDeltaSeqOut(pip, link);
651 			AddSeq(pip, link, delta + slen - dlen);
652 		}
653 
654 /* Revise IP header */
655 		{
656 			u_short new_len;
657 
658 			new_len = htons(hlen + slen);
659 			DifferentialChecksum(&pip->ip_sum,
660 			    &new_len,
661 			    &pip->ip_len,
662 			    1);
663 			pip->ip_len = new_len;
664 		}
665 
666 /* Compute TCP checksum for revised packet */
667 		tc->th_sum = 0;
668 		tc->th_sum = TcpChecksum(pip);
669 	} else {
670 #ifdef DEBUG
671 		fprintf(stderr,
672 		    "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
673 #endif
674 	}
675 }
676