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