xref: /freebsd/sys/netinet/libalias/alias_irc.c (revision 39beb93c)
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 <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)
86 char *newpacket;
87 
88 /* Local defines */
89 #define DBprintf(a)
90 
91 static void
92 AliasHandleIrcOut(struct libalias *, struct ip *, struct alias_link *,
93 		  int maxpacketsize);
94 
95 static int
96 fingerprint(struct libalias *la, struct ip *pip, 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 
108 static int
109 protohandler(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 
120 struct proto_handler handlers[] = {
121 	{
122 	  .pri = 90,
123 	  .dir = OUT,
124 	  .proto = TCP,
125 	  .fingerprint = &fingerprint,
126 	  .protohandler = &protohandler
127 	},
128 	{ EOH }
129 };
130 
131 static int
132 mod_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
152 static
153 #endif
154 moduledata_t alias_mod = {
155        "alias_irc", mod_handler, NULL
156 };
157 
158 /* Kernel module definition. */
159 #ifdef	_KERNEL
160 DECLARE_MODULE(alias_irc, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
161 MODULE_VERSION(alias_irc, 1);
162 MODULE_DEPEND(alias_irc, libalias, 1, 1, 1);
163 #endif
164 
165 static void
166 AliasHandleIrcOut(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 */
206 lFOUND_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 
215 lCTCP_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 		 */
416 lBAD_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 */
425 lNORMAL_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 */
433 lPACKET_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