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