xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision 0b381bf1fd8fbb2df974c318d58643ecfeec44b0)
1 /*
2     Alias_ftp.c performs special processing for FTP sessions under
3     TCP.  Specifically, when a PORT/EPRT command from the client
4     side or 227/229 reply from the server is sent, it is intercepted
5     and modified.  The address is changed to the gateway machine
6     and an aliasing port is used.
7 
8     For this routine to work, the message must fit entirely into a
9     single TCP packet.  This is typically the case, but exceptions
10     can easily be envisioned under the actual specifications.
11 
12     Probably the most troubling aspect of the approach taken here is
13     that the new message will typically be a different length, and
14     this causes a certain amount of bookkeeping to keep track of the
15     changes of sequence and acknowledgment numbers, since the client
16     machine is totally unaware of the modification to the TCP stream.
17 
18 
19     This software is placed into the public domain with no restrictions
20     on its distribution.
21 
22     References: RFC 959, RFC 2428.
23 
24     Initial version:  August, 1996  (cjm)
25 
26     Version 1.6
27          Brian Somers and Martin Renters identified an IP checksum
28          error for modified IP packets.
29 
30     Version 1.7:  January 9, 1996 (cjm)
31          Differential checksum computation for change
32          in IP packet length.
33 
34     Version 2.1:  May, 1997 (cjm)
35          Very minor changes to conform with
36          local/global/function naming conventions
37          within the packet aliasing module.
38 
39     Version 3.1:  May, 2000 (eds)
40 	 Add support for passive mode, alias the 227 replies.
41 
42     See HISTORY file for record of revisions.
43 
44     $FreeBSD$
45 */
46 
47 /* Includes */
48 #include <ctype.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <sys/types.h>
52 #include <netinet/in_systm.h>
53 #include <netinet/in.h>
54 #include <netinet/ip.h>
55 #include <netinet/tcp.h>
56 
57 #include "alias_local.h"
58 
59 #define FTP_CONTROL_PORT_NUMBER 21
60 #define MAX_MESSAGE_SIZE	128
61 
62 /* FTP protocol flags. */
63 #define WAIT_CRLF		0x01
64 
65 enum ftp_message_type {
66     FTP_PORT_COMMAND,
67     FTP_EPRT_COMMAND,
68     FTP_227_REPLY,
69     FTP_229_REPLY,
70     FTP_UNKNOWN_MESSAGE
71 };
72 
73 static int ParseFtpPortCommand(char *, int);
74 static int ParseFtpEprtCommand(char *, int);
75 static int ParseFtp227Reply(char *, int);
76 static int ParseFtp229Reply(char *, int);
77 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
78 
79 static struct in_addr true_addr;	/* in network byte order. */
80 static u_short true_port;		/* in host byte order. */
81 
82 void
83 AliasHandleFtpOut(
84 struct ip *pip,	  /* IP packet to examine/patch */
85 struct alias_link *link, /* The link to go through (aliased port) */
86 int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
87 {
88     int hlen, tlen, dlen, pflags;
89     char *sptr;
90     struct tcphdr *tc;
91     int ftp_message_type;
92 
93 /* Calculate data length of TCP packet */
94     tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
95     hlen = (pip->ip_hl + tc->th_off) << 2;
96     tlen = ntohs(pip->ip_len);
97     dlen = tlen - hlen;
98 
99 /* Place string pointer and beginning of data */
100     sptr = (char *) pip;
101     sptr += hlen;
102 
103 /*
104  * Check that data length is not too long and previous message was
105  * properly terminated with CRLF.
106  */
107     pflags = GetProtocolFlags(link);
108     if (dlen <= MAX_MESSAGE_SIZE && (pflags & WAIT_CRLF)) {
109 	ftp_message_type = FTP_UNKNOWN_MESSAGE;
110 
111 	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
112 /*
113  * When aliasing a client, check for the PORT/EPRT command.
114  */
115 	    if (ParseFtpPortCommand(sptr, dlen))
116 		ftp_message_type = FTP_PORT_COMMAND;
117 	    else if (ParseFtpEprtCommand(sptr, dlen))
118 		ftp_message_type = FTP_EPRT_COMMAND;
119 	} else {
120 /*
121  * When aliasing a server, check for the 227/229 reply.
122  */
123 	    if (ParseFtp227Reply(sptr, dlen))
124 		ftp_message_type = FTP_227_REPLY;
125 	    else if (ParseFtp229Reply(sptr, dlen))
126 		ftp_message_type = FTP_229_REPLY;
127 	}
128 
129 	if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
130 	    NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
131     }
132 
133 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
134 
135     if (dlen) {                  /* only if there's data */
136       sptr = (char *) pip; 	 /* start over at beginning */
137       tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
138       if (sptr[tlen-2] == '\r' && sptr[tlen-1] == '\n')
139 	pflags &= ~WAIT_CRLF;
140       else
141 	pflags |= WAIT_CRLF;
142       SetProtocolFlags(link, pflags);
143     }
144 }
145 
146 static int
147 ParseFtpPortCommand(char *sptr, int dlen)
148 {
149     char ch;
150     int i, state;
151     u_int32_t addr;
152     u_short port;
153     u_int8_t octet;
154 
155     /* Format: "PORT A,D,D,R,PO,RT". */
156 
157     /* Return if data length is too short. */
158     if (dlen < 18)
159 	return 0;
160 
161     addr = port = octet = 0;
162     state = -4;
163     for (i = 0; i < dlen; i++) {
164 	ch = sptr[i];
165 	switch (state) {
166 	case -4: if (ch == 'P') state++; else return 0; break;
167 	case -3: if (ch == 'O') state++; else return 0; break;
168 	case -2: if (ch == 'R') state++; else return 0; break;
169 	case -1: if (ch == 'T') state++; else return 0; break;
170 
171 	case 0:
172 	    if (isspace(ch))
173 		break;
174 	    else
175 		state++;
176 	case 1: case 3: case 5: case 7: case 9: case 11:
177 	    if (isdigit(ch)) {
178 		octet = ch - '0';
179 		state++;
180 	    } else
181 		return 0;
182 	    break;
183 	case 2: case 4: case 6: case 8:
184 	    if (isdigit(ch))
185 		octet = 10 * octet + ch - '0';
186             else if (ch == ',') {
187 		addr = (addr << 8) + octet;
188 		state++;
189 	    } else
190 		return 0;
191 	    break;
192 	case 10: case 12:
193 	    if (isdigit(ch))
194 		octet = 10 * octet + ch - '0';
195 	    else if (ch == ',' || state == 12) {
196 		port = (port << 8) + octet;
197 		state++;
198 	    } else
199 		return 0;
200 	    break;
201 	}
202     }
203 
204     if (state == 13) {
205 	true_addr.s_addr = htonl(addr);
206 	true_port = port;
207 	return 1;
208     } else
209 	return 0;
210 }
211 
212 static int
213 ParseFtpEprtCommand(char *sptr, int dlen)
214 {
215     char ch, delim;
216     int i, state;
217     u_int32_t addr;
218     u_short port;
219     u_int8_t octet;
220 
221     /* Format: "EPRT |1|A.D.D.R|PORT|". */
222 
223     /* Return if data length is too short. */
224     if (dlen < 18)
225 	return 0;
226 
227     addr = port = octet = 0;
228     delim = '|';			/* XXX gcc -Wuninitialized */
229     state = -4;
230     for (i = 0; i < dlen; i++) {
231 	ch = sptr[i];
232 	switch (state)
233 	{
234 	case -4: if (ch == 'E') state++; else return 0; break;
235 	case -3: if (ch == 'P') state++; else return 0; break;
236 	case -2: if (ch == 'R') state++; else return 0; break;
237 	case -1: if (ch == 'T') state++; else return 0; break;
238 
239 	case 0:
240 	    if (!isspace(ch)) {
241 		delim = ch;
242 		state++;
243 	    }
244 	    break;
245 	case 1:
246 	    if (ch == '1')	/* IPv4 address */
247 		state++;
248 	    else
249 		return 0;
250 	    break;
251 	case 2:
252 	    if (ch == delim)
253 		state++;
254 	    else
255 		return 0;
256 	    break;
257 	case 3: case 5: case 7: case 9:
258 	    if (isdigit(ch)) {
259 		octet = ch - '0';
260 		state++;
261 	    } else
262 		return 0;
263 	    break;
264 	case 4: case 6: case 8: case 10:
265 	    if (isdigit(ch))
266 		octet = 10 * octet + ch - '0';
267             else if (ch == '.' || state == 10) {
268 		addr = (addr << 8) + octet;
269 		state++;
270 	    } else
271 		return 0;
272 	    break;
273 	case 11:
274 	    if (isdigit(ch)) {
275 		port = ch - '0';
276 		state++;
277 	    } else
278 		return 0;
279 	    break;
280 	case 12:
281 	    if (isdigit(ch))
282 		port = 10 * port + ch - '0';
283 	    else if (ch == delim)
284 		state++;
285 	    else
286 		return 0;
287 	    break;
288 	}
289     }
290 
291     if (state == 13) {
292 	true_addr.s_addr = htonl(addr);
293 	true_port = port;
294 	return 1;
295     } else
296 	return 0;
297 }
298 
299 static int
300 ParseFtp227Reply(char *sptr, int dlen)
301 {
302     char ch;
303     int i, state;
304     u_int32_t addr;
305     u_short port;
306     u_int8_t octet;
307 
308     /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
309 
310     /* Return if data length is too short. */
311     if (dlen < 17)
312 	return 0;
313 
314     addr = port = octet = 0;
315 
316     state = -3;
317     for (i = 0; i < dlen; i++) {
318         ch = sptr[i];
319         switch (state)
320         {
321         case -3: if (ch == '2') state++; else return 0; break;
322         case -2: if (ch == '2') state++; else return 0; break;
323         case -1: if (ch == '7') state++; else return 0; break;
324 
325 	case 0:
326 	    if (ch == '(')
327 		state++;
328 	    break;
329 	case 1: case 3: case 5: case 7: case 9: case 11:
330 	    if (isdigit(ch)) {
331 		octet = ch - '0';
332 		state++;
333 	    } else
334 		return 0;
335 	    break;
336 	case 2: case 4: case 6: case 8:
337 	    if (isdigit(ch))
338 		octet = 10 * octet + ch - '0';
339             else if (ch == ',') {
340 		addr = (addr << 8) + octet;
341 		state++;
342 	    } else
343 		return 0;
344 	    break;
345 	case 10: case 12:
346 	    if (isdigit(ch))
347 		octet = 10 * octet + ch - '0';
348 	    else if (ch == ',' || (state == 12 && ch == ')')) {
349 		port = (port << 8) + octet;
350 		state++;
351 	    } else
352 		return 0;
353 	    break;
354 	}
355     }
356 
357     if (state == 13) {
358         true_port = port;
359         true_addr.s_addr = htonl(addr);
360 	return 1;
361     } else
362 	return 0;
363 }
364 
365 static int
366 ParseFtp229Reply(char *sptr, int dlen)
367 {
368     char ch, delim;
369     int i, state;
370     u_short port;
371 
372     /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
373 
374     /* Return if data length is too short. */
375     if (dlen < 11)
376 	return 0;
377 
378     port = 0;
379     delim = '|';			/* XXX gcc -Wuninitialized */
380 
381     state = -3;
382     for (i = 0; i < dlen; i++) {
383 	ch = sptr[i];
384 	switch (state)
385 	{
386 	case -3: if (ch == '2') state++; else return 0; break;
387 	case -2: if (ch == '2') state++; else return 0; break;
388 	case -1: if (ch == '9') state++; else return 0; break;
389 
390 	case 0:
391 	    if (ch == '(')
392 		state++;
393 	    break;
394 	case 1:
395 	    delim = ch;
396 	    state++;
397 	    break;
398 	case 2: case 3:
399 	    if (ch == delim)
400 		state++;
401 	    else
402 		return 0;
403 	    break;
404 	case 4:
405 	    if (isdigit(ch)) {
406 		port = ch - '0';
407 		state++;
408 	    } else
409 		return 0;
410 	    break;
411 	case 5:
412 	    if (isdigit(ch))
413 		port = 10 * port + ch - '0';
414 	    else if (ch == delim)
415 		state++;
416 	    else
417 		return 0;
418 	    break;
419 	case 6:
420 	    if (ch == ')')
421 		state++;
422 	    else
423 		return 0;
424 	    break;
425 	}
426     }
427 
428     if (state == 7) {
429 	true_port = port;
430 	return 1;
431     } else
432 	return 0;
433 }
434 
435 static void
436 NewFtpMessage(struct ip *pip,
437               struct alias_link *link,
438               int maxpacketsize,
439               int ftp_message_type)
440 {
441     struct alias_link *ftp_link;
442 
443 /* Security checks. */
444     if (ftp_message_type != FTP_229_REPLY &&
445 	pip->ip_src.s_addr != true_addr.s_addr)
446 	return;
447 
448     if (true_port < IPPORT_RESERVED)
449 	return;
450 
451 /* Establish link to address and port found in FTP control message. */
452     ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
453                              htons(true_port), 0, IPPROTO_TCP, 1);
454 
455     if (ftp_link != NULL)
456     {
457         int slen, hlen, tlen, dlen;
458         struct tcphdr *tc;
459 
460 #ifndef NO_FW_PUNCH
461 	if (ftp_message_type == FTP_PORT_COMMAND ||
462 	    ftp_message_type == FTP_EPRT_COMMAND) {
463 	    /* Punch hole in firewall */
464 	    PunchFWHole(ftp_link);
465 	}
466 #endif
467 
468 /* Calculate data length of TCP packet */
469         tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
470         hlen = (pip->ip_hl + tc->th_off) << 2;
471         tlen = ntohs(pip->ip_len);
472         dlen = tlen - hlen;
473 
474 /* Create new FTP message. */
475         {
476             char stemp[MAX_MESSAGE_SIZE + 1];
477             char *sptr;
478             u_short alias_port;
479             u_char *ptr;
480             int a1, a2, a3, a4, p1, p2;
481             struct in_addr alias_address;
482 
483 /* Decompose alias address into quad format */
484             alias_address = GetAliasAddress(link);
485             ptr = (u_char *) &alias_address.s_addr;
486             a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
487 
488 	    alias_port = GetAliasPort(ftp_link);
489 
490 	    switch (ftp_message_type)
491 	    {
492 	    case FTP_PORT_COMMAND:
493 	    case FTP_227_REPLY:
494 		/* Decompose alias port into pair format. */
495 		ptr = (char *) &alias_port;
496 		p1 = *ptr++; p2=*ptr;
497 
498 		if (ftp_message_type == FTP_PORT_COMMAND) {
499 		    /* Generate PORT command string. */
500 		    sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
501 			    a1,a2,a3,a4,p1,p2);
502 		} else {
503 		    /* Generate 227 reply string. */
504 		    sprintf(stemp,
505 			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
506 			    a1,a2,a3,a4,p1,p2);
507 		}
508 		break;
509 	    case FTP_EPRT_COMMAND:
510 		/* Generate EPRT command string. */
511 		sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
512 			a1,a2,a3,a4,ntohs(alias_port));
513 		break;
514 	    case FTP_229_REPLY:
515 		/* Generate 229 reply string. */
516 		sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
517 			ntohs(alias_port));
518 		break;
519 	    }
520 
521 /* Save string length for IP header modification */
522             slen = strlen(stemp);
523 
524 /* Copy modified buffer into IP packet. */
525             sptr = (char *) pip; sptr += hlen;
526             strncpy(sptr, stemp, maxpacketsize-hlen);
527         }
528 
529 /* Save information regarding modified seq and ack numbers */
530         {
531             int delta;
532 
533             SetAckModified(link);
534             delta = GetDeltaSeqOut(pip, link);
535             AddSeq(pip, link, delta+slen-dlen);
536         }
537 
538 /* Revise IP header */
539         {
540             u_short new_len;
541 
542             new_len = htons(hlen + slen);
543             DifferentialChecksum(&pip->ip_sum,
544                                  &new_len,
545                                  &pip->ip_len,
546                                  1);
547             pip->ip_len = new_len;
548         }
549 
550 /* Compute TCP checksum for revised packet */
551         tc->th_sum = 0;
552         tc->th_sum = TcpChecksum(pip);
553     }
554     else
555     {
556 #ifdef DEBUG
557         fprintf(stderr,
558         "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
559 #endif
560     }
561 }
562