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