xref: /freebsd/sys/netinet/libalias/alias_irc.c (revision 535af610)
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 __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 	if (ah->dport == NULL || ah->lnk == NULL || 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 	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 			true_port = htons(org_port);
351 			true_addr.s_addr = htonl(org_addr);
352 			destaddr.s_addr = 0;
353 
354 			/* Sanity/Security checking */
355 			if (!org_addr || !org_port ||
356 			    pip->ip_src.s_addr != true_addr.s_addr ||
357 			    org_port < IPPORT_RESERVED)
358 				goto lBAD_CTCP;
359 
360 			/*
361 			 * Steal the FTP_DATA_PORT - it doesn't really
362 			 * matter, and this would probably allow it through
363 			 * at least _some_ firewalls.
364 			 */
365 			dcc_lnk = FindUdpTcpOut(la, true_addr, destaddr,
366 			    true_port, 0,
367 			    IPPROTO_TCP, 1);
368 			DBprintf(("Got a DCC link\n"));
369 			if (dcc_lnk) {
370 				struct in_addr alias_address;	/* Address from aliasing */
371 				u_short alias_port;	/* Port given by
372 							 * aliasing */
373 				int n;
374 
375 #ifndef NO_FW_PUNCH
376 				/* Generate firewall hole as appropriate */
377 				PunchFWHole(dcc_lnk);
378 #endif
379 
380 				alias_address = GetAliasAddress(lnk);
381 				n = snprintf(&newpacket[iCopy],
382 				    PKTSIZE - iCopy,
383 				    "%lu ", (u_long) htonl(alias_address.s_addr));
384 				if (n < 0) {
385 					DBprintf(("DCC packet construct failure.\n"));
386 					goto lBAD_CTCP;
387 				}
388 				if ((iCopy += n) >= PKTSIZE) {	/* Truncated/fit exactly
389 										 * - bad news */
390 					DBprintf(("DCC constructed packet overflow.\n"));
391 					goto lBAD_CTCP;
392 				}
393 				alias_port = GetAliasPort(dcc_lnk);
394 				n = snprintf(&newpacket[iCopy],
395 				    PKTSIZE - iCopy,
396 				    "%u", htons(alias_port));
397 				if (n < 0) {
398 					DBprintf(("DCC packet construct failure.\n"));
399 					goto lBAD_CTCP;
400 				}
401 				iCopy += n;
402 				/*
403 				 * Done - truncated cases will be taken
404 				 * care of by lBAD_CTCP
405 				 */
406 				DBprintf(("Aliased IP %lu and port %u\n", alias_address.s_addr, (unsigned)alias_port));
407 			}
408 		}
409 		/*
410 		 * An uninteresting CTCP - state entered right after '\001'
411 		 * has been pushed.  Also used to copy the rest of a DCC,
412 		 * after IP address and port has been handled
413 		 */
414 		lBAD_CTCP:
415 		for (; i < dlen && iCopy < PKTSIZE; i++, iCopy++) {
416 			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
417 			if (sptr[i] == '\001') {
418 				goto lNORMAL_TEXT;
419 			}
420 		}
421 		goto lPACKET_DONE;
422 		/* Normal text */
423 		lNORMAL_TEXT:
424 		for (; i < dlen && iCopy < PKTSIZE; i++, iCopy++) {
425 			newpacket[iCopy] = sptr[i];	/* Copy CTCP unchanged */
426 			if (sptr[i] == '\001') {
427 				goto lCTCP_START;
428 			}
429 		}
430 		/* Handle the end of a packet */
431 		lPACKET_DONE:
432 		iCopy = iCopy > maxsize - copyat ? maxsize - copyat : iCopy;
433 		memcpy(sptr + copyat, newpacket, iCopy);
434 
435 		/* Save information regarding modified seq and ack numbers */
436 		{
437 			int delta;
438 
439 			SetAckModified(lnk);
440 			tc = (struct tcphdr *)ip_next(pip);
441 			delta = GetDeltaSeqOut(tc->th_seq, lnk);
442 			AddSeq(lnk, delta + copyat + iCopy - dlen, pip->ip_hl,
443 			    pip->ip_len, tc->th_seq, tc->th_off);
444 		}
445 
446 		/* Revise IP header */
447 		{
448 			u_short new_len;
449 
450 			new_len = htons(hlen + iCopy + copyat);
451 			DifferentialChecksum(&pip->ip_sum,
452 			    &new_len,
453 			    &pip->ip_len,
454 			    1);
455 			pip->ip_len = new_len;
456 		}
457 
458 		/* Compute TCP checksum for revised packet */
459 		tc->th_sum = 0;
460 #ifdef _KERNEL
461 		tc->th_x2 = (TH_RES1 >> 8);
462 #else
463 		tc->th_sum = TcpChecksum(pip);
464 #endif
465 		return;
466 	}
467 }
468 
469 /* Notes:
470   [Note 1]
471   The initial search will most often fail; it could be replaced with a 32-bit specific search.
472   Such a search would be done for 32-bit unsigned value V:
473    V ^= 0x01010101;          (Search is for null bytes)
474    if( ((V-0x01010101)^V) & 0x80808080 ) {
475      (found a null bytes which was a 01 byte)
476    }
477   To assert that the processor is 32-bits, do
478    extern int ircdccar[32];        (32 bits)
479    extern int ircdccar[CHAR_BIT*sizeof(unsigned int)];
480   which will generate a type-error on all but 32-bit machines.
481 
482   [Note 2] This routine really ought to be replaced with one that
483   creates a transparent proxy on the aliasing host, to allow arbitrary
484   changes in the TCP stream.  This should not be too difficult given
485   this base;  I (ee) will try to do this some time later.
486 */
487