xref: /freebsd/sys/netinet/libalias/alias_ftp.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
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 enum ftp_message_type {
63     FTP_PORT_COMMAND,
64     FTP_EPRT_COMMAND,
65     FTP_227_REPLY,
66     FTP_229_REPLY,
67     FTP_UNKNOWN_MESSAGE
68 };
69 
70 static int ParseFtpPortCommand(char *, int);
71 static int ParseFtpEprtCommand(char *, int);
72 static int ParseFtp227Reply(char *, int);
73 static int ParseFtp229Reply(char *, int);
74 static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
75 
76 static struct in_addr true_addr;	/* in network byte order. */
77 static u_short true_port;		/* in host byte order. */
78 
79 void
80 AliasHandleFtpOut(
81 struct ip *pip,	  /* IP packet to examine/patch */
82 struct alias_link *link, /* The link to go through (aliased port) */
83 int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
84 {
85     int hlen, tlen, dlen;
86     char *sptr;
87     struct tcphdr *tc;
88     int ftp_message_type;
89 
90 /* Calculate data length of TCP packet */
91     tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
92     hlen = (pip->ip_hl + tc->th_off) << 2;
93     tlen = ntohs(pip->ip_len);
94     dlen = tlen - hlen;
95 
96 /* Place string pointer and beginning of data */
97     sptr = (char *) pip;
98     sptr += hlen;
99 
100 /*
101  * Check that data length is not too long and previous message was
102  * properly terminated with CRLF.
103  */
104     if (dlen <= MAX_MESSAGE_SIZE && GetLastLineCrlfTermed(link)) {
105 	ftp_message_type = FTP_UNKNOWN_MESSAGE;
106 
107 	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
108 /*
109  * When aliasing a client, check for the PORT/EPRT command.
110  */
111 	    if (ParseFtpPortCommand(sptr, dlen))
112 		ftp_message_type = FTP_PORT_COMMAND;
113 	    else if (ParseFtpEprtCommand(sptr, dlen))
114 		ftp_message_type = FTP_EPRT_COMMAND;
115 	} else {
116 /*
117  * When aliasing a server, check for the 227/229 reply.
118  */
119 	    if (ParseFtp227Reply(sptr, dlen))
120 		ftp_message_type = FTP_227_REPLY;
121 	    else if (ParseFtp229Reply(sptr, dlen))
122 		ftp_message_type = FTP_229_REPLY;
123 	}
124 
125 	if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
126 	    NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
127     }
128 
129 /* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
130 
131     if (dlen) {                  /* only if there's data */
132       sptr = (char *) pip; 	 /* start over at beginning */
133       tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
134       SetLastLineCrlfTermed(link,
135 			    (sptr[tlen-2] == '\r') && (sptr[tlen-1] == '\n'));
136     }
137 }
138 
139 static int
140 ParseFtpPortCommand(char *sptr, int dlen)
141 {
142     char ch;
143     int i, state;
144     u_int32_t addr;
145     u_short port;
146     u_int8_t octet;
147 
148     /* Format: "PORT A,D,D,R,PO,RT". */
149 
150     /* Return if data length is too short. */
151     if (dlen < 18)
152 	return 0;
153 
154     addr = port = octet = 0;
155     state = -4;
156     for (i = 0; i < dlen; i++) {
157 	ch = sptr[i];
158 	switch (state) {
159 	case -4: if (ch == 'P') state++; else return 0; break;
160 	case -3: if (ch == 'O') state++; else return 0; break;
161 	case -2: if (ch == 'R') state++; else return 0; break;
162 	case -1: if (ch == 'T') state++; else return 0; break;
163 
164 	case 0:
165 	    if (isspace(ch))
166 		break;
167 	    else
168 		state++;
169 	case 1: case 3: case 5: case 7: case 9: case 11:
170 	    if (isdigit(ch)) {
171 		octet = ch - '0';
172 		state++;
173 	    } else
174 		return 0;
175 	    break;
176 	case 2: case 4: case 6: case 8:
177 	    if (isdigit(ch))
178 		octet = 10 * octet + ch - '0';
179             else if (ch == ',') {
180 		addr = (addr << 8) + octet;
181 		state++;
182 	    } else
183 		return 0;
184 	    break;
185 	case 10: case 12:
186 	    if (isdigit(ch))
187 		octet = 10 * octet + ch - '0';
188 	    else if (ch == ',' || state == 12) {
189 		port = (port << 8) + octet;
190 		state++;
191 	    } else
192 		return 0;
193 	    break;
194 	}
195     }
196 
197     if (state == 13) {
198 	true_addr.s_addr = htonl(addr);
199 	true_port = port;
200 	return 1;
201     } else
202 	return 0;
203 }
204 
205 static int
206 ParseFtpEprtCommand(char *sptr, int dlen)
207 {
208     char ch, delim;
209     int i, state;
210     u_int32_t addr;
211     u_short port;
212     u_int8_t octet;
213 
214     /* Format: "EPRT |1|A.D.D.R|PORT|". */
215 
216     /* Return if data length is too short. */
217     if (dlen < 18)
218 	return 0;
219 
220     addr = port = octet = 0;
221     delim = '|';			/* XXX gcc -Wuninitialized */
222     state = -4;
223     for (i = 0; i < dlen; i++) {
224 	ch = sptr[i];
225 	switch (state)
226 	{
227 	case -4: if (ch == 'E') state++; else return 0; break;
228 	case -3: if (ch == 'P') state++; else return 0; break;
229 	case -2: if (ch == 'R') state++; else return 0; break;
230 	case -1: if (ch == 'T') state++; else return 0; break;
231 
232 	case 0:
233 	    if (!isspace(ch)) {
234 		delim = ch;
235 		state++;
236 	    }
237 	    break;
238 	case 1:
239 	    if (ch == '1')	/* IPv4 address */
240 		state++;
241 	    else
242 		return 0;
243 	    break;
244 	case 2:
245 	    if (ch == delim)
246 		state++;
247 	    else
248 		return 0;
249 	    break;
250 	case 3: case 5: case 7: case 9:
251 	    if (isdigit(ch)) {
252 		octet = ch - '0';
253 		state++;
254 	    } else
255 		return 0;
256 	    break;
257 	case 4: case 6: case 8: case 10:
258 	    if (isdigit(ch))
259 		octet = 10 * octet + ch - '0';
260             else if (ch == '.' || state == 10) {
261 		addr = (addr << 8) + octet;
262 		state++;
263 	    } else
264 		return 0;
265 	    break;
266 	case 11:
267 	    if (isdigit(ch)) {
268 		port = ch - '0';
269 		state++;
270 	    } else
271 		return 0;
272 	    break;
273 	case 12:
274 	    if (isdigit(ch))
275 		port = 10 * port + ch - '0';
276 	    else if (ch == delim)
277 		state++;
278 	    else
279 		return 0;
280 	    break;
281 	}
282     }
283 
284     if (state == 13) {
285 	true_addr.s_addr = htonl(addr);
286 	true_port = port;
287 	return 1;
288     } else
289 	return 0;
290 }
291 
292 static int
293 ParseFtp227Reply(char *sptr, int dlen)
294 {
295     char ch;
296     int i, state;
297     u_int32_t addr;
298     u_short port;
299     u_int8_t octet;
300 
301     /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
302 
303     /* Return if data length is too short. */
304     if (dlen < 17)
305 	return 0;
306 
307     addr = port = octet = 0;
308 
309     state = -3;
310     for (i = 0; i < dlen; i++) {
311         ch = sptr[i];
312         switch (state)
313         {
314         case -3: if (ch == '2') state++; else return 0; break;
315         case -2: if (ch == '2') state++; else return 0; break;
316         case -1: if (ch == '7') state++; else return 0; break;
317 
318 	case 0:
319 	    if (ch == '(')
320 		state++;
321 	    break;
322 	case 1: case 3: case 5: case 7: case 9: case 11:
323 	    if (isdigit(ch)) {
324 		octet = ch - '0';
325 		state++;
326 	    } else
327 		return 0;
328 	    break;
329 	case 2: case 4: case 6: case 8:
330 	    if (isdigit(ch))
331 		octet = 10 * octet + ch - '0';
332             else if (ch == ',') {
333 		addr = (addr << 8) + octet;
334 		state++;
335 	    } else
336 		return 0;
337 	    break;
338 	case 10: case 12:
339 	    if (isdigit(ch))
340 		octet = 10 * octet + ch - '0';
341 	    else if (ch == ',' || (state == 12 && ch == ')')) {
342 		port = (port << 8) + octet;
343 		state++;
344 	    } else
345 		return 0;
346 	    break;
347 	}
348     }
349 
350     if (state == 13) {
351         true_port = port;
352         true_addr.s_addr = htonl(addr);
353 	return 1;
354     } else
355 	return 0;
356 }
357 
358 static int
359 ParseFtp229Reply(char *sptr, int dlen)
360 {
361     char ch, delim;
362     int i, state;
363     u_short port;
364 
365     /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
366 
367     /* Return if data length is too short. */
368     if (dlen < 11)
369 	return 0;
370 
371     port = 0;
372     delim = '|';			/* XXX gcc -Wuninitialized */
373 
374     state = -3;
375     for (i = 0; i < dlen; i++) {
376 	ch = sptr[i];
377 	switch (state)
378 	{
379 	case -3: if (ch == '2') state++; else return 0; break;
380 	case -2: if (ch == '2') state++; else return 0; break;
381 	case -1: if (ch == '9') state++; else return 0; break;
382 
383 	case 0:
384 	    if (ch == '(')
385 		state++;
386 	    break;
387 	case 1:
388 	    delim = ch;
389 	    state++;
390 	    break;
391 	case 2: case 3:
392 	    if (ch == delim)
393 		state++;
394 	    else
395 		return 0;
396 	    break;
397 	case 4:
398 	    if (isdigit(ch)) {
399 		port = ch - '0';
400 		state++;
401 	    } else
402 		return 0;
403 	    break;
404 	case 5:
405 	    if (isdigit(ch))
406 		port = 10 * port + ch - '0';
407 	    else if (ch == delim)
408 		state++;
409 	    else
410 		return 0;
411 	    break;
412 	case 6:
413 	    if (ch == ')')
414 		state++;
415 	    else
416 		return 0;
417 	    break;
418 	}
419     }
420 
421     if (state == 7) {
422 	true_port = port;
423 	return 1;
424     } else
425 	return 0;
426 }
427 
428 static void
429 NewFtpMessage(struct ip *pip,
430               struct alias_link *link,
431               int maxpacketsize,
432               int ftp_message_type)
433 {
434     struct alias_link *ftp_link;
435 
436 /* Security checks. */
437     if (ftp_message_type != FTP_229_REPLY &&
438 	pip->ip_src.s_addr != true_addr.s_addr)
439 	return;
440 
441     if (true_port < IPPORT_RESERVED)
442 	return;
443 
444 /* Establish link to address and port found in FTP control message. */
445     ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
446                              htons(true_port), 0, IPPROTO_TCP, 1);
447 
448     if (ftp_link != NULL)
449     {
450         int slen, hlen, tlen, dlen;
451         struct tcphdr *tc;
452 
453 #ifndef NO_FW_PUNCH
454 	if (ftp_message_type == FTP_PORT_COMMAND ||
455 	    ftp_message_type == FTP_EPRT_COMMAND) {
456 	    /* Punch hole in firewall */
457 	    PunchFWHole(ftp_link);
458 	}
459 #endif
460 
461 /* Calculate data length of TCP packet */
462         tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
463         hlen = (pip->ip_hl + tc->th_off) << 2;
464         tlen = ntohs(pip->ip_len);
465         dlen = tlen - hlen;
466 
467 /* Create new FTP message. */
468         {
469             char stemp[MAX_MESSAGE_SIZE + 1];
470             char *sptr;
471             u_short alias_port;
472             u_char *ptr;
473             int a1, a2, a3, a4, p1, p2;
474             struct in_addr alias_address;
475 
476 /* Decompose alias address into quad format */
477             alias_address = GetAliasAddress(link);
478             ptr = (u_char *) &alias_address.s_addr;
479             a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
480 
481 	    alias_port = GetAliasPort(ftp_link);
482 
483 	    switch (ftp_message_type)
484 	    {
485 	    case FTP_PORT_COMMAND:
486 	    case FTP_227_REPLY:
487 		/* Decompose alias port into pair format. */
488 		ptr = (char *) &alias_port;
489 		p1 = *ptr++; p2=*ptr;
490 
491 		if (ftp_message_type == FTP_PORT_COMMAND) {
492 		    /* Generate PORT command string. */
493 		    sprintf(stemp, "PORT %d,%d,%d,%d,%d,%d\r\n",
494 			    a1,a2,a3,a4,p1,p2);
495 		} else {
496 		    /* Generate 227 reply string. */
497 		    sprintf(stemp,
498 			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
499 			    a1,a2,a3,a4,p1,p2);
500 		}
501 		break;
502 	    case FTP_EPRT_COMMAND:
503 		/* Generate EPRT command string. */
504 		sprintf(stemp, "EPRT |1|%d.%d.%d.%d|%d|\r\n",
505 			a1,a2,a3,a4,ntohs(alias_port));
506 		break;
507 	    case FTP_229_REPLY:
508 		/* Generate 229 reply string. */
509 		sprintf(stemp, "229 Entering Extended Passive Mode (|||%d|)\r\n",
510 			ntohs(alias_port));
511 		break;
512 	    }
513 
514 /* Save string length for IP header modification */
515             slen = strlen(stemp);
516 
517 /* Copy modified buffer into IP packet. */
518             sptr = (char *) pip; sptr += hlen;
519             strncpy(sptr, stemp, maxpacketsize-hlen);
520         }
521 
522 /* Save information regarding modified seq and ack numbers */
523         {
524             int delta;
525 
526             SetAckModified(link);
527             delta = GetDeltaSeqOut(pip, link);
528             AddSeq(pip, link, delta+slen-dlen);
529         }
530 
531 /* Revise IP header */
532         {
533             u_short new_len;
534 
535             new_len = htons(hlen + slen);
536             DifferentialChecksum(&pip->ip_sum,
537                                  &new_len,
538                                  &pip->ip_len,
539                                  1);
540             pip->ip_len = new_len;
541         }
542 
543 /* Compute TCP checksum for revised packet */
544         tc->th_sum = 0;
545         tc->th_sum = TcpChecksum(pip);
546     }
547     else
548     {
549 #ifdef DEBUG
550         fprintf(stderr,
551         "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
552 #endif
553     }
554 }
555