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 (!isset($config)) include './config.php'; |
|
30 |
|
31 print("UCIS UDPMSG3 IRC server (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); |
|
32 print("More information: http://wiki.ucis.nl/UDPMSG3\n"); |
|
33 print("\n"); |
|
34 |
|
35 srand(); |
|
36 |
|
37 class Channel { |
|
38 public $name = NULL; |
|
39 public $udpmsg = FALSE; |
|
40 public $users = array(); |
|
41 public $persistent = FALSE; |
|
42 public function __construct($name) { $this->name = $name; } |
|
43 } |
|
44 class Client { |
|
45 public $socket = NULL; |
|
46 public $name = NULL; |
|
47 public $channels = array(); |
|
48 public $readbuffer = ''; |
|
49 } |
|
50 |
|
51 $channels = array(); |
|
52 $clients = array(); |
|
53 $udpmsg = NULL; |
|
54 $udpmsg_config = NULL; |
|
55 $udpmsg_connected = FALSE; |
|
56 $udpmsg_timer = 0; |
|
57 $udpmsg_buffer = ''; |
|
58 $udpmsg_channels = array(); |
|
59 $listener = NULL; |
|
60 |
|
61 srand(); |
|
62 |
|
63 prepare(); |
|
64 configure(); |
|
65 init(); |
|
66 readloop(); |
|
67 |
|
68 function getchannel($name) { |
|
69 global $channels; |
|
70 if (array_key_exists($name, $channels)) return $channels[$name]; |
|
71 $ch = new Channel($name); |
|
72 $channels[$name] = $ch; |
|
73 return $ch; |
|
74 } |
|
75 function configure() { |
|
76 global $channels, $config; |
|
77 global $udpmsg, $udpmsg_config, $udpmsg_channels; |
|
78 |
|
79 print("Parsing configuration...\n"); |
|
80 //require 'config.php'; |
|
81 |
|
82 if (isset($config['udpmsg'])) { |
|
83 $udpmsg_config = $config['udpmsg']; |
|
84 $udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
85 socket_set_nonblock($udpmsg); |
|
86 @socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']); |
|
87 } |
|
88 foreach ($config['channels'] as $key => $cf) { |
|
89 $ch = getchannel($key); |
|
90 $ch->persistent = TRUE; |
|
91 if (isset($cf['udpmsg'])) { |
|
92 $ch->udpmsg = $cf['udpmsg']; |
|
93 $udpmsg_channels[$cf['udpmsg']] = $ch; |
|
94 } |
|
95 } |
|
96 } |
|
97 |
|
98 function prepare() { |
|
99 global $channels; |
|
100 print("Starting IRC Relay...\n"); |
|
101 } |
|
102 |
|
103 function init() { |
|
104 global $listener; |
|
105 print("Initializing...\n"); |
|
106 $listener = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
107 socket_bind($listener, '0.0.0.0', 7777) or die("Could not bind listening socket.\n"); |
|
108 socket_listen($listener) or die("Could not start listening.\n"); |
|
109 } |
|
110 |
|
111 function readloop() { |
|
112 global $channels, $clients, $udpmsg, $listener, $udpmsg_connected, $udpmsg_config, $udpmsg_timer; |
|
113 |
|
114 $ltime = 0; |
|
115 |
|
116 print("Running\n"); |
|
117 while (TRUE) { |
|
118 $ntime = time(); |
|
119 $tdiff = $ntime - $ltime; |
|
120 $ltime = $ntime; |
|
121 |
|
122 if ($udpmsg === NULL && $udpmsg_config['host'] !== NULL) { |
|
123 $udpmsg_timer += $tdiff; |
|
124 if ($udpmsg_timer >= 30) { |
|
125 fprintf(STDERR, "UDPMSG: Reconnecting...\n"); |
|
126 $udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
|
127 socket_set_nonblock($udpmsg); |
|
128 @socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']); |
|
129 $udpmsg_timer = 0; |
|
130 } |
|
131 } |
|
132 |
|
133 $selread = array(); |
|
134 $selwrite = array(); |
|
135 $selerror = array(); |
|
136 |
|
137 if ($udpmsg !== NULL) { |
|
138 $selread[] = $udpmsg; |
|
139 $selerror[] = $udpmsg; |
|
140 if (!$udpmsg_connected) $selwrite[] = $udpmsg; |
|
141 } |
|
142 $selread[] = $listener; |
|
143 |
|
144 foreach ($clients as $client) { |
|
145 $selread[] = $client->socket; |
|
146 $selerror[] = $client->socket; |
|
147 } |
|
148 |
|
149 socket_select(&$selread, &$selwrite, &$selerror, 10); |
|
150 |
|
151 if (in_array($listener, $selread)) { |
|
152 client_accept($listener); |
|
153 } |
|
154 |
|
155 if (in_array($udpmsg, $selerror)) { |
|
156 fprintf(STDERR, "UDPMSG: Error: select error.\n"); |
|
157 socket_close($udpmsg); |
|
158 $udpmsg = NULL; |
|
159 $udpmsg_connected = FALSE; |
|
160 } else { |
|
161 if (in_array($udpmsg, $selwrite) && !$udpmsg_connected) { |
|
162 fprintf(STDERR, "UDPMSG: Connected.\n"); |
|
163 $udpmsg_buffer = ''; |
|
164 $udpmsg_connected = TRUE; |
|
165 } |
|
166 if (in_array($udpmsg, $selread)) { |
|
167 if (!udpmsg_read($udpmsg)) { |
|
168 fprintf(STDERR, "UDPMSG: Error: closing connection.\n"); |
|
169 socket_close($udpmsg); |
|
170 $udpmsg = NULL; |
|
171 $udpmsg_connected = FALSE; |
|
172 } |
|
173 } |
|
174 } |
|
175 |
|
176 foreach ($clients as $client) { |
|
177 if (in_array($client->socket, $selerror)) { |
|
178 client_close($client); |
|
179 } else if (in_array($client->socket, $selread)) { |
|
180 if (!client_read($client)) { |
|
181 client_close($client); |
|
182 } |
|
183 } |
|
184 } |
|
185 } |
|
186 } |
|
187 |
|
188 function client_accept($listener) { |
|
189 global $clients; |
|
190 $client = new Client(); |
|
191 $client->socket = socket_accept($listener); |
|
192 if (!$client->socket) return; |
|
193 if (!socket_getpeername($client->socket, &$addr, &$port)) $addr = 'unknown'; |
|
194 fprintf(STDERR, 'Accepted client '.$addr.':'.$port."\n"); |
|
195 client_send($client, ':server NOTICE AUTH :*** Not looking up your hostname, we do not care.'); |
|
196 client_send($client, ':server NOTICE AUTH :*** Not checking ident, are you serious?'); |
|
197 $clients[(int)($client->socket)] = $client; |
|
198 } |
|
199 |
|
200 function client_close($client, $reason = '') { |
|
201 global $clients, $channels; |
|
202 socket_close($client->socket); |
|
203 array_remove($clients, $client); |
|
204 $users = array(); |
|
205 foreach ($client->channels as $ch) { |
|
206 array_remove($ch->users, $client); |
|
207 if (count($ch->users)) { |
|
208 foreach ($ch->users as $user) if (!in_array($user, $users)) $users[] = $user; |
|
209 } elseif (!$ch->persistent) { |
|
210 array_remove($channels, $ch); |
|
211 } |
|
212 } |
|
213 foreach ($users as $user) client_send($user, ':'.$client->name.' QUIT :'.$reason); |
|
214 } |
|
215 |
|
216 function udpmsg_read() { |
|
217 global $channels, $clients, $udpmsg, $udpmsg_buffer, $udpmsg_channels, $udpmsg_config; |
|
218 $msg = socket_read($udpmsg, 1024); |
|
219 if (!strlen($msg)) { |
|
220 fprintf(STDERR, "UDPMSG: End of file\n"); |
|
221 return FALSE; |
|
222 } |
|
223 $udpmsg_buffer .= $msg; |
|
224 while (strlen($udpmsg_buffer) > 2) { |
|
225 $len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]); |
|
226 if ($len <= 0 || $len > 1024) { |
|
227 fprintf(STDERR, "UDPMSG: Error: protocol error\n"); |
|
228 return FALSE; |
|
229 } |
|
230 if (strlen($udpmsg_buffer) < 2 + $len) break; |
|
231 $msg = substr($udpmsg_buffer, 2, $len); |
|
232 $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); |
|
233 $parts = explode("\0", $msg); |
|
234 $ret = array(); |
|
235 for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; |
|
236 |
|
237 if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) continue; |
|
238 if (!array_key_exists($ret['CHN'], $udpmsg_channels)) continue; |
|
239 $ch = $udpmsg_channels[$ret['CHN']]; |
|
240 if ($ret['CMD'] == 'MSG') { |
|
241 if (!isset($ret['MSG'])) continue; |
|
242 $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['USR']); |
|
243 if ($udpmsg_config['nicknetwork'] && isset($ret['NET'])) $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['NET']).$udpmsg_config['nickseparator'].$from; |
|
244 $from = $udpmsg_config['nickprefix'].$from; |
|
245 $message = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); |
|
246 channel_send($ch, NULL, ':'.$from.' PRIVMSG '.$ch->name.' :'.$message); |
|
247 } |
|
248 } |
|
249 return TRUE; |
|
250 } |
|
251 |
|
252 function udpmsg_send($arr) { |
|
253 global $udpmsg, $udpmsg_connected; |
|
254 if (!$udpmsg || !$udpmsg_connected) return; |
|
255 $tmp = array(); |
|
256 foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } |
|
257 $tmp[] = ''; |
|
258 $msg = implode("\0", $tmp); |
|
259 $len = strlen($msg); |
|
260 if ($len > 1024) { |
|
261 fprintf(STDERR, "UDPMSG: Error: message too long!\n"); |
|
262 return; |
|
263 } |
|
264 $lens = chr(floor($len / 256)).chr($len % 256); |
|
265 socket_write($udpmsg, $lens.$msg); |
|
266 } |
|
267 |
|
268 |
|
269 function array_remove(&$arr, $item) { |
|
270 foreach ($arr as $key => $value) if ($value === $item) unset($arr[$key]); |
|
271 } |
|
272 |
|
273 function find_nick($nick) { |
|
274 global $clients; |
|
275 $nick = strtoupper($nick); |
|
276 foreach ($clients as $client) if (strtoupper($client->name) == $nick) return $client; |
|
277 return FALSE; |
|
278 } |
|
279 |
|
280 function client_read($client) { |
|
281 $newdata = socket_read($client->socket, 1024); |
|
282 if ($newdata === FALSE || !strlen($newdata)) return FALSE; |
|
283 $client->readbuffer .= $newdata; |
|
284 $offset = 0; |
|
285 $len = strlen($client->readbuffer); |
|
286 while ($offset < $len) { |
|
287 $posa = strpos($client->readbuffer, "\n", $offset); |
|
288 $posb = strpos($client->readbuffer, "\r", $offset); |
|
289 if ($posa !== FALSE && $posb !== FALSE) { |
|
290 $pos = min($posa, $posb); |
|
291 } else if ($posa !== FALSE) { |
|
292 $pos = $posa; |
|
293 } else if ($posb !== FALSE) { |
|
294 $pos = $posb; |
|
295 } else { |
|
296 break; |
|
297 } |
|
298 $line = substr($client->readbuffer, $offset, $pos - $offset); |
|
299 if (strlen($line)) { |
|
300 if (!client_process($client, $line)) return FALSE; |
|
301 } |
|
302 $offset = $pos + 1; |
|
303 } |
|
304 if ($offset == $len) { |
|
305 $client->readbuffer = ''; |
|
306 } else if ($offset != 0) { |
|
307 $client->readbuffer = substr($client->readbuffer, $offset); |
|
308 } |
|
309 return TRUE; |
|
310 } |
|
311 |
|
312 function client_process($client, $line) { |
|
313 $partsa = explode(' :', $line, 2); |
|
314 $parts = explode(' ', $partsa[0]); |
|
315 $command = array_shift(&$parts); |
|
316 if (count($partsa) > 1) array_push(&$parts, $partsa[1]); |
|
317 $clientname = $client->name; |
|
318 switch (strtoupper($command)) { |
|
319 case 'NICK': //Nickname change: newnick = $parts[0] |
|
320 if (find_nick($parts[0])) { |
|
321 client_sendnumeric($client, 433, $parts[0].' :Nickname in use'); |
|
322 } elseif (!preg_match('/^[a-zA-Z0-9\-_]+$/', $parts[0])) { |
|
323 client_sendnumeric($client, 432, $parts[0].' :Erroneous nickname'); |
|
324 } elseif ($client->name === NULL) { |
|
325 $client->name = $parts[0]; |
|
326 client_sendnumeric($client, 001, ':Welcome to the Internet Relay Chat Network'); |
|
327 client_sendnumeric($client, 002, ':Your host is UDPMSG3-IRCD'); |
|
328 client_sendnumeric($client, 003, ':This server was created some time ago'); |
|
329 client_sendnumeric($client, 004, 'irc.udpmsg3.local 1.0 + +'); |
|
330 client_sendnumeric($client, 005, 'NICKLEN=16 CHANNELLEN=16 CHANTYPES=# NETWORK=UDPMSG3 :are supported by this server'); |
|
331 client_sendnumeric($client, 251, ':There are '.count($GLOBALS['clients']).' users and some invisible on a bunch of servers'); |
|
332 client_sendnumeric($client, 252, '0 :operator(s) online'); |
|
333 client_sendnumeric($client, 254, count($GLOBALS['channels']).' :channels formed'); |
|
334 client_sendnumeric($client, 255, ':I have a bunch of clients and a bunch of servers'); |
|
335 client_sendnumeric($client, 265, ':Current Local Users: undisclosed'); |
|
336 client_sendnumeric($client, 266, ':Current Global Users: unknown'); |
|
337 client_sendnumeric($client, 375, ':- server Message of the Day - '); |
|
338 client_sendnumeric($client, 372, ':- No message, sorry.'); |
|
339 client_sendnumeric($client, 376, ':End of /MOTD command.'); |
|
340 } else { |
|
341 $users = array($client); |
|
342 foreach ($client->channels as $ch) foreach ($ch->users as $user) if (!in_array($user, $users)) $users[] = $user; |
|
343 foreach ($users as $user) client_send($user, ':'.$client->name.' NICK :'.$parts[0]); |
|
344 $client->name = $parts[0]; |
|
345 } |
|
346 break; |
|
347 case 'USER': |
|
348 case 'PASS': |
|
349 break; |
|
350 default: |
|
351 if ($client->name === NULL) { |
|
352 client_send($client, ':server 451 '.$command.' :You have not registered'); |
|
353 break; |
|
354 } |
|
355 switch (strtoupper($command)) { |
|
356 case 'PING': |
|
357 client_send($client, 'PONG :'.$parts[0]); |
|
358 break; |
|
359 case 'PRIVMSG': |
|
360 case 'NOTICE': |
|
361 $channels = explode(',', $parts[0]); |
|
362 foreach ($channels as $chn) { |
|
363 if ($chn[0] == '#') { |
|
364 if (!array_key_exists($chn, $client->channels)) { |
|
365 client_sendnumeric($client, 404, $chn.' :Can not send to channel'); |
|
366 } else { |
|
367 channel_sendmsg($client->channels[$chn], $client, $parts[1]); |
|
368 } |
|
369 } else { |
|
370 if ($ch = find_nick($chn)) { |
|
371 client_send($ch, ':'.$client->name.' PRIVMSG '.$chn.' :'.$parts[1]); |
|
372 } else { |
|
373 client_sendnumeric($client, 401, $chn.' :No such nickname'); |
|
374 } |
|
375 } |
|
376 } |
|
377 break; |
|
378 case 'JOIN': |
|
379 $channels = explode(',', $parts[0]); |
|
380 foreach ($channels as $chn) { |
|
381 if (!preg_match('/^#[a-zA-Z0-9\-_]+$/', $chn)) { |
|
382 client_sendnumeric($client, 403, $chn.' :No such channel'); |
|
383 continue; |
|
384 } |
|
385 $ch = getchannel($chn); |
|
386 if (!isset($client->channels[$chn])) { |
|
387 $client->channels[$chn] = $ch; |
|
388 $ch->users[] = $client; |
|
389 print('JOIN '.$ch->name.': '.$client->name."\n"); |
|
390 channel_send($ch, NULL, ':'.$client->name.'!user@host JOIN :'.$ch->name); |
|
391 } |
|
392 client_sendnames($ch, $client); |
|
393 } |
|
394 break; |
|
395 case 'PART': |
|
396 $channels = explode(',', $parts[0]); |
|
397 foreach ($channels as $chn) { |
|
398 if (!isset($client->channels[$chn])) continue; |
|
399 $ch = $client->channels[$chn]; |
|
400 print('PART '.$ch->name.': '.$client->name."\n"); |
|
401 channel_send($ch, NULL, ':'.$client->name.' PART :'.$ch->name); |
|
402 unset($client->channels[$chn]); |
|
403 array_remove(&$ch->users, $client); |
|
404 if (!count($ch->users) && !$ch->persistent) array_remove($GLOBALS['channels'], $ch); |
|
405 } |
|
406 break; |
|
407 case 'QUIT': |
|
408 client_close($client, isset($parts[0]) ? 'Quit: '.$parts[0] : 'Quit'); |
|
409 break; |
|
410 case 'NAMES': |
|
411 $channels = explode(',', $parts[0]); |
|
412 foreach ($channels as $chn) { |
|
413 if (!isset($client->channels[$chn])) { |
|
414 client_sendnumeric($client, 403, $chn.' :No such channel'); |
|
415 } else { |
|
416 client_sendnames($client->channels[$chn], $client); |
|
417 } |
|
418 } |
|
419 break; |
|
420 case 'LIST': |
|
421 client_sendnumeric($client, 321, 'Channel :Users Name'); |
|
422 foreach ($GLOBALS['channels'] as $ch) { |
|
423 client_sendnumeric($client, 322, $ch->name.' '.count($ch->users).' :'); |
|
424 } |
|
425 client_sendnumeric($client, 323, 'End of /LIST'); |
|
426 break; |
|
427 case 'MODE': |
|
428 case 'USER': |
|
429 case 'USERHOST': |
|
430 break; |
|
431 default: |
|
432 client_sendnumeric($client, 421, $command.' :Unknown command'); |
|
433 } |
|
434 } |
|
435 return TRUE; |
|
436 } |
|
437 |
|
438 function client_sendnames($ch, $client) { |
|
439 $nicks = array(); |
|
440 foreach ($ch->users as $u) if ($u->name !== NULL) $nicks[] = $u->name; |
|
441 client_sendnumeric($client, 353, '= '.$ch->name.' :'.implode(' ', $nicks)); |
|
442 client_sendnumeric($client, 366, $ch->name.' :End of /NAMES list.'); |
|
443 } |
|
444 |
|
445 function client_sendnumeric($client, $num, $line) { |
|
446 client_send($client, ':server '.str_pad($num, 3, '0', STR_PAD_LEFT).' '.$client->name.' '.$line); |
|
447 } |
|
448 |
|
449 function client_send($client, $line) { |
|
450 print('W: '.$line."\n"); |
|
451 $line .= "\r\n"; |
|
452 socket_send($client->socket, $line, strlen($line), 0); |
|
453 } |
|
454 |
|
455 function channel_sendmsg($ch, $sender, $msg) { |
|
456 global $udpmsg_config; |
|
457 print('MESSAGE '.$ch->name.' <'.$sender->name.'> '.$msg."\n"); |
|
458 channel_send($ch, $sender, ':'.$sender->name.' PRIVMSG '.$ch->name.' :'.$msg); |
|
459 if (!$ch->udpmsg) return; |
|
460 $umsg = array('CMD' => 'MSG', 'CHN' => $ch->udpmsg, 'MSG' => $msg, 'USR' => $sender->name, 'DUMMY' => rand(10000, 99999)); |
|
461 if ($udpmsg_config['netname']) $umsg['NET'] = $udpmsg_config['netname']; |
|
462 udpmsg_send($umsg); |
|
463 } |
|
464 |
|
465 function channel_send($ch, $sender, $msg) { |
|
466 foreach ($ch->users as $client) { |
|
467 if ($client === $sender) continue; |
|
468 client_send($client, $msg); |
|
469 } |
|
470 } |