xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision 6780ab54325a71e7e70112b11657973edde8655e)
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(char *, int);
98 static int ParseFtpEprtCommand(char *, int);
99 static int ParseFtp227Reply(char *, int);
100 static int ParseFtp229Reply(char *, int);
101 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
102 
103 static struct in_addr true_addr;	/* in network byte order. */
104 static u_short true_port;		/* in host byte order. */
105 
106 void
107 AliasHandleFtpOut(
108 struct ip *pip,	  /* IP packet to examine/patch */
109 struct alias_link *link, /* The link to go through (aliased port) */
110 int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
111 {
112     int hlen, tlen, dlen, pflags;
113     char *sptr;
114     struct tcphdr *tc;
115     int ftp_message_type;
116 
117 /* Calculate data length of TCP packet */
118     tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
119     hlen = (pip->ip_hl + tc->th_off) << 2;
120     tlen = ntohs(pip->ip_len);
121     dlen = tlen - hlen;
122 
123 /* Place string pointer and beginning of data */
124     sptr = (char *) pip;
125     sptr += hlen;
126 
127 /*
128  * Check that data length is not too long and previous message was
129  * properly terminated with CRLF.
130  */
131     pflags = GetProtocolFlags(link);
132     if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
133 	ftp_message_type = FTP_UNKNOWN_MESSAGE;
134 
135 	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
136 /*
137  * When aliasing a client, check for the PORT/EPRT command.
138  */
139 	    if (ParseFtpPortCommand(sptr, dlen))
140 		ftp_message_type = FTP_PORT_COMMAND;
141 	    else if (ParseFtpEprtCommand(sptr, dlen))
142 		ftp_message_type = FTP_EPRT_COMMAND;
143 	} else {
144 /*
145  * When aliasing a server, check for the 227/229 reply.
146  */
147 	    if (ParseFtp227Reply(sptr, dlen))
148 		ftp_message_type = FTP_227_REPLY;
149 	    else if (ParseFtp229Reply(sptr, dlen)) {
150 		ftp_message_type = FTP_229_REPLY;
151 		true_addr.s_addr = pip->ip_src.s_addr;
152 	    }
153 	}
154 
155 	if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
156 	    NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
157     }
158 
159 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
160 
161     if (dlen) {                  /* only if there's data */
162       sptr = (char *) pip; 	 /* start over at beginning */
163       tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
164       if (sptr[tlen-2] == '\r' && sptr[tlen-1] == '\n')
165 	pflags &= ~WAIT_CRLF;
166       else
167 	pflags |= WAIT_CRLF;
168       SetProtocolFlags(link, pflags);
169     }
170 }
171 
172 static int
173 ParseFtpPortCommand(char *sptr, int dlen)
174 {
175     char ch;
176     int i, state;
177     u_int32_t addr;
178     u_short port;
179     u_int8_t octet;
180 
181     /* Format: "PORT A,D,D,R,PO,RT". */
182 
183     /* Return if data length is too short. */
184     if (dlen < 18)
185 	return 0;
186 
187     addr = port = octet = 0;
188     state = -4;
189     for (i = 0; i < dlen; i++) {
190 	ch = sptr[i];
191 	switch (state) {
192 	case -4: if (ch == 'P') state++; else return 0; break;
193 	case -3: if (ch == 'O') state++; else return 0; break;
194 	case -2: if (ch == 'R') state++; else return 0; break;
195 	case -1: if (ch == 'T') state++; else return 0; break;
196 
197 	case 0:
198 	    if (isspace(ch))
199 		break;
200 	    else
201 		state++;
202 	case 1: case 3: case 5: case 7: case 9: case 11:
203 	    if (isdigit(ch)) {
204 		octet = ch - '0';
205 		state++;
206 	    } else
207 		return 0;
208 	    break;
209 	case 2: case 4: case 6: case 8:
210 	    if (isdigit(ch))
211 		octet = 10 * octet + ch - '0';
212             else if (ch == ',') {
213 		addr = (addr << 8) + octet;
214 		state++;
215 	    } else
216 		return 0;
217 	    break;
218 	case 10: case 12:
219 	    if (isdigit(ch))
220 		octet = 10 * octet + ch - '0';
221 	    else if (ch == ',' || state == 12) {
222 		port = (port << 8) + octet;
223 		state++;
224 	    } else
225 		return 0;
226 	    break;
227 	}
228     }
229 
230     if (state == 13) {
231 	true_addr.s_addr = htonl(addr);
232 	true_port = port;
233 	return 1;
234     } else
235 	return 0;
236 }
237 
238 static int
239 ParseFtpEprtCommand(char *sptr, int dlen)
240 {
241     char ch, delim;
242     int i, state;
243     u_int32_t addr;
244     u_short port;
245     u_int8_t octet;
246 
247     /* Format: "EPRT |1|A.D.D.R|PORT|". */
248 
249     /* Return if data length is too short. */
250     if (dlen < 18)
251 	return 0;
252 
253     addr = port = octet = 0;
254     delim = '|';			/* XXX gcc -Wuninitialized */
255     state = -4;
256     for (i = 0; i < dlen; i++) {
257 	ch = sptr[i];
258 	switch (state)
259 	{
260 	case -4: if (ch == 'E') state++; else return 0; break;
261 	case -3: if (ch == 'P') state++; else return 0; break;
262 	case -2: if (ch == 'R') state++; else return 0; break;
263 	case -1: if (ch == 'T') state++; else return 0; break;
264 
265 	case 0:
266 	    if (!isspace(ch)) {
267 		delim = ch;
268 		state++;
269 	    }
270 	    break;
271 	case 1:
272 	    if (ch == '1')	/* IPv4 address */
273 		state++;
274 	    else
275 		return 0;
276 	    break;
277 	case 2:
278 	    if (ch == delim)
279 		state++;
280 	    else
281 		return 0;
282 	    break;
283 	case 3: case 5: case 7: case 9:
284 	    if (isdigit(ch)) {
285 		octet = ch - '0';
286 		state++;
287 	    } else
288 		return 0;
289 	    break;
290 	case 4: case 6: case 8: case 10:
291 	    if (isdigit(ch))
292 		octet = 10 * octet + ch - '0';
293             else if (ch == '.' || state == 10) {
294 		addr = (addr << 8) + octet;
295 		state++;
296 	    } else
297 		return 0;
298 	    break;
299 	case 11:
300 	    if (isdigit(ch)) {
301 		port = ch - '0';
302 		state++;
303 	    } else
304 		return 0;
305 	    break;
306 	case 12:
307 	    if (isdigit(ch))
308 		port = 10 * port + ch - '0';
309 	    else if (ch == delim)
310 		state++;
311 	    else
312 		return 0;
313 	    break;
314 	}
315     }
316 
317     if (state == 13) {
318 	true_addr.s_addr = htonl(addr);
319 	true_port = port;
320 	return 1;
321     } else
322 	return 0;
323 }
324 
325 static int
326 ParseFtp227Reply(char *sptr, int dlen)
327 {
328     char ch;
329     int i, state;
330     u_int32_t addr;
331     u_short port;
332     u_int8_t octet;
333 
334     /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
335 
336     /* Return if data length is too short. */
337     if (dlen < 17)
338 	return 0;
339 
340     addr = port = octet = 0;
341 
342     state = -3;
343     for (i = 0; i < dlen; i++) {
344         ch = sptr[i];
345         switch (state)
346         {
347         case -3: if (ch == '2') state++; else return 0; break;
348         case -2: if (ch == '2') state++; else return 0; break;
349         case -1: if (ch == '7') state++; else return 0; break;
350 
351 	case 0:
352 	    if (ch == '(')
353 		state++;
354 	    break;
355 	case 1: case 3: case 5: case 7: case 9: case 11:
356 	    if (isdigit(ch)) {
357 		octet = ch - '0';
358 		state++;
359 	    } else
360 		return 0;
361 	    break;
362 	case 2: case 4: case 6: case 8:
363 	    if (isdigit(ch))
364 		octet = 10 * octet + ch - '0';
365             else if (ch == ',') {
366 		addr = (addr << 8) + octet;
367 		state++;
368 	    } else
369 		return 0;
370 	    break;
371 	case 10: case 12:
372 	    if (isdigit(ch))
373 		octet = 10 * octet + ch - '0';
374 	    else if (ch == ',' || (state == 12 && ch == ')')) {
375 		port = (port << 8) + octet;
376 		state++;
377 	    } else
378 		return 0;
379 	    break;
380 	}
381     }
382 
383     if (state == 13) {
384         true_port = port;
385         true_addr.s_addr = htonl(addr);
386 	return 1;
387     } else
388 	return 0;
389 }
390 
391 static int
392 ParseFtp229Reply(char *sptr, int dlen)
393 {
394     char ch, delim;
395     int i, state;
396     u_short port;
397 
398     /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
399 
400     /* Return if data length is too short. */
401     if (dlen < 11)
402 	return 0;
403 
404     port = 0;
405     delim = '|';			/* XXX gcc -Wuninitialized */
406 
407     state = -3;
408     for (i = 0; i < dlen; i++) {
409 	ch = sptr[i];
410 	switch (state)
411 	{
412 	case -3: if (ch == '2') state++; else return 0; break;
413 	case -2: if (ch == '2') state++; else return 0; break;
414 	case -1: if (ch == '9') state++; else return 0; break;
415 
416 	case 0:
417 	    if (ch == '(')
418 		state++;
419 	    break;
420 	case 1:
421 	    delim = ch;
422 	    state++;
423 	    break;
424 	case 2: case 3:
425 	    if (ch == delim)
426 		state++;
427 	    else
428 		return 0;
429 	    break;
430 	case 4:
431 	    if (isdigit(ch)) {
432 		port = ch - '0';
433 		state++;
434 	    } else
435 		return 0;
436 	    break;
437 	case 5:
438 	    if (isdigit(ch))
439 		port = 10 * port + ch - '0';
440 	    else if (ch == delim)
441 		state++;
442 	    else
443 		return 0;
444 	    break;
445 	case 6:
446 	    if (ch == ')')
447 		state++;
448 	    else
449 		return 0;
450 	    break;
451 	}
452     }
453 
454     if (state == 7) {
455 	true_port = port;
456 	return 1;
457     } else
458 	return 0;
459 }
460 
461 static void
462 NewFtpMessage(struct ip *pip,
463               struct alias_link *link,
464               int maxpacketsize,
465               int ftp_message_type)
466 {
467     struct alias_link *ftp_link;
468 
469 /* Security checks. */
470     if (pip->ip_src.s_addr != true_addr.s_addr)
471 	return;
472 
473     if (true_port < IPPORT_RESERVED)
474 	return;
475 
476 /* Establish link to address and port found in FTP control message. */
477     ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
478                              htons(true_port), 0, IPPROTO_TCP, 1);
479 
480     if (ftp_link != NULL)
481     {
482         int slen, hlen, tlen, dlen;
483         struct tcphdr *tc;
484 
485 #ifndef NO_FW_PUNCH
486 	/* Punch hole in firewall */
487 	PunchFWHole(ftp_link);
488 #endif
489 
490 /* Calculate data length of TCP packet */
491         tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
492         hlen = (pip->ip_hl + tc->th_off) << 2;
493         tlen = ntohs(pip->ip_len);
494         dlen = tlen - hlen;
495 
496 /* Create new FTP message. */
497         {
498             char stemp[MAX_MESSAGE_SIZE + 1];
499             char *sptr;
500             u_short alias_port;
501             u_char *ptr;
502             int a1, a2, a3, a4, p1, p2;
503             struct in_addr alias_address;
504 
505 /* Decompose alias address into quad format */
506             alias_address = GetAliasAddress(link);
507             ptr = (u_char *) &alias_address.s_addr;
508             a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
509 
510 	    alias_port = GetAliasPort(ftp_link);
511 
512 	    switch (ftp_message_type)
513 	    {
514 	    case FTP_PORT_COMMAND:
515 	    case FTP_227_REPLY:
516 		/* Decompose alias port into pair format. */
517 		ptr = (char *) &alias_port;
518 		p1 = *ptr++; p2=*ptr;
519 
520 		if (ftp_message_type == FTP_PORT_COMMAND) {
521 		    /* Generate PORT command string. */
522 		    sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
523 			    a1,a2,a3,a4,p1,p2);
524 		} else {
525 		    /* Generate 227 reply string. */
526 		    sprintf(stemp,
527 			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
528 			    a1,a2,a3,a4,p1,p2);
529 		}
530 		break;
531 	    case FTP_EPRT_COMMAND:
532 		/* Generate EPRT command string. */
533 		sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
534 			a1,a2,a3,a4,ntohs(alias_port));
535 		break;
536 	    case FTP_229_REPLY:
537 		/* Generate 229 reply string. */
538 		sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
539 			ntohs(alias_port));
540 		break;
541 	    }
542 
543 /* Save string length for IP header modification */
544             slen = strlen(stemp);
545 
546 /* Copy modified buffer into IP packet. */
547             sptr = (char *) pip; sptr += hlen;
548             strncpy(sptr, stemp, maxpacketsize-hlen);
549         }
550 
551 /* Save information regarding modified seq and ack numbers */
552         {
553             int delta;
554 
555             SetAckModified(link);
556             delta = GetDeltaSeqOut(pip, link);
557             AddSeq(pip, link, delta+slen-dlen);
558         }
559 
560 /* Revise IP header */
561         {
562             u_short new_len;
563 
564             new_len = htons(hlen + slen);
565             DifferentialChecksum(&pip->ip_sum,
566                                  &new_len,
567                                  &pip->ip_len,
568                                  1);
569             pip->ip_len = new_len;
570         }
571 
572 /* Compute TCP checksum for revised packet */
573         tc->th_sum = 0;
574         tc->th_sum = TcpChecksum(pip);
575     }
576     else
577     {
578 #ifdef DEBUG
579         fprintf(stderr,
580         "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
581 #endif
582     }
583 }
584