0
|
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 IRC Relay bot (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); |
|
32 print("More information: http://wiki.qontrol.nl/IRCRelay\n"); |
|
33 print("\n"); |
|
34 |
|
35 class UUser { |
|
36 public $iname = NULL; |
|
37 public $unet = NULL; |
|
38 public $uname = NULL; |
|
39 public $channels = array(); |
|
40 public $seen = 0; |
|
41 public $explicit = FALSE; |
|
42 } |
|
43 class IUser { |
|
44 public $iname = NULL; |
|
45 public $channels = array(); |
|
46 } |
|
47 class Channel { |
|
48 public $name = NULL; |
|
49 public $iname = NULL; |
|
50 public $uname = NULL; |
|
51 } |
|
52 |
|
53 $iusers = array(); |
|
54 $uusers = array(); |
|
55 $ichannels = array(); |
|
56 $uchannels = array(); |
|
57 |
|
58 $ircd_socket = NULL; |
|
59 $ircd_buffer = ''; |
|
60 $ircd_name = ''; |
|
61 |
|
62 $udpmsg_socket = NULL; |
|
63 $udpmsg_buffer = ''; |
|
64 $udpmsg_config = array(); |
|
65 |
|
66 $bot_nick = NULL; |
|
67 |
|
68 srand(); |
|
69 |
|
70 print("Loading configuration...\n"); |
|
71 if (!isset($config)) require constant('CONFIGFILE'); |
|
72 |
|
73 $ircd_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
74 socket_connect($ircd_socket, $config['ircd']['host'], $config['ircd']['port']); |
|
75 $ircd_name = $config['ircd']['name']; |
|
76 $bot_nick = $config['ircd']['bot_nick']; |
|
77 if (!strlen($bot_nick)) $bot_nick = NULL; |
|
78 ircd_send('PROTOCTL'); |
|
79 ircd_send('PASS :'.$config['ircd']['password']); |
|
80 ircd_send('SERVER '.$ircd_name.' 0 0 :'.$config['ircd']['desc']); |
|
81 ircd_send('AO 0 0 0 0 - 0 :Kwaaknet.org'); |
|
82 if ($bot_nick) ircd_send('NICK '.$bot_nick.' 0 0 gateway '.$ircd_name.' '.$ircd_name.' 0 :'.$config['ircd']['desc']); |
|
83 ircd_send('ES'); |
|
84 $udpmsg_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
85 socket_connect($udpmsg_socket, $config['udpmsg']['host'], $config['udpmsg']['port']); |
|
86 foreach ($config['channels'] as $iname => $uname) { |
|
87 $ch = new Channel(); |
|
88 $ch->iname = $iname; |
|
89 $ch->uname = $uname; |
|
90 $ch->name = $iname; |
|
91 $uchannels[$ch->uname] = $ch; |
|
92 $ichannels[strtoupper($ch->iname)] = $ch; |
|
93 if ($bot_nick) ircd_send(':'.$bot_nick.' JOIN :'.$ch->iname); |
|
94 } |
|
95 $iusers[strtoupper($bot_nick)] = new IUser(); |
|
96 $iusers[strtoupper($bot_nick)]->iname = $bot_nick; |
|
97 |
|
98 function reconfigure() { |
|
99 global $config; |
|
100 include constant('CONFIGFILE'); |
|
101 } |
|
102 |
|
103 $ntime = 0; |
|
104 $atime = 0; |
|
105 print("Running\n"); |
|
106 while (TRUE) { |
|
107 $selread = array($udpmsg_socket, $ircd_socket); |
|
108 $selwrite = NULL; |
|
109 $selerror = array($udpmsg_socket, $ircd_socket); |
|
110 socket_select(&$selread, &$selwrite, &$selerror, 60); |
|
111 if (in_array($udpmsg_socket, $selerror)) die("UDPMSG: Error: select error.\n"); |
|
112 if (in_array($ircd_socket, $selerror)) die("IRCd: Error: select error.\n"); |
|
113 if (in_array($udpmsg_socket, $selread) && !udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); |
|
114 if (in_array($ircd_socket, $selread) && !ircd_read()) die("IRCd: read error.\n"); |
|
115 $ctime = time(); |
|
116 if ($ntime < $ctime) { |
|
117 foreach ($uusers as $usr) { |
|
118 if ($usr->seen > $ctime - $config['udpmsg'][$usr->explicit ? 'timeout_explicit' : 'timeout_implicit']) continue; |
|
119 unset($uusers[$usr->uname]); |
|
120 unset($iusers[strtoupper($usr->iname)]); |
|
121 ircd_send(':'.$usr->iname.' QUIT :Inactivity'); |
|
122 } |
|
123 $ntime = $ctime + 120; //min($config['udpmsg']['timeout_explicit'], $config['udpmsg']['timeout_implicit']); |
|
124 } |
|
125 if ($atime < $ctime) { |
|
126 foreach ($iusers as $usr) if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'ALIVE')); |
|
127 $atime = $ctime + (isset($config['udpmsg']['send_alive']) ? $config['udpmsg']['send_alive'] : 1800); |
|
128 } |
|
129 } |
|
130 |
|
131 function udpmsg_read() { |
|
132 global $udpmsg_socket, $udpmsg_buffer; |
|
133 $msg = socket_read($udpmsg_socket, 1024); |
|
134 if (!strlen($msg)) { |
|
135 fprintf(STDERR, "UDPMSG: End of file\n"); |
|
136 return FALSE; |
|
137 } |
|
138 $udpmsg_buffer .= $msg; |
|
139 while (strlen($udpmsg_buffer) > 2) { |
|
140 $len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]); |
|
141 if ($len <= 0 || $len > 1024) { |
|
142 fprintf(STDERR, "UDPMSG: Error: protocol error\n"); |
|
143 return FALSE; |
|
144 } |
|
145 if (strlen($udpmsg_buffer) < 2 + $len) break; |
|
146 $msg = substr($udpmsg_buffer, 2, $len); |
|
147 $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); |
|
148 $parts = explode("\0", $msg); |
|
149 $ret = array(); |
|
150 for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; |
|
151 udpmsg_process($ret); |
|
152 } |
|
153 return TRUE; |
|
154 } |
|
155 |
|
156 function udpmsg_send($arr) { |
|
157 global $udpmsg_socket, $config; |
|
158 $tmp = array(); |
|
159 $arr['DUMMY'] = rand(0, 999999); |
|
160 $arr['NET'] = $config['udpmsg']['netname']; |
|
161 foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } |
|
162 $tmp[] = ''; |
|
163 $msg = implode("\0", $tmp); |
|
164 $len = strlen($msg); |
|
165 if ($len > 1024) { |
|
166 fprintf(STDERR, "UDPMSG: Error: message too long!\n"); |
|
167 return; |
|
168 } |
|
169 $lens = chr(floor($len / 256)).chr($len % 256); |
|
170 socket_write($udpmsg_socket, $lens.$msg); |
|
171 } |
|
172 |
|
173 function ircd_read() { |
|
174 global $ircd_socket, $ircd_buffer; |
|
175 $newdata = socket_read($ircd_socket, 1024); |
|
176 if ($newdata === FALSE || !strlen($newdata)) return FALSE; |
|
177 $ircd_buffer .= $newdata; |
|
178 $offset = 0; |
|
179 $len = strlen($ircd_buffer); |
|
180 while ($offset < $len) { |
|
181 $posa = strpos($ircd_buffer, "\n", $offset); |
|
182 $posb = strpos($ircd_buffer, "\r", $offset); |
|
183 if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb); |
|
184 else if ($posa !== FALSE) $pos = $posa; |
|
185 else if ($posb !== FALSE) $pos = $posb; |
|
186 else break; |
|
187 $line = substr($ircd_buffer, $offset, $pos - $offset); |
|
188 if (strlen($line) && !ircd_process($line)) { |
|
189 fprintf(STDERR, "IRCd: process error\n"); |
|
190 return FALSE; |
|
191 } |
|
192 $offset = $pos + 1; |
|
193 } |
|
194 $ircd_buffer = ($offset == $len) ? '' : substr($ircd_buffer, $offset); |
|
195 return TRUE; |
|
196 } |
|
197 |
|
198 function ircd_send($line) { |
|
199 global $ircd_socket; |
|
200 print('IW: '.$line."\n"); |
|
201 $line .= "\r\n"; |
|
202 socket_send($ircd_socket, $line, strlen($line), 0); |
|
203 } |
|
204 |
|
205 function udpmsg_process($ret) { |
|
206 global $uchannels; |
|
207 if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; |
|
208 if (!array_key_exists($ret['CHN'], $uchannels)) return; |
|
209 $ch = $uchannels[$ret['CHN']]; |
|
210 $net = isset($ret['NET']) ? $ret['NET'] : NULL; |
|
211 switch ($ret['CMD']) { |
|
212 case 'MSG': |
|
213 if (!isset($ret['MSG'])) break; |
|
214 $usr = udpmsg_join($ret['USR'], $ch, $net); |
|
215 ircd_send(':'.$usr->iname.' PRIVMSG '.$ch->iname.' :'.preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG'])); |
|
216 break; |
|
217 case 'ALIVE': |
|
218 case 'JOIN': |
|
219 udpmsg_join($ret['USR'], $ch, $net, TRUE); |
|
220 break; |
|
221 case 'PART': |
|
222 udpmsg_part($ret['USR'], $ch); |
|
223 break; |
|
224 case 'NICK': |
|
225 udpmsg_part($ret['USR'], $ch); |
|
226 if (!isset($ret['NEWNICK'])) break; |
|
227 udpmsg_join($ret['NEWNICK'], $ch, $net, TRUE); |
|
228 break; |
|
229 } |
|
230 } |
|
231 |
|
232 function udpmsg_join($name, $ch, $net = NULL, $explicit = FALSE) { |
|
233 $usr = udpmsg_getuser($name, TRUE, $net, $explicit); |
|
234 if (array_key_exists($ch->name, $usr->channels)) return $usr; |
|
235 $usr->channels[$ch->name] = $ch; |
|
236 ircd_send(':'.$usr->iname.' JOIN :'.$ch->iname); |
|
237 return $usr; |
|
238 } |
|
239 |
|
240 function udpmsg_part($name, $ch) { |
|
241 global $uusers, $iusers; |
|
242 $usr = udpmsg_getuser($name, FALSE); |
|
243 if ($usr === NULL || !array_key_exists($ch->name, $usr->channels)) return; |
|
244 unset($usr->channels[$ch->name]); |
|
245 if (count($usr->channels)) { |
|
246 ircd_send(':'.$usr->iname.' PART :'.$ch->iname); |
|
247 } else { |
|
248 unset($uusers[$usr->uname]); |
|
249 unset($iusers[strtoupper($usr->iname)]); |
|
250 ircd_send(':'.$usr->iname.' QUIT :No more channels'); |
|
251 } |
|
252 } |
|
253 |
|
254 function udpmsg_getuser($nick, $create = FALSE, $net = NULL, $explicit = FALSE) { |
|
255 global $uusers, $iusers, $ircd_name; |
|
256 if (array_key_exists($nick, $uusers)) { |
|
257 $usr = $uusers[$nick]; |
|
258 } else { |
|
259 if (!$create) return NULL; |
|
260 $usr = new UUser(); |
|
261 $usr->uname = $nick; |
|
262 $usr->unet = $net; |
|
263 $usr->explicit = $explicit; |
|
264 $usr->iname = udpmsg_getnick($usr, $nick); |
|
265 $ident = preg_replace('/[^a-zA-Z0-9\-_]/', '', $usr->uname); |
|
266 if (!strlen($ident)) $ident = 'unknown'; |
|
267 $net = preg_replace('/[^a-zA-Z0-9\-_]/', '', $net); |
|
268 if (!strlen($net)) $net = 'unknown'; |
|
269 ircd_send('NICK '.$usr->iname.' 0 0 '.$ident.' '.$net.'.udpmsg3 '.$ircd_name.' 0 :UDPMSG3 user'); |
|
270 $uusers[$nick] = $iusers[strtoupper($usr->iname)] = $usr; |
|
271 } |
|
272 $usr->seen = time(); |
|
273 return $usr; |
|
274 } |
|
275 |
|
276 function udpmsg_getnick($usr, $req) { |
|
277 global $iusers, $config; |
|
278 $nick = preg_replace('/[^a-zA-Z0-9\-_]/', '', $req); |
|
279 if (!strlen($nick)) $nick = 'NoNick'; |
|
280 switch ($config['ircd']['nick_format']) { |
|
281 case 'plain': break; |
|
282 case 'prefix': $nick = $config['ircd']['nick_prefix'].$nick; break; |
|
283 case 'suffix': $nick = $nick.$config['ircd']['nick_suffix']; break; |
|
284 } |
|
285 if (isset($iusers[strtoupper($nick)])) { |
|
286 switch ($config['ircd']['nick_in_use']) { |
|
287 case 'prefix': $nick = $config['ircd']['nick_prefix'].$nick; break; |
|
288 case 'suffix': $nick = $nick.$config['ircd']['nick_suffix']; break; |
|
289 } |
|
290 $bnick = $nick; |
|
291 while (isset($iusers[strtoupper($nick)])) $nick = $bnick.rand(0, 999); |
|
292 } |
|
293 return $nick; |
|
294 } |
|
295 |
|
296 function ircd_process($line) { |
|
297 global $ichannels, $iusers, $ircd_name, $bot_nick; |
|
298 print('IR: '.$line."\n"); |
|
299 $partsa = explode(' :', $line, 2); |
|
300 $parts = explode(' ', $partsa[0]); |
|
301 $sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : NULL; |
|
302 $command = array_shift(&$parts); |
|
303 if (count($partsa) > 1) array_push(&$parts, $partsa[1]); |
|
304 $partsa = explode('!', $sender); |
|
305 $sendernick = $partsa[0]; |
|
306 switch (strtoupper($command)) { |
|
307 case 'REHASH': |
|
308 if (strtoupper($parts[0]) != strtoupper($ircd_name)) break; |
|
309 reconfigure(); |
|
310 ircd_send('NOTICE '.$sendernick.' :Reloaded configuration.'); |
|
311 break; |
|
312 case 'PING': |
|
313 ircd_send('PONG'.($sender?' '.$sender:'').' :'.$parts[0]); |
|
314 break; |
|
315 case 'SVSNICK': |
|
316 $sendernick = array_shift($parts); |
|
317 case 'NICK': |
|
318 if ($sender === NULL) { |
|
319 ircd_getuser($parts[0], TRUE); |
|
320 } else { |
|
321 $usr = ircd_getuser($sendernick, FALSE); |
|
322 if ($usr === NULL) break; |
|
323 if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); |
|
324 unset($iusers[strtoupper($usr->iname)]); |
|
325 $usr->iname = $parts[0]; |
|
326 $iusers[strtoupper($usr->iname)] = $usr; |
|
327 } |
|
328 break; |
|
329 case 'PRIVMSG': case 'NOTICE': |
|
330 if ($parts[1] == '--print-internals' && $parts[0][0] == '#') { |
|
331 ircd_send(':'.$bot_nick.' PRIVMSG '.$parts[0].' :We have '.count($GLOBALS['iusers']).' IRC users of which '.count($GLOBALS['uusers']).' UDPMSG users, and '.count($GLOBALS['ichannels']).' channels.'); |
|
332 break; |
|
333 } |
|
334 $ch = ircd_getchannel($parts[0]); |
|
335 $usr = ircd_getuser($sendernick, FALSE); |
|
336 if ($ch === NULL || $usr === NULL) break; |
|
337 udpmsg_send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $usr->iname)); |
|
338 break; |
|
339 case 'SAJOIN': |
|
340 case 'SVSJOIN': |
|
341 $sendernick = array_shift($parts); |
|
342 case 'JOIN': |
|
343 $usr = ircd_getuser($sendernick, FALSE); |
|
344 if ($usr === NULL) break; |
|
345 $chs = explode(',', $parts[0]); |
|
346 foreach ($chs as $chn) { |
|
347 $ch = ircd_getchannel($chn); |
|
348 if ($ch === NULL) break; |
|
349 $usr->channels[$ch->name] = $ch; |
|
350 if (is_a($usr, 'IUser')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); |
|
351 } |
|
352 break; |
|
353 case 'SAPART': |
|
354 case 'SVSPART': |
|
355 $sendernick = array_shift($parts); |
|
356 case 'PART': |
|
357 $usr = ircd_getuser($sendernick, FALSE); |
|
358 if ($usr === NULL) break; |
|
359 $chs = explode(',', $parts[0]); |
|
360 foreach ($chs as $chn) ircd_part($usr, $chn); |
|
361 break; |
|
362 case 'KICK': |
|
363 ircd_part(ircd_getuser($parts[1], FALSE), $parts[0]); |
|
364 break; |
|
365 case 'QUIT': |
|
366 ircd_quit($sendernick); |
|
367 break; |
|
368 case 'KILL': |
|
369 case 'SVSKILL': |
|
370 ircd_quit($parts[0]); |
|
371 break; |
|
372 } |
|
373 return TRUE; |
|
374 } |
|
375 |
|
376 function ircd_quit($nick) { |
|
377 global $iusers, $uusers; |
|
378 $usr = ircd_getuser($nick, FALSE); |
|
379 if ($usr === NULL) return; |
|
380 if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'PART')); |
|
381 if (is_a($usr, 'UUser')) unset($uusers[$usr->uname]); |
|
382 unset($iusers[strtoupper($usr->iname)]); |
|
383 } |
|
384 |
|
385 function ircd_getchannel($chn) { |
|
386 global $ichannels; |
|
387 $chn = strtoupper($chn); |
|
388 if (!array_key_exists($chn, $ichannels)) return NULL; |
|
389 return $ichannels[$chn]; |
|
390 } |
|
391 function ircd_getuser($nick, $create = TRUE) { |
|
392 global $iusers; |
|
393 $snick = strtoupper($nick); |
|
394 if (array_key_exists($snick, $iusers)) return $iusers[$snick]; |
|
395 if (!$create) return NULL; |
|
396 $usr = new IUser(); |
|
397 $usr->iname = $nick; |
|
398 $iusers[$snick] = $usr; |
|
399 return $usr; |
|
400 } |
|
401 function ircd_part($usr, $chn) { |
|
402 $ch = ircd_getchannel($chn); |
|
403 if ($ch === NULL || $usr === NULL) return; |
|
404 unset($usr->channels[$ch->name]); |
|
405 if (is_a($usr, 'IUser')) udpmsg_send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname)); |
|
406 } |
|
407 function ircd_userchannels($usr, $msg) { |
|
408 $msg['USR'] = $usr->iname; |
|
409 foreach ($usr->channels as $ch) { |
|
410 $msg['CHN'] = $ch->uname; |
|
411 udpmsg_send($msg); |
|
412 } |
|
413 } |