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