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