alias_irc.c revision 168346
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: head/sys/netinet/libalias/alias_irc.c 168346 2007-04-04 03:16:59Z kan $");
29
30/* Alias_irc.c intercepts packages contain IRC CTCP commands, and
31	changes DCC commands to export a port on the aliasing host instead
32	of an aliased host.
33
34    For this routine to work, the DCC command must fit entirely into a
35    single TCP packet.  This will usually happen, but is not
36    guaranteed.
37
38	 The interception is likely to change the length of the packet.
39	 The handling of this is copied more-or-less verbatim from
40	 ftp_alias.c
41
42	 Initial version: Eivind Eklund <perhaps@yes.no> (ee) 97-01-29
43
44	 Version 2.1:  May, 1997 (cjm)
45	     Very minor changes to conform with
46	     local/global/function naming conventions
47	     withing the packet alising module.
48*/
49
50/* Includes */
51#ifdef _KERNEL
52#include <sys/param.h>
53#include <sys/ctype.h>
54#include <sys/limits.h>
55#include <sys/systm.h>
56#include <sys/kernel.h>
57#include <sys/module.h>
58#else
59#include <errno.h>
60#include <sys/types.h>
61#include <stdio.h>
62#include <string.h>
63#include <limits.h>
64#endif
65
66#include <netinet/in_systm.h>
67#include <netinet/in.h>
68#include <netinet/ip.h>
69#include <netinet/tcp.h>
70
71#ifdef _KERNEL
72#include <netinet/libalias/alias.h>
73#include <netinet/libalias/alias_local.h>
74#include <netinet/libalias/alias_mod.h>
75#else
76#include "alias_local.h"
77#include "alias_mod.h"
78#endif
79
80#define IRC_CONTROL_PORT_NUMBER_1 6667
81#define IRC_CONTROL_PORT_NUMBER_2 6668
82
83/* Local defines */
84#define DBprintf(a)
85
86static void
87AliasHandleIrcOut(struct libalias *, struct ip *, struct alias_link *,
88		  int maxpacketsize);
89
90static int
91fingerprint(struct libalias *la, struct ip *pip, struct alias_data *ah)
92{
93
94	if (ah->dport == NULL || ah->dport == NULL || ah->lnk == NULL ||
95	    ah->maxpktsize == 0)
96		return (-1);
97	if (ntohs(*ah->dport) == IRC_CONTROL_PORT_NUMBER_1
98	    || ntohs(*ah->dport) == IRC_CONTROL_PORT_NUMBER_2)
99		return (0);
100	return (-1);
101}
102
103static int
104protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
105{
106
107	AliasHandleIrcOut(la, pip, ah->lnk, ah->maxpktsize);
108	return (0);
109}
110
111struct proto_handler handlers[] = {
112	{
113	  .pri = 90,
114	  .dir = OUT,
115	  .proto = TCP,
116	  .fingerprint = &fingerprint,
117	  .protohandler = &protohandler
118	},
119	{ EOH }
120};
121
122static int
123mod_handler(module_t mod, int type, void *data)
124{
125	int error;
126
127	switch (type) {
128	case MOD_LOAD:
129		error = 0;
130		LibAliasAttachHandlers(handlers);
131		break;
132	case MOD_UNLOAD:
133		error = 0;
134		LibAliasDetachHandlers(handlers);
135		break;
136	default:
137		error = EINVAL;
138	}
139	return (error);
140}
141
142#ifdef _KERNEL
143static
144#endif
145moduledata_t alias_mod = {
146       "alias_irc", mod_handler, NULL
147};
148
149/* Kernel module definition. */
150#ifdef	_KERNEL
151DECLARE_MODULE(alias_irc, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
152MODULE_VERSION(alias_irc, 1);
153MODULE_DEPEND(alias_irc, libalias, 1, 1, 1);
154#endif
155
156static void
157AliasHandleIrcOut(struct libalias *la,
158    struct ip *pip,		/* IP packet to examine */
159    struct alias_link *lnk,	/* Which link are we on? */
160    int maxsize			/* Maximum size of IP packet including
161				 * headers */
162)
163{
164	int hlen, tlen, dlen;
165	struct in_addr true_addr;
166	u_short true_port;
167	char *sptr;
168	struct tcphdr *tc;
169	int i;			/* Iterator through the source */
170
171/* Calculate data length of TCP packet */
172	tc = (struct tcphdr *)ip_next(pip);
173	hlen = (pip->ip_hl + tc->th_off) << 2;
174	tlen = ntohs(pip->ip_len);
175	dlen = tlen - hlen;
176
177	/*
178	 * Return if data length is too short - assume an entire PRIVMSG in
179	 * each packet.
180	 */
181	if (dlen < (int)sizeof(":A!a@n.n PRIVMSG A :aDCC 1 1a") - 1)
182		return;
183
184/* Place string pointer at beginning of data */
185	sptr = (char *)pip;
186	sptr += hlen;
187	maxsize -= hlen;	/* We're interested in maximum size of
188				 * data, not packet */
189
190	/* Search for a CTCP command [Note 1] */
191	for (i = 0; i < dlen; i++) {
192		if (sptr[i] == '\001')
193			goto lFOUND_CTCP;
194	}
195	return;			/* No CTCP commands in  */
196	/* Handle CTCP commands - the buffer may have to be copied */
197lFOUND_CTCP:
198	{
199		char newpacket[65536];	/* Estimate of maximum packet size
200					 * :) */
201		unsigned int copyat = i;	/* Same */
202		unsigned int iCopy = 0;	/* How much data have we written to
203					 * copy-back string? */
204		unsigned long org_addr;	/* Original IP address */
205		unsigned short org_port;	/* Original source port
206						 * address */
207
208lCTCP_START:
209		if (i >= dlen || iCopy >= sizeof(newpacket))
210			goto lPACKET_DONE;
211		newpacket[iCopy++] = sptr[i++];	/* Copy the CTCP start
212						 * character */
213		/* Start of a CTCP */
214		if (i + 4 >= dlen)	/* Too short for DCC */
215			goto lBAD_CTCP;
216		if (sptr[i + 0] != 'D')
217			goto lBAD_CTCP;
218		if (sptr[i + 1] != 'C')
219			goto lBAD_CTCP;
220		if (sptr[i + 2] != 'C')
221			goto lBAD_CTCP;
222		if (sptr[i + 3] != ' ')
223			goto lBAD_CTCP;
224		/* We have a DCC command - handle it! */
225		i += 4;		/* Skip "DCC " */
226		if (iCopy + 4 > sizeof(newpacket))
227			goto lPACKET_DONE;
228		newpacket[iCopy++] = 'D';
229		newpacket[iCopy++] = 'C';
230		newpacket[iCopy++] = 'C';
231		newpacket[iCopy++] = ' ';
232
233		DBprintf(("Found DCC\n"));
234		/*
235		 * Skip any extra spaces (should not occur according to
236		 * protocol, but DCC breaks CTCP protocol anyway
237		 */
238		while (sptr[i] == ' ') {
239			if (++i >= dlen) {
240				DBprintf(("DCC packet terminated in just spaces\n"));
241				goto lPACKET_DONE;
242			}
243		}
244
245		DBprintf(("Transferring command...\n"));
246		while (sptr[i] != ' ') {
247			newpacket[iCopy++] = sptr[i];
248			if (++i >= dlen || iCopy >= sizeof(newpacket)) {
249				DBprintf(("DCC packet terminated during command\n"));
250				goto lPACKET_DONE;
251			}
252		}
253		/* Copy _one_ space */
254		if (i + 1 < dlen && iCopy < sizeof(newpacket))
255			newpacket[iCopy++] = sptr[i++];
256
257		DBprintf(("Done command - removing spaces\n"));
258		/*
259		 * Skip any extra spaces (should not occur according to
260		 * protocol, but DCC breaks CTCP protocol anyway
261		 */
262		while (sptr[i] == ' ') {
263			if (++i >= dlen) {
264				DBprintf(("DCC packet terminated in just spaces (post-command)\n"));
265				goto lPACKET_DONE;
266			}
267		}
268
269		DBprintf(("Transferring filename...\n"));
270		while (sptr[i] != ' ') {
271			newpacket[iCopy++] = sptr[i];
272			if (++i >= dlen || iCopy >= sizeof(newpacket)) {
273				DBprintf(("DCC packet terminated during filename\n"));
274				goto lPACKET_DONE;
275			}
276		}
277		/* Copy _one_ space */
278		if (i + 1 < dlen && iCopy < sizeof(newpacket))
279			newpacket[iCopy++] = sptr[i++];
280
281		DBprintf(("Done filename - removing spaces\n"));
282		/*
283		 * Skip any extra spaces (should not occur according to
284		 * protocol, but DCC breaks CTCP protocol anyway
285		 */
286		while (sptr[i] == ' ') {
287			if (++i >= dlen) {
288				DBprintf(("DCC packet terminated in just spaces (post-filename)\n"));
289				goto lPACKET_DONE;
290			}
291		}
292
293		DBprintf(("Fetching IP address\n"));
294		/* Fetch IP address */
295		org_addr = 0;
296		while (i < dlen && isdigit(sptr[i])) {
297			if (org_addr > ULONG_MAX / 10UL) {	/* Terminate on overflow */
298				DBprintf(("DCC Address overflow (org_addr == 0x%08lx, next char %c\n", org_addr, sptr[i]));
299				goto lBAD_CTCP;
300			}
301			org_addr *= 10;
302			org_addr += sptr[i++] - '0';
303		}
304		DBprintf(("Skipping space\n"));
305		if (i + 1 >= dlen || sptr[i] != ' ') {
306			DBprintf(("Overflow (%d >= %d) or bad character (%02x) terminating IP address\n", i + 1, dlen, sptr[i]));
307			goto lBAD_CTCP;
308		}
309		/*
310		 * Skip any extra spaces (should not occur according to
311		 * protocol, but DCC breaks CTCP protocol anyway, so we
312		 * might as well play it safe
313		 */
314		while (sptr[i] == ' ') {
315			if (++i >= dlen) {
316				DBprintf(("Packet failure - space overflow.\n"));
317				goto lPACKET_DONE;
318			}
319		}
320		DBprintf(("Fetching port number\n"));
321		/* Fetch source port */
322		org_port = 0;
323		while (i < dlen && isdigit(sptr[i])) {
324			if (org_port > 6554) {	/* Terminate on overflow
325						 * (65536/10 rounded up */
326				DBprintf(("DCC: port number overflow\n"));
327				goto lBAD_CTCP;
328			}
329			org_port *= 10;
330			org_port += sptr[i++] - '0';
331		}
332		/* Skip illegal addresses (or early termination) */
333		if (i >= dlen || (sptr[i] != '\001' && sptr[i] != ' ')) {
334			DBprintf(("Bad port termination\n"));
335			goto lBAD_CTCP;
336		}
337		DBprintf(("Got IP %lu and port %u\n", org_addr, (unsigned)org_port));
338
339		/* We've got the address and port - now alias it */
340		{
341			struct alias_link *dcc_lnk;
342			struct in_addr destaddr;
343
344
345			true_port = htons(org_port);
346			true_addr.s_addr = htonl(org_addr);
347			destaddr.s_addr = 0;
348
349			/* Sanity/Security checking */
350			if (!org_addr || !org_port ||
351			    pip->ip_src.s_addr != true_addr.s_addr ||
352			    org_port < IPPORT_RESERVED)
353				goto lBAD_CTCP;
354
355			/*
356			 * Steal the FTP_DATA_PORT - it doesn't really
357			 * matter, and this would probably allow it through
358			 * at least _some_ firewalls.
359			 */
360			dcc_lnk = FindUdpTcpOut(la, true_addr, destaddr,
361			    true_port, 0,
362			    IPPROTO_TCP, 1);
363			DBprintf(("Got a DCC link\n"));
364			if (dcc_lnk) {
365				struct in_addr alias_address;	/* Address from aliasing */
366				u_short alias_port;	/* Port given by
367							 * aliasing */
368				int n;
369
370#ifndef NO_FW_PUNCH
371				/* Generate firewall hole as appropriate */
372				PunchFWHole(dcc_lnk);
373#endif
374
375				alias_address = GetAliasAddress(lnk);
376				n = snprintf(&newpacket[iCopy],
377				    sizeof(newpacket) - iCopy,
378				    "%lu ", (u_long) htonl(alias_address.s_addr));
379				if (n < 0) {
380					DBprintf(("DCC packet construct failure.\n"));
381					goto lBAD_CTCP;
382				}
383				if ((iCopy += n) >= sizeof(newpacket)) {	/* Truncated/fit exactly
384										 * - bad news */
385					DBprintf(("DCC constructed packet overflow.\n"));
386					goto lBAD_CTCP;
387				}
388				alias_port = GetAliasPort(dcc_lnk);
389				n = snprintf(&newpacket[iCopy],
390				    sizeof(newpacket) - iCopy,
391				    "%u", htons(alias_port));
392				if (n < 0) {
393					DBprintf(("DCC packet construct failure.\n"));
394					goto lBAD_CTCP;
395				}
396				iCopy += n;
397				/*
398				 * Done - truncated cases will be taken
399				 * care of by lBAD_CTCP
400				 */
401				DBprintf(("Aliased IP %lu and port %u\n", alias_address.s_addr, (unsigned)alias_port));
402			}
403		}
404		/*
405		 * An uninteresting CTCP - state entered right after '\001'
406		 * has been pushed.  Also used to copy the rest of a DCC,
407		 * after IP address and port has been handled
408		 */
409lBAD_CTCP:
410		for (; i < dlen && iCopy < sizeof(newpacket); i++, iCopy++) {
411			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
412			if (sptr[i] == '\001') {
413				goto lNORMAL_TEXT;
414			}
415		}
416		goto lPACKET_DONE;
417		/* Normal text */
418lNORMAL_TEXT:
419		for (; i < dlen && iCopy < sizeof(newpacket); i++, iCopy++) {
420			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
421			if (sptr[i] == '\001') {
422				goto lCTCP_START;
423			}
424		}
425		/* Handle the end of a packet */
426lPACKET_DONE:
427		iCopy = iCopy > maxsize - copyat ? maxsize - copyat : iCopy;
428		memcpy(sptr + copyat, newpacket, iCopy);
429
430/* Save information regarding modified seq and ack numbers */
431		{
432			int delta;
433
434			SetAckModified(lnk);
435			delta = GetDeltaSeqOut(pip, lnk);
436			AddSeq(pip, lnk, delta + copyat + iCopy - dlen);
437		}
438
439		/* Revise IP header */
440		{
441			u_short new_len;
442
443			new_len = htons(hlen + iCopy + copyat);
444			DifferentialChecksum(&pip->ip_sum,
445			    &new_len,
446			    &pip->ip_len,
447			    1);
448			pip->ip_len = new_len;
449		}
450
451		/* Compute TCP checksum for revised packet */
452		tc->th_sum = 0;
453#ifdef _KERNEL
454		tc->th_x2 = 1;
455#else
456		tc->th_sum = TcpChecksum(pip);
457#endif
458		return;
459	}
460}
461
462/* Notes:
463	[Note 1]
464	The initial search will most often fail; it could be replaced with a 32-bit specific search.
465	Such a search would be done for 32-bit unsigned value V:
466	V ^= 0x01010101;				  (Search is for null bytes)
467	if( ((V-0x01010101)^V) & 0x80808080 ) {
468     (found a null bytes which was a 01 byte)
469	}
470   To assert that the processor is 32-bits, do
471   extern int ircdccar[32];        (32 bits)
472   extern int ircdccar[CHAR_BIT*sizeof(unsigned int)];
473   which will generate a type-error on all but 32-bit machines.
474
475	[Note 2] This routine really ought to be replaced with one that
476	creates a transparent proxy on the aliasing host, to allow arbitary
477	changes in the TCP stream.  This should not be too difficult given
478	this base;  I (ee) will try to do this some time later.
479	*/
480