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)
7 class SpeedportHybrid
{
12 private $challenge = '';
30 private $session = '';
42 private $derivedk = '';
44 public function __construct ($password, $url = 'http://speedport.ip/') {
46 $this->getChallenge();
48 if (empty($this->challenge
)) {
49 throw new Exception('unable to get the challenge from the router');
52 $login = $this->login($password);
54 if ($login === false) {
55 throw new Exception('unable to login');
60 * Requests the password-challenge from the router.
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);
69 if (isset($data['challengev']) && !empty($data['challengev'])) {
70 $this->challenge
= $data['challengev'];
75 * login into the router with the given password
77 * @param string $password
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];
94 throw new Exception('unable to get the session cookie from the router');
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);
105 $this->derivedk
= hash_pbkdf2('sha1', hash('sha256', $password), substr($this->challenge
, 0, 16), 1000, 32);
108 // get the csrf_token
109 $this->token
= $this->getToken();
123 public function logout () {
124 $path = 'data/Login.json';
125 $fields = array('logout' => 'byby');
126 $data = $this->sentRequest($path, $fields);
127 // reset challenge and session
128 $this->challenge
= '';
132 $json = json_decode($data['body'], true);
142 public function reboot () {
143 $path = 'data/Reboot.json';
144 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash
, 'reboot_device' => 'true');
145 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
146 $data = $this->sentRequest($path, $fields, $cookie);
148 $json = json_decode($data['body'], true);
154 * change dsl connection status
156 * @param string $status
158 public function changeConnectionStatus ($status) {
159 $path = 'data/Connect.json';
161 if ($status == 'online' ||
$status == 'offline') {
162 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash
, 'req_connect' => $status);
163 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
164 $data = $this->sentRequest($path, $fields, $cookie);
166 $json = json_decode($this->decrypt($data['body']), true);
171 throw new Exception();
176 * return the given json as array
178 * @param string $file
181 public function getData ($file) {
182 $path = 'data/'.$file.'.json';
184 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
185 $data = $this->sentRequest($path, $fields, $cookie);
187 if (empty($data['body'])) {
188 throw new Exception('unable to get '.$file.' data');
191 $json = json_decode($data['body'], true);
197 * get the router syslog
201 public function getSyslog() {
202 $path = 'data/Syslog.json';
203 $fields = array('exporttype' => '0');
204 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
205 $data = $this->sentRequest($path, $fields, $cookie);
207 if (empty($data['body'])) {
208 throw new Exception('unable to get syslog data');
211 return explode("\n", $data['body']);
215 * get the Missed Calls from router
219 public function getMissedCalls() {
220 $path = 'data/ExportMissedCalls.json';
221 $fields = array('exporttype' => '1');
222 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
223 $data = $this->sentRequest($path, $fields, $cookie);
225 if (empty($data['body'])) {
226 throw new Exception('unable to get syslog data');
229 return explode("\n", $data['body']);
233 * get the Taken Calls from router
237 public function getTakenCalls() {
238 $path = 'data/ExportTakenCalls.json';
239 $fields = array('exporttype' => '2');
240 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
241 $data = $this->sentRequest($path, $fields, $cookie);
243 if (empty($data['body'])) {
244 throw new Exception('unable to get syslog data');
247 return explode("\n", $data['body']);
251 * get the Dialed Calls from router
255 public function getDialedCalls() {
256 $path = 'data/ExportDialedCalls.json';
257 $fields = array('exporttype' => '3');
258 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
259 $data = $this->sentRequest($path, $fields, $cookie);
261 if (empty($data['body'])) {
262 throw new Exception('unable to get syslog data');
265 return explode("\n", $data['body']);
273 public function reconnectLte () {
274 $path = 'data/modules.json';
275 $fields = array('csrf_token' => $this->token
, 'lte_reconn' => '1');
276 $fields = $this->encrypt($fields);
277 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
278 $data = $this->sentRequest($path, $fields, $cookie, 2);
279 $json = json_decode($data['body'], true);
285 * reset the router to Factory Default
290 public function resetToFactoryDefault () {
291 $path = 'data/resetAllSetting.json';
292 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash
, 'reset_all' => 'true');
293 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
294 $data = $this->sentRequest($path, $fields, $cookie);
295 $json = json_decode($data['body'], true);
302 * check if firmware is actual
306 public function checkFirmware () {
307 $path = 'data/checkfirmware.json';
308 $fields = array('checkfirmware' => 'true');
309 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
310 $data = $this->sentRequest($path, $fields, $cookie);
312 if (empty($data['body'])) {
313 throw new Exception('unable to get checkfirmware data');
316 $json = json_decode($data['body'], true);
322 * decrypt data from router
324 * @param string $data
327 private function decrypt ($data) {
328 require_once 'CryptLib/CryptLib.php';
329 $factory = new CryptLib\Cipher\
Factory();
330 $aes = $factory->getBlockCipher('rijndael-128');
332 $iv = hex2bin(substr($this->challenge
, 16, 16));
333 $adata = hex2bin(substr($this->challenge
, 32, 16));
334 $dkey = hex2bin($this->derivedk
);
335 $enc = hex2bin($data);
338 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
340 $mode->decrypt($enc);
342 return $mode->finish();
346 * decrypt data for the router
351 private function encrypt ($data) {
352 require_once 'CryptLib/CryptLib.php';
353 $factory = new CryptLib\Cipher\
Factory();
354 $aes = $factory->getBlockCipher('rijndael-128');
356 $iv = hex2bin(substr($this->challenge
, 16, 16));
357 $adata = hex2bin(substr($this->challenge
, 32, 16));
358 $dkey = hex2bin($this->derivedk
);
361 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
362 $mode->encrypt(http_build_query($data));
364 return bin2hex($mode->finish());
368 * get the values from array
370 * @param array $array
373 private function getValues($array) {
375 foreach ($array as $item) {
376 $data[$item['varid']] = $item['varvalue'];
383 * sends the request to router
385 * @param string $path
386 * @param mixed $fields
387 * @param string $cookie
388 * @param integer $count
391 private function sentRequest ($path, $fields, $cookie = '', $count = 0) {
392 $url = $this->url
.$path;
394 curl_setopt($ch, CURLOPT_URL
, $url);
396 if (!empty($fields)) {
397 if (is_array($fields)) {
398 curl_setopt($ch, CURLOPT_POST
, count($fields));
399 curl_setopt($ch, CURLOPT_POSTFIELDS
, http_build_query($fields));
402 curl_setopt($ch, CURLOPT_POST
, $count);
403 curl_setopt($ch, CURLOPT_POSTFIELDS
, $fields);
407 if (!empty($cookie)) {
408 curl_setopt($ch, CURLOPT_COOKIE
, $cookie);
411 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
412 curl_setopt($ch, CURLOPT_HEADER
, true);
418 $result = curl_exec($ch);
420 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE
);
421 $header = substr($result, 0, $header_size);
422 $body = substr($result, $header_size);
427 $body = preg_replace("/(\r\n)|(\r)/", "\n", $body);
428 $body = preg_replace('/\'/i', '"', $body);
429 $body = preg_replace("/\[\s+\]/i", '[ {} ]', $body);
430 $body = preg_replace("/},\s+]/", "}\n]", $body);
432 return array('header' => $this->parse_headers($header), 'body' => $body);
440 private function getToken () {
441 $path = 'html/content/overview/index.html?lang=de';
443 $cookie = 'challengev='.$this->challenge
.'; '.$this->session
;
444 $data = $this->sentRequest($path, $fields, $cookie);
446 if (empty($data['body'])) {
447 throw new Exception('unable to get csrf_token');
450 $a = explode('csrf_token = "', $data['body']);
451 $a = explode('";', $a[1]);
453 if (isset($a[0]) && !empty($a[0])) {
457 throw new Exception('unable to get csrf_token');
462 * parse the curl return header into an array
464 * @param string $response
467 private function parse_headers($response) {
469 $header_text = substr($response, 0, strpos($response, "\r\n\r\n"));
471 $header_text = explode("\r\n", $header_text);
472 foreach ($header_text as $i => $line) {
474 $headers['http_code'] = $line;
477 list ($key, $value) = explode(': ', $line);
478 $headers[$key] = $value;