update tests
[GitHub/Stricted/speedport-hybrid-php-api.git] / SpeedportHybrid.class.php
CommitLineData
a91317a6 1<?php
1d934afe
S
2/**
3 * @author Jan Altensen (Stricted)
4 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
5 * @copyright 2015 Jan Altensen (Stricted)
6 */
21f877e4 7class SpeedportHybrid {
0dca2d61
S
8 /**
9 *
10 *
11 */
12 const VERSION = '1.0.2';
13
a91317a6
S
14 /**
15 * password-challenge
16 * @var string
17 */
18 private $challenge = '';
19
809e25bd
S
20 /**
21 * csrf_token
22 * @var string
23 */
24 private $token = '';
25
a91317a6
S
26 /**
27 * hashed password
28 * @var string
29 */
30 private $hash = '';
31
32 /**
33 * session cookie
34 * @var string
35 */
36 private $session = '';
37
38 /**
39 * router url
40 * @var string
41 */
b5a532a8 42 private $url = '';
a91317a6 43
c9e082da
S
44 /**
45 * derivedk cookie
46 * @var string
47 */
48 private $derivedk = '';
49
e58a96d1 50 public function __construct ($url = 'http://speedport.ip/') {
b5a532a8 51 $this->url = $url;
a91317a6
S
52 }
53
54 /**
55 * Requests the password-challenge from the router.
56 */
e58a96d1 57 private function getChallenge () {
b5a532a8 58 $path = 'data/Login.json';
a91317a6 59 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'challengev' => 'null');
b5a532a8 60 $data = $this->sentRequest($path, $fields);
a91317a6 61 $data = json_decode($data['body'], true);
c2678616
S
62 $data = $this->getValues($data);
63
64 if (isset($data['challengev']) && !empty($data['challengev'])) {
a1ef7d4f 65 $this->challenge = $data['challengev'];
a91317a6
S
66 }
67 }
68
69 /**
70 * login into the router with the given password
5e44cffa 71 *
a91317a6
S
72 * @param string $password
73 * @return boolean
74 */
75 public function login ($password) {
e58a96d1
S
76 $this->getChallenge();
77
78 if (empty($this->challenge)) {
79 throw new Exception('unable to get the challenge from the router');
80 }
81
b5a532a8 82 $path = 'data/Login.json';
a91317a6
S
83 $this->hash = hash('sha256', $this->challenge.':'.$password);
84 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash);
b5a532a8 85 $data = $this->sentRequest($path, $fields);
a91317a6 86 $json = json_decode($data['body'], true);
c2678616
S
87 $json = $this->getValues($json);
88 if (isset($json['login']) && $json['login'] == 'success') {
a91317a6
S
89 if (isset($data['header']['Set-Cookie']) && !empty($data['header']['Set-Cookie'])) {
90 preg_match('/^.*(SessionID_R3=[a-z0-9]*).*/i', $data['header']['Set-Cookie'], $match);
91 if (isset($match[1]) && !empty($match[1])) {
92 $this->session = $match[1];
93 }
94 else {
95 throw new Exception('unable to get the session cookie from the router');
96 }
97
c9e082da 98 // calculate derivedk
aebf6b76 99 if (!function_exists("hash_pbkdf2")) {
c0c3776a 100 require_once 'CryptLib/CryptLib.php';
aebf6b76
S
101 $pbkdf2 = new CryptLib\Key\Derivation\PBKDF\PBKDF2(array('hash' => 'sha1'));
102 $this->derivedk = bin2hex($pbkdf2->derive(hash('sha256', $password), substr($this->challenge, 0, 16), 1000, 32));
103 $this->derivedk = substr($this->derivedk, 0, 32);
c0c3776a
S
104 }
105 else {
106 $this->derivedk = hash_pbkdf2('sha1', hash('sha256', $password), substr($this->challenge, 0, 16), 1000, 32);
107 }
c9e082da 108
809e25bd
S
109 // get the csrf_token
110 $this->token = $this->getToken();
111
df1e1394
S
112 if ($this->checkLogin() === true) {
113 return true;
114 }
a91317a6
S
115 }
116 }
117
118 return false;
119 }
120
df1e1394
S
121 /**
122 * check if we are logged in
123 *
124 * @return boolean
125 */
126 public function checkLogin () {
0dca2d61
S
127 if (empty($this->challenge) && empty($this->session)) {
128 return false;
129 }
130
34701575 131 $path = 'data/SecureStatus.json';
df1e1394 132 $fields = array();
0dca2d61 133 $data = $this->sentRequest($path, $fields, true);
df1e1394
S
134
135 if (empty($data['body'])) {
e8db2f0b 136 throw new Exception('unable to get SecureStatus data');
df1e1394
S
137 }
138
139 $json = json_decode($data['body'], true);
140 $json = $this->getValues($json);
141
142 if ($json['loginstate'] != 1) {
143 return false;
144 }
145
146 return true;
147 }
148
a91317a6
S
149 /**
150 * logout
5e44cffa 151 *
a91317a6
S
152 * @return array
153 */
154 public function logout () {
e58a96d1
S
155 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
156
b5a532a8 157 $path = 'data/Login.json';
df1e1394 158 $fields = array('csrf_token' => $this->token, 'logout' => 'byby');
0dca2d61 159 $data = $this->sentRequest($path, $fields, true);
df1e1394
S
160 if ($this->checkLogin() === false) {
161 // reset challenge and session
162 $this->challenge = '';
163 $this->session = '';
164 $this->token = "";
165
166 $json = json_decode($data['body'], true);
167
168 return $json;
169 }
170 else {
171 throw new Exception('logout failed');
172 }
a91317a6
S
173 }
174
175 /**
176 * reboot the router
5e44cffa 177 *
a91317a6
S
178 * @return array
179 */
180 public function reboot () {
e58a96d1
S
181 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
182
b5a532a8 183 $path = 'data/Reboot.json';
a91317a6 184 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'reboot_device' => 'true');
0dca2d61 185 $data = $this->sentRequest($path, $fields, true);
14d4f286 186
a91317a6
S
187 $json = json_decode($data['body'], true);
188
189 return $json;
190 }
191
87026df3
S
192 /**
193 * change dsl connection status
5e44cffa 194 *
87026df3
S
195 * @param string $status
196 */
197 public function changeConnectionStatus ($status) {
e58a96d1
S
198 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
199
b5a532a8 200 $path = 'data/Connect.json';
87026df3
S
201
202 if ($status == 'online' || $status == 'offline') {
203 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'req_connect' => $status);
0dca2d61 204 $data = $this->sentRequest($path, $fields, true);
c2678616
S
205
206 $json = json_decode($this->decrypt($data['body']), true);
207
208 return $json;
87026df3
S
209 }
210 else {
211 throw new Exception();
212 }
213 }
214
a91317a6
S
215 /**
216 * return the given json as array
5e44cffa 217 *
a91317a6
S
218 * @param string $file
219 * @return array
220 */
221 public function getData ($file) {
0dca2d61 222 if ($this->checkLogin() !== true && $file != "Status") throw new Exception('you musst be logged in to use this method');
e58a96d1 223
b5a532a8 224 $path = 'data/'.$file.'.json';
a91317a6 225 $fields = array();
0dca2d61 226 $data = $this->sentRequest($path, $fields, true);
a91317a6
S
227
228 if (empty($data['body'])) {
229 throw new Exception('unable to get '.$file.' data');
230 }
231
232 $json = json_decode($data['body'], true);
233
234 return $json;
235 }
236
5e44cffa
S
237 /**
238 * get the router syslog
239 *
240 * @return array
241 */
242 public function getSyslog() {
0dca2d61 243 return $this->exportData('0');
5e44cffa
S
244 }
245
a35e3e67
S
246 /**
247 * get the Missed Calls from router
248 *
249 * @return array
250 */
251 public function getMissedCalls() {
0dca2d61 252 return $this->exportData('1');
a35e3e67
S
253 }
254
15260eeb
S
255 /**
256 * get the Taken Calls from router
257 *
258 * @return array
259 */
260 public function getTakenCalls() {
0dca2d61 261 return $this->exportData('2');
15260eeb
S
262 }
263
abf641bb
S
264 /**
265 * get the Dialed Calls from router
266 *
267 * @return array
268 */
269 public function getDialedCalls() {
0dca2d61
S
270 return $this->exportData('3');
271 }
272
273 /**
274 * export data from router
275 *
276 * @return array
277 */
278 private function exportData ($type) {
e58a96d1
S
279 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
280
b383ace1 281 $path = 'data/Syslog.json';
0dca2d61
S
282 $fields = array('exporttype' => $type);
283 $data = $this->sentRequest($path, $fields, true);
9bd25bb4 284
abf641bb 285 if (empty($data['body'])) {
0dca2d61 286 throw new Exception('unable to get export data');
abf641bb
S
287 }
288
289 return explode("\n", $data['body']);
290 }
291
809e25bd
S
292 /**
293 * reconnect LTE
294 *
295 * @return array
296 */
c9e082da 297 public function reconnectLte () {
e58a96d1
S
298 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
299
c9e082da 300 $path = 'data/modules.json';
809e25bd
S
301 $fields = array('csrf_token' => $this->token, 'lte_reconn' => '1');
302 $fields = $this->encrypt($fields);
0dca2d61 303 $data = $this->sentRequest($path, $fields, true, 2);
c9e082da
S
304 $json = json_decode($data['body'], true);
305
306 return $json;
307 }
809e25bd 308
7f4a51d2
S
309 /**
310 * reset the router to Factory Default
311 * not tested
312 *
313 * @return array
314 */
9bd25bb4 315 public function resetToFactoryDefault () {
e58a96d1
S
316 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
317
9bd25bb4
S
318 $path = 'data/resetAllSetting.json';
319 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'reset_all' => 'true');
0dca2d61 320 $data = $this->sentRequest($path, $fields, true);
9bd25bb4
S
321 $json = json_decode($data['body'], true);
322
323 return $json;
324 }
7f4a51d2 325
9bd25bb4 326
4ae464d1
S
327 /**
328 * check if firmware is actual
329 *
330 * @return array
331 */
332 public function checkFirmware () {
e58a96d1
S
333 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
334
4ae464d1
S
335 $path = 'data/checkfirmware.json';
336 $fields = array('checkfirmware' => 'true');
0dca2d61 337 $data = $this->sentRequest($path, $fields, true);
4ae464d1
S
338
339 if (empty($data['body'])) {
340 throw new Exception('unable to get checkfirmware data');
341 }
342
343 $json = json_decode($data['body'], true);
344
345 return $json;
346 }
347
14d4f286
S
348 /**
349 * decrypt data from router
350 *
351 * @param string $data
352 * @return array
353 */
c2678616 354 private function decrypt ($data) {
14d4f286
S
355 require_once 'CryptLib/CryptLib.php';
356 $factory = new CryptLib\Cipher\Factory();
357 $aes = $factory->getBlockCipher('rijndael-128');
358
359 $iv = hex2bin(substr($this->challenge, 16, 16));
360 $adata = hex2bin(substr($this->challenge, 32, 16));
361 $dkey = hex2bin($this->derivedk);
362 $enc = hex2bin($data);
363
364 $aes->setKey($dkey);
365 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
366
367 $mode->decrypt($enc);
368
369 return $mode->finish();
370 }
371
372 /**
373 * decrypt data for the router
374 *
375 * @param array $data
376 * @return string
377 */
c2678616 378 private function encrypt ($data) {
14d4f286
S
379 require_once 'CryptLib/CryptLib.php';
380 $factory = new CryptLib\Cipher\Factory();
381 $aes = $factory->getBlockCipher('rijndael-128');
382
383 $iv = hex2bin(substr($this->challenge, 16, 16));
384 $adata = hex2bin(substr($this->challenge, 32, 16));
385 $dkey = hex2bin($this->derivedk);
386
387 $aes->setKey($dkey);
388 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
389 $mode->encrypt(http_build_query($data));
390
809e25bd 391 return bin2hex($mode->finish());
14d4f286
S
392 }
393
c2678616
S
394 /**
395 * get the values from array
396 *
397 * @param array $array
398 * @return array
399 */
400 private function getValues($array) {
401 $data = array();
402 foreach ($array as $item) {
403 $data[$item['varid']] = $item['varvalue'];
404 }
405
406 return $data;
407 }
408
a91317a6
S
409 /**
410 * sends the request to router
5e44cffa 411 *
b5a532a8 412 * @param string $path
809e25bd 413 * @param mixed $fields
a91317a6 414 * @param string $cookie
809e25bd 415 * @param integer $count
a91317a6
S
416 * @return array
417 */
0dca2d61 418 private function sentRequest ($path, $fields, $cookie = false, $count = 0) {
49abc701 419 $url = $this->url.$path.'?lang=en';
a91317a6
S
420 $ch = curl_init();
421 curl_setopt($ch, CURLOPT_URL, $url);
422
423 if (!empty($fields)) {
809e25bd
S
424 if (is_array($fields)) {
425 curl_setopt($ch, CURLOPT_POST, count($fields));
426 curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
427 }
428 else {
429 curl_setopt($ch, CURLOPT_POST, $count);
430 curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
431 }
a91317a6
S
432 }
433
0dca2d61
S
434 if ($cookie === true) {
435 curl_setopt($ch, CURLOPT_COOKIE, 'challengev='.$this->challenge.'; '.$this->session);
a91317a6
S
436 }
437
438 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
439 curl_setopt($ch, CURLOPT_HEADER, true);
440
a91317a6
S
441 if ($cookie) {
442
443 }
444
445 $result = curl_exec($ch);
446
447 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
448 $header = substr($result, 0, $header_size);
449 $body = substr($result, $header_size);
450 curl_close($ch);
451
452 // fix invalid json
453 $body = preg_replace("/(\r\n)|(\r)/", "\n", $body);
454 $body = preg_replace('/\'/i', '"', $body);
5e44cffa 455 $body = preg_replace("/\[\s+\]/i", '[ {} ]', $body);
dae16c50 456 $body = preg_replace("/},\s+]/", "}\n]", $body);
a91317a6
S
457
458 return array('header' => $this->parse_headers($header), 'body' => $body);
459 }
460
809e25bd
S
461 /**
462 * get the csrf_token
463 *
464 * @return string
465 */
466 private function getToken () {
e58a96d1
S
467 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
468
49abc701 469 $path = 'html/content/overview/index.html';
809e25bd 470 $fields = array();
0dca2d61 471 $data = $this->sentRequest($path, $fields, true);
809e25bd
S
472
473 if (empty($data['body'])) {
474 throw new Exception('unable to get csrf_token');
475 }
476
477 $a = explode('csrf_token = "', $data['body']);
478 $a = explode('";', $a[1]);
479
480 if (isset($a[0]) && !empty($a[0])) {
481 return $a[0];
482 }
483 else {
484 throw new Exception('unable to get csrf_token');
485 }
486 }
487
a91317a6
S
488 /**
489 * parse the curl return header into an array
5e44cffa 490 *
a91317a6
S
491 * @param string $response
492 * @return array
493 */
494 private function parse_headers($response) {
495 $headers = array();
496 $header_text = substr($response, 0, strpos($response, "\r\n\r\n"));
497
7f4a51d2
S
498 $header_text = explode("\r\n", $header_text);
499 foreach ($header_text as $i => $line) {
a91317a6
S
500 if ($i === 0) {
501 $headers['http_code'] = $line;
502 }
503 else {
504 list ($key, $value) = explode(': ', $line);
505 $headers[$key] = $value;
506 }
507 }
508
509 return $headers;
510 }
7f4a51d2 511}