xref: /freebsd/sys/netinet/libalias/alias_irc.c (revision 1669d8afc64812c8d2d1d147ae1fd42ff441e1b1)
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$");
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 
86 static void
87 AliasHandleIrcOut(struct libalias *, struct ip *, struct alias_link *,
88 		  int maxpacketsize);
89 
90 static int
91 fingerprint(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 
103 static int
104 protohandler(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 
111 struct proto_handler handlers[] = {
112 	{
113 	  .pri = 90,
114 	  .dir = OUT,
115 	  .proto = TCP,
116 	  .fingerprint = &fingerprint,
117 	  .protohandler = &protohandler
118 	},
119 	{ EOH }
120 };
121 
122 static int
123 mod_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
143 static
144 #endif
145 moduledata_t alias_mod = {
146        "alias_irc", mod_handler, NULL
147 };
148 
149 /* Kernel module definition. */
150 #ifdef	_KERNEL
151 DECLARE_MODULE(alias_irc, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
152 MODULE_VERSION(alias_irc, 1);
153 MODULE_DEPEND(alias_irc, libalias, 1, 1, 1);
154 #endif
155 
156 static void
157 AliasHandleIrcOut(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 */
197 lFOUND_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 
208 lCTCP_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 		 */
409 lBAD_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 */
418 lNORMAL_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 */
426 lPACKET_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