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