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