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