0
|
1 <?php |
|
2 /* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved. |
|
3 Redistribution and use in source and binary forms, with or without modification, are |
|
4 permitted provided that the following conditions are met: |
|
5 |
|
6 1. Redistributions of source code must retain the above copyright notice, this list of |
|
7 conditions and the following disclaimer. |
|
8 |
|
9 2. Redistributions in binary form must reproduce the above copyright notice, this list |
|
10 of conditions and the following disclaimer in the documentation and/or other materials |
|
11 provided with the distribution. |
|
12 |
|
13 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
|
14 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
|
15 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR |
|
16 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
17 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
18 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
|
19 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
20 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
21 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
22 |
|
23 The views and conclusions contained in the software and documentation are those of the |
|
24 authors and should not be interpreted as representing official policies, either expressed |
|
25 or implied, of Ivo Smits.*/ |
|
26 |
|
27 if (defined('app_loaded')) return; |
|
28 define('app_loaded', TRUE); |
|
29 |
|
30 print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); |
|
31 print("More information: http://wiki.ucis.nl/UDPMSG3\n"); |
|
32 print("\n"); |
|
33 |
|
34 $irc_socket = NULL; |
|
35 $irc_nick = NULL; |
|
36 $irc_channels = array(); |
|
37 $irc_buffer = ''; |
|
38 $irc_connected = FALSE; |
|
39 $irc_users = array(); |
|
40 $irc_clients = array(); |
|
41 |
|
42 $udpmsg_channels = array(); |
|
43 $udpmsg_buffer = ''; |
|
44 |
|
45 class Channel { |
|
46 public $uname = NULL; |
|
47 public $iname = NULL; |
|
48 } |
|
49 class User { |
|
50 public $iname = NULL; |
|
51 public $channels = array(); |
|
52 } |
|
53 class Client { |
|
54 public $uname = NULL; |
|
55 public $iname = NULL; |
|
56 public $channels = array(); |
|
57 public $socket = NULL; |
|
58 public $buffer = ''; |
|
59 public $connected = FALSE; |
|
60 public $identified = FALSE; |
|
61 public $joinchannels = array(); |
|
62 } |
|
63 |
|
64 srand(); |
|
65 |
|
66 print("Loading configuration...\n"); |
|
67 if (!isset($config)) require './config.php'; |
|
68 $udpmsg_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
69 socket_connect($udpmsg_socket, $config['udpmsg']['host'], $config['udpmsg']['port']); |
|
70 |
|
71 $irc_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
72 socket_connect($irc_socket, $config['irc']['host'], $config['irc']['port']); |
|
73 irc_send('USER '.$config['irc']['ident'].' host server :'.$config['irc']['realname']); |
|
74 $irc_nick = $config['irc']['nick']; |
|
75 irc_send('NICK '.$irc_nick); |
|
76 foreach ($config['channels'] as $iname => $uname) { |
|
77 $ch = new Channel(); |
|
78 $ch->iname = $iname; |
|
79 $ch->uname = $uname; |
|
80 $irc_channels[strtoupper($ch->iname)] = $ch; |
|
81 $udpmsg_channels[$ch->uname] = $ch; |
|
82 } |
|
83 |
|
84 print("Starting IRC Relay...\n"); |
|
85 print("Running\n"); |
|
86 $atime = 0; |
|
87 while (TRUE) { |
|
88 $selread = array($udpmsg_socket, $irc_socket); |
|
89 $selwrite = array(); |
|
90 $selerror = array($udpmsg_socket, $irc_socket); |
|
91 foreach ($irc_clients as $client) { |
|
92 if ($client->connected) { |
|
93 $selread[] = $client->socket; |
|
94 $selerror[] = $client->socket; |
|
95 } else { |
|
96 $selwrite[] = $client->socket; |
|
97 $selerror[] = $client->socket; |
|
98 } |
|
99 } |
|
100 socket_select(&$selread, &$selwrite, &$selerror, 120); |
|
101 if (in_array($udpmsg_socket, $selerror)) die("UDPMSG: Error: select error.\n"); |
|
102 if (in_array($irc_socket, $selerror)) die("IRC: Error: select error.\n"); |
|
103 if (in_array($udpmsg_socket, $selread) && !udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); |
|
104 if (in_array($irc_socket, $selread) && !irc_read()) die("IRC: read error.\n"); |
|
105 foreach ($irc_clients as $client) { |
|
106 if (in_array($client->socket, $selerror)) { |
|
107 irc_client_close($client); |
|
108 } else if ($client->connected && in_array($client->socket, $selread)) { |
|
109 if (!ircc_read($client)) { |
|
110 print("IRCC: read error.\n"); |
|
111 irc_client_close($client); |
|
112 } |
|
113 } else if (!$client->connected && in_array($client->socket, $selwrite)) { |
|
114 $client->connected = TRUE; |
|
115 ircc_send($client, 'USER '.$client->iname.' host server :'.$client->iname); |
|
116 ircc_send($client, 'NICK '.$client->iname); |
|
117 } |
|
118 } |
|
119 $ctime = time(); |
|
120 if ($atime < $ctime) { |
|
121 foreach ($irc_users as $usr) if (is_a($usr, 'User')) irc_userchannels($usr, array('CMD' => 'ALIVE')); |
|
122 $atime = $ctime + 1800; |
|
123 } |
|
124 } |
|
125 |
|
126 function udpmsg_read() { |
|
127 global $udpmsg_socket, $udpmsg_buffer; |
|
128 $msg = socket_read($udpmsg_socket, 1024); |
|
129 if (!strlen($msg)) { |
|
130 fprintf(STDERR, "UDPMSG: End of file\n"); |
|
131 return FALSE; |
|
132 } |
|
133 $udpmsg_buffer .= $msg; |
|
134 while (strlen($udpmsg_buffer) > 2) { |
|
135 $len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]); |
|
136 if ($len <= 0 || $len > 1024) { |
|
137 fprintf(STDERR, "UDPMSG: Error: protocol error\n"); |
|
138 return FALSE; |
|
139 } |
|
140 if (strlen($udpmsg_buffer) < 2 + $len) break; |
|
141 $msg = substr($udpmsg_buffer, 2, $len); |
|
142 $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); |
|
143 $parts = explode("\0", $msg); |
|
144 $ret = array(); |
|
145 for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; |
|
146 udpmsg_process($ret); |
|
147 } |
|
148 return TRUE; |
|
149 } |
|
150 |
|
151 function udpmsg_send($arr) { |
|
152 global $udpmsg_socket, $config; |
|
153 $tmp = array(); |
|
154 $arr['DUMMY'] = rand(0, 999999); |
|
155 $arr['NET'] = $config['udpmsg']['netname']; |
|
156 foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } |
|
157 $tmp[] = ''; |
|
158 $msg = implode("\0", $tmp); |
|
159 $len = strlen($msg); |
|
160 if ($len > 1024) { |
|
161 fprintf(STDERR, "UDPMSG: Error: message too long!\n"); |
|
162 return; |
|
163 } |
|
164 $lens = chr(floor($len / 256)).chr($len % 256); |
|
165 socket_write($udpmsg_socket, $lens.$msg); |
|
166 } |
|
167 |
|
168 function ircc_send($client, $line) { |
|
169 print('ICW: '.$line."\n"); |
|
170 $line .= "\r\n"; |
|
171 socket_send($client->socket, $line, strlen($line), 0); |
|
172 } |
|
173 function ircc_read($client) { |
|
174 $newdata = socket_read($client->socket, 1024); |
|
175 if ($newdata === FALSE || !strlen($newdata)) return FALSE; |
|
176 $irc_buffer = $client->buffer . $newdata; |
|
177 $offset = 0; |
|
178 $len = strlen($irc_buffer); |
|
179 while ($offset < $len) { |
|
180 $posa = strpos($irc_buffer, "\n", $offset); |
|
181 $posb = strpos($irc_buffer, "\r", $offset); |
|
182 if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb); |
|
183 else if ($posa !== FALSE) $pos = $posa; |
|
184 else if ($posb !== FALSE) $pos = $posb; |
|
185 else break; |
|
186 $line = substr($irc_buffer, $offset, $pos - $offset); |
|
187 if (strlen($line) && !ircc_process($client, $line)) return FALSE; |
|
188 $offset = $pos + 1; |
|
189 } |
|
190 $client->buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); |
|
191 return TRUE; |
|
192 } |
|
193 function ircc_process($client, $line) { |
|
194 global $irc_channels, $irc_users; |
|
195 print('ICR: '.$line."\n"); |
|
196 $partsa = explode(' :', $line, 2); |
|
197 $parts = explode(' ', $partsa[0]); |
|
198 $sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : NULL; |
|
199 $command = array_shift(&$parts); |
|
200 if (count($partsa) > 1) array_push(&$parts, $partsa[1]); |
|
201 $partsa = explode('!', $sender); |
|
202 $sendernick = $partsa[0]; |
|
203 switch (strtoupper($command)) { |
|
204 case '001': //Welcome |
|
205 $client->iname = $parts[0]; |
|
206 $client->identified = TRUE; |
|
207 $irc_users[strtoupper($client->iname)] = $client; |
|
208 foreach ($client->joinchannels as $ch) ircc_send($client, "JOIN :".$ch->iname); |
|
209 break; |
|
210 case '433': //Nickname in use |
|
211 ircc_send($client, "NICK ".$client->iname.rand(100,999)); |
|
212 break; |
|
213 case 'PING': |
|
214 ircc_send($client, 'PONG :'.$parts[0]); |
|
215 break; |
|
216 case 'NICK': //Nickname change |
|
217 case 'JOIN': |
|
218 case '353': //:server 353 me = channel :nickname nickname nickname |
|
219 case 'PART': |
|
220 case 'KICK': |
|
221 case 'QUIT': |
|
222 case 'PRIVMSG': |
|
223 case 'NOTICE': |
|
224 //These messages are also received and processed by the master relay bot, so we ignore them here |
|
225 break; |
|
226 } |
|
227 return TRUE; |
|
228 } |
|
229 function ircc_channel_send($client, $ch, $msg) { |
|
230 ircc_send($client, 'PRIVMSG '.$ch->iname.' :'.$msg); |
|
231 } |
|
232 |
|
233 function irc_read() { |
|
234 global $irc_socket, $irc_buffer; |
|
235 $newdata = socket_read($irc_socket, 1024); |
|
236 if ($newdata === FALSE || !strlen($newdata)) return FALSE; |
|
237 $irc_buffer .= $newdata; |
|
238 $offset = 0; |
|
239 $len = strlen($irc_buffer); |
|
240 while ($offset < $len) { |
|
241 $posa = strpos($irc_buffer, "\n", $offset); |
|
242 $posb = strpos($irc_buffer, "\r", $offset); |
|
243 if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb); |
|
244 else if ($posa !== FALSE) $pos = $posa; |
|
245 else if ($posb !== FALSE) $pos = $posb; |
|
246 else break; |
|
247 $line = substr($irc_buffer, $offset, $pos - $offset); |
|
248 if (strlen($line) && !irc_process($line)) return FALSE; |
|
249 $offset = $pos + 1; |
|
250 } |
|
251 $irc_buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); |
|
252 return TRUE; |
|
253 } |
|
254 |
|
255 function irc_send($line) { |
|
256 global $irc_socket; |
|
257 print('IW: '.$line."\n"); |
|
258 $line .= "\r\n"; |
|
259 socket_send($irc_socket, $line, strlen($line), 0); |
|
260 } |
|
261 |
|
262 function get_irc_client($name, $ch) { |
|
263 if (!isset($irc_clients[$name])) { |
|
264 if (count($irc_clients) < $config['irc']['clientconnections']) { |
|
265 $client = new Client(); |
|
266 $client->uname = $client->iname = $name; |
|
267 $client->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
268 socket_connect($client->socket, $config['irc']['host'], $config['irc']['port']); |
|
269 $client->joinchannels[$ch->iname] = $ch; |
|
270 $irc_clients[$name] = $client; |
|
271 } |
|
272 return NULL; |
|
273 } |
|
274 $client = $irc_clients[$name]; |
|
275 if (!$client->connected || !$client->identified) return NULL; |
|
276 if (!isset($client->channels[$ch->iname])) { |
|
277 $client->joinchannels[$ch->iname] = $ch; |
|
278 ircc_send($client, 'JOIN :'.$ch->iname); |
|
279 return NULL; |
|
280 } |
|
281 return $client; |
|
282 } |
|
283 function irc_client_part($name, $ch) { |
|
284 if (!isset($irc_clients[$name])) return FALSE; |
|
285 $client = $irc_clients[$name]; |
|
286 unset($client->channels[$ch->iname]); |
|
287 unset($client->joinchannels[$ch->iname]); |
|
288 if (!count($client->channels) && !count($client->joinchannels)) { |
|
289 ircc_send($client, 'QUIT :No remaining channels.'); |
|
290 irc_client_close($client); |
|
291 } else { |
|
292 ircc_send($client, 'PART :'.$ch->iname); |
|
293 } |
|
294 } |
|
295 function irc_client_close($client) { |
|
296 global $irc_clients, $irc_users; |
|
297 unset($irc_clients[$client->uname]); |
|
298 if ($client->identified) unset($irc_users[strtoupper($client->iname)]); |
|
299 socket_close($client->socket); |
|
300 } |
|
301 |
|
302 function udpmsg_process($ret) { |
|
303 global $udpmsg_channels, $irc_clients, $config; |
|
304 if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; |
|
305 if (!array_key_exists($ret['CHN'], $udpmsg_channels)) return; |
|
306 $ch = $udpmsg_channels[$ret['CHN']]; |
|
307 $net = isset($ret['NET']) ? preg_replace('/[\x00\x10-\x13]/', '', $ret['NET']) : NULL; |
|
308 $usr = preg_replace('/[\x00\x10-\x13]/', '', $ret['USR']); |
|
309 switch ($ret['CMD']) { |
|
310 case 'MSG': |
|
311 if (!isset($ret['MSG'])) break; |
|
312 $msg = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); |
|
313 $client = NULL; |
|
314 $client = get_irc_client($usr, $ch); |
|
315 if ($client === NULL) { |
|
316 $pfx = '['.$usr.($net?' @ '.$net:'').'] '; |
|
317 if (ord($msg[0]) == 1) { |
|
318 if (substr($message, 1, 6) != 'ACTION') break; |
|
319 $msg = chr(1).'ACTION '.$pfx.substr($msg, 8, -1).chr(1); |
|
320 } else { |
|
321 $msg = $pfx.$msg; |
|
322 } |
|
323 irc_channel_send($ch, $msg); |
|
324 } else { |
|
325 ircc_channel_send($client, $ch, $msg); |
|
326 } |
|
327 break; |
|
328 case 'JOIN': |
|
329 if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); |
|
330 break; |
|
331 case 'PART': |
|
332 if (!irc_client_part($usr, $ch)) irc_channel_send($ch, '* '.$usr.' has left'.($net?' on network '.$net:'')); |
|
333 break; |
|
334 case 'NICK': |
|
335 if (!isset($ret['NEWNICK'])) break; |
|
336 $newnick = preg_replace('/[\x00\x10-\x13]/', '', $ret['NEWNICK']); |
|
337 if (irc_client_part($usr, $ch)) { |
|
338 if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); |
|
339 } else { |
|
340 irc_channel_send($ch, '* '.$usr.' has changed their nickname to '.$newnick.' '.($net?' on network '.$net:'')); |
|
341 } |
|
342 break; |
|
343 } |
|
344 } |
|
345 |
|
346 function irc_process($line) { |
|
347 global $irc_nick, $irc_connected, $irc_channels, $config, $irc_users; |
|
348 print('IR: '.$line."\n"); |
|
349 $partsa = explode(' :', $line, 2); |
|
350 $parts = explode(' ', $partsa[0]); |
|
351 $sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : NULL; |
|
352 $command = array_shift(&$parts); |
|
353 if (count($partsa) > 1) array_push(&$parts, $partsa[1]); |
|
354 $partsa = explode('!', $sender); |
|
355 $sendernick = $partsa[0]; |
|
356 switch (strtoupper($command)) { |
|
357 case '001': //Welcome |
|
358 $irc_nick = $parts[0]; |
|
359 $irc_connected = TRUE; |
|
360 foreach ($irc_channels as $ch) irc_send("JOIN :".$ch->iname); |
|
361 break; |
|
362 case '433': //Nickname in use |
|
363 irc_send("NICK ".$config['irc']['nick'].rand(100,999)); |
|
364 break; |
|
365 case 'PING': |
|
366 irc_send('PONG :'.$parts[0]); |
|
367 break; |
|
368 case 'NICK': //Nickname change |
|
369 if (strcasecmp($sendernick, $irc_nick)) { |
|
370 $irc_nick = $parts[0]; |
|
371 } else { |
|
372 $usr = irc_getuser($sendernick, FALSE); |
|
373 if ($usr === NULL) break; |
|
374 irc_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); |
|
375 unset($irc_users[strtoupper($usr->iname)]); |
|
376 $usr->iname = $parts[0]; |
|
377 $irc_users[strtoupper($usr->iname)] = $usr; |
|
378 } |
|
379 break; |
|
380 case 'JOIN': |
|
381 if (!strcasecmp($sendernick, $irc_nick)) break; |
|
382 $chs = explode(',', $parts[0]); |
|
383 $usr = NULL; |
|
384 foreach ($chs as $chn) { |
|
385 $ch = irc_getchannel($chn); |
|
386 if ($ch === NULL) continue; |
|
387 if ($usr === NULL) $usr = irc_getuser($sendernick, TRUE); |
|
388 $usr->channels[$ch->iname] = $ch; |
|
389 if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); |
|
390 } |
|
391 break; |
|
392 case '353': //:server 353 me = channel :nickname nickname nickname |
|
393 $ch = irc_getchannel($parts[2]); |
|
394 if ($ch === NULL) break; |
|
395 $partsa = explode(' ', $parts[3]); |
|
396 foreach ($partsa as $un) { |
|
397 if (!strlen($un)) continue; |
|
398 $usr = irc_getuser(ltrim($un, '~&@%+'), TRUE); |
|
399 $usr->channels[$ch->iname] = $ch; |
|
400 if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); |
|
401 } |
|
402 break; |
|
403 case 'PART': |
|
404 $usr = irc_getuser($sendernick, FALSE); |
|
405 if ($usr === NULL) break; |
|
406 $chs = explode(',', $parts[0]); |
|
407 foreach ($chs as $chn) irc_part($usr, $chn); |
|
408 break; |
|
409 case 'KICK': |
|
410 $usr = irc_getuser($parts[1], FALSE); |
|
411 if (is_a($usr, 'Client')) break; |
|
412 irc_part($usr, $parts[0]); |
|
413 break; |
|
414 case 'QUIT': |
|
415 $usr = irc_getuser($sendernick, FALSE); |
|
416 if ($usr === NULL) break; |
|
417 if (is_a($usr, 'Client')) break; |
|
418 irc_userchannels($usr, array('CMD' => 'PART')); |
|
419 unset($irc_users[strtoupper($usr->iname)]); |
|
420 break; |
|
421 case 'PRIVMSG': |
|
422 case 'NOTICE': |
|
423 $ch = irc_getchannel($parts[0]); |
|
424 if (is_a(irc_getuser($sendernick, FALSE), 'Client')) break; |
|
425 if ($ch === NULL) break; |
|
426 udpmsg_send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $sendernick)); |
|
427 break; |
|
428 } |
|
429 return TRUE; |
|
430 } |
|
431 |
|
432 function irc_getchannel($chn) { |
|
433 global $irc_channels; |
|
434 $chn = strtoupper($chn); |
|
435 if (!array_key_exists($chn, $irc_channels)) return NULL; |
|
436 return $irc_channels[$chn]; |
|
437 } |
|
438 function irc_getuser($nick, $create = TRUE) { |
|
439 global $irc_users; |
|
440 $snick = strtoupper($nick); |
|
441 if (array_key_exists($snick, $irc_users)) return $irc_users[$snick]; |
|
442 if (!$create) return NULL; |
|
443 $usr = new User(); |
|
444 $usr->iname = $nick; |
|
445 $irc_users[$snick] = $usr; |
|
446 return $usr; |
|
447 } |
|
448 function irc_part($usr, $chn) { |
|
449 $ch = irc_getchannel($chn); |
|
450 if ($ch === NULL || $usr === NULL) return; |
|
451 unset($usr->channels[$ch->name]); |
|
452 if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname)); |
|
453 } |
|
454 function irc_userchannels($usr, $msg) { |
|
455 $msg['USR'] = $usr->iname; |
|
456 foreach ($usr->channels as $ch) { |
|
457 $msg['CHN'] = $ch->uname; |
|
458 udpmsg_send($msg); |
|
459 } |
|
460 } |
|
461 function irc_channel_send($ch, $msg) { |
|
462 global $irc_connected; |
|
463 if (!$irc_connected) return; |
|
464 irc_send('PRIVMSG '.$ch->iname.' :'.$msg); |
|
465 } |