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