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