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