Initial import from FreeBSD RELENG_4:
[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  */
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 }