]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - sys/netinet/libalias/alias_ftp.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / sys / netinet / 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
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
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 #ifdef _KERNEL
73 #include <sys/param.h>
74 #include <sys/ctype.h>
75 #include <sys/systm.h>
76 #include <sys/kernel.h>
77 #include <sys/module.h>
78 #else
79 #include <ctype.h>
80 #include <errno.h>
81 #include <sys/types.h>
82 #include <stdio.h>
83 #include <string.h>
84 #endif
85
86 #include <netinet/in_systm.h>
87 #include <netinet/in.h>
88 #include <netinet/ip.h>
89 #include <netinet/tcp.h>
90
91 #ifdef _KERNEL
92 #include <netinet/libalias/alias.h>
93 #include <netinet/libalias/alias_local.h>
94 #include <netinet/libalias/alias_mod.h>
95 #else
96 #include "alias_local.h"
97 #include "alias_mod.h"
98 #endif
99
100 #define FTP_CONTROL_PORT_NUMBER 21
101
102 static void
103 AliasHandleFtpOut(struct libalias *, struct ip *, struct alias_link *,  
104                   int maxpacketsize);
105
106 static int 
107 fingerprint(struct libalias *la, struct alias_data *ah)
108 {
109
110         if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL || 
111                 ah->maxpktsize == 0)
112                 return (-1);
113         if (ntohs(*ah->dport) == FTP_CONTROL_PORT_NUMBER
114             || ntohs(*ah->sport) == FTP_CONTROL_PORT_NUMBER)
115                 return (0);
116         return (-1);
117 }
118
119 static int 
120 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
121 {
122         
123         AliasHandleFtpOut(la, pip, ah->lnk, ah->maxpktsize);
124         return (0);
125 }
126
127 struct proto_handler handlers[] = {
128         { 
129           .pri = 80, 
130           .dir = OUT, 
131           .proto = TCP, 
132           .fingerprint = &fingerprint, 
133           .protohandler = &protohandler
134         }, 
135         { EOH }
136 };
137
138 static int
139 mod_handler(module_t mod, int type, void *data)
140 {
141         int error;
142
143         switch (type) {   
144         case MOD_LOAD:
145                 error = 0;
146                 LibAliasAttachHandlers(handlers);
147                 break;
148         case MOD_UNLOAD:
149                 error = 0;
150                 LibAliasDetachHandlers(handlers);
151                 break;
152         default:
153                 error = EINVAL;
154         }
155         return (error);
156 }
157
158 #ifdef _KERNEL
159 static
160 #endif
161 moduledata_t alias_mod = {
162        "alias_ftp", mod_handler, NULL
163 };
164
165 #ifdef  _KERNEL
166 DECLARE_MODULE(alias_ftp, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
167 MODULE_VERSION(alias_ftp, 1);
168 MODULE_DEPEND(alias_ftp, libalias, 1, 1, 1);
169 #endif
170
171 #define FTP_CONTROL_PORT_NUMBER 21
172 #define MAX_MESSAGE_SIZE        128
173
174 /* FTP protocol flags. */
175 #define WAIT_CRLF               0x01
176
177 enum ftp_message_type {
178         FTP_PORT_COMMAND,
179         FTP_EPRT_COMMAND,
180         FTP_227_REPLY,
181         FTP_229_REPLY,
182         FTP_UNKNOWN_MESSAGE
183 };
184
185 static int      ParseFtpPortCommand(struct libalias *la, char *, int);
186 static int      ParseFtpEprtCommand(struct libalias *la, char *, int);
187 static int      ParseFtp227Reply(struct libalias *la, char *, int);
188 static int      ParseFtp229Reply(struct libalias *la, char *, int);
189 static void     NewFtpMessage(struct libalias *la, struct ip *, struct alias_link *, int, int);
190
191 static void
192 AliasHandleFtpOut(
193     struct libalias *la,
194     struct ip *pip,             /* IP packet to examine/patch */
195     struct alias_link *lnk,     /* The link to go through (aliased port) */
196     int maxpacketsize           /* The maximum size this packet can grow to
197         (including headers) */ )
198 {
199         int hlen, tlen, dlen, pflags;
200         char *sptr;
201         struct tcphdr *tc;
202         int ftp_message_type;
203
204 /* Calculate data length of TCP packet */
205         tc = (struct tcphdr *)ip_next(pip);
206         hlen = (pip->ip_hl + tc->th_off) << 2;
207         tlen = ntohs(pip->ip_len);
208         dlen = tlen - hlen;
209
210 /* Place string pointer and beginning of data */
211         sptr = (char *)pip;
212         sptr += hlen;
213
214 /*
215  * Check that data length is not too long and previous message was
216  * properly terminated with CRLF.
217  */
218         pflags = GetProtocolFlags(lnk);
219         if (dlen <= MAX_MESSAGE_SIZE && !(pflags & WAIT_CRLF)) {
220                 ftp_message_type = FTP_UNKNOWN_MESSAGE;
221
222                 if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
223 /*
224  * When aliasing a client, check for the PORT/EPRT command.
225  */
226                         if (ParseFtpPortCommand(la, sptr, dlen))
227                                 ftp_message_type = FTP_PORT_COMMAND;
228                         else if (ParseFtpEprtCommand(la, sptr, dlen))
229                                 ftp_message_type = FTP_EPRT_COMMAND;
230                 } else {
231 /*
232  * When aliasing a server, check for the 227/229 reply.
233  */
234                         if (ParseFtp227Reply(la, sptr, dlen))
235                                 ftp_message_type = FTP_227_REPLY;
236                         else if (ParseFtp229Reply(la, sptr, dlen)) {
237                                 ftp_message_type = FTP_229_REPLY;
238                                 la->true_addr.s_addr = pip->ip_src.s_addr;
239                         }
240                 }
241
242                 if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
243                         NewFtpMessage(la, pip, lnk, maxpacketsize, ftp_message_type);
244         }
245 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
246
247         if (dlen) {             /* only if there's data */
248                 sptr = (char *)pip;     /* start over at beginning */
249                 tlen = ntohs(pip->ip_len);      /* recalc tlen, pkt may
250                                                  * have grown */
251                 if (sptr[tlen - 2] == '\r' && sptr[tlen - 1] == '\n')
252                         pflags &= ~WAIT_CRLF;
253                 else
254                         pflags |= WAIT_CRLF;
255                 SetProtocolFlags(lnk, pflags);
256         }
257 }
258
259 static int
260 ParseFtpPortCommand(struct libalias *la, char *sptr, int dlen)
261 {
262         char ch;
263         int i, state;
264         u_int32_t addr;
265         u_short port;
266         u_int8_t octet;
267
268         /* Format: "PORT A,D,D,R,PO,RT". */
269
270         /* Return if data length is too short. */
271         if (dlen < 18)
272                 return (0);
273
274         if (strncasecmp("PORT ", sptr, 5))
275                 return (0);
276
277         addr = port = octet = 0;
278         state = 0;
279         for (i = 5; i < dlen; i++) {
280                 ch = sptr[i];
281                 switch (state) {
282                 case 0:
283                         if (isspace(ch))
284                                 break;
285                         else
286                                 state++;
287                 case 1:
288                 case 3:
289                 case 5:
290                 case 7:
291                 case 9:
292                 case 11:
293                         if (isdigit(ch)) {
294                                 octet = ch - '0';
295                                 state++;
296                         } else
297                                 return (0);
298                         break;
299                 case 2:
300                 case 4:
301                 case 6:
302                 case 8:
303                         if (isdigit(ch))
304                                 octet = 10 * octet + ch - '0';
305                         else if (ch == ',') {
306                                 addr = (addr << 8) + octet;
307                                 state++;
308                         } else
309                                 return (0);
310                         break;
311                 case 10:
312                 case 12:
313                         if (isdigit(ch))
314                                 octet = 10 * octet + ch - '0';
315                         else if (ch == ',' || state == 12) {
316                                 port = (port << 8) + octet;
317                                 state++;
318                         } else
319                                 return (0);
320                         break;
321                 }
322         }
323
324         if (state == 13) {
325                 la->true_addr.s_addr = htonl(addr);
326                 la->true_port = port;
327                 return (1);
328         } else
329                 return (0);
330 }
331
332 static int
333 ParseFtpEprtCommand(struct libalias *la, char *sptr, int dlen)
334 {
335         char ch, delim;
336         int i, state;
337         u_int32_t addr;
338         u_short port;
339         u_int8_t octet;
340
341         /* Format: "EPRT |1|A.D.D.R|PORT|". */
342
343         /* Return if data length is too short. */
344         if (dlen < 18)
345                 return (0);
346
347         if (strncasecmp("EPRT ", sptr, 5))
348                 return (0);
349
350         addr = port = octet = 0;
351         delim = '|';            /* XXX gcc -Wuninitialized */
352         state = 0;
353         for (i = 5; i < dlen; i++) {
354                 ch = sptr[i];
355                 switch (state) {
356                 case 0:
357                         if (!isspace(ch)) {
358                                 delim = ch;
359                                 state++;
360                         }
361                         break;
362                 case 1:
363                         if (ch == '1')  /* IPv4 address */
364                                 state++;
365                         else
366                                 return (0);
367                         break;
368                 case 2:
369                         if (ch == delim)
370                                 state++;
371                         else
372                                 return (0);
373                         break;
374                 case 3:
375                 case 5:
376                 case 7:
377                 case 9:
378                         if (isdigit(ch)) {
379                                 octet = ch - '0';
380                                 state++;
381                         } else
382                                 return (0);
383                         break;
384                 case 4:
385                 case 6:
386                 case 8:
387                 case 10:
388                         if (isdigit(ch))
389                                 octet = 10 * octet + ch - '0';
390                         else if (ch == '.' || state == 10) {
391                                 addr = (addr << 8) + octet;
392                                 state++;
393                         } else
394                                 return (0);
395                         break;
396                 case 11:
397                         if (isdigit(ch)) {
398                                 port = ch - '0';
399                                 state++;
400                         } else
401                                 return (0);
402                         break;
403                 case 12:
404                         if (isdigit(ch))
405                                 port = 10 * port + ch - '0';
406                         else if (ch == delim)
407                                 state++;
408                         else
409                                 return (0);
410                         break;
411                 }
412         }
413
414         if (state == 13) {
415                 la->true_addr.s_addr = htonl(addr);
416                 la->true_port = port;
417                 return (1);
418         } else
419                 return (0);
420 }
421
422 static int
423 ParseFtp227Reply(struct libalias *la, char *sptr, int dlen)
424 {
425         char ch;
426         int i, state;
427         u_int32_t addr;
428         u_short port;
429         u_int8_t octet;
430
431         /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
432
433         /* Return if data length is too short. */
434         if (dlen < 17)
435                 return (0);
436
437         if (strncmp("227 ", sptr, 4))
438                 return (0);
439
440         addr = port = octet = 0;
441
442         state = 0;
443         for (i = 4; i < dlen; i++) {
444                 ch = sptr[i];
445                 switch (state) {
446                 case 0:
447                         if (ch == '(')
448                                 state++;
449                         break;
450                 case 1:
451                 case 3:
452                 case 5:
453                 case 7:
454                 case 9:
455                 case 11:
456                         if (isdigit(ch)) {
457                                 octet = ch - '0';
458                                 state++;
459                         } else
460                                 return (0);
461                         break;
462                 case 2:
463                 case 4:
464                 case 6:
465                 case 8:
466                         if (isdigit(ch))
467                                 octet = 10 * octet + ch - '0';
468                         else if (ch == ',') {
469                                 addr = (addr << 8) + octet;
470                                 state++;
471                         } else
472                                 return (0);
473                         break;
474                 case 10:
475                 case 12:
476                         if (isdigit(ch))
477                                 octet = 10 * octet + ch - '0';
478                         else if (ch == ',' || (state == 12 && ch == ')')) {
479                                 port = (port << 8) + octet;
480                                 state++;
481                         } else
482                                 return (0);
483                         break;
484                 }
485         }
486
487         if (state == 13) {
488                 la->true_port = port;
489                 la->true_addr.s_addr = htonl(addr);
490                 return (1);
491         } else
492                 return (0);
493 }
494
495 static int
496 ParseFtp229Reply(struct libalias *la, char *sptr, int dlen)
497 {
498         char ch, delim;
499         int i, state;
500         u_short port;
501
502         /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
503
504         /* Return if data length is too short. */
505         if (dlen < 11)
506                 return (0);
507
508         if (strncmp("229 ", sptr, 4))
509                 return (0);
510
511         port = 0;
512         delim = '|';            /* XXX gcc -Wuninitialized */
513
514         state = 0;
515         for (i = 4; i < dlen; i++) {
516                 ch = sptr[i];
517                 switch (state) {
518                 case 0:
519                         if (ch == '(')
520                                 state++;
521                         break;
522                 case 1:
523                         delim = ch;
524                         state++;
525                         break;
526                 case 2:
527                 case 3:
528                         if (ch == delim)
529                                 state++;
530                         else
531                                 return (0);
532                         break;
533                 case 4:
534                         if (isdigit(ch)) {
535                                 port = ch - '0';
536                                 state++;
537                         } else
538                                 return (0);
539                         break;
540                 case 5:
541                         if (isdigit(ch))
542                                 port = 10 * port + ch - '0';
543                         else if (ch == delim)
544                                 state++;
545                         else
546                                 return (0);
547                         break;
548                 case 6:
549                         if (ch == ')')
550                                 state++;
551                         else
552                                 return (0);
553                         break;
554                 }
555         }
556
557         if (state == 7) {
558                 la->true_port = port;
559                 return (1);
560         } else
561                 return (0);
562 }
563
564 static void
565 NewFtpMessage(struct libalias *la, struct ip *pip,
566     struct alias_link *lnk,
567     int maxpacketsize,
568     int ftp_message_type)
569 {
570         struct alias_link *ftp_lnk;
571
572 /* Security checks. */
573         if (pip->ip_src.s_addr != la->true_addr.s_addr)
574                 return;
575
576         if (la->true_port < IPPORT_RESERVED)
577                 return;
578
579 /* Establish link to address and port found in FTP control message. */
580         ftp_lnk = FindUdpTcpOut(la, la->true_addr, GetDestAddress(lnk),
581             htons(la->true_port), 0, IPPROTO_TCP, 1);
582
583         if (ftp_lnk != NULL) {
584                 int slen, hlen, tlen, dlen;
585                 struct tcphdr *tc;
586
587 #ifndef NO_FW_PUNCH
588                 /* Punch hole in firewall */
589                 PunchFWHole(ftp_lnk);
590 #endif
591
592 /* Calculate data length of TCP packet */
593                 tc = (struct tcphdr *)ip_next(pip);
594                 hlen = (pip->ip_hl + tc->th_off) << 2;
595                 tlen = ntohs(pip->ip_len);
596                 dlen = tlen - hlen;
597
598 /* Create new FTP message. */
599                 {
600                         char stemp[MAX_MESSAGE_SIZE + 1];
601                         char *sptr;
602                         u_short alias_port;
603                         u_char *ptr;
604                         int a1, a2, a3, a4, p1, p2;
605                         struct in_addr alias_address;
606
607 /* Decompose alias address into quad format */
608                         alias_address = GetAliasAddress(lnk);
609                         ptr = (u_char *) & alias_address.s_addr;
610                         a1 = *ptr++;
611                         a2 = *ptr++;
612                         a3 = *ptr++;
613                         a4 = *ptr;
614
615                         alias_port = GetAliasPort(ftp_lnk);
616
617 /* Prepare new command */
618                         switch (ftp_message_type) {
619                         case FTP_PORT_COMMAND:
620                         case FTP_227_REPLY:
621                                 /* Decompose alias port into pair format. */
622                                 ptr = (char *)&alias_port;
623                                 p1 = *ptr++;
624                                 p2 = *ptr;
625
626                                 if (ftp_message_type == FTP_PORT_COMMAND) {
627                                         /* Generate PORT command string. */
628                                         sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
629                                             a1, a2, a3, a4, p1, p2);
630                                 } else {
631                                         /* Generate 227 reply string. */
632                                         sprintf(stemp,
633                                             "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
634                                             a1, a2, a3, a4, p1, p2);
635                                 }
636                                 break;
637                         case FTP_EPRT_COMMAND:
638                                 /* Generate EPRT command string. */
639                                 sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
640                                     a1, a2, a3, a4, ntohs(alias_port));
641                                 break;
642                         case FTP_229_REPLY:
643                                 /* Generate 229 reply string. */
644                                 sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
645                                     ntohs(alias_port));
646                                 break;
647                         }
648
649 /* Save string length for IP header modification */
650                         slen = strlen(stemp);
651
652 /* Copy modified buffer into IP packet. */
653                         sptr = (char *)pip;
654                         sptr += hlen;
655                         strncpy(sptr, stemp, maxpacketsize - hlen);
656                 }
657
658 /* Save information regarding modified seq and ack numbers */
659                 {
660                         int delta;
661
662                         SetAckModified(lnk);
663                         tc = (struct tcphdr *)ip_next(pip);                             
664                         delta = GetDeltaSeqOut(tc->th_seq, lnk);
665                         AddSeq(lnk, delta + slen - dlen, pip->ip_hl, 
666                             pip->ip_len, tc->th_seq, tc->th_off);
667                 }
668
669 /* Revise IP header */
670                 {
671                         u_short new_len;
672
673                         new_len = htons(hlen + slen);
674                         DifferentialChecksum(&pip->ip_sum,
675                             &new_len,
676                             &pip->ip_len,
677                             1);
678                         pip->ip_len = new_len;
679                 }
680
681 /* Compute TCP checksum for revised packet */
682                 tc->th_sum = 0;
683 #ifdef _KERNEL
684                 tc->th_x2 = 1;
685 #else
686                 tc->th_sum = TcpChecksum(pip);
687 #endif
688         } else {
689 #ifdef LIBALIAS_DEBUG
690                 fprintf(stderr,
691                     "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
692 #endif
693         }
694 }