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