xref: /dragonfly/lib/libalias/alias_ftp.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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
AliasHandleFtpOut(struct ip * pip,struct alias_link * link,int maxpacketsize)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
ParseFtpPortCommand(char * sptr,int dlen)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
ParseFtpEprtCommand(char * sptr,int dlen)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
ParseFtp227Reply(char * sptr,int dlen)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
ParseFtp229Reply(char * sptr,int dlen)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
NewFtpMessage(struct ip * pip,struct alias_link * link,int maxpacketsize,int ftp_message_type)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