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