|
1
|
|
|
<?php |
|
2
|
|
|
namespace PHPDaemon\Servers\IRCBouncer; |
|
3
|
|
|
|
|
4
|
|
|
use PHPDaemon\Core\Daemon; |
|
5
|
|
|
use PHPDaemon\Core\Timer; |
|
6
|
|
|
use PHPDaemon\Utils\IRC; |
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* @package NetworkServers |
|
10
|
|
|
* @subpackage IRCBouncer |
|
11
|
|
|
* @author Vasily Zorin <[email protected]> |
|
12
|
|
|
*/ |
|
13
|
|
|
class Connection extends \PHPDaemon\Network\Connection |
|
14
|
|
|
{ |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* @var string |
|
18
|
|
|
*/ |
|
19
|
|
|
public $EOL = "\r\n"; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* @var object |
|
23
|
|
|
*/ |
|
24
|
|
|
public $attachedServer; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* @var string |
|
28
|
|
|
*/ |
|
29
|
|
|
public $usermask; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* @var integer |
|
33
|
|
|
*/ |
|
34
|
|
|
public $latency; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @var integer |
|
38
|
|
|
*/ |
|
39
|
|
|
public $lastPingTS; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* @var integer |
|
43
|
|
|
*/ |
|
44
|
|
|
public $timeout = 180; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @var boolean |
|
48
|
|
|
*/ |
|
49
|
|
|
public $protologging = false; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Called when the connection is handshaked (at low-level), and peer is ready to recv. data |
|
53
|
|
|
* @return void |
|
54
|
|
|
*/ |
|
55
|
|
|
public function onReady() |
|
56
|
|
|
{ |
|
57
|
|
|
$conn = $this; |
|
58
|
|
|
$this->keepaliveTimer = setTimeout(function ($timer) use ($conn) { |
|
|
|
|
|
|
59
|
|
|
$conn->ping(); |
|
60
|
|
|
}, 10e6); |
|
61
|
|
|
} |
|
62
|
|
|
|
|
63
|
|
|
/** |
|
64
|
|
|
* @TODO DESCR |
|
65
|
|
|
*/ |
|
66
|
|
|
public function onFinish() |
|
67
|
|
|
{ |
|
68
|
|
|
if ($this->attachedServer) { |
|
69
|
|
|
$this->attachedServer->attachedClients->detach($this); |
|
70
|
|
|
} |
|
71
|
|
|
Timer::remove($this->keepaliveTimer); |
|
|
|
|
|
|
72
|
|
|
parent::onFinish(); |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* @TODO DESCR |
|
77
|
|
|
*/ |
|
78
|
|
|
public function ping() |
|
79
|
|
|
{ |
|
80
|
|
|
$this->lastPingTS = microtime(true); |
|
|
|
|
|
|
81
|
|
|
$this->writeln('PING :' . $this->usermask); |
|
82
|
|
|
Timer::setTimeout($this->keepaliveTimer); |
|
|
|
|
|
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* @TODO DESCR |
|
87
|
|
|
* @param string $from From |
|
88
|
|
|
* @param string $cmd Command |
|
89
|
|
|
* @param mixed ...$args Arguments |
|
90
|
|
|
*/ |
|
91
|
|
|
public function command($from, $cmd) |
|
92
|
|
|
{ |
|
93
|
|
|
if ($from === null) { |
|
94
|
|
|
$from = $this->pool->config->servername->value; |
|
95
|
|
|
} |
|
96
|
|
|
$cmd = IRC::getCodeByCommand($cmd); |
|
97
|
|
|
$line = ':' . $from . ' ' . $cmd; |
|
98
|
|
View Code Duplication |
for ($i = 2, $s = func_num_args(); $i < $s; ++$i) { |
|
|
|
|
|
|
99
|
|
|
$arg = func_get_arg($i); |
|
100
|
|
|
if (($i + 1 === $s) && (mb_orig_strpos($arg, "\x20") !== false)) { |
|
101
|
|
|
$line .= ' :'; |
|
102
|
|
|
} else { |
|
103
|
|
|
$line .= ' '; |
|
104
|
|
|
} |
|
105
|
|
|
$line .= $arg; |
|
106
|
|
|
} |
|
107
|
|
|
$this->writeln($line); |
|
108
|
|
|
if ($this->pool->protologging && $cmd !== 'PONG') { |
|
109
|
|
|
Daemon::log('=>=>=>=> ' . json_encode($line)); |
|
110
|
|
|
} |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* @TODO |
|
115
|
|
|
* @param string $from From |
|
116
|
|
|
* @param string $cmd Command |
|
117
|
|
|
* @param array $args Arguments |
|
118
|
|
|
*/ |
|
119
|
|
|
public function commandArr($from, $cmd, $args) |
|
120
|
|
|
{ |
|
121
|
|
|
if ($from === null) { |
|
122
|
|
|
$from = $this->pool->config->servername->value; |
|
123
|
|
|
} |
|
124
|
|
|
if (is_string($args)) { |
|
125
|
|
|
Daemon::log(get_class($this) . '->commandArr: args is string'); |
|
126
|
|
|
return; |
|
127
|
|
|
} |
|
128
|
|
|
$cmd = IRC::getCodeByCommand($cmd); |
|
129
|
|
|
$line = ':' . $from . ' ' . $cmd; |
|
130
|
|
View Code Duplication |
for ($i = 0, $s = sizeof($args); $i < $s; ++$i) { |
|
|
|
|
|
|
131
|
|
|
if (($i + 1 === $s) && (mb_orig_strpos($args[$i], "\x20") !== false)) { |
|
132
|
|
|
$line .= ' :'; |
|
133
|
|
|
} else { |
|
134
|
|
|
$line .= ' '; |
|
135
|
|
|
} |
|
136
|
|
|
$line .= $args[$i]; |
|
137
|
|
|
} |
|
138
|
|
|
$this->writeln($line); |
|
139
|
|
|
if ($this->pool->protologging && $cmd !== 'PONG') { |
|
140
|
|
|
Daemon::log('=>=>=>=> ' . json_encode($line)); |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* @TODO DESCR |
|
146
|
|
|
*/ |
|
147
|
|
|
public function detach() |
|
148
|
|
|
{ |
|
149
|
|
|
if ($this->attachedServer) { |
|
150
|
|
|
$this->attachedServer->attachedClients->detach($this); |
|
151
|
|
|
$this->attachedServer = null; |
|
152
|
|
|
} |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* @TODO DESCR |
|
157
|
|
|
*/ |
|
158
|
|
|
public function attachTo() |
|
159
|
|
|
{ |
|
160
|
|
|
if ($this->pool->conn) { |
|
161
|
|
|
$this->attachedServer = $this->pool->conn; |
|
162
|
|
|
$this->attachedServer->attachedClients->attach($this); |
|
163
|
|
|
} else { |
|
164
|
|
|
return; |
|
165
|
|
|
} |
|
166
|
|
|
$this->msgFromBNC('Attached to ' . $this->attachedServer->url); |
|
167
|
|
|
$this->usermask = $this->attachedServer->nick . '!' . $this->attachedServer->user . '@' . $this->pool->config->servername->value; |
|
168
|
|
|
$this->command(null, 'RPL_WELCOME', $this->attachedServer->nick, |
|
169
|
|
|
'Welcome to phpDaemon bouncer -- ' . $this->pool->config->servername->value); |
|
170
|
|
|
foreach ($this->attachedServer->channels as $chan) { |
|
171
|
|
|
$this->exportChannel($chan); |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @TODO DESCR |
|
177
|
|
|
* @param object $chan |
|
178
|
|
|
*/ |
|
179
|
|
|
public function exportChannel($chan) |
|
180
|
|
|
{ |
|
181
|
|
|
$this->command($this->usermask, 'JOIN', $chan->name); |
|
182
|
|
|
$this->command($this->usermask, 'RPL_TOPIC', $chan->irc->nick, $chan->name, $chan->topic); |
|
183
|
|
|
$names = $chan->exportNicksArray(); |
|
184
|
|
|
$packet = ''; |
|
185
|
|
|
$maxlen = 510 - 7 - mb_orig_strlen($this->pool->config->servername->value) - $chan->irc->nick - 1; |
|
186
|
|
|
for ($i = 0, $s = sizeof($names); $i < $s; ++$i) { |
|
187
|
|
|
$packet .= ($packet !== '' ? ' ' : '') . $names[$i]; |
|
188
|
|
|
if (!isset($names[$i + 1]) || (mb_orig_strlen($packet) + mb_orig_strlen($names[$i + 1]) + 1 > $maxlen)) { |
|
189
|
|
|
$this->command(null, 'RPL_NAMREPLY', $chan->irc->nick, $chan->type, $chan->name, $packet); |
|
190
|
|
|
$packet = ''; |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
$this->command(null, 'RPL_ENDOFNAMES', $chan->irc->nick, $chan->name, 'End of /NAMES list'); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* @TODO DESCR |
|
198
|
|
|
* @param string $cmd Command |
|
199
|
|
|
* @param array $args Arguments |
|
200
|
|
|
*/ |
|
201
|
|
|
public function onCommand($cmd, $args) |
|
202
|
|
|
{ |
|
203
|
|
|
if ($cmd === 'USER') { |
|
204
|
|
|
//list ($nick) = $args; |
|
205
|
|
|
$this->attachTo(); |
|
206
|
|
|
return; |
|
207
|
|
|
} elseif ($cmd === 'QUIT') { |
|
208
|
|
|
$this->finish(); |
|
209
|
|
|
return; |
|
210
|
|
|
} elseif ($cmd === 'PING') { |
|
211
|
|
|
$this->writeln(isset($args[0]) ? 'PONG :' . $args[0] : 'PONG'); |
|
212
|
|
|
return; |
|
213
|
|
View Code Duplication |
} elseif ($cmd === 'PONG') { |
|
|
|
|
|
|
214
|
|
|
if ($this->lastPingTS) { |
|
215
|
|
|
$this->latency = microtime(true) - $this->lastPingTS; |
|
|
|
|
|
|
216
|
|
|
$this->lastPingTS = null; |
|
217
|
|
|
$this->event('lantency'); |
|
218
|
|
|
} |
|
219
|
|
|
return; |
|
220
|
|
|
} elseif ($cmd === 'NICK') { |
|
221
|
|
|
return; |
|
222
|
|
|
} elseif ($cmd === 'PRIVMSG') { |
|
223
|
|
|
list($target, $msg) = $args; |
|
224
|
|
|
if ($target === '$') { |
|
225
|
|
|
if (preg_match('~^\s*(NICK\s+\S+|DETACH|ATTACH|BYE)\s*$~i', $msg, $m)) { |
|
226
|
|
|
$clientCmd = strtoupper($m[1]); |
|
227
|
|
|
if ($clientCmd === 'NICK') { |
|
228
|
|
|
} elseif ($clientCmd === 'DETACH') { |
|
229
|
|
|
$this->detach(); |
|
230
|
|
|
$this->msgFromBNC('Detached.'); |
|
231
|
|
|
} elseif ($clientCmd === 'ATTACH') { |
|
232
|
|
|
$this->attachTo(); |
|
233
|
|
|
} elseif ($clientCmd === 'BYE') { |
|
234
|
|
|
$this->detach(); |
|
235
|
|
|
$this->msgFromBNC('Bye-bye.'); |
|
236
|
|
|
$this->finish(); |
|
237
|
|
|
} |
|
238
|
|
|
} else { |
|
239
|
|
|
$this->msgFromBNC('Unknown command: ' . $msg); |
|
240
|
|
|
} |
|
241
|
|
|
return; |
|
242
|
|
|
} |
|
243
|
|
|
$this->pool->messages->insert([ |
|
244
|
|
|
'from' => $this->usermask, |
|
245
|
|
|
'to' => $target, |
|
246
|
|
|
'body' => $msg, |
|
247
|
|
|
'ts' => microtime(true), |
|
248
|
|
|
'dir' => 'o', |
|
249
|
|
|
]); |
|
250
|
|
|
} |
|
251
|
|
|
if ($this->attachedServer) { |
|
252
|
|
|
$this->attachedServer->commandArr($cmd, $args); |
|
253
|
|
|
} |
|
254
|
|
|
if ($this->protologging) { |
|
255
|
|
|
Daemon::$process->log('<=<=<=< ' . $cmd . ': ' . json_encode($args)); |
|
256
|
|
|
} |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
/** |
|
260
|
|
|
* @TODO DESCR |
|
261
|
|
|
* @param string $msg |
|
262
|
|
|
*/ |
|
263
|
|
|
public function msgFromBNC($msg) |
|
264
|
|
|
{ |
|
265
|
|
|
if ($this->usermask === null) { |
|
266
|
|
|
return; |
|
267
|
|
|
} |
|
268
|
|
|
$this->command('$!@' . $this->pool->config->servername->value, 'PRIVMSG', $this->usermask, $msg); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
/** |
|
272
|
|
|
* Called when new data received |
|
273
|
|
|
* @return void |
|
274
|
|
|
*/ |
|
275
|
|
|
public function onRead() |
|
276
|
|
|
{ |
|
277
|
|
|
Timer::setTimeout($this->keepaliveTimer); |
|
|
|
|
|
|
278
|
|
|
while (($line = $this->readline()) !== null) { |
|
279
|
|
|
if ($line === '') { |
|
280
|
|
|
continue; |
|
281
|
|
|
} |
|
282
|
|
|
if (mb_orig_strlen($line) > 512) { |
|
283
|
|
|
Daemon::$process->log('IRCBouncerConnection error: buffer overflow.'); |
|
284
|
|
|
$this->finish(); |
|
285
|
|
|
return; |
|
286
|
|
|
} |
|
287
|
|
|
$line = mb_orig_substr($line, 0, -mb_orig_strlen($this->EOL)); |
|
288
|
|
|
$p = mb_orig_strpos($line, ':', 1); |
|
289
|
|
|
$max = $p ? substr_count($line, "\x20", 0, $p) + 1 : 18; |
|
290
|
|
|
$e = explode("\x20", $line, $max); |
|
291
|
|
|
$i = 0; |
|
292
|
|
|
$cmd = $e[$i++]; |
|
293
|
|
|
$args = []; |
|
294
|
|
|
|
|
295
|
|
View Code Duplication |
for ($s = min(sizeof($e), 14); $i < $s; ++$i) { |
|
|
|
|
|
|
296
|
|
|
if ($e[$i][0] === ':') { |
|
297
|
|
|
$args[] = mb_orig_substr($e[$i], 1); |
|
298
|
|
|
break; |
|
299
|
|
|
} |
|
300
|
|
|
$args[] = $e[$i]; |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
View Code Duplication |
if (ctype_digit($cmd)) { |
|
|
|
|
|
|
304
|
|
|
$code = (int)$cmd; |
|
305
|
|
|
$cmd = isset(IRC::$codes[$code]) ? IRC::$codes[$code] : 'UNKNOWN-' . $code; |
|
306
|
|
|
} |
|
307
|
|
|
$this->onCommand($cmd, $args); |
|
308
|
|
|
} |
|
309
|
|
|
if (mb_orig_strlen($this->buf) > 512) { |
|
|
|
|
|
|
310
|
|
|
Daemon::$process->log('IRCClientConnection error: buffer overflow.'); |
|
311
|
|
|
$this->finish(); |
|
312
|
|
|
} |
|
313
|
|
|
} |
|
314
|
|
|
} |
|
315
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.