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 }