xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision b46d3e21)
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 	if (strncasecmp("PORT ", sptr, 5))
274 		return (0);
275 
276 	addr = port = octet = 0;
277 	state = 0;
278 	for (i = 5; i < dlen; i++) {
279 		ch = sptr[i];
280 		switch (state) {
281 		case 0:
282 			if (isspace(ch))
283 				break;
284 			else
285 				state++;
286 		case 1:
287 		case 3:
288 		case 5:
289 		case 7:
290 		case 9:
291 		case 11:
292 			if (isdigit(ch)) {
293 				octet = ch - '0';
294 				state++;
295 			} else
296 				return (0);
297 			break;
298 		case 2:
299 		case 4:
300 		case 6:
301 		case 8:
302 			if (isdigit(ch))
303 				octet = 10 * octet + ch - '0';
304 			else if (ch == ',') {
305 				addr = (addr << 8) + octet;
306 				state++;
307 			} else
308 				return (0);
309 			break;
310 		case 10:
311 		case 12:
312 			if (isdigit(ch))
313 				octet = 10 * octet + ch - '0';
314 			else if (ch == ',' || state == 12) {
315 				port = (port << 8) + octet;
316 				state++;
317 			} else
318 				return (0);
319 			break;
320 		}
321 	}
322 
323 	if (state == 13) {
324 		la->true_addr.s_addr = htonl(addr);
325 		la->true_port = port;
326 		return (1);
327 	} else
328 		return (0);
329 }
330 
331 static int
332 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen)
333 {
334 	char ch, delim;
335 	int i, state;
336 	u_int32_t addr;
337 	u_short port;
338 	u_int8_t octet;
339 
340 	/* Format: "EPRT |1|A.D.D.R|PORT|". */
341 
342 	/* Return if data length is too short. */
343 	if (dlen < 18)
344 		return (0);
345 
346 	if (strncasecmp("EPRT ", sptr, 5))
347 		return (0);
348 
349 	addr = port = octet = 0;
350 	delim = '|';		/* XXX gcc -Wuninitialized */
351 	state = 0;
352 	for (i = 5; i < dlen; i++) {
353 		ch = sptr[i];
354 		switch (state) {
355 		case 0:
356 			if (!isspace(ch)) {
357 				delim = ch;
358 				state++;
359 			}
360 			break;
361 		case 1:
362 			if (ch == '1')	/* IPv4 address */
363 				state++;
364 			else
365 				return (0);
366 			break;
367 		case 2:
368 			if (ch == delim)
369 				state++;
370 			else
371 				return (0);
372 			break;
373 		case 3:
374 		case 5:
375 		case 7:
376 		case 9:
377 			if (isdigit(ch)) {
378 				octet = ch - '0';
379 				state++;
380 			} else
381 				return (0);
382 			break;
383 		case 4:
384 		case 6:
385 		case 8:
386 		case 10:
387 			if (isdigit(ch))
388 				octet = 10 * octet + ch - '0';
389 			else if (ch == '.' || state == 10) {
390 				addr = (addr << 8) + octet;
391 				state++;
392 			} else
393 				return (0);
394 			break;
395 		case 11:
396 			if (isdigit(ch)) {
397 				port = ch - '0';
398 				state++;
399 			} else
400 				return (0);
401 			break;
402 		case 12:
403 			if (isdigit(ch))
404 				port = 10 * port + ch - '0';
405 			else if (ch == delim)
406 				state++;
407 			else
408 				return (0);
409 			break;
410 		}
411 	}
412 
413 	if (state == 13) {
414 		la->true_addr.s_addr = htonl(addr);
415 		la->true_port = port;
416 		return (1);
417 	} else
418 		return (0);
419 }
420 
421 static int
422 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen)
423 {
424 	char ch;
425 	int i, state;
426 	u_int32_t addr;
427 	u_short port;
428 	u_int8_t octet;
429 
430 	/* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
431 
432 	/* Return if data length is too short. */
433 	if (dlen < 17)
434 		return (0);
435 
436 	if (strncmp("227 ", sptr, 4))
437 		return (0);
438 
439 	addr = port = octet = 0;
440 
441 	state = 0;
442 	for (i = 4; i < dlen; i++) {
443 		ch = sptr[i];
444 		switch (state) {
445 		case 0:
446 			if (ch == '(')
447 				state++;
448 			break;
449 		case 1:
450 		case 3:
451 		case 5:
452 		case 7:
453 		case 9:
454 		case 11:
455 			if (isdigit(ch)) {
456 				octet = ch - '0';
457 				state++;
458 			} else
459 				return (0);
460 			break;
461 		case 2:
462 		case 4:
463 		case 6:
464 		case 8:
465 			if (isdigit(ch))
466 				octet = 10 * octet + ch - '0';
467 			else if (ch == ',') {
468 				addr = (addr << 8) + octet;
469 				state++;
470 			} else
471 				return (0);
472 			break;
473 		case 10:
474 		case 12:
475 			if (isdigit(ch))
476 				octet = 10 * octet + ch - '0';
477 			else if (ch == ',' || (state == 12 && ch == ')')) {
478 				port = (port << 8) + octet;
479 				state++;
480 			} else
481 				return (0);
482 			break;
483 		}
484 	}
485 
486 	if (state == 13) {
487 		la->true_port = port;
488 		la->true_addr.s_addr = htonl(addr);
489 		return (1);
490 	} else
491 		return (0);
492 }
493 
494 static int
495 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen)
496 {
497 	char ch, delim;
498 	int i, state;
499 	u_short port;
500 
501 	/* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
502 
503 	/* Return if data length is too short. */
504 	if (dlen < 11)
505 		return (0);
506 
507 	if (strncmp("229 ", sptr, 4))
508 		return (0);
509 
510 	port = 0;
511 	delim = '|';		/* XXX gcc -Wuninitialized */
512 
513 	state = 0;
514 	for (i = 4; i < dlen; i++) {
515 		ch = sptr[i];
516 		switch (state) {
517 		case 0:
518 			if (ch == '(')
519 				state++;
520 			break;
521 		case 1:
522 			delim = ch;
523 			state++;
524 			break;
525 		case 2:
526 		case 3:
527 			if (ch == delim)
528 				state++;
529 			else
530 				return (0);
531 			break;
532 		case 4:
533 			if (isdigit(ch)) {
534 				port = ch - '0';
535 				state++;
536 			} else
537 				return (0);
538 			break;
539 		case 5:
540 			if (isdigit(ch))
541 				port = 10 * port + ch - '0';
542 			else if (ch == delim)
543 				state++;
544 			else
545 				return (0);
546 			break;
547 		case 6:
548 			if (ch == ')')
549 				state++;
550 			else
551 				return (0);
552 			break;
553 		}
554 	}
555 
556 	if (state == 7) {
557 		la->true_port = port;
558 		return (1);
559 	} else
560 		return (0);
561 }
562 
563 static void
564 NewFtpMessage(struct libalias *la, struct ip *pip,
565     struct alias_link *lnk,
566     int maxpacketsize,
567     int ftp_message_type)
568 {
569 	struct alias_link *ftp_lnk;
570 
571 /* Security checks. */
572 	if (pip->ip_src.s_addr != la->true_addr.s_addr)
573 		return;
574 
575 	if (la->true_port < IPPORT_RESERVED)
576 		return;
577 
578 /* Establish link to address and port found in FTP control message. */
579 	ftp_lnk = FindUdpTcpOut(la, la->true_addr, GetDestAddress(lnk),
580 	    htons(la->true_port), 0, IPPROTO_TCP, 1);
581 
582 	if (ftp_lnk != NULL) {
583 		int slen, hlen, tlen, dlen;
584 		struct tcphdr *tc;
585 
586 #ifndef NO_FW_PUNCH
587 		/* Punch hole in firewall */
588 		PunchFWHole(ftp_lnk);
589 #endif
590 
591 /* Calculate data length of TCP packet */
592 		tc = (struct tcphdr *)ip_next(pip);
593 		hlen = (pip->ip_hl + tc->th_off) << 2;
594 		tlen = ntohs(pip->ip_len);
595 		dlen = tlen - hlen;
596 
597 /* Create new FTP message. */
598 		{
599 			char stemp[MAX_MESSAGE_SIZE + 1];
600 			char *sptr;
601 			u_short alias_port;
602 			u_char *ptr;
603 			int a1, a2, a3, a4, p1, p2;
604 			struct in_addr alias_address;
605 
606 /* Decompose alias address into quad format */
607 			alias_address = GetAliasAddress(lnk);
608 			ptr = (u_char *) & alias_address.s_addr;
609 			a1 = *ptr++;
610 			a2 = *ptr++;
611 			a3 = *ptr++;
612 			a4 = *ptr;
613 
614 			alias_port = GetAliasPort(ftp_lnk);
615 
616 /* Prepare new command */
617 			switch (ftp_message_type) {
618 			case FTP_PORT_COMMAND:
619 			case FTP_227_REPLY:
620 				/* Decompose alias port into pair format. */
621 				ptr = (char *)&alias_port;
622 				p1 = *ptr++;
623 				p2 = *ptr;
624 
625 				if (ftp_message_type == FTP_PORT_COMMAND) {
626 					/* Generate PORT command string. */
627 					sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
628 					    a1, a2, a3, a4, p1, p2);
629 				} else {
630 					/* Generate 227 reply string. */
631 					sprintf(stemp,
632 					    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
633 					    a1, a2, a3, a4, p1, p2);
634 				}
635 				break;
636 			case FTP_EPRT_COMMAND:
637 				/* Generate EPRT command string. */
638 				sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
639 				    a1, a2, a3, a4, ntohs(alias_port));
640 				break;
641 			case FTP_229_REPLY:
642 				/* Generate 229 reply string. */
643 				sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
644 				    ntohs(alias_port));
645 				break;
646 			}
647 
648 /* Save string length for IP header modification */
649 			slen = strlen(stemp);
650 
651 /* Copy modified buffer into IP packet. */
652 			sptr = (char *)pip;
653 			sptr += hlen;
654 			strncpy(sptr, stemp, maxpacketsize - hlen);
655 		}
656 
657 /* Save information regarding modified seq and ack numbers */
658 		{
659 			int delta;
660 
661 			SetAckModified(lnk);
662 			tc = (struct tcphdr *)ip_next(pip);
663 			delta = GetDeltaSeqOut(tc->th_seq, lnk);
664 			AddSeq(lnk, delta + slen - dlen, pip->ip_hl,
665 			    pip->ip_len, tc->th_seq, tc->th_off);
666 		}
667 
668 /* Revise IP header */
669 		{
670 			u_short new_len;
671 
672 			new_len = htons(hlen + slen);
673 			DifferentialChecksum(&pip->ip_sum,
674 			    &new_len,
675 			    &pip->ip_len,
676 			    1);
677 			pip->ip_len = new_len;
678 		}
679 
680 /* Compute TCP checksum for revised packet */
681 		tc->th_sum = 0;
682 #ifdef _KERNEL
683 		tc->th_x2 = 1;
684 #else
685 		tc->th_sum = TcpChecksum(pip);
686 #endif
687 	} else {
688 #ifdef LIBALIAS_DEBUG
689 		fprintf(stderr,
690 		    "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
691 #endif
692 	}
693 }
694