- include sys/param.h for endian macros
[dragonfly.git] / lib / libalias / alias_ftp.c
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 }