update tests
[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 *
10 *
11 */
12 const VERSION = '1.0.2';
13
14 /**
15 * password-challenge
16 * @var string
17 */
18 private $challenge = '';
19
20 /**
21 * csrf_token
22 * @var string
23 */
24 private $token = '';
25
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 */
42 private $url = '';
43
44 /**
45 * derivedk cookie
46 * @var string
47 */
48 private $derivedk = '';
49
50 public function __construct ($url = 'http://speedport.ip/') {
51 $this->url = $url;
52 }
53
54 /**
55 * Requests the password-challenge from the router.
56 */
57 private function getChallenge () {
58 $path = 'data/Login.json';
59 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'challengev' => 'null');
60 $data = $this->sentRequest($path, $fields);
61 $data = json_decode($data['body'], true);
62 $data = $this->getValues($data);
63
64 if (isset($data['challengev']) && !empty($data['challengev'])) {
65 $this->challenge = $data['challengev'];
66 }
67 }
68
69 /**
70 * login into the router with the given password
71 *
72 * @param string $password
73 * @return boolean
74 */
75 public function login ($password) {
76 $this->getChallenge();
77
78 if (empty($this->challenge)) {
79 throw new Exception('unable to get the challenge from the router');
80 }
81
82 $path = 'data/Login.json';
83 $this->hash = hash('sha256', $this->challenge.':'.$password);
84 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash);
85 $data = $this->sentRequest($path, $fields);
86 $json = json_decode($data['body'], true);
87 $json = $this->getValues($json);
88 if (isset($json['login']) && $json['login'] == 'success') {
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
98 // calculate derivedk
99 if (!function_exists("hash_pbkdf2")) {
100 require_once 'CryptLib/CryptLib.php';
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);
104 }
105 else {
106 $this->derivedk = hash_pbkdf2('sha1', hash('sha256', $password), substr($this->challenge, 0, 16), 1000, 32);
107 }
108
109 // get the csrf_token
110 $this->token = $this->getToken();
111
112 if ($this->checkLogin() === true) {
113 return true;
114 }
115 }
116 }
117
118 return false;
119 }
120
121 /**
122 * check if we are logged in
123 *
124 * @return boolean
125 */
126 public function checkLogin () {
127 if (empty($this->challenge) && empty($this->session)) {
128 return false;
129 }
130
131 $path = 'data/SecureStatus.json';
132 $fields = array();
133 $data = $this->sentRequest($path, $fields, true);
134
135 if (empty($data['body'])) {
136 throw new Exception('unable to get SecureStatus data');
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
149 /**
150 * logout
151 *
152 * @return array
153 */
154 public function logout () {
155 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
156
157 $path = 'data/Login.json';
158 $fields = array('csrf_token' => $this->token, 'logout' => 'byby');
159 $data = $this->sentRequest($path, $fields, true);
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 }
173 }
174
175 /**
176 * reboot the router
177 *
178 * @return array
179 */
180 public function reboot () {
181 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
182
183 $path = 'data/Reboot.json';
184 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'reboot_device' => 'true');
185 $data = $this->sentRequest($path, $fields, true);
186
187 $json = json_decode($data['body'], true);
188
189 return $json;
190 }
191
192 /**
193 * change dsl connection status
194 *
195 * @param string $status
196 */
197 public function changeConnectionStatus ($status) {
198 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
199
200 $path = 'data/Connect.json';
201
202 if ($status == 'online' || $status == 'offline') {
203 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'req_connect' => $status);
204 $data = $this->sentRequest($path, $fields, true);
205
206 $json = json_decode($this->decrypt($data['body']), true);
207
208 return $json;
209 }
210 else {
211 throw new Exception();
212 }
213 }
214
215 /**
216 * return the given json as array
217 *
218 * @param string $file
219 * @return array
220 */
221 public function getData ($file) {
222 if ($this->checkLogin() !== true && $file != "Status") throw new Exception('you musst be logged in to use this method');
223
224 $path = 'data/'.$file.'.json';
225 $fields = array();
226 $data = $this->sentRequest($path, $fields, true);
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
237 /**
238 * get the router syslog
239 *
240 * @return array
241 */
242 public function getSyslog() {
243 return $this->exportData('0');
244 }
245
246 /**
247 * get the Missed Calls from router
248 *
249 * @return array
250 */
251 public function getMissedCalls() {
252 return $this->exportData('1');
253 }
254
255 /**
256 * get the Taken Calls from router
257 *
258 * @return array
259 */
260 public function getTakenCalls() {
261 return $this->exportData('2');
262 }
263
264 /**
265 * get the Dialed Calls from router
266 *
267 * @return array
268 */
269 public function getDialedCalls() {
270 return $this->exportData('3');
271 }
272
273 /**
274 * export data from router
275 *
276 * @return array
277 */
278 private function exportData ($type) {
279 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
280
281 $path = 'data/Syslog.json';
282 $fields = array('exporttype' => $type);
283 $data = $this->sentRequest($path, $fields, true);
284
285 if (empty($data['body'])) {
286 throw new Exception('unable to get export data');
287 }
288
289 return explode("\n", $data['body']);
290 }
291
292 /**
293 * reconnect LTE
294 *
295 * @return array
296 */
297 public function reconnectLte () {
298 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
299
300 $path = 'data/modules.json';
301 $fields = array('csrf_token' => $this->token, 'lte_reconn' => '1');
302 $fields = $this->encrypt($fields);
303 $data = $this->sentRequest($path, $fields, true, 2);
304 $json = json_decode($data['body'], true);
305
306 return $json;
307 }
308
309 /**
310 * reset the router to Factory Default
311 * not tested
312 *
313 * @return array
314 */
315 public function resetToFactoryDefault () {
316 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
317
318 $path = 'data/resetAllSetting.json';
319 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash, 'reset_all' => 'true');
320 $data = $this->sentRequest($path, $fields, true);
321 $json = json_decode($data['body'], true);
322
323 return $json;
324 }
325
326
327 /**
328 * check if firmware is actual
329 *
330 * @return array
331 */
332 public function checkFirmware () {
333 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
334
335 $path = 'data/checkfirmware.json';
336 $fields = array('checkfirmware' => 'true');
337 $data = $this->sentRequest($path, $fields, true);
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
348 /**
349 * decrypt data from router
350 *
351 * @param string $data
352 * @return array
353 */
354 private function decrypt ($data) {
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 */
378 private function encrypt ($data) {
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
391 return bin2hex($mode->finish());
392 }
393
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
409 /**
410 * sends the request to router
411 *
412 * @param string $path
413 * @param mixed $fields
414 * @param string $cookie
415 * @param integer $count
416 * @return array
417 */
418 private function sentRequest ($path, $fields, $cookie = false, $count = 0) {
419 $url = $this->url.$path.'?lang=en';
420 $ch = curl_init();
421 curl_setopt($ch, CURLOPT_URL, $url);
422
423 if (!empty($fields)) {
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 }
432 }
433
434 if ($cookie === true) {
435 curl_setopt($ch, CURLOPT_COOKIE, 'challengev='.$this->challenge.'; '.$this->session);
436 }
437
438 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
439 curl_setopt($ch, CURLOPT_HEADER, true);
440
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);
455 $body = preg_replace("/\[\s+\]/i", '[ {} ]', $body);
456 $body = preg_replace("/},\s+]/", "}\n]", $body);
457
458 return array('header' => $this->parse_headers($header), 'body' => $body);
459 }
460
461 /**
462 * get the csrf_token
463 *
464 * @return string
465 */
466 private function getToken () {
467 if ($this->checkLogin() !== true) throw new Exception('you musst be logged in to use this method');
468
469 $path = 'html/content/overview/index.html';
470 $fields = array();
471 $data = $this->sentRequest($path, $fields, true);
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
488 /**
489 * parse the curl return header into an array
490 *
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
498 $header_text = explode("\r\n", $header_text);
499 foreach ($header_text as $i => $line) {
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 }
511 }