Mercurial > hg > udpmsg3
comparison multiclientrelay/multiclientrelay2.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 <?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 if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php'); | |
30 | |
31 print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); | |
32 print("More information: http://wiki.ucis.nl/UDPMSG3\n"); | |
33 print("\n"); | |
34 | |
35 $irc_channels = array(); | |
36 $irc_users = array(); | |
37 $irc_clients = array(); | |
38 | |
39 $sock_fd_connecting = array(); | |
40 $sock_fd_connected = array(); | |
41 $sock_obj = array(); | |
42 | |
43 class Channel { | |
44 public $uname = NULL; | |
45 public $iname = NULL; | |
46 } | |
47 class User { | |
48 public $iname = NULL; | |
49 public $channels = array(); | |
50 } | |
51 class UDPMSGClient { | |
52 public $socket = NULL; | |
53 public $buffer = ''; | |
54 public $channels = array(); | |
55 public function select_read() { | |
56 if (!$this->udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); | |
57 } | |
58 public function select_error() { die("UDPMSG: Error: select error.\n"); } | |
59 private function udpmsg_read() { | |
60 $msg = socket_read($this->socket, 1024); | |
61 if (!strlen($msg)) { | |
62 fprintf(STDERR, "UDPMSG: End of file\n"); | |
63 return FALSE; | |
64 } | |
65 $udpmsg_buffer = &$this->buffer; | |
66 $udpmsg_buffer .= $msg; | |
67 while (strlen($udpmsg_buffer) > 2) { | |
68 $len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]); | |
69 if ($len <= 0 || $len > 1024) { | |
70 fprintf(STDERR, "UDPMSG: Error: protocol error\n"); | |
71 return FALSE; | |
72 } | |
73 if (strlen($udpmsg_buffer) < 2 + $len) break; | |
74 $msg = substr($udpmsg_buffer, 2, $len); | |
75 $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); | |
76 $parts = explode("\0", $msg); | |
77 $ret = array(); | |
78 for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; | |
79 $this->udpmsg_process($ret); | |
80 } | |
81 return TRUE; | |
82 } | |
83 function udpmsg_process($ret) { | |
84 global $config, $ircbot; | |
85 if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; | |
86 if (!array_key_exists($ret['CHN'], $this->channels)) return; | |
87 $ch = $this->channels[$ret['CHN']]; | |
88 $net = isset($ret['NET']) ? preg_replace('/[\x00\x10-\x13]/', '', $ret['NET']) : NULL; | |
89 $usr = preg_replace('/[\x00\x10-\x13]/', '', $ret['USR']); | |
90 switch ($ret['CMD']) { | |
91 case 'MSG': | |
92 if (!isset($ret['MSG'])) break; | |
93 $msg = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); | |
94 $client = $this->get_irc_client($usr, $ch); | |
95 if ($client === NULL) { | |
96 $pfx = '['.$usr.($net?' @ '.$net:'').'] '; | |
97 if (ord($msg[0]) == 1) { | |
98 if (substr($message, 1, 6) != 'ACTION') break; | |
99 $msg = chr(1).'ACTION '.$pfx.substr($msg, 8, -1).chr(1); | |
100 } else { | |
101 $msg = $pfx.$msg; | |
102 } | |
103 $client = $ircbot; | |
104 } | |
105 $client->irc_channel_send($ch, $msg); | |
106 break; | |
107 case 'JOIN': | |
108 if ($this->get_irc_client($usr, $ch) === NULL && $config['irc']['joinpartmessages']) $ircbot->irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); | |
109 break; | |
110 case 'PART': | |
111 if (!$this->irc_client_part($usr, $ch) && $config['irc']['joinpartmessages']) $ircbot->irc_channel_send($ch, '* '.$usr.' has left'.($net?' on network '.$net:'')); | |
112 break; | |
113 case 'NICK': | |
114 if (!isset($ret['NEWNICK'])) break; | |
115 $newnick = preg_replace('/[\x00\x10-\x13]/', '', $ret['NEWNICK']); | |
116 if ($config['irc']['joinpartmessages']) $ircbot->irc_channel_send($ch, '* '.$usr.' has changed their nickname to '.$newnick.($net?' on network '.$net:'')); | |
117 $this->irc_client_part($usr, $ch); | |
118 $this->get_irc_client($usr, $ch); | |
119 break; | |
120 } | |
121 } | |
122 function send($arr) { | |
123 global $config; | |
124 $tmp = array(); | |
125 $arr['DUMMY'] = rand(0, 999999); | |
126 $arr['NET'] = $config['udpmsg']['netname']; | |
127 foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } | |
128 $tmp[] = ''; | |
129 $msg = implode("\0", $tmp); | |
130 $len = strlen($msg); | |
131 if ($len > 1024) { | |
132 fprintf(STDERR, "UDPMSG: Error: message too long!\n"); | |
133 return; | |
134 } | |
135 $lens = chr(floor($len / 256)).chr($len % 256); | |
136 socket_write($this->socket, $lens.$msg); | |
137 } | |
138 function irc_client_part($name, $ch) { | |
139 if (!isset($irc_clients[$name])) return FALSE; | |
140 $client = $irc_clients[$name]; | |
141 unset($client->channels[$ch->iname]); | |
142 unset($client->joinchannels[$ch->iname]); | |
143 if (!count($client->channels) && !count($client->joinchannels)) { | |
144 $client->irc_send('QUIT :No remaining channels.'); | |
145 $client->close(); | |
146 } else { | |
147 $client->irc_send('PART :'.$ch->iname); | |
148 } | |
149 } | |
150 function get_irc_client($name, $ch) { | |
151 global $irc_clients, $config; | |
152 if (!isset($irc_clients[$name])) { | |
153 if (count($irc_clients) < $config['irc']['clientconnections']) { | |
154 $client = new Client(); | |
155 $client->uname = $client->iname = $name; | |
156 $client->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
157 socket_connect($client->socket, $config['irc']['host'], $config['irc']['port']); | |
158 $client->joinchannels[$ch->iname] = $ch; | |
159 $irc_clients[$name] = $client; | |
160 } | |
161 return NULL; | |
162 } | |
163 $client = $irc_clients[$name]; | |
164 if (!$client->connected || !$client->identified) return NULL; | |
165 if (!isset($client->channels[$ch->iname])) { | |
166 $client->joinchannels[$ch->iname] = $ch; | |
167 $client->irc_send('JOIN :'.$ch->iname); | |
168 return NULL; | |
169 } | |
170 return $client; | |
171 } | |
172 } | |
173 class IRCClientBase { | |
174 public $iname = NULL; | |
175 public $channels = array(); | |
176 public $socket = NULL; | |
177 public $buffer = ''; | |
178 public $connected = FALSE; | |
179 public $identified = FALSE; | |
180 public function irc_read() { | |
181 $newdata = socket_read($this->socket, 1024); | |
182 if ($newdata === FALSE || !strlen($newdata)) return FALSE; | |
183 $irc_buffer = $this->buffer . $newdata; | |
184 $offset = 0; | |
185 $len = strlen($irc_buffer); | |
186 while ($offset < $len) { | |
187 $posa = strpos($irc_buffer, "\n", $offset); | |
188 $posb = strpos($irc_buffer, "\r", $offset); | |
189 if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb); | |
190 else if ($posa !== FALSE) $pos = $posa; | |
191 else if ($posb !== FALSE) $pos = $posb; | |
192 else break; | |
193 $line = substr($irc_buffer, $offset, $pos - $offset); | |
194 if (strlen($line)) { | |
195 print('IR: '.$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 if (!$this->process($sender, $sendernick, $command, $parts)) return FALSE; | |
204 } | |
205 $offset = $pos + 1; | |
206 } | |
207 $this->buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); | |
208 return TRUE; | |
209 } | |
210 public function irc_send($line) { | |
211 print('IW: '.$line."\n"); | |
212 $line .= "\r\n"; | |
213 socket_send($this->socket, $line, strlen($line), 0); | |
214 } | |
215 public function irc_channel_send($ch, $msg) { | |
216 if (!$this->identified) return; | |
217 $this->irc_send('PRIVMSG '.$ch->iname.' :'.$msg); | |
218 } | |
219 } | |
220 class MasterClient extends IRCClientBase { | |
221 public function select_error() { die("IRC: Error: select error.\n"); } | |
222 public function select_read() { | |
223 if (!$this->irc_read()) die("IRC: read error.\n"); | |
224 } | |
225 public function process($sender, $sendernick, $command, $parts) { | |
226 global $irc_channels, $config, $irc_users, $udpmsg; | |
227 switch (strtoupper($command)) { | |
228 case '001': //Welcome | |
229 $this->iname = $parts[0]; | |
230 $this->identified = TRUE; | |
231 irc_user_add($this, TRUE); | |
232 foreach ($irc_channels as $ch) $this->irc_send("JOIN :".$ch->iname); | |
233 break; | |
234 case '433': //Nickname in use | |
235 $this->irc_send("NICK ".$config['irc']['nick'].rand(100,999)); | |
236 break; | |
237 case 'PING': | |
238 $this->irc_send('PONG :'.$parts[0]); | |
239 break; | |
240 case 'NICK': //Nickname change | |
241 //if (strcasecmp($sendernick, $this->iname)) $this->iname = $parts[0]; | |
242 $usr = $this->irc_getuser($sendernick, FALSE); | |
243 if ($usr === NULL) break; | |
244 $this->irc_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); | |
245 irc_user_delete($usr); | |
246 $usr->iname = $parts[0]; | |
247 irc_user_add($usr); | |
248 break; | |
249 case 'JOIN': | |
250 if (!strcasecmp($sendernick, $this->iname)) break; | |
251 $chs = explode(',', $parts[0]); | |
252 $usr = NULL; | |
253 foreach ($chs as $chn) { | |
254 $ch = $this->irc_getchannel($chn); | |
255 if ($ch === NULL) continue; | |
256 if ($usr === NULL) $usr = $this->irc_getuser($sendernick, TRUE); | |
257 $usr->channels[$ch->iname] = $ch; | |
258 if (!is_a($usr, 'Client')) $udpmsg->send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); | |
259 } | |
260 break; | |
261 case '353': //:server 353 me = channel :nickname nickname nickname | |
262 $ch = $this->irc_getchannel($parts[2]); | |
263 if ($ch === NULL) break; | |
264 $partsa = explode(' ', $parts[3]); | |
265 foreach ($partsa as $un) { | |
266 if (!strlen($un)) continue; | |
267 $usr = $this->irc_getuser(ltrim($un, '~&@%+'), TRUE); | |
268 $usr->channels[$ch->iname] = $ch; | |
269 if (!is_a($usr, 'Client')) $udpmsg->send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); | |
270 } | |
271 break; | |
272 case 'PART': | |
273 $usr = $this->irc_getuser($sendernick, FALSE); | |
274 if ($usr === NULL) break; | |
275 $chs = explode(',', $parts[0]); | |
276 foreach ($chs as $chn) $this->irc_part($usr, $chn); | |
277 break; | |
278 case 'KICK': | |
279 $usr = $this->irc_getuser($parts[1], FALSE); | |
280 if (is_a($usr, 'Client')) break; | |
281 $this->irc_part($usr, $parts[0]); | |
282 break; | |
283 case 'QUIT': | |
284 $usr = $this->irc_getuser($sendernick, FALSE); | |
285 if ($usr === NULL) break; | |
286 if (is_a($usr, 'Client')) break; | |
287 $this->irc_userchannels($usr, array('CMD' => 'PART')); | |
288 irc_user_delete($usr, TRUE); | |
289 break; | |
290 case 'PRIVMSG': | |
291 case 'NOTICE': | |
292 $ch = $this->irc_getchannel($parts[0]); | |
293 if (is_a($this->irc_getuser($sendernick, FALSE), 'Client')) break; | |
294 if ($ch === NULL) break; | |
295 $udpmsg->send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $sendernick)); | |
296 break; | |
297 } | |
298 return TRUE; | |
299 } | |
300 function irc_userchannels($usr, $msg) { | |
301 global $udpmsg; | |
302 $msg['USR'] = $usr->iname; | |
303 foreach ($usr->channels as $ch) { | |
304 $msg['CHN'] = $ch->uname; | |
305 $udpmsg->send($msg); | |
306 } | |
307 } | |
308 function irc_part($usr, $chn) { | |
309 global $udpmsg; | |
310 $ch = $this->irc_getchannel($chn); | |
311 if ($ch === NULL || $usr === NULL) return; | |
312 unset($usr->channels[$ch->name]); | |
313 if (!is_a($usr, 'Client')) $udpmsg->send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname)); | |
314 if (!count($usr->channels) && is_a($usr, 'User')) irc_user_delete($user, TRUE); | |
315 } | |
316 function irc_getchannel($chn) { | |
317 global $irc_channels; | |
318 $chn = strtoupper($chn); | |
319 if (!array_key_exists($chn, $irc_channels)) return NULL; | |
320 return $irc_channels[$chn]; | |
321 } | |
322 function irc_getuser($nick, $create = TRUE) { | |
323 global $irc_users; | |
324 $snick = strtoupper($nick); | |
325 if (array_key_exists($snick, $irc_users)) return $irc_users[$snick]; | |
326 if (!$create) return NULL; | |
327 $usr = new User(); | |
328 $usr->iname = $nick; | |
329 irc_user_add($usr, TRUE); | |
330 return $usr; | |
331 } | |
332 } | |
333 class Client extends IRCClientBase { | |
334 public $uname = NULL; | |
335 public $joinchannels = array(); | |
336 public function select_error() { | |
337 $this->close(); | |
338 } | |
339 public function select_write() { | |
340 $this->connected = TRUE; | |
341 $this->irc_send('USER '.$this->iname.' host server :'.$this->iname); | |
342 $this->irc_send('NICK '.$this->iname); | |
343 } | |
344 public function select_read() { | |
345 if (!$this->irc_read()) { | |
346 print("IRCC: read error.\n"); | |
347 $this->close(); | |
348 } | |
349 } | |
350 public function process($sender, $sendernick, $command, $parts) { | |
351 switch (strtoupper($command)) { | |
352 case '001': //Welcome | |
353 $this->iname = $parts[0]; | |
354 $this->identified = TRUE; | |
355 irc_user_add($this, TRUE); | |
356 foreach ($this->joinchannels as $ch) $this->irc_send("JOIN :".$ch->iname); | |
357 break; | |
358 case '433': //Nickname in use | |
359 $this->irc_send("NICK ".$this->iname.rand(100,999)); | |
360 break; | |
361 case 'PING': | |
362 $this->irc_send('PONG :'.$parts[0]); | |
363 break; | |
364 //Other messages are also received and processed by the master relay bot, so we ignore them here | |
365 } | |
366 return TRUE; | |
367 } | |
368 public function close() { | |
369 global $irc_clients; | |
370 unset($irc_clients[$this->uname]); | |
371 if ($this->identified) irc_user_delete($this, TRUE); | |
372 socket_close($this->socket); | |
373 socket_delete($this); | |
374 } | |
375 } | |
376 | |
377 srand(); | |
378 | |
379 print("Loading configuration...\n"); | |
380 if (!isset($config)) require constant('CONFIGFILE'); | |
381 if (!isset($config['irc']['joinpartmessages'])) $config['irc']['joinpartmessages'] = TRUE; | |
382 if (!isset($config['udpmsg']['send_alive'])) $config['udpmsg']['send_alive'] = 1800; | |
383 | |
384 $udpmsg = new UDPMSGClient(); | |
385 $udpmsg->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
386 socket_connect($udpmsg->socket, $config['udpmsg']['host'], $config['udpmsg']['port']); | |
387 socket_add_connected($udpmsg); | |
388 | |
389 $ircbot = new MasterClient(); | |
390 $ircbot->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
391 socket_connect($ircbot->socket, $config['irc']['host'], $config['irc']['port']); | |
392 socket_add_connected($ircbot); | |
393 $ircbot->irc_send('USER '.$config['irc']['ident'].' host server :'.$config['irc']['realname']); | |
394 $ircbot->irc_send('NICK '.$config['irc']['nick']); | |
395 | |
396 foreach ($config['channels'] as $iname => $uname) { | |
397 $ch = new Channel(); | |
398 $ch->iname = $iname; | |
399 $ch->uname = $uname; | |
400 $irc_channels[strtoupper($ch->iname)] = $ch; | |
401 $udpmsg->channels[$ch->uname] = $ch; | |
402 } | |
403 | |
404 print("Starting IRC Relay...\n"); | |
405 | |
406 function socket_add_connecting($obj) { | |
407 global $sock_fd_connecting, $sock_obj; | |
408 $sock_fd_connecting[(int)$obj->socket] = $obj->socket; | |
409 $sock_obj[(int)$obj->socket] = $obj; | |
410 } | |
411 function socket_add_connected($obj) { | |
412 global $sock_fd_connected, $sock_obj; | |
413 $sock_fd_connected[(int)$obj->socket] = $obj->socket; | |
414 $sock_obj[(int)$obj->socket] = $obj; | |
415 } | |
416 function socket_delete($obj) { | |
417 global $sock_fd_connecting, $sock_fd_connected, $sock_obj; | |
418 unset($sock_fd_connecting[(int)$obj->socket]); | |
419 unset($sock_fd_connected[(int)$obj->socket]); | |
420 unset($sock_obj[(int)$obj->socket]); | |
421 } | |
422 | |
423 function irc_user_add($user, $replace = FALSE) { | |
424 global $irc_users; | |
425 $name = strtoupper($user->iname); | |
426 if (!$replace && isset($irc_users[$name])) return FALSE; | |
427 $irc_users[$name] = $user; | |
428 return TRUE; | |
429 } | |
430 function irc_user_delete($user, $checkobject = FALSE) { | |
431 global $irc_users; | |
432 $name = strtoupper(is_object($user) ? $user->iname : $user); | |
433 if (!isset($irc_users[$name])) return TRUE; | |
434 if ($checkobject && $irc_users[$name] !== $user) return FALSE; | |
435 unset($irc_users[$name]); | |
436 return TRUE; | |
437 } | |
438 | |
439 $atime = 0; | |
440 print("Running\n"); | |
441 while (TRUE) { | |
442 $selread = $sock_fd_connected; | |
443 $selwrite = $sock_fd_connecting; | |
444 $selerror = array_merge($selread, $selwrite); | |
445 socket_select(&$selread, &$selwrite, &$selerror, 120); | |
446 foreach ($selread as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_read($socket); | |
447 foreach ($selwrite as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_write($socket); | |
448 foreach ($selerror as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_error($socket); | |
449 | |
450 $ctime = time(); | |
451 if ($atime < $ctime) { | |
452 foreach ($irc_users as $usr) if (is_a($usr, 'IUser')) $ircbot->irc_userchannels($usr, array('CMD' => 'ALIVE')); | |
453 $atime = $ctime + $config['udpmsg']['send_alive']; | |
454 } | |
455 } |