comparison multiclientrelay/multiclientrelay.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
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 }