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