Mercurial > hg > udpmsg3
comparison ircd/ircd.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 (!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 } |