- include sys/param.h for endian macros
[dragonfly.git] / lib / libalias / alias_irc.c
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  * $FreeBSD: src/lib/libalias/alias_irc.c,v 1.5.2.5 2001/11/03 11:34:33 brian Exp $
27  * $DragonFly: src/lib/libalias/alias_irc.c,v 1.3 2004/08/20 00:08:17 joerg Exp $
28  */
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 <sys/param.h>
52 #include <ctype.h>
53 #include <stdio.h> 
54 #include <string.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
257 #ifndef NO_FW_PUNCH
258                                  /* Generate firewall hole as appropriate */
259                                  PunchFWHole(dcc_link);
260 #endif
261
262                                  alias_address = GetAliasAddress(link);
263                                  iCopy += snprintf(&newpacket[iCopy],
264                                                                                  sizeof(newpacket)-iCopy, 
265                                                                                  "%lu ", (u_long)htonl(alias_address.s_addr));
266                                  if( iCopy >= sizeof(newpacket) ) { /* Truncated/fit exactly - bad news */
267                                          DBprintf(("DCC constructed packet overflow.\n"));
268                                          goto lBAD_CTCP;
269                                  }
270                                  alias_port = GetAliasPort(dcc_link);
271                                  iCopy += snprintf(&newpacket[iCopy],
272                                                                                  sizeof(newpacket)-iCopy, 
273                                                                                  "%u", htons(alias_port) );
274                                  /* Done - truncated cases will be taken care of by lBAD_CTCP */
275                                  DBprintf(("Aliased IP %lu and port %u\n", alias_address.s_addr, (unsigned)alias_port));
276                          }
277                  }
278                  /* An uninteresting CTCP - state entered right after '\001' has
279           been pushed.  Also used to copy the rest of a DCC, after IP
280           address and port has been handled */
281          lBAD_CTCP:
282                  for(; i<dlen && iCopy<sizeof(newpacket); i++,iCopy++) {
283                          newpacket[iCopy] = sptr[i]; /* Copy CTCP unchanged */
284                          if(sptr[i] == '\001') {
285                                  goto lNORMAL_TEXT;
286                          }
287                  }
288                  goto lPACKET_DONE;
289                  /* Normal text */
290          lNORMAL_TEXT:
291                  for(; i<dlen && iCopy<sizeof(newpacket); i++,iCopy++) {
292                          newpacket[iCopy] = sptr[i]; /* Copy CTCP unchanged */
293                          if(sptr[i] == '\001') {
294                                  goto lCTCP_START;
295                          }
296                  }
297                  /* Handle the end of a packet */
298          lPACKET_DONE:
299                  iCopy = iCopy > maxsize-copyat ? maxsize-copyat : iCopy;
300                  memcpy(sptr+copyat, newpacket, iCopy);
301
302 /* Save information regarding modified seq and ack numbers */
303         {
304             int delta;
305
306             SetAckModified(link);
307             delta = GetDeltaSeqOut(pip, link);
308             AddSeq(pip, link, delta+copyat+iCopy-dlen);
309         }
310
311                   /* Revise IP header */
312         {
313                           u_short new_len;
314                           
315                           new_len = htons(hlen + iCopy + copyat);
316                           DifferentialChecksum(&pip->ip_sum,
317                                                                                   &new_len,
318                                                                                   &pip->ip_len,
319                                                                                   1);
320                           pip->ip_len = new_len;
321         }
322
323                   /* Compute TCP checksum for revised packet */
324         tc->th_sum = 0;
325         tc->th_sum = TcpChecksum(pip);
326                   return;
327          }
328 }
329
330 /* Notes:
331         [Note 1]
332         The initial search will most often fail; it could be replaced with a 32-bit specific search.
333         Such a search would be done for 32-bit unsigned value V:
334         V ^= 0x01010101;                                  (Search is for null bytes)
335         if( ((V-0x01010101)^V) & 0x80808080 ) {
336      (found a null bytes which was a 01 byte)
337         }
338    To assert that the processor is 32-bits, do
339    extern int ircdccar[32];        (32 bits)
340    extern int ircdccar[CHAR_BIT*sizeof(unsigned int)];
341    which will generate a type-error on all but 32-bit machines.
342
343         [Note 2] This routine really ought to be replaced with one that
344         creates a transparent proxy on the aliasing host, to allow arbitary
345         changes in the TCP stream.  This should not be too difficult given
346         this base;  I (ee) will try to do this some time later.
347         */