1/*
2 * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29/*-
30 * Copyright (c) 2001 Charles Mott <cmott@scientech.com>
31 * All rights reserved.
32 *
33 * Redistribution and use in source and binary forms, with or without
34 * modification, are permitted provided that the following conditions
35 * are met:
36 * 1. Redistributions of source code must retain the above copyright
37 *    notice, this list of conditions and the following disclaimer.
38 * 2. Redistributions in binary form must reproduce the above copyright
39 *    notice, this list of conditions and the following disclaimer in the
40 *    documentation and/or other materials provided with the distribution.
41 *
42 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
43 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
45 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
46 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
48 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
49 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
50 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
51 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 *
54 * Based upon:
55 * $FreeBSD: src/lib/libalias/alias_ftp.c,v 1.5.2.4 2001/08/21 03:50:25 brian Exp $
56 */
57
58/*
59    Alias_ftp.c performs special processing for FTP sessions under
60    TCP.  Specifically, when a PORT/EPRT command from the client
61    side or 227/229 reply from the server is sent, it is intercepted
62    and modified.  The address is changed to the gateway machine
63    and an aliasing port is used.
64
65    For this routine to work, the message must fit entirely into a
66    single TCP packet.  This is typically the case, but exceptions
67    can easily be envisioned under the actual specifications.
68
69    Probably the most troubling aspect of the approach taken here is
70    that the new message will typically be a different length, and
71    this causes a certain amount of bookkeeping to keep track of the
72    changes of sequence and acknowledgment numbers, since the client
73    machine is totally unaware of the modification to the TCP stream.
74
75
76    References: RFC 959, RFC 2428.
77
78    Initial version:  August, 1996  (cjm)
79
80    Version 1.6
81         Brian Somers and Martin Renters identified an IP checksum
82         error for modified IP packets.
83
84    Version 1.7:  January 9, 1996 (cjm)
85         Differential checksum computation for change
86         in IP packet length.
87
88    Version 2.1:  May, 1997 (cjm)
89         Very minor changes to conform with
90         local/global/function naming conventions
91         within the packet aliasing module.
92
93    Version 3.1:  May, 2000 (eds)
94	 Add support for passive mode, alias the 227 replies.
95
96    See HISTORY file for record of revisions.
97*/
98
99/* Includes */
100#include <ctype.h>
101#include <stdio.h>
102#include <string.h>
103#include <sys/types.h>
104#include <netinet/in_systm.h>
105#include <netinet/in.h>
106#include <netinet/ip.h>
107#include <netinet/tcp.h>
108
109#include "alias_local.h"
110
111#define FTP_CONTROL_PORT_NUMBER 21
112#define MAX_MESSAGE_SIZE	128
113
114enum ftp_message_type {
115    FTP_PORT_COMMAND,
116    FTP_EPRT_COMMAND,
117    FTP_227_REPLY,
118    FTP_229_REPLY,
119    FTP_UNKNOWN_MESSAGE
120};
121
122static int ParseFtpPortCommand(char *, int);
123static int ParseFtpEprtCommand(char *, int);
124static int ParseFtp227Reply(char *, int);
125static int ParseFtp229Reply(char *, int);
126static void NewFtpMessage(struct ip *, struct alias_link *, int, int);
127
128static struct in_addr true_addr;	/* in network byte order. */
129static u_short true_port;		/* in host byte order. */
130
131void
132AliasHandleFtpOut(
133struct ip *pip,	  /* IP packet to examine/patch */
134struct alias_link *link, /* The link to go through (aliased port) */
135int maxpacketsize  /* The maximum size this packet can grow to (including headers) */)
136{
137    int hlen, tlen, dlen;
138    char *sptr;
139    struct tcphdr *tc;
140    int ftp_message_type;
141
142/* Calculate data length of TCP packet */
143    tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
144    hlen = (pip->ip_hl + tc->th_off) << 2;
145    tlen = ntohs(pip->ip_len);
146    dlen = tlen - hlen;
147
148/* Place string pointer and beginning of data */
149    sptr = (char *) pip;
150    sptr += hlen;
151
152/*
153 * Check that data length is not too long and previous message was
154 * properly terminated with CRLF.
155 */
156    if (dlen <= MAX_MESSAGE_SIZE && GetLastLineCrlfTermed(link)) {
157	ftp_message_type = FTP_UNKNOWN_MESSAGE;
158
159	if (ntohs(tc->th_dport) == FTP_CONTROL_PORT_NUMBER) {
160/*
161 * When aliasing a client, check for the PORT/EPRT command.
162 */
163	    if (ParseFtpPortCommand(sptr, dlen))
164		ftp_message_type = FTP_PORT_COMMAND;
165	    else if (ParseFtpEprtCommand(sptr, dlen))
166		ftp_message_type = FTP_EPRT_COMMAND;
167	} else {
168/*
169 * When aliasing a server, check for the 227/229 reply.
170 */
171	    if (ParseFtp227Reply(sptr, dlen))
172		ftp_message_type = FTP_227_REPLY;
173	    else if (ParseFtp229Reply(sptr, dlen))
174		ftp_message_type = FTP_229_REPLY;
175	}
176
177	if (ftp_message_type != FTP_UNKNOWN_MESSAGE)
178	    NewFtpMessage(pip, link, maxpacketsize, ftp_message_type);
179    }
180
181/* Track the msgs which are CRLF term'd for PORT/PASV FW breach */
182
183    if (dlen) {                  /* only if there's data */
184      sptr = (char *) pip; 	 /* start over at beginning */
185      tlen = ntohs(pip->ip_len); /* recalc tlen, pkt may have grown */
186      SetLastLineCrlfTermed(link,
187			    (sptr[tlen-2] == '\r') && (sptr[tlen-1] == '\n'));
188    }
189}
190
191static int
192ParseFtpPortCommand(char *sptr, int dlen)
193{
194    char ch;
195    int i, state;
196    u_int32_t addr;
197    u_short port;
198    u_int8_t octet;
199
200    /* Format: "PORT A,D,D,R,PO,RT". */
201
202    /* Return if data length is too short. */
203    if (dlen < 18)
204	return 0;
205
206    addr = port = octet = 0;
207    state = -4;
208    for (i = 0; i < dlen; i++) {
209	ch = sptr[i];
210	switch (state) {
211	case -4: if (ch == 'P') state++; else return 0; break;
212	case -3: if (ch == 'O') state++; else return 0; break;
213	case -2: if (ch == 'R') state++; else return 0; break;
214	case -1: if (ch == 'T') state++; else return 0; break;
215
216	case 0:
217	    if (isspace(ch))
218		break;
219	    else
220		state++;
221	case 1: case 3: case 5: case 7: case 9: case 11:
222	    if (isdigit(ch)) {
223		octet = ch - '0';
224		state++;
225	    } else
226		return 0;
227	    break;
228	case 2: case 4: case 6: case 8:
229	    if (isdigit(ch))
230		octet = 10 * octet + ch - '0';
231            else if (ch == ',') {
232		addr = (addr << 8) + octet;
233		state++;
234	    } else
235		return 0;
236	    break;
237	case 10: case 12:
238	    if (isdigit(ch))
239		octet = 10 * octet + ch - '0';
240	    else if (ch == ',' || state == 12) {
241		port = (port << 8) + octet;
242		state++;
243	    } else
244		return 0;
245	    break;
246	}
247    }
248
249    if (state == 13) {
250	true_addr.s_addr = htonl(addr);
251	true_port = port;
252	return 1;
253    } else
254	return 0;
255}
256
257static int
258ParseFtpEprtCommand(char *sptr, int dlen)
259{
260    char ch, delim;
261    int i, state;
262    u_int32_t addr;
263    u_short port;
264    u_int8_t octet;
265
266    /* Format: "EPRT |1|A.D.D.R|PORT|". */
267
268    /* Return if data length is too short. */
269    if (dlen < 18)
270	return 0;
271
272    addr = port = octet = 0;
273    delim = '|';			/* XXX gcc -Wuninitialized */
274    state = -4;
275    for (i = 0; i < dlen; i++) {
276	ch = sptr[i];
277	switch (state)
278	{
279	case -4: if (ch == 'E') state++; else return 0; break;
280	case -3: if (ch == 'P') state++; else return 0; break;
281	case -2: if (ch == 'R') state++; else return 0; break;
282	case -1: if (ch == 'T') state++; else return 0; break;
283
284	case 0:
285	    if (!isspace(ch)) {
286		delim = ch;
287		state++;
288	    }
289	    break;
290	case 1:
291	    if (ch == '1')	/* IPv4 address */
292		state++;
293	    else
294		return 0;
295	    break;
296	case 2:
297	    if (ch == delim)
298		state++;
299	    else
300		return 0;
301	    break;
302	case 3: case 5: case 7: case 9:
303	    if (isdigit(ch)) {
304		octet = ch - '0';
305		state++;
306	    } else
307		return 0;
308	    break;
309	case 4: case 6: case 8: case 10:
310	    if (isdigit(ch))
311		octet = 10 * octet + ch - '0';
312            else if (ch == '.' || state == 10) {
313		addr = (addr << 8) + octet;
314		state++;
315	    } else
316		return 0;
317	    break;
318	case 11:
319	    if (isdigit(ch)) {
320		port = ch - '0';
321		state++;
322	    } else
323		return 0;
324	    break;
325	case 12:
326	    if (isdigit(ch))
327		port = 10 * port + ch - '0';
328	    else if (ch == delim)
329		state++;
330	    else
331		return 0;
332	    break;
333	}
334    }
335
336    if (state == 13) {
337	true_addr.s_addr = htonl(addr);
338	true_port = port;
339	return 1;
340    } else
341	return 0;
342}
343
344static int
345ParseFtp227Reply(char *sptr, int dlen)
346{
347    char ch;
348    int i, state;
349    u_int32_t addr;
350    u_short port;
351    u_int8_t octet;
352
353    /* Format: "227 Entering Passive Mode (A,D,D,R,PO,RT)" */
354
355    /* Return if data length is too short. */
356    if (dlen < 17)
357	return 0;
358
359    addr = port = octet = 0;
360
361    state = -3;
362    for (i = 0; i < dlen; i++) {
363        ch = sptr[i];
364        switch (state)
365        {
366        case -3: if (ch == '2') state++; else return 0; break;
367        case -2: if (ch == '2') state++; else return 0; break;
368        case -1: if (ch == '7') state++; else return 0; break;
369
370	case 0:
371	    if (ch == '(')
372		state++;
373	    break;
374	case 1: case 3: case 5: case 7: case 9: case 11:
375	    if (isdigit(ch)) {
376		octet = ch - '0';
377		state++;
378	    } else
379		return 0;
380	    break;
381	case 2: case 4: case 6: case 8:
382	    if (isdigit(ch))
383		octet = 10 * octet + ch - '0';
384            else if (ch == ',') {
385		addr = (addr << 8) + octet;
386		state++;
387	    } else
388		return 0;
389	    break;
390	case 10: case 12:
391	    if (isdigit(ch))
392		octet = 10 * octet + ch - '0';
393	    else if (ch == ',' || (state == 12 && ch == ')')) {
394		port = (port << 8) + octet;
395		state++;
396	    } else
397		return 0;
398	    break;
399	}
400    }
401
402    if (state == 13) {
403        true_port = port;
404        true_addr.s_addr = htonl(addr);
405	return 1;
406    } else
407	return 0;
408}
409
410static int
411ParseFtp229Reply(char *sptr, int dlen)
412{
413    char ch, delim;
414    int i, state;
415    u_short port;
416
417    /* Format: "229 Entering Extended Passive Mode (|||PORT|)" */
418
419    /* Return if data length is too short. */
420    if (dlen < 11)
421	return 0;
422
423    port = 0;
424    delim = '|';			/* XXX gcc -Wuninitialized */
425
426    state = -3;
427    for (i = 0; i < dlen; i++) {
428	ch = sptr[i];
429	switch (state)
430	{
431	case -3: if (ch == '2') state++; else return 0; break;
432	case -2: if (ch == '2') state++; else return 0; break;
433	case -1: if (ch == '9') state++; else return 0; break;
434
435	case 0:
436	    if (ch == '(')
437		state++;
438	    break;
439	case 1:
440	    delim = ch;
441	    state++;
442	    break;
443	case 2: case 3:
444	    if (ch == delim)
445		state++;
446	    else
447		return 0;
448	    break;
449	case 4:
450	    if (isdigit(ch)) {
451		port = ch - '0';
452		state++;
453	    } else
454		return 0;
455	    break;
456	case 5:
457	    if (isdigit(ch))
458		port = 10 * port + ch - '0';
459	    else if (ch == delim)
460		state++;
461	    else
462		return 0;
463	    break;
464	case 6:
465	    if (ch == ')')
466		state++;
467	    else
468		return 0;
469	    break;
470	}
471    }
472
473    if (state == 7) {
474	true_port = port;
475	return 1;
476    } else
477	return 0;
478}
479
480static void
481NewFtpMessage(struct ip *pip,
482              struct alias_link *link,
483              int maxpacketsize,
484              int ftp_message_type)
485{
486    struct alias_link *ftp_link;
487
488/* Security checks. */
489    if (ftp_message_type != FTP_229_REPLY &&
490	pip->ip_src.s_addr != true_addr.s_addr)
491	return;
492
493    if (true_port < IPPORT_RESERVED)
494	return;
495
496/* Establish link to address and port found in FTP control message. */
497    ftp_link = FindUdpTcpOut(true_addr, GetDestAddress(link),
498                             htons(true_port), 0, IPPROTO_TCP, 1);
499
500    if (ftp_link != NULL)
501    {
502        int slen, hlen, tlen, dlen;
503        struct tcphdr *tc;
504
505#ifndef NO_FW_PUNCH
506	if (ftp_message_type == FTP_PORT_COMMAND ||
507	    ftp_message_type == FTP_EPRT_COMMAND) {
508	    /* Punch hole in firewall */
509	    PunchFWHole(ftp_link);
510	}
511#endif
512
513/* Calculate data length of TCP packet */
514        tc = (struct tcphdr *) ((char *) pip + (pip->ip_hl << 2));
515        hlen = (pip->ip_hl + tc->th_off) << 2;
516        tlen = ntohs(pip->ip_len);
517        dlen = tlen - hlen;
518
519/* Create new FTP message. */
520        {
521            char stemp[MAX_MESSAGE_SIZE + 1];
522            char *sptr;
523            u_short alias_port;
524            u_char *ptr;
525            int a1, a2, a3, a4, p1, p2;
526            struct in_addr alias_address;
527
528/* Decompose alias address into quad format */
529            alias_address = GetAliasAddress(link);
530            ptr = (u_char *) &alias_address.s_addr;
531            a1 = *ptr++; a2=*ptr++; a3=*ptr++; a4=*ptr;
532
533	    alias_port = GetAliasPort(ftp_link);
534
535	    switch (ftp_message_type)
536	    {
537	    case FTP_PORT_COMMAND:
538	    case FTP_227_REPLY:
539		/* Decompose alias port into pair format. */
540		ptr = (u_char *) &alias_port;
541		p1 = *ptr++; p2=*ptr;
542
543		if (ftp_message_type == FTP_PORT_COMMAND) {
544		    /* Generate PORT command string. */
545		    snprintf(stemp, sizeof(stemp), "PORT %d,%d,%d,%d,%d,%d\r\n",
546			    a1,a2,a3,a4,p1,p2);
547		} else {
548		    /* Generate 227 reply string. */
549		    snprintf(stemp, sizeof(stemp),
550			    "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
551			    a1,a2,a3,a4,p1,p2);
552		}
553		break;
554	    case FTP_EPRT_COMMAND:
555		/* Generate EPRT command string. */
556		snprintf(stemp, sizeof(stemp), "EPRT |1|%d.%d.%d.%d|%d|\r\n",
557			a1,a2,a3,a4,ntohs(alias_port));
558		break;
559	    case FTP_229_REPLY:
560		/* Generate 229 reply string. */
561		snprintf(stemp, sizeof(stemp), "229 Entering Extended Passive Mode (|||%d|)\r\n",
562			ntohs(alias_port));
563		break;
564	    }
565
566/* Save string length for IP header modification */
567            slen = strlen(stemp);
568
569/* Copy modified buffer into IP packet. */
570            sptr = (char *) pip; sptr += hlen;
571            strncpy(sptr, stemp, maxpacketsize-hlen);
572        }
573
574/* Save information regarding modified seq and ack numbers */
575        {
576            int delta;
577
578            SetAckModified(link);
579            delta = GetDeltaSeqOut(pip, link);
580            AddSeq(pip, link, delta+slen-dlen);
581        }
582
583/* Revise IP header */
584        {
585            u_short new_len;
586
587            new_len = htons(hlen + slen);
588            DifferentialChecksum(&pip->ip_sum,
589                                 &new_len,
590                                 &pip->ip_len,
591                                 1);
592            pip->ip_len = new_len;
593        }
594
595/* Compute TCP checksum for revised packet */
596        tc->th_sum = 0;
597        tc->th_sum = TcpChecksum(pip);
598    }
599    else
600    {
601#ifdef DEBUG
602        fprintf(stderr,
603        "PacketAlias/HandleFtpOut: Cannot allocate FTP data port\n");
604#endif
605    }
606}
607