xref: /freebsd/sys/netinet/libalias/alias_irc.c (revision c697fb7f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2001 Charles Mott <cm@linktel.net>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 /* Alias_irc.c intercepts packages contain IRC CTCP commands, and
33 	changes DCC commands to export a port on the aliasing host instead
34 	of an aliased host.
35 
36     For this routine to work, the DCC command must fit entirely into a
37     single TCP packet.  This will usually happen, but is not
38     guaranteed.
39 
40 	 The interception is likely to change the length of the packet.
41 	 The handling of this is copied more-or-less verbatim from
42 	 ftp_alias.c
43 
44 	 Initial version: Eivind Eklund <perhaps@yes.no> (ee) 97-01-29
45 
46 	 Version 2.1:  May, 1997 (cjm)
47 	     Very minor changes to conform with
48 	     local/global/function naming conventions
49 	     within the packet alising module.
50 */
51 
52 /* Includes */
53 #ifdef _KERNEL
54 #include <sys/param.h>
55 #include <sys/ctype.h>
56 #include <sys/limits.h>
57 #include <sys/systm.h>
58 #include <sys/kernel.h>
59 #include <sys/module.h>
60 #else
61 #include <ctype.h>
62 #include <errno.h>
63 #include <sys/types.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <limits.h>
68 #endif
69 
70 #include <netinet/in_systm.h>
71 #include <netinet/in.h>
72 #include <netinet/ip.h>
73 #include <netinet/tcp.h>
74 
75 #ifdef _KERNEL
76 #include <netinet/libalias/alias.h>
77 #include <netinet/libalias/alias_local.h>
78 #include <netinet/libalias/alias_mod.h>
79 #else
80 #include "alias_local.h"
81 #include "alias_mod.h"
82 #endif
83 
84 #define IRC_CONTROL_PORT_NUMBER_1 6667
85 #define IRC_CONTROL_PORT_NUMBER_2 6668
86 
87 #define PKTSIZE (IP_MAXPACKET + 1)
88 char *newpacket;
89 
90 /* Local defines */
91 #define DBprintf(a)
92 
93 static void
94 AliasHandleIrcOut(struct libalias *, struct ip *, struct alias_link *,
95 		  int maxpacketsize);
96 
97 static int
98 fingerprint(struct libalias *la, struct alias_data *ah)
99 {
100 
101 	if (ah->dport == NULL || ah->lnk == NULL || ah->maxpktsize == 0)
102 		return (-1);
103 	if (ntohs(*ah->dport) == IRC_CONTROL_PORT_NUMBER_1
104 	    || ntohs(*ah->dport) == IRC_CONTROL_PORT_NUMBER_2)
105 		return (0);
106 	return (-1);
107 }
108 
109 static int
110 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
111 {
112 
113 	newpacket = malloc(PKTSIZE);
114 	if (newpacket) {
115 		AliasHandleIrcOut(la, pip, ah->lnk, ah->maxpktsize);
116 		free(newpacket);
117 	}
118 	return (0);
119 }
120 
121 struct proto_handler handlers[] = {
122 	{
123 	  .pri = 90,
124 	  .dir = OUT,
125 	  .proto = TCP,
126 	  .fingerprint = &fingerprint,
127 	  .protohandler = &protohandler
128 	},
129 	{ EOH }
130 };
131 
132 static int
133 mod_handler(module_t mod, int type, void *data)
134 {
135 	int error;
136 
137 	switch (type) {
138 	case MOD_LOAD:
139 		error = 0;
140 		LibAliasAttachHandlers(handlers);
141 		break;
142 	case MOD_UNLOAD:
143 		error = 0;
144 		LibAliasDetachHandlers(handlers);
145 		break;
146 	default:
147 		error = EINVAL;
148 	}
149 	return (error);
150 }
151 
152 #ifdef _KERNEL
153 static
154 #endif
155 moduledata_t alias_mod = {
156        "alias_irc", mod_handler, NULL
157 };
158 
159 /* Kernel module definition. */
160 #ifdef	_KERNEL
161 DECLARE_MODULE(alias_irc, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
162 MODULE_VERSION(alias_irc, 1);
163 MODULE_DEPEND(alias_irc, libalias, 1, 1, 1);
164 #endif
165 
166 static void
167 AliasHandleIrcOut(struct libalias *la,
168     struct ip *pip,		/* IP packet to examine */
169     struct alias_link *lnk,	/* Which link are we on? */
170     int maxsize			/* Maximum size of IP packet including
171 				 * headers */
172 )
173 {
174 	int hlen, tlen, dlen;
175 	struct in_addr true_addr;
176 	u_short true_port;
177 	char *sptr;
178 	struct tcphdr *tc;
179 	int i;			/* Iterator through the source */
180 
181 /* Calculate data length of TCP packet */
182 	tc = (struct tcphdr *)ip_next(pip);
183 	hlen = (pip->ip_hl + tc->th_off) << 2;
184 	tlen = ntohs(pip->ip_len);
185 	dlen = tlen - hlen;
186 
187 	/*
188 	 * Return if data length is too short - assume an entire PRIVMSG in
189 	 * each packet.
190 	 */
191 	if (dlen < (int)sizeof(":A!a@n.n PRIVMSG A :aDCC 1 1a") - 1)
192 		return;
193 
194 /* Place string pointer at beginning of data */
195 	sptr = (char *)pip;
196 	sptr += hlen;
197 	maxsize -= hlen;	/* We're interested in maximum size of
198 				 * data, not packet */
199 
200 	/* Search for a CTCP command [Note 1] */
201 	for (i = 0; i < dlen; i++) {
202 		if (sptr[i] == '\001')
203 			goto lFOUND_CTCP;
204 	}
205 	return;			/* No CTCP commands in  */
206 	/* Handle CTCP commands - the buffer may have to be copied */
207 lFOUND_CTCP:
208 	{
209 		unsigned int copyat = i;
210 		unsigned int iCopy = 0;	/* How much data have we written to
211 					 * copy-back string? */
212 		unsigned long org_addr;	/* Original IP address */
213 		unsigned short org_port;	/* Original source port
214 						 * address */
215 
216 lCTCP_START:
217 		if (i >= dlen || iCopy >= PKTSIZE)
218 			goto lPACKET_DONE;
219 		newpacket[iCopy++] = sptr[i++];	/* Copy the CTCP start
220 						 * character */
221 		/* Start of a CTCP */
222 		if (i + 4 >= dlen)	/* Too short for DCC */
223 			goto lBAD_CTCP;
224 		if (sptr[i + 0] != 'D')
225 			goto lBAD_CTCP;
226 		if (sptr[i + 1] != 'C')
227 			goto lBAD_CTCP;
228 		if (sptr[i + 2] != 'C')
229 			goto lBAD_CTCP;
230 		if (sptr[i + 3] != ' ')
231 			goto lBAD_CTCP;
232 		/* We have a DCC command - handle it! */
233 		i += 4;		/* Skip "DCC " */
234 		if (iCopy + 4 > PKTSIZE)
235 			goto lPACKET_DONE;
236 		newpacket[iCopy++] = 'D';
237 		newpacket[iCopy++] = 'C';
238 		newpacket[iCopy++] = 'C';
239 		newpacket[iCopy++] = ' ';
240 
241 		DBprintf(("Found DCC\n"));
242 		/*
243 		 * Skip any extra spaces (should not occur according to
244 		 * protocol, but DCC breaks CTCP protocol anyway
245 		 */
246 		while (sptr[i] == ' ') {
247 			if (++i >= dlen) {
248 				DBprintf(("DCC packet terminated in just spaces\n"));
249 				goto lPACKET_DONE;
250 			}
251 		}
252 
253 		DBprintf(("Transferring command...\n"));
254 		while (sptr[i] != ' ') {
255 			newpacket[iCopy++] = sptr[i];
256 			if (++i >= dlen || iCopy >= PKTSIZE) {
257 				DBprintf(("DCC packet terminated during command\n"));
258 				goto lPACKET_DONE;
259 			}
260 		}
261 		/* Copy _one_ space */
262 		if (i + 1 < dlen && iCopy < PKTSIZE)
263 			newpacket[iCopy++] = sptr[i++];
264 
265 		DBprintf(("Done command - removing spaces\n"));
266 		/*
267 		 * Skip any extra spaces (should not occur according to
268 		 * protocol, but DCC breaks CTCP protocol anyway
269 		 */
270 		while (sptr[i] == ' ') {
271 			if (++i >= dlen) {
272 				DBprintf(("DCC packet terminated in just spaces (post-command)\n"));
273 				goto lPACKET_DONE;
274 			}
275 		}
276 
277 		DBprintf(("Transferring filename...\n"));
278 		while (sptr[i] != ' ') {
279 			newpacket[iCopy++] = sptr[i];
280 			if (++i >= dlen || iCopy >= PKTSIZE) {
281 				DBprintf(("DCC packet terminated during filename\n"));
282 				goto lPACKET_DONE;
283 			}
284 		}
285 		/* Copy _one_ space */
286 		if (i + 1 < dlen && iCopy < PKTSIZE)
287 			newpacket[iCopy++] = sptr[i++];
288 
289 		DBprintf(("Done filename - removing spaces\n"));
290 		/*
291 		 * Skip any extra spaces (should not occur according to
292 		 * protocol, but DCC breaks CTCP protocol anyway
293 		 */
294 		while (sptr[i] == ' ') {
295 			if (++i >= dlen) {
296 				DBprintf(("DCC packet terminated in just spaces (post-filename)\n"));
297 				goto lPACKET_DONE;
298 			}
299 		}
300 
301 		DBprintf(("Fetching IP address\n"));
302 		/* Fetch IP address */
303 		org_addr = 0;
304 		while (i < dlen && isdigit(sptr[i])) {
305 			if (org_addr > ULONG_MAX / 10UL) {	/* Terminate on overflow */
306 				DBprintf(("DCC Address overflow (org_addr == 0x%08lx, next char %c\n", org_addr, sptr[i]));
307 				goto lBAD_CTCP;
308 			}
309 			org_addr *= 10;
310 			org_addr += sptr[i++] - '0';
311 		}
312 		DBprintf(("Skipping space\n"));
313 		if (i + 1 >= dlen || sptr[i] != ' ') {
314 			DBprintf(("Overflow (%d >= %d) or bad character (%02x) terminating IP address\n", i + 1, dlen, sptr[i]));
315 			goto lBAD_CTCP;
316 		}
317 		/*
318 		 * Skip any extra spaces (should not occur according to
319 		 * protocol, but DCC breaks CTCP protocol anyway, so we
320 		 * might as well play it safe
321 		 */
322 		while (sptr[i] == ' ') {
323 			if (++i >= dlen) {
324 				DBprintf(("Packet failure - space overflow.\n"));
325 				goto lPACKET_DONE;
326 			}
327 		}
328 		DBprintf(("Fetching port number\n"));
329 		/* Fetch source port */
330 		org_port = 0;
331 		while (i < dlen && isdigit(sptr[i])) {
332 			if (org_port > 6554) {	/* Terminate on overflow
333 						 * (65536/10 rounded up */
334 				DBprintf(("DCC: port number overflow\n"));
335 				goto lBAD_CTCP;
336 			}
337 			org_port *= 10;
338 			org_port += sptr[i++] - '0';
339 		}
340 		/* Skip illegal addresses (or early termination) */
341 		if (i >= dlen || (sptr[i] != '\001' && sptr[i] != ' ')) {
342 			DBprintf(("Bad port termination\n"));
343 			goto lBAD_CTCP;
344 		}
345 		DBprintf(("Got IP %lu and port %u\n", org_addr, (unsigned)org_port));
346 
347 		/* We've got the address and port - now alias it */
348 		{
349 			struct alias_link *dcc_lnk;
350 			struct in_addr destaddr;
351 
352 
353 			true_port = htons(org_port);
354 			true_addr.s_addr = htonl(org_addr);
355 			destaddr.s_addr = 0;
356 
357 			/* Sanity/Security checking */
358 			if (!org_addr || !org_port ||
359 			    pip->ip_src.s_addr != true_addr.s_addr ||
360 			    org_port < IPPORT_RESERVED)
361 				goto lBAD_CTCP;
362 
363 			/*
364 			 * Steal the FTP_DATA_PORT - it doesn't really
365 			 * matter, and this would probably allow it through
366 			 * at least _some_ firewalls.
367 			 */
368 			dcc_lnk = FindUdpTcpOut(la, true_addr, destaddr,
369 			    true_port, 0,
370 			    IPPROTO_TCP, 1);
371 			DBprintf(("Got a DCC link\n"));
372 			if (dcc_lnk) {
373 				struct in_addr alias_address;	/* Address from aliasing */
374 				u_short alias_port;	/* Port given by
375 							 * aliasing */
376 				int n;
377 
378 #ifndef NO_FW_PUNCH
379 				/* Generate firewall hole as appropriate */
380 				PunchFWHole(dcc_lnk);
381 #endif
382 
383 				alias_address = GetAliasAddress(lnk);
384 				n = snprintf(&newpacket[iCopy],
385 				    PKTSIZE - iCopy,
386 				    "%lu ", (u_long) htonl(alias_address.s_addr));
387 				if (n < 0) {
388 					DBprintf(("DCC packet construct failure.\n"));
389 					goto lBAD_CTCP;
390 				}
391 				if ((iCopy += n) >= PKTSIZE) {	/* Truncated/fit exactly
392 										 * - bad news */
393 					DBprintf(("DCC constructed packet overflow.\n"));
394 					goto lBAD_CTCP;
395 				}
396 				alias_port = GetAliasPort(dcc_lnk);
397 				n = snprintf(&newpacket[iCopy],
398 				    PKTSIZE - iCopy,
399 				    "%u", htons(alias_port));
400 				if (n < 0) {
401 					DBprintf(("DCC packet construct failure.\n"));
402 					goto lBAD_CTCP;
403 				}
404 				iCopy += n;
405 				/*
406 				 * Done - truncated cases will be taken
407 				 * care of by lBAD_CTCP
408 				 */
409 				DBprintf(("Aliased IP %lu and port %u\n", alias_address.s_addr, (unsigned)alias_port));
410 			}
411 		}
412 		/*
413 		 * An uninteresting CTCP - state entered right after '\001'
414 		 * has been pushed.  Also used to copy the rest of a DCC,
415 		 * after IP address and port has been handled
416 		 */
417 lBAD_CTCP:
418 		for (; i < dlen && iCopy < PKTSIZE; i++, iCopy++) {
419 			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
420 			if (sptr[i] == '\001') {
421 				goto lNORMAL_TEXT;
422 			}
423 		}
424 		goto lPACKET_DONE;
425 		/* Normal text */
426 lNORMAL_TEXT:
427 		for (; i < dlen && iCopy < PKTSIZE; i++, iCopy++) {
428 			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
429 			if (sptr[i] == '\001') {
430 				goto lCTCP_START;
431 			}
432 		}
433 		/* Handle the end of a packet */
434 lPACKET_DONE:
435 		iCopy = iCopy > maxsize - copyat ? maxsize - copyat : iCopy;
436 		memcpy(sptr + copyat, newpacket, iCopy);
437 
438 /* Save information regarding modified seq and ack numbers */
439 		{
440 			int delta;
441 
442 			SetAckModified(lnk);
443 			tc = (struct tcphdr *)ip_next(pip);
444 			delta = GetDeltaSeqOut(tc->th_seq, lnk);
445 			AddSeq(lnk, delta + copyat + iCopy - dlen, pip->ip_hl,
446 			    pip->ip_len, tc->th_seq, tc->th_off);
447 		}
448 
449 		/* Revise IP header */
450 		{
451 			u_short new_len;
452 
453 			new_len = htons(hlen + iCopy + copyat);
454 			DifferentialChecksum(&pip->ip_sum,
455 			    &new_len,
456 			    &pip->ip_len,
457 			    1);
458 			pip->ip_len = new_len;
459 		}
460 
461 		/* Compute TCP checksum for revised packet */
462 		tc->th_sum = 0;
463 #ifdef _KERNEL
464 		tc->th_x2 = 1;
465 #else
466 		tc->th_sum = TcpChecksum(pip);
467 #endif
468 		return;
469 	}
470 }
471 
472 /* Notes:
473 	[Note 1]
474 	The initial search will most often fail; it could be replaced with a 32-bit specific search.
475 	Such a search would be done for 32-bit unsigned value V:
476 	V ^= 0x01010101;				  (Search is for null bytes)
477 	if( ((V-0x01010101)^V) & 0x80808080 ) {
478      (found a null bytes which was a 01 byte)
479 	}
480    To assert that the processor is 32-bits, do
481    extern int ircdccar[32];        (32 bits)
482    extern int ircdccar[CHAR_BIT*sizeof(unsigned int)];
483    which will generate a type-error on all but 32-bit machines.
484 
485 	[Note 2] This routine really ought to be replaced with one that
486 	creates a transparent proxy on the aliasing host, to allow arbitrary
487 	changes in the TCP stream.  This should not be too difficult given
488 	this base;  I (ee) will try to do this some time later.
489 	*/
490