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