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