xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
1 /*-
2  * Copyright (c) 2001 Charles Mott <cmott@scientech.com>
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  * $FreeBSD$
27  */
28 
29 /*
30     Alias_ftp.c performs special processing for FTP sessions under
31     TCP.  Specifically, when a PORT/EPRT command from the client
32     side or 227/229 reply from the server is sent, it is intercepted
33     and modified.  The address is changed to the gateway machine
34     and an aliasing port is used.
35 
36     For this routine to work, the message must fit entirely into a
37     single TCP packet.  This is typically the case, but exceptions
38     can easily be envisioned under the actual specifications.
39 
40     Probably the most troubling aspect of the approach taken here is
41     that the new message will typically be a different length, and
42     this causes a certain amount of bookkeeping to keep track of the
43     changes of sequence and acknowledgment numbers, since the client
44     machine is totally unaware of the modification to the TCP stream.
45 
46 
47     This software is placed into the public domain with no restrictions
48     on its distribution.
49 
50     References: RFC 959, RFC 2428.
51 
52     Initial version:  August, 1996  (cjm)
53 
54     Version 1.6
55          Brian Somers and Martin Renters identified an IP checksum
56          error for modified IP packets.
57 
58     Version 1.7:  January 9, 1996 (cjm)
59          Differential checksum computation for change
60          in IP packet length.
61 
62     Version 2.1:  May, 1997 (cjm)
63          Very minor changes to conform with
64          local/global/function naming conventions
65          within the packet aliasing module.
66 
67     Version 3.1:  May, 2000 (eds)
68 	 Add support for passive mode, alias the 227 replies.
69 
70     See HISTORY file for record of revisions.
71 */
72 
73 /* Includes */
74 #include <ctype.h>
75 #include <stdio.h>
76 #include <string.h>
77 #include <sys/types.h>
78 #include <netinet/in_systm.h>
79 #include <netinet/in.h>
80 #include <netinet/ip.h>
81 #include <netinet/tcp.h>
82 
83 #include "alias_local.h"
84 
85 #define FTP_CONTROL_PORT_NUMBER 21
86 #define MAX_MESSAGE_SIZE	128
87 
88 /* FTP protocol flags. */
89 #define WAIT_CRLF		0x01
90 
91 enum ftp_message_type {
92     FTP_PORT_COMMAND,
93     FTP_EPRT_COMMAND,
94     FTP_227_REPLY,
95     FTP_229_REPLY,
96     FTP_UNKNOWN_MESSAGE
97 };
98 
99 static int ParseFtpPortCommand(char *, int);
100 static int ParseFtpEprtCommand(char *, int);
101 static int ParseFtp227Reply(char *, int);
102 static int ParseFtp229Reply(char *, int);
103 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
104 
105 static struct in_addr true_addr;	/* in network byte order. */
106 static u_short true_port;		/* in host byte order. */
107 
108 void
109 AliasHandleFtpOut(
110 struct ip *pip,	  /* IP packet to examine/patch */
111 struct alias_link *link, /* The link to go through (aliased port) */
112 int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
113 {
114     int hlen, tlen, dlen, pflags;
115     char *sptr;
116     struct tcphdr *tc;
117     int ftp_message_type;
118 
119 /* Calculate data length of TCP packet */
120     tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
121     hlen = (pip->ip_hl + tc->th_off) << 2;
122     tlen = ntohs(pip->ip_len);
123     dlen = tlen - hlen;
124 
125 /* Place string pointer and beginning of data */
126     sptr = (char *) pip;
127     sptr += hlen;
128 
129 /*
130  * Check that data length is not too long and previous message was
131  * properly terminated with CRLF.
132  */
133     pflags = GetProtocolFlags(link);
134     if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
135 	ftp_message_type = FTP_UNKNOWN_MESSAGE;
136 
137 	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
138 /*
139  * When aliasing a client, check for the PORT/EPRT command.
140  */
141 	    if (ParseFtpPortCommand(sptr, dlen))
142 		ftp_message_type = FTP_PORT_COMMAND;
143 	    else if (ParseFtpEprtCommand(sptr, dlen))
144 		ftp_message_type = FTP_EPRT_COMMAND;
145 	} else {
146 /*
147  * When aliasing a server, check for the 227/229 reply.
148  */
149 	    if (ParseFtp227Reply(sptr, dlen))
150 		ftp_message_type = FTP_227_REPLY;
151 	    else if (ParseFtp229Reply(sptr, dlen))
152 		ftp_message_type = FTP_229_REPLY;
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 (ftp_message_type != FTP_229_REPLY &&
471 	pip->ip_src.s_addr != true_addr.s_addr)
472 	return;
473 
474     if (true_port < IPPORT_RESERVED)
475 	return;
476 
477 /* Establish link to address and port found in FTP control message. */
478     ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
479                              htons(true_port), 0, IPPROTO_TCP, 1);
480 
481     if (ftp_link != NULL)
482     {
483         int slen, hlen, tlen, dlen;
484         struct tcphdr *tc;
485 
486 #ifndef NO_FW_PUNCH
487 	if (ftp_message_type == FTP_PORT_COMMAND ||
488 	    ftp_message_type == FTP_EPRT_COMMAND) {
489 	    /* Punch hole in firewall */
490 	    PunchFWHole(ftp_link);
491 	}
492 #endif
493 
494 /* Calculate data length of TCP packet */
495         tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
496         hlen = (pip->ip_hl + tc->th_off) << 2;
497         tlen = ntohs(pip->ip_len);
498         dlen = tlen - hlen;
499 
500 /* Create new FTP message. */
501         {
502             char stemp[MAX_MESSAGE_SIZE + 1];
503             char *sptr;
504             u_short alias_port;
505             u_char *ptr;
506             int a1, a2, a3, a4, p1, p2;
507             struct in_addr alias_address;
508 
509 /* Decompose alias address into quad format */
510             alias_address = GetAliasAddress(link);
511             ptr = (u_char *) &alias_address.s_addr;
512             a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
513 
514 	    alias_port = GetAliasPort(ftp_link);
515 
516 	    switch (ftp_message_type)
517 	    {
518 	    case FTP_PORT_COMMAND:
519 	    case FTP_227_REPLY:
520 		/* Decompose alias port into pair format. */
521 		ptr = (char *) &alias_port;
522 		p1 = *ptr++; p2=*ptr;
523 
524 		if (ftp_message_type == FTP_PORT_COMMAND) {
525 		    /* Generate PORT command string. */
526 		    sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
527 			    a1,a2,a3,a4,p1,p2);
528 		} else {
529 		    /* Generate 227 reply string. */
530 		    sprintf(stemp,
531 			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
532 			    a1,a2,a3,a4,p1,p2);
533 		}
534 		break;
535 	    case FTP_EPRT_COMMAND:
536 		/* Generate EPRT command string. */
537 		sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
538 			a1,a2,a3,a4,ntohs(alias_port));
539 		break;
540 	    case FTP_229_REPLY:
541 		/* Generate 229 reply string. */
542 		sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
543 			ntohs(alias_port));
544 		break;
545 	    }
546 
547 /* Save string length for IP header modification */
548             slen = strlen(stemp);
549 
550 /* Copy modified buffer into IP packet. */
551             sptr = (char *) pip; sptr += hlen;
552             strncpy(sptr, stemp, maxpacketsize-hlen);
553         }
554 
555 /* Save information regarding modified seq and ack numbers */
556         {
557             int delta;
558 
559             SetAckModified(link);
560             delta = GetDeltaSeqOut(pip, link);
561             AddSeq(pip, link, delta+slen-dlen);
562         }
563 
564 /* Revise IP header */
565         {
566             u_short new_len;
567 
568             new_len = htons(hlen + slen);
569             DifferentialChecksum(&pip->ip_sum,
570                                  &new_len,
571                                  &pip->ip_len,
572                                  1);
573             pip->ip_len = new_len;
574         }
575 
576 /* Compute TCP checksum for revised packet */
577         tc->th_sum = 0;
578         tc->th_sum = TcpChecksum(pip);
579     }
580     else
581     {
582 #ifdef DEBUG
583         fprintf(stderr,
584         "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
585 #endif
586     }
587 }
588