1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace RouterOS; |
4
|
|
|
|
5
|
|
|
use DivineOmega\SSHConnection\SSHConnection; |
6
|
|
|
use RouterOS\Exceptions\ClientException; |
7
|
|
|
use RouterOS\Exceptions\ConfigException; |
8
|
|
|
use RouterOS\Interfaces\ClientInterface; |
9
|
|
|
use RouterOS\Interfaces\QueryInterface; |
10
|
|
|
use RouterOS\Helpers\ArrayHelper; |
11
|
|
|
use function array_keys; |
12
|
|
|
use function array_shift; |
13
|
|
|
use function chr; |
14
|
|
|
use function count; |
15
|
|
|
use function is_array; |
16
|
|
|
use function md5; |
17
|
|
|
use function pack; |
18
|
|
|
use function preg_match_all; |
19
|
|
|
use function sleep; |
20
|
|
|
use function trim; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Class Client for RouterOS management |
24
|
|
|
* |
25
|
|
|
* @package RouterOS |
26
|
|
|
* @since 0.1 |
27
|
|
|
*/ |
28
|
|
|
class Client implements Interfaces\ClientInterface |
29
|
|
|
{ |
30
|
|
|
use SocketTrait, ShortsTrait; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Configuration of connection |
34
|
|
|
* |
35
|
|
|
* @var \RouterOS\Config |
36
|
|
|
*/ |
37
|
|
|
private $config; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* API communication object |
41
|
|
|
* |
42
|
|
|
* @var \RouterOS\APIConnector |
43
|
|
|
*/ |
44
|
|
|
private $connector; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Some strings with custom output |
48
|
|
|
* |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
private $customOutput; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Client constructor. |
55
|
|
|
* |
56
|
|
|
* @param array|\RouterOS\Interfaces\ConfigInterface $config Array with configuration or Config object |
57
|
|
|
* @param bool $autoConnect If false it will skip auto-connect stage if not need to instantiate connection |
58
|
|
|
* |
59
|
|
|
* @throws \RouterOS\Exceptions\ClientException |
60
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
61
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
62
|
|
|
*/ |
63
|
9 |
|
public function __construct($config, bool $autoConnect = true) |
64
|
|
|
{ |
65
|
|
|
// If array then need create object |
66
|
9 |
|
if (is_array($config)) { |
67
|
6 |
|
$config = new Config($config); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
// Check for important keys |
71
|
9 |
|
if (true !== $key = ArrayHelper::checkIfKeysNotExist(['host', 'user', 'pass'], $config->getParameters())) { |
72
|
1 |
|
throw new ConfigException("One or few parameters '$key' of Config is not set or empty"); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// Save config if everything is okay |
76
|
8 |
|
$this->config = $config; |
77
|
|
|
|
78
|
|
|
// Skip next step if not need to instantiate connection |
79
|
8 |
|
if (false === $autoConnect) { |
80
|
|
|
return; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// Throw error if cannot to connect |
84
|
8 |
|
if (false === $this->connect()) { |
85
|
|
|
throw new ClientException('Unable to connect to ' . $config->get('host') . ':' . $config->get('port')); |
86
|
|
|
} |
87
|
6 |
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Get some parameter from config |
91
|
|
|
* |
92
|
|
|
* @param string $parameter Name of required parameter |
93
|
|
|
* |
94
|
|
|
* @return mixed |
95
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
96
|
|
|
*/ |
97
|
8 |
|
private function config(string $parameter) |
98
|
|
|
{ |
99
|
8 |
|
return $this->config->get($parameter); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Send write query to RouterOS (modern version of write) |
104
|
|
|
* |
105
|
|
|
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object |
106
|
|
|
* @param array|null $where List of where filters |
107
|
|
|
* @param string|null $operations Some operations which need make on response |
108
|
|
|
* @param string|null $tag Mark query with tag |
109
|
|
|
* |
110
|
|
|
* @return \RouterOS\Interfaces\ClientInterface |
111
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
112
|
|
|
* @throws \RouterOS\Exceptions\ClientException |
113
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
114
|
|
|
* @since 1.0.0 |
115
|
|
|
*/ |
116
|
7 |
|
public function query($endpoint, array $where = null, string $operations = null, string $tag = null): ClientInterface |
117
|
|
|
{ |
118
|
|
|
// If endpoint is string then build Query object |
119
|
7 |
|
$query = ($endpoint instanceof Query) |
120
|
7 |
|
? $endpoint |
121
|
7 |
|
: new Query($endpoint); |
122
|
|
|
|
123
|
|
|
// Parse where array |
124
|
7 |
|
if (!empty($where)) { |
125
|
|
|
|
126
|
|
|
// If array is multidimensional, then parse each line |
127
|
1 |
|
if (is_array($where[0])) { |
128
|
|
|
foreach ($where as $item) { |
129
|
|
|
$query = $this->preQuery($item, $query); |
130
|
|
|
} |
131
|
|
|
} else { |
132
|
1 |
|
$query = $this->preQuery($where, $query); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
// Append operations if set |
138
|
7 |
|
if (!empty($operations)) { |
139
|
|
|
$query->operations($operations); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
// Append tag if set |
143
|
7 |
|
if (!empty($tag)) { |
144
|
|
|
$query->tag($tag); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
// Submit query to RouterOS |
148
|
7 |
|
return $this->writeRAW($query); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Query helper |
153
|
|
|
* |
154
|
|
|
* @param array $item |
155
|
|
|
* @param \RouterOS\Interfaces\QueryInterface $query |
156
|
|
|
* |
157
|
|
|
* @return \RouterOS\Query |
158
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
159
|
|
|
* @throws \RouterOS\Exceptions\ClientException |
160
|
|
|
*/ |
161
|
1 |
|
private function preQuery(array $item, QueryInterface $query): QueryInterface |
162
|
|
|
{ |
163
|
|
|
// Null by default |
164
|
1 |
|
$key = null; |
165
|
1 |
|
$operator = null; |
166
|
1 |
|
$value = null; |
167
|
|
|
|
168
|
1 |
|
switch (count($item)) { |
169
|
1 |
|
case 1: |
170
|
1 |
|
[$key] = $item; |
171
|
1 |
|
break; |
172
|
|
|
case 2: |
173
|
|
|
[$key, $operator] = $item; |
174
|
|
|
break; |
175
|
|
|
case 3: |
176
|
|
|
[$key, $operator, $value] = $item; |
177
|
|
|
break; |
178
|
|
|
default: |
179
|
|
|
throw new ClientException('From 1 to 3 parameters of "where" condition is allowed'); |
180
|
|
|
break; |
|
|
|
|
181
|
|
|
} |
182
|
|
|
|
183
|
1 |
|
return $query->where($key, $operator, $value); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Send write query object to RouterOS |
188
|
|
|
* |
189
|
|
|
* @param \RouterOS\Interfaces\QueryInterface $query |
190
|
|
|
* |
191
|
|
|
* @return \RouterOS\Interfaces\ClientInterface |
192
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
193
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
194
|
|
|
* @since 1.0.0 |
195
|
|
|
*/ |
196
|
7 |
|
private function writeRAW(QueryInterface $query): ClientInterface |
197
|
|
|
{ |
198
|
7 |
|
$commands = $query->getQuery(); |
199
|
|
|
|
200
|
|
|
// Check if first command is export |
201
|
7 |
|
if (strpos($commands[0], '/export') === 0) { |
202
|
|
|
|
203
|
|
|
// Convert export command with all arguments to valid SSH command |
204
|
|
|
$arguments = explode('/', $commands[0]); |
205
|
|
|
unset($arguments[1]); |
206
|
|
|
$arguments = implode(' ', $arguments); |
207
|
|
|
|
208
|
|
|
// Call the router via ssh and store output of export |
209
|
|
|
$this->customOutput = $this->export($arguments); |
210
|
|
|
|
211
|
|
|
// Return current object |
212
|
|
|
return $this; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
// Send commands via loop to router |
216
|
7 |
|
foreach ($commands as $command) { |
217
|
7 |
|
$this->connector->writeWord(trim($command)); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
// Write zero-terminator (empty string) |
221
|
7 |
|
$this->connector->writeWord(''); |
222
|
|
|
|
223
|
|
|
// Return current object |
224
|
7 |
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Read RAW response from RouterOS, it can be /export command results also, not only array from API |
229
|
|
|
* |
230
|
|
|
* @return array|string |
231
|
|
|
* @since 1.0.0 |
232
|
|
|
*/ |
233
|
7 |
|
private function readRAW() |
234
|
|
|
{ |
235
|
|
|
// By default response is empty |
236
|
7 |
|
$response = []; |
237
|
|
|
// We have to wait a !done or !fatal |
238
|
7 |
|
$lastReply = false; |
239
|
|
|
|
240
|
|
|
// Convert strings to array and return results |
241
|
7 |
|
if ($this->isCustomOutput()) { |
242
|
|
|
// Return RAW configuration |
243
|
|
|
return $this->customOutput; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
// Read answer from socket in loop |
247
|
7 |
|
while (true) { |
248
|
7 |
|
$word = $this->connector->readWord(); |
249
|
|
|
|
250
|
7 |
|
if ('' === $word) { |
251
|
7 |
|
if ($lastReply) { |
252
|
|
|
// We received a !done or !fatal message in a precedent loop |
253
|
|
|
// response is complete |
254
|
7 |
|
break; |
255
|
|
|
} |
256
|
|
|
// We did not receive the !done or !fatal message |
257
|
|
|
// This 0 length message is the end of a reply !re or !trap |
258
|
|
|
// We have to wait the router to send a !done or !fatal reply followed by optionals values and a 0 length message |
259
|
2 |
|
continue; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Save output line to response array |
263
|
7 |
|
$response[] = $word; |
264
|
|
|
|
265
|
|
|
// If we get a !done or !fatal line in response, we are now ready to finish the read |
266
|
|
|
// but we need to wait a 0 length message, switch the flag |
267
|
7 |
|
if ('!done' === $word || '!fatal' === $word) { |
268
|
7 |
|
$lastReply = true; |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
// Parse results and return |
273
|
7 |
|
return $response; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Read answer from server after query was executed |
278
|
|
|
* |
279
|
|
|
* A Mikrotik reply is formed of blocks |
280
|
|
|
* Each block starts with a word, one of ('!re', '!trap', '!done', '!fatal') |
281
|
|
|
* Each block end with an zero byte (empty line) |
282
|
|
|
* Reply ends with a complete !done or !fatal block (ended with 'empty line') |
283
|
|
|
* A !fatal block precedes TCP connexion close |
284
|
|
|
* |
285
|
|
|
* @param bool $parse If need parse output to array |
286
|
|
|
* |
287
|
|
|
* @return mixed |
288
|
|
|
*/ |
289
|
7 |
|
public function read(bool $parse = true) |
290
|
|
|
{ |
291
|
|
|
// Read RAW response |
292
|
7 |
|
$response = $this->readRAW(); |
293
|
|
|
|
294
|
|
|
// Return RAW configuration if custom output is set |
295
|
7 |
|
if ($this->isCustomOutput()) { |
296
|
|
|
$this->customOutput = null; |
297
|
|
|
return $response; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
// Parse results and return |
301
|
7 |
|
return $parse ? $this->rosario($response) : $response; |
|
|
|
|
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Read using Iterators to improve performance on large dataset |
306
|
|
|
* |
307
|
|
|
* @return \RouterOS\ResponseIterator |
308
|
|
|
* @since 1.0.0 |
309
|
|
|
*/ |
310
|
|
|
public function readAsIterator(): ResponseIterator |
311
|
|
|
{ |
312
|
|
|
return new ResponseIterator($this); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* This method was created by memory save reasons, it convert response |
317
|
|
|
* from RouterOS to readable array in safe way. |
318
|
|
|
* |
319
|
|
|
* @param array $raw Array RAW response from server |
320
|
|
|
* |
321
|
|
|
* @return mixed |
322
|
|
|
* |
323
|
|
|
* Based on RouterOSResponseArray solution by @arily |
324
|
|
|
* |
325
|
|
|
* @link https://github.com/arily/RouterOSResponseArray |
326
|
|
|
* @since 1.0.0 |
327
|
|
|
*/ |
328
|
3 |
|
private function rosario(array $raw): array |
329
|
|
|
{ |
330
|
|
|
// This RAW should't be an error |
331
|
3 |
|
$positions = array_keys($raw, '!re'); |
332
|
3 |
|
$count = count($raw); |
333
|
3 |
|
$result = []; |
334
|
|
|
|
335
|
3 |
|
if (isset($positions[1])) { |
336
|
|
|
|
337
|
|
|
foreach ($positions as $key => $position) { |
338
|
|
|
// Get length of future block |
339
|
|
|
$length = isset($positions[$key + 1]) |
340
|
|
|
? $positions[$key + 1] - $position + 1 |
341
|
|
|
: $count - $position; |
342
|
|
|
|
343
|
|
|
// Convert array to simple items |
344
|
|
|
$item = []; |
345
|
|
|
for ($i = 1; $i < $length; $i++) { |
346
|
|
|
$item[] = array_shift($raw); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
// Save as result |
350
|
|
|
$result[] = $this->parseResponse($item)[0]; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
} else { |
354
|
3 |
|
$result = $this->parseResponse($raw); |
355
|
|
|
} |
356
|
|
|
|
357
|
3 |
|
return $result; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Parse response from Router OS |
362
|
|
|
* |
363
|
|
|
* @param array $response Response data |
364
|
|
|
* |
365
|
|
|
* @return array Array with parsed data |
366
|
|
|
*/ |
367
|
3 |
|
public function parseResponse(array $response): array |
368
|
|
|
{ |
369
|
3 |
|
$result = []; |
370
|
3 |
|
$i = -1; |
371
|
3 |
|
$lines = count($response); |
372
|
3 |
|
foreach ($response as $key => $value) { |
373
|
3 |
|
switch ($value) { |
374
|
3 |
|
case '!re': |
375
|
1 |
|
$i++; |
376
|
1 |
|
break; |
377
|
3 |
|
case '!fatal': |
378
|
|
|
$result = $response; |
379
|
|
|
break 2; |
380
|
3 |
|
case '!trap': |
381
|
3 |
|
case '!done': |
382
|
|
|
// Check for =ret=, .tag and any other following messages |
383
|
3 |
|
for ($j = $key + 1; $j <= $lines; $j++) { |
384
|
|
|
// If we have lines after current one |
385
|
3 |
|
if (isset($response[$j])) { |
386
|
2 |
|
$this->preParseResponse($response[$j], $result, $matches); |
387
|
|
|
} |
388
|
|
|
} |
389
|
3 |
|
break 2; |
390
|
|
|
default: |
391
|
1 |
|
$this->preParseResponse($value, $result, $matches, $i); |
392
|
1 |
|
break; |
393
|
|
|
} |
394
|
|
|
} |
395
|
3 |
|
return $result; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* Response helper |
400
|
|
|
* |
401
|
|
|
* @param string $value Value which should be parsed |
402
|
|
|
* @param array $result Array with parsed response |
403
|
|
|
* @param null|array $matches Matched words |
404
|
|
|
* @param string|int $iterator Type of iterations or number of item |
405
|
|
|
*/ |
406
|
3 |
|
private function preParseResponse(string $value, array &$result, ?array &$matches, $iterator = 'after'): void |
407
|
|
|
{ |
408
|
3 |
|
$this->pregResponse($value, $matches); |
409
|
3 |
|
if (isset($matches[1][0], $matches[2][0])) { |
410
|
3 |
|
$result[$iterator][$matches[1][0]] = $matches[2][0]; |
411
|
|
|
} |
412
|
3 |
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Parse result from RouterOS by regular expression |
416
|
|
|
* |
417
|
|
|
* @param string $value |
418
|
|
|
* @param null|array $matches |
419
|
|
|
*/ |
420
|
3 |
|
private function pregResponse(string $value, ?array &$matches): void |
421
|
|
|
{ |
422
|
3 |
|
preg_match_all('/^[=|.](.*)=(.*)/', $value, $matches); |
423
|
3 |
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Authorization logic |
427
|
|
|
* |
428
|
|
|
* @param bool $legacyRetry Retry login if we detect legacy version of RouterOS |
429
|
|
|
* |
430
|
|
|
* @return bool |
431
|
|
|
* @throws \RouterOS\Exceptions\ClientException |
432
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
433
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
434
|
|
|
*/ |
435
|
7 |
|
private function login(bool $legacyRetry = false): bool |
436
|
|
|
{ |
437
|
|
|
// If legacy login scheme is enabled |
438
|
7 |
|
if ($this->config('legacy')) { |
439
|
|
|
// For the first we need get hash with salt |
440
|
2 |
|
$response = $this->query('/login')->read(); |
441
|
|
|
|
442
|
|
|
// Now need use this hash for authorization |
443
|
2 |
|
$query = new Query('/login', [ |
444
|
2 |
|
'=name=' . $this->config('user'), |
445
|
2 |
|
'=response=00' . md5(chr(0) . $this->config('pass') . pack('H*', $response['after']['ret'])) |
446
|
|
|
]); |
447
|
|
|
} else { |
448
|
|
|
// Just login with our credentials |
449
|
6 |
|
$query = new Query('/login', [ |
450
|
6 |
|
'=name=' . $this->config('user'), |
451
|
6 |
|
'=password=' . $this->config('pass') |
452
|
|
|
]); |
453
|
|
|
|
454
|
|
|
// If we set modern auth scheme but router with legacy firmware then need to retry query, |
455
|
|
|
// but need to prevent endless loop |
456
|
6 |
|
$legacyRetry = true; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
// Execute query and get response |
460
|
7 |
|
$response = $this->query($query)->read(false); |
461
|
|
|
|
462
|
|
|
// if: |
463
|
|
|
// - we have more than one response |
464
|
|
|
// - response is '!done' |
465
|
|
|
// => problem with legacy version, swap it and retry |
466
|
|
|
// Only tested with ROS pre 6.43, will test with post 6.43 => this could make legacy parameter obsolete? |
467
|
7 |
|
if ($legacyRetry && $this->isLegacy($response)) { |
468
|
1 |
|
$this->config->set('legacy', true); |
469
|
1 |
|
return $this->login(); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
// If RouterOS answered with invalid credentials then throw error |
473
|
7 |
|
if (!empty($response[0]) && $response[0] === '!trap') { |
474
|
1 |
|
throw new ClientException('Invalid user name or password'); |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
// Return true if we have only one line from server and this line is !done |
478
|
6 |
|
return (1 === count($response)) && isset($response[0]) && ($response[0] === '!done'); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Detect by login request if firmware is legacy |
483
|
|
|
* |
484
|
|
|
* @param array $response |
485
|
|
|
* |
486
|
|
|
* @return bool |
487
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
488
|
|
|
*/ |
489
|
6 |
|
private function isLegacy(array $response): bool |
490
|
|
|
{ |
491
|
6 |
|
return count($response) > 1 && $response[0] === '!done' && !$this->config('legacy'); |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
/** |
495
|
|
|
* Connect to socket server |
496
|
|
|
* |
497
|
|
|
* @return bool |
498
|
|
|
* @throws \RouterOS\Exceptions\ClientException |
499
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
500
|
|
|
* @throws \RouterOS\Exceptions\QueryException |
501
|
|
|
*/ |
502
|
8 |
|
public function connect(): bool |
503
|
|
|
{ |
504
|
|
|
// By default we not connected |
505
|
8 |
|
$connected = false; |
506
|
|
|
|
507
|
|
|
// Few attempts in loop |
508
|
8 |
|
for ($attempt = 1; $attempt <= $this->config('attempts'); $attempt++) { |
509
|
|
|
|
510
|
|
|
// Initiate socket session |
511
|
8 |
|
$this->openSocket(); |
512
|
|
|
|
513
|
|
|
// If socket is active |
514
|
7 |
|
if (null !== $this->getSocket()) { |
515
|
7 |
|
$this->connector = new APIConnector(new Streams\ResourceStream($this->getSocket())); |
516
|
|
|
// If we logged in then exit from loop |
517
|
7 |
|
if (true === $this->login()) { |
518
|
6 |
|
$connected = true; |
519
|
6 |
|
break; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
// Else close socket and start from begin |
523
|
|
|
$this->closeSocket(); |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
// Sleep some time between tries |
527
|
|
|
sleep($this->config('delay')); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
// Return status of connection |
531
|
6 |
|
return $connected; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Check if custom output is not empty |
536
|
|
|
* |
537
|
|
|
* @return bool |
538
|
|
|
*/ |
539
|
7 |
|
private function isCustomOutput(): bool |
540
|
|
|
{ |
541
|
7 |
|
return $this->customOutput !== null; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* Execute export command on remote host, it also will be used |
546
|
|
|
* if "/export" command passed to query. |
547
|
|
|
* |
548
|
|
|
* @param string|null $arguments String with arguments which should be passed to export command |
549
|
|
|
* |
550
|
|
|
* @return string |
551
|
|
|
* @throws \RouterOS\Exceptions\ConfigException |
552
|
|
|
* @since 1.3.0 |
553
|
|
|
*/ |
554
|
|
|
public function export(string $arguments = null): string |
555
|
|
|
{ |
556
|
|
|
// Connect to remote host |
557
|
|
|
$connection = |
558
|
|
|
(new SSHConnection()) |
559
|
|
|
->timeout($this->config('timeout')) |
560
|
|
|
->to($this->config('host')) |
561
|
|
|
->onPort($this->config('ssh_port')) |
562
|
|
|
->as($this->config('user') . '+etc') |
563
|
|
|
->withPassword($this->config('pass')) |
564
|
|
|
->connect(); |
565
|
|
|
|
566
|
|
|
// Run export command |
567
|
|
|
$command = $connection->run('/export' . ' ' . $arguments); |
568
|
|
|
|
569
|
|
|
// Return the output |
570
|
|
|
return $command->getOutput(); |
571
|
|
|
} |
572
|
|
|
} |
573
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.