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