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