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