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