Mercurial > hg > udpmsg3
comparison ircrelay/ircrelay.php @ 0:dd81c38b513a
Initial commit
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Mon, 28 Feb 2011 00:49:07 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dd81c38b513a |
---|---|
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 } |