alias_irc.c revision 147623
177701Sbrian/*-
285964Sbrian * Copyright (c) 2001 Charles Mott <cm@linktel.net>
377701Sbrian * All rights reserved.
477701Sbrian *
577701Sbrian * Redistribution and use in source and binary forms, with or without
677701Sbrian * modification, are permitted provided that the following conditions
777701Sbrian * are met:
877701Sbrian * 1. Redistributions of source code must retain the above copyright
977701Sbrian *    notice, this list of conditions and the following disclaimer.
1077701Sbrian * 2. Redistributions in binary form must reproduce the above copyright
1177701Sbrian *    notice, this list of conditions and the following disclaimer in the
1277701Sbrian *    documentation and/or other materials provided with the distribution.
1377701Sbrian *
1477701Sbrian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1577701Sbrian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1677701Sbrian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1777701Sbrian * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1877701Sbrian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1977701Sbrian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2077701Sbrian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2177701Sbrian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2277701Sbrian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2377701Sbrian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2477701Sbrian * SUCH DAMAGE.
2577701Sbrian */
2677701Sbrian
2784195Sdillon#include <sys/cdefs.h>
2884195Sdillon__FBSDID("$FreeBSD: head/sys/netinet/libalias/alias_irc.c 147623 2005-06-27 07:36:02Z glebius $");
2984195Sdillon
3026026Sbrian/* Alias_irc.c intercepts packages contain IRC CTCP commands, and
3126026Sbrian	changes DCC commands to export a port on the aliasing host instead
3226026Sbrian	of an aliased host.
3326026Sbrian
3426026Sbrian    For this routine to work, the DCC command must fit entirely into a
3526026Sbrian    single TCP packet.  This will usually happen, but is not
3626026Sbrian    guaranteed.
3726026Sbrian
3826026Sbrian	 The interception is likely to change the length of the packet.
3926026Sbrian	 The handling of this is copied more-or-less verbatim from
4026026Sbrian	 ftp_alias.c
4126026Sbrian
4226026Sbrian	 Initial version: Eivind Eklund <perhaps@yes.no> (ee) 97-01-29
4326026Sbrian
44131612Sdes	 Version 2.1:  May, 1997 (cjm)
45131612Sdes	     Very minor changes to conform with
46131612Sdes	     local/global/function naming conventions
47131612Sdes	     withing the packet alising module.
4826026Sbrian*/
4926026Sbrian
5026026Sbrian/* Includes */
51145921Sglebius#ifdef _KERNEL
52145921Sglebius#include <sys/param.h>
53145921Sglebius#include <sys/libkern.h>
54145921Sglebius#include <sys/ctype.h>
55145921Sglebius#include <sys/limits.h>
56145921Sglebius#else
57145921Sglebius#include <sys/types.h>
5826026Sbrian#include <ctype.h>
5999207Sbrian#include <stdio.h>
6026026Sbrian#include <string.h>
61145921Sglebius#include <limits.h>
62145921Sglebius#endif
63145921Sglebius
6426026Sbrian#include <netinet/in_systm.h>
6526026Sbrian#include <netinet/in.h>
6626026Sbrian#include <netinet/ip.h>
6726026Sbrian#include <netinet/tcp.h>
6826026Sbrian
69145921Sglebius#ifdef _KERNEL
70145932Sglebius#include <netinet/libalias/alias.h>
71145921Sglebius#include <netinet/libalias/alias_local.h>
72145921Sglebius#else
7326026Sbrian#include "alias_local.h"
74145921Sglebius#endif
7526026Sbrian
7626026Sbrian/* Local defines */
7726026Sbrian#define DBprintf(a)
7826026Sbrian
7926026Sbrian
8026026Sbrianvoid
81127094SdesAliasHandleIrcOut(struct libalias *la,
82127094Sdes    struct ip *pip,		/* IP packet to examine */
83131614Sdes    struct alias_link *lnk,	/* Which link are we on? */
84127094Sdes    int maxsize			/* Maximum size of IP packet including
85127094Sdes				 * headers */
86127094Sdes)
8799207Sbrian{
88127094Sdes	int hlen, tlen, dlen;
89127094Sdes	struct in_addr true_addr;
90127094Sdes	u_short true_port;
91127094Sdes	char *sptr;
92127094Sdes	struct tcphdr *tc;
93127094Sdes	int i;			/* Iterator through the source */
9499207Sbrian
9526026Sbrian/* Calculate data length of TCP packet */
96131699Sdes	tc = (struct tcphdr *)ip_next(pip);
97127094Sdes	hlen = (pip->ip_hl + tc->th_off) << 2;
98127094Sdes	tlen = ntohs(pip->ip_len);
99127094Sdes	dlen = tlen - hlen;
10026026Sbrian
101127094Sdes	/*
102127094Sdes	 * Return if data length is too short - assume an entire PRIVMSG in
103127094Sdes	 * each packet.
104127094Sdes	 */
105131614Sdes	if (dlen < (int)sizeof(":A!a@n.n PRIVMSG A :aDCC 1 1a") - 1)
106127094Sdes		return;
10726026Sbrian
10826026Sbrian/* Place string pointer at beginning of data */
109127094Sdes	sptr = (char *)pip;
110127094Sdes	sptr += hlen;
111127094Sdes	maxsize -= hlen;	/* We're interested in maximum size of
112127094Sdes				 * data, not packet */
11326026Sbrian
114127094Sdes	/* Search for a CTCP command [Note 1] */
115127094Sdes	for (i = 0; i < dlen; i++) {
116127094Sdes		if (sptr[i] == '\001')
117127094Sdes			goto lFOUND_CTCP;
118127094Sdes	}
119127094Sdes	return;			/* No CTCP commands in  */
120127094Sdes	/* Handle CTCP commands - the buffer may have to be copied */
12126026SbrianlFOUND_CTCP:
122127094Sdes	{
123127094Sdes		char newpacket[65536];	/* Estimate of maximum packet size
124127094Sdes					 * :) */
125131614Sdes		unsigned int copyat = i;	/* Same */
126131614Sdes		unsigned int iCopy = 0;	/* How much data have we written to
127131614Sdes					 * copy-back string? */
128127094Sdes		unsigned long org_addr;	/* Original IP address */
129127094Sdes		unsigned short org_port;	/* Original source port
130127094Sdes						 * address */
13126026Sbrian
132127094SdeslCTCP_START:
133127094Sdes		if (i >= dlen || iCopy >= sizeof(newpacket))
134127094Sdes			goto lPACKET_DONE;
135127094Sdes		newpacket[iCopy++] = sptr[i++];	/* Copy the CTCP start
136127094Sdes						 * character */
137127094Sdes		/* Start of a CTCP */
138127094Sdes		if (i + 4 >= dlen)	/* Too short for DCC */
139127094Sdes			goto lBAD_CTCP;
140127094Sdes		if (sptr[i + 0] != 'D')
141127094Sdes			goto lBAD_CTCP;
142127094Sdes		if (sptr[i + 1] != 'C')
143127094Sdes			goto lBAD_CTCP;
144127094Sdes		if (sptr[i + 2] != 'C')
145127094Sdes			goto lBAD_CTCP;
146127094Sdes		if (sptr[i + 3] != ' ')
147127094Sdes			goto lBAD_CTCP;
148127094Sdes		/* We have a DCC command - handle it! */
149127094Sdes		i += 4;		/* Skip "DCC " */
150127094Sdes		if (iCopy + 4 > sizeof(newpacket))
151127094Sdes			goto lPACKET_DONE;
152127094Sdes		newpacket[iCopy++] = 'D';
153127094Sdes		newpacket[iCopy++] = 'C';
154127094Sdes		newpacket[iCopy++] = 'C';
155127094Sdes		newpacket[iCopy++] = ' ';
15626026Sbrian
157127094Sdes		DBprintf(("Found DCC\n"));
158127094Sdes		/*
159127094Sdes		 * Skip any extra spaces (should not occur according to
160127094Sdes		 * protocol, but DCC breaks CTCP protocol anyway
161127094Sdes		 */
162127094Sdes		while (sptr[i] == ' ') {
163127094Sdes			if (++i >= dlen) {
164127094Sdes				DBprintf(("DCC packet terminated in just spaces\n"));
165127094Sdes				goto lPACKET_DONE;
166127094Sdes			}
167127094Sdes		}
16826026Sbrian
169127094Sdes		DBprintf(("Transferring command...\n"));
170127094Sdes		while (sptr[i] != ' ') {
171127094Sdes			newpacket[iCopy++] = sptr[i];
172127094Sdes			if (++i >= dlen || iCopy >= sizeof(newpacket)) {
173127094Sdes				DBprintf(("DCC packet terminated during command\n"));
174127094Sdes				goto lPACKET_DONE;
175127094Sdes			}
176127094Sdes		}
177127094Sdes		/* Copy _one_ space */
178127094Sdes		if (i + 1 < dlen && iCopy < sizeof(newpacket))
179127094Sdes			newpacket[iCopy++] = sptr[i++];
18026026Sbrian
181127094Sdes		DBprintf(("Done command - removing spaces\n"));
182127094Sdes		/*
183127094Sdes		 * Skip any extra spaces (should not occur according to
184127094Sdes		 * protocol, but DCC breaks CTCP protocol anyway
185127094Sdes		 */
186127094Sdes		while (sptr[i] == ' ') {
187127094Sdes			if (++i >= dlen) {
188127094Sdes				DBprintf(("DCC packet terminated in just spaces (post-command)\n"));
189127094Sdes				goto lPACKET_DONE;
190127094Sdes			}
191127094Sdes		}
19226026Sbrian
193127094Sdes		DBprintf(("Transferring filename...\n"));
194127094Sdes		while (sptr[i] != ' ') {
195127094Sdes			newpacket[iCopy++] = sptr[i];
196127094Sdes			if (++i >= dlen || iCopy >= sizeof(newpacket)) {
197127094Sdes				DBprintf(("DCC packet terminated during filename\n"));
198127094Sdes				goto lPACKET_DONE;
199127094Sdes			}
200127094Sdes		}
201127094Sdes		/* Copy _one_ space */
202127094Sdes		if (i + 1 < dlen && iCopy < sizeof(newpacket))
203127094Sdes			newpacket[iCopy++] = sptr[i++];
20426026Sbrian
205127094Sdes		DBprintf(("Done filename - removing spaces\n"));
206127094Sdes		/*
207127094Sdes		 * Skip any extra spaces (should not occur according to
208127094Sdes		 * protocol, but DCC breaks CTCP protocol anyway
209127094Sdes		 */
210127094Sdes		while (sptr[i] == ' ') {
211127094Sdes			if (++i >= dlen) {
212127094Sdes				DBprintf(("DCC packet terminated in just spaces (post-filename)\n"));
213127094Sdes				goto lPACKET_DONE;
214127094Sdes			}
215127094Sdes		}
21626026Sbrian
217127094Sdes		DBprintf(("Fetching IP address\n"));
218127094Sdes		/* Fetch IP address */
219127094Sdes		org_addr = 0;
220127094Sdes		while (i < dlen && isdigit(sptr[i])) {
221127094Sdes			if (org_addr > ULONG_MAX / 10UL) {	/* Terminate on overflow */
222127094Sdes				DBprintf(("DCC Address overflow (org_addr == 0x%08lx, next char %c\n", org_addr, sptr[i]));
223127094Sdes				goto lBAD_CTCP;
224127094Sdes			}
225127094Sdes			org_addr *= 10;
226127094Sdes			org_addr += sptr[i++] - '0';
227127094Sdes		}
228127094Sdes		DBprintf(("Skipping space\n"));
229127094Sdes		if (i + 1 >= dlen || sptr[i] != ' ') {
230127094Sdes			DBprintf(("Overflow (%d >= %d) or bad character (%02x) terminating IP address\n", i + 1, dlen, sptr[i]));
231127094Sdes			goto lBAD_CTCP;
232127094Sdes		}
233127094Sdes		/*
234127094Sdes		 * Skip any extra spaces (should not occur according to
235127094Sdes		 * protocol, but DCC breaks CTCP protocol anyway, so we
236127094Sdes		 * might as well play it safe
237127094Sdes		 */
238127094Sdes		while (sptr[i] == ' ') {
239127094Sdes			if (++i >= dlen) {
240127094Sdes				DBprintf(("Packet failure - space overflow.\n"));
241127094Sdes				goto lPACKET_DONE;
242127094Sdes			}
243127094Sdes		}
244127094Sdes		DBprintf(("Fetching port number\n"));
245127094Sdes		/* Fetch source port */
246127094Sdes		org_port = 0;
247127094Sdes		while (i < dlen && isdigit(sptr[i])) {
248127094Sdes			if (org_port > 6554) {	/* Terminate on overflow
249127094Sdes						 * (65536/10 rounded up */
250127094Sdes				DBprintf(("DCC: port number overflow\n"));
251127094Sdes				goto lBAD_CTCP;
252127094Sdes			}
253127094Sdes			org_port *= 10;
254127094Sdes			org_port += sptr[i++] - '0';
255127094Sdes		}
256127094Sdes		/* Skip illegal addresses (or early termination) */
257127094Sdes		if (i >= dlen || (sptr[i] != '\001' && sptr[i] != ' ')) {
258127094Sdes			DBprintf(("Bad port termination\n"));
259127094Sdes			goto lBAD_CTCP;
260127094Sdes		}
261127094Sdes		DBprintf(("Got IP %lu and port %u\n", org_addr, (unsigned)org_port));
26226026Sbrian
263127094Sdes		/* We've got the address and port - now alias it */
264127094Sdes		{
265131614Sdes			struct alias_link *dcc_lnk;
266127094Sdes			struct in_addr destaddr;
26799207Sbrian
26826026Sbrian
269127094Sdes			true_port = htons(org_port);
270127094Sdes			true_addr.s_addr = htonl(org_addr);
271127094Sdes			destaddr.s_addr = 0;
27282050Sru
273127094Sdes			/* Sanity/Security checking */
274127094Sdes			if (!org_addr || !org_port ||
275127094Sdes			    pip->ip_src.s_addr != true_addr.s_addr ||
276127094Sdes			    org_port < IPPORT_RESERVED)
277127094Sdes				goto lBAD_CTCP;
27826026Sbrian
279127094Sdes			/*
280127094Sdes			 * Steal the FTP_DATA_PORT - it doesn't really
281127094Sdes			 * matter, and this would probably allow it through
282127094Sdes			 * at least _some_ firewalls.
283127094Sdes			 */
284131614Sdes			dcc_lnk = FindUdpTcpOut(la, true_addr, destaddr,
285127094Sdes			    true_port, 0,
286127094Sdes			    IPPROTO_TCP, 1);
287127094Sdes			DBprintf(("Got a DCC link\n"));
288131614Sdes			if (dcc_lnk) {
289127094Sdes				struct in_addr alias_address;	/* Address from aliasing */
290127094Sdes				u_short alias_port;	/* Port given by
291127094Sdes							 * aliasing */
292127094Sdes				int n;
293127094Sdes
29436711Sbrian#ifndef NO_FW_PUNCH
295127094Sdes				/* Generate firewall hole as appropriate */
296131614Sdes				PunchFWHole(dcc_lnk);
29736711Sbrian#endif
29832377Seivind
299131614Sdes				alias_address = GetAliasAddress(lnk);
300127094Sdes				n = snprintf(&newpacket[iCopy],
301127094Sdes				    sizeof(newpacket) - iCopy,
302127094Sdes				    "%lu ", (u_long) htonl(alias_address.s_addr));
303127094Sdes				if (n < 0) {
304127094Sdes					DBprintf(("DCC packet construct failure.\n"));
305127094Sdes					goto lBAD_CTCP;
306127094Sdes				}
307127094Sdes				if ((iCopy += n) >= sizeof(newpacket)) {	/* Truncated/fit exactly
308127094Sdes										 * - bad news */
309127094Sdes					DBprintf(("DCC constructed packet overflow.\n"));
310127094Sdes					goto lBAD_CTCP;
311127094Sdes				}
312131614Sdes				alias_port = GetAliasPort(dcc_lnk);
313127094Sdes				n = snprintf(&newpacket[iCopy],
314127094Sdes				    sizeof(newpacket) - iCopy,
315127094Sdes				    "%u", htons(alias_port));
316127094Sdes				if (n < 0) {
317127094Sdes					DBprintf(("DCC packet construct failure.\n"));
318127094Sdes					goto lBAD_CTCP;
319127094Sdes				}
320127094Sdes				iCopy += n;
321127094Sdes				/*
322127094Sdes				 * Done - truncated cases will be taken
323127094Sdes				 * care of by lBAD_CTCP
324127094Sdes				 */
325127094Sdes				DBprintf(("Aliased IP %lu and port %u\n", alias_address.s_addr, (unsigned)alias_port));
326127094Sdes			}
327127094Sdes		}
328127094Sdes		/*
329127094Sdes		 * An uninteresting CTCP - state entered right after '\001'
330127094Sdes		 * has been pushed.  Also used to copy the rest of a DCC,
331127094Sdes		 * after IP address and port has been handled
332127094Sdes		 */
333127094SdeslBAD_CTCP:
334127094Sdes		for (; i < dlen && iCopy < sizeof(newpacket); i++, iCopy++) {
335127094Sdes			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
336127094Sdes			if (sptr[i] == '\001') {
337127094Sdes				goto lNORMAL_TEXT;
338127094Sdes			}
339127094Sdes		}
340127094Sdes		goto lPACKET_DONE;
341127094Sdes		/* Normal text */
342127094SdeslNORMAL_TEXT:
343127094Sdes		for (; i < dlen && iCopy < sizeof(newpacket); i++, iCopy++) {
344127094Sdes			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
345127094Sdes			if (sptr[i] == '\001') {
346127094Sdes				goto lCTCP_START;
347127094Sdes			}
348127094Sdes		}
349127094Sdes		/* Handle the end of a packet */
350127094SdeslPACKET_DONE:
351127094Sdes		iCopy = iCopy > maxsize - copyat ? maxsize - copyat : iCopy;
352127094Sdes		memcpy(sptr + copyat, newpacket, iCopy);
35326026Sbrian
35426026Sbrian/* Save information regarding modified seq and ack numbers */
355127094Sdes		{
356127094Sdes			int delta;
35726026Sbrian
358131614Sdes			SetAckModified(lnk);
359131614Sdes			delta = GetDeltaSeqOut(pip, lnk);
360131614Sdes			AddSeq(pip, lnk, delta + copyat + iCopy - dlen);
361127094Sdes		}
36226026Sbrian
363127094Sdes		/* Revise IP header */
364127094Sdes		{
365127094Sdes			u_short new_len;
36699207Sbrian
367127094Sdes			new_len = htons(hlen + iCopy + copyat);
368127094Sdes			DifferentialChecksum(&pip->ip_sum,
369127094Sdes			    &new_len,
370127094Sdes			    &pip->ip_len,
371127094Sdes			    1);
372127094Sdes			pip->ip_len = new_len;
373127094Sdes		}
37426026Sbrian
375127094Sdes		/* Compute TCP checksum for revised packet */
376127094Sdes		tc->th_sum = 0;
377147623Sglebius#ifdef _KERNEL
378147623Sglebius		tc->th_x2 = 1;
379147623Sglebius#else
380127094Sdes		tc->th_sum = TcpChecksum(pip);
381147623Sglebius#endif
382127094Sdes		return;
383127094Sdes	}
38426026Sbrian}
38526026Sbrian
38626026Sbrian/* Notes:
38726026Sbrian	[Note 1]
38826026Sbrian	The initial search will most often fail; it could be replaced with a 32-bit specific search.
38926026Sbrian	Such a search would be done for 32-bit unsigned value V:
39026026Sbrian	V ^= 0x01010101;				  (Search is for null bytes)
39126026Sbrian	if( ((V-0x01010101)^V) & 0x80808080 ) {
39226026Sbrian     (found a null bytes which was a 01 byte)
39326026Sbrian	}
39426026Sbrian   To assert that the processor is 32-bits, do
39526026Sbrian   extern int ircdccar[32];        (32 bits)
39626026Sbrian   extern int ircdccar[CHAR_BIT*sizeof(unsigned int)];
39726026Sbrian   which will generate a type-error on all but 32-bit machines.
39826026Sbrian
39926026Sbrian	[Note 2] This routine really ought to be replaced with one that
40026026Sbrian	creates a transparent proxy on the aliasing host, to allow arbitary
40126026Sbrian	changes in the TCP stream.  This should not be too difficult given
40226026Sbrian	this base;  I (ee) will try to do this some time later.
40326026Sbrian	*/
404