2 require_once('RebootException.class.php');
3 require_once('RouterException.class.php');
4 require_once('CryptLib/CryptLib.php');
5 require_once('Connection.class.php');
6 require_once('Phone.class.php');
7 require_once('System.class.php');
10 * @author Jan Altensen (Stricted)
11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12 * @copyright 2015 Jan Altensen (Stricted)
14 class SpeedportHybrid
{
23 const VERSION
= '1.0.3';
29 private $challenge = '';
59 private $derivedk = '';
61 public function __construct ($url = 'http://speedport.ip/') {
66 * Requests the password-challenge from the router.
68 private function getChallenge () {
69 $path = 'data/Login.json';
70 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'challengev' => 'null');
71 $data = $this->sentRequest($path, $fields);
72 $data = $this->getValues($data['body']);
74 if (isset($data['challengev']) && !empty($data['challengev'])) {
75 return $data['challengev'];
78 throw new RouterException('unable to get the challenge from the router');
83 * login into the router with the given password
85 * @param string $password
88 public function login ($password) {
89 $this->challenge
= $this->getChallenge();
91 $path = 'data/Login.json';
92 $this->hash
= hash('sha256', $this->challenge
.':'.$password);
93 $fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash
);
94 $data = $this->sentRequest($path, $fields);
95 $json = $this->getValues($data['body']);
97 if (isset($json['login']) && $json['login'] == 'success') {
98 $this->cookie
= $this->getCookie($data);
100 $this->derivedk
= $this->getDerviedk($password);
102 // get the csrf_token
103 $this->token
= $this->getToken();
105 if ($this->checkLogin(false) === true) {
114 * check if we are logged in
116 * @param boolean $exception
119 public function checkLogin ($exception = true) {
120 // check if challenge or session is empty
121 if (empty($this->challenge
) ||
empty($this->cookie
)) {
122 if ($exception === true) {
123 throw new RouterException('you musst be logged in to use this method');
129 $path = 'data/SecureStatus.json';
131 $data = $this->sentRequest($path, $fields, true);
132 $data = $this->getValues($data['body']);
134 if ($data['loginstate'] != 1) {
135 if ($exception === true) {
136 throw new RouterException('you musst be logged in to use this method');
150 public function logout () {
153 $path = 'data/Login.json';
154 $fields = array('csrf_token' => $this->token
, 'logout' => 'byby');
155 $data = $this->sentRequest($path, $fields, true);
156 $data = $this->getValues($data['body']);
157 if ((isset($data['status']) && $data['status'] == 'ok') && $this->checkLogin(false) === false) {
158 // reset challenge and session
159 $this->challenge
= '';
162 $this->derivedk
= '';
175 public function reboot () {
178 $path = 'data/Reboot.json';
179 $fields = array('csrf_token' => $this->token
, 'reboot_device' => 'true');
180 $data = $this->sentEncryptedRequest($path, $fields, true);
181 $data = $this->getValues($data['body']);
183 if ($data['status'] == 'ok') {
184 // reset challenge and session
185 $this->challenge
= '';
188 $this->derivedk
= '';
190 // throw an exception because router is unavailable for other tasks
191 // like $this->logout() or $this->checkLogin
192 throw new RebootException('Router Reboot');
199 * decrypt data from router
201 * @param string $data
204 private function decrypt ($data) {
205 $iv = hex2bin(substr($this->challenge
, 16, 16));
206 $adata = hex2bin(substr($this->challenge
, 32, 16));
207 $key = hex2bin($this->derivedk
);
208 $enc = hex2bin($data);
210 $factory = new CryptLib\Cipher\
Factory();
211 $aes = $factory->getBlockCipher('rijndael-128');
213 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
215 $mode->decrypt($enc);
217 return $mode->finish();
221 * decrypt data for the router
223 * @param string $data
226 private function encrypt ($data) {
227 $iv = hex2bin(substr($this->challenge
, 16, 16));
228 $adata = hex2bin(substr($this->challenge
, 32, 16));
229 $key = hex2bin($this->derivedk
);
231 $factory = new CryptLib\Cipher\
Factory();
232 $aes = $factory->getBlockCipher('rijndael-128');
234 $mode = $factory->getMode('ccm', $aes, $iv, [ 'adata' => $adata, 'lSize' => 7]);
235 $mode->encrypt($data);
237 return bin2hex($mode->finish());
241 * get the values from array
243 * @param array $array
246 private function getValues($array) {
248 foreach ($array as $item) {
249 // thank you telekom for this piece of shit
250 if ($item['vartype'] == 'template') {
251 if (is_array($item['varvalue'])) {
252 $data[$item['varid']][] = $this->getValues($item['varvalue']);
255 // i dont know if we need this
256 $data[$item['varid']] = $item['varvalue'];
260 if (is_array($item['varvalue'])) {
261 $data[$item['varid']] = $this->getValues($item['varvalue']);
264 $data[$item['varid']] = $item['varvalue'];
273 * sends the encrypted request to router
275 * @param string $path
276 * @param mixed $fields
277 * @param string $cookie
280 private function sentEncryptedRequest ($path, $fields, $cookie = false) {
281 $count = count($fields);
282 $fields = $this->encrypt(http_build_query($fields));
283 return $this->sentRequest($path, $fields, $cookie, $count);
287 * sends the request to router
289 * @param string $path
290 * @param mixed $fields
291 * @param string $cookie
292 * @param integer $count
295 private function sentRequest ($path, $fields, $cookie = false, $count = 0) {
296 $url = $this->url
.$path.'?lang=en';
298 curl_setopt($ch, CURLOPT_URL
, $url);
300 if (!empty($fields)) {
301 if (is_array($fields)) {
302 curl_setopt($ch, CURLOPT_POST
, count($fields));
303 curl_setopt($ch, CURLOPT_POSTFIELDS
, http_build_query($fields));
306 curl_setopt($ch, CURLOPT_POST
, $count);
307 curl_setopt($ch, CURLOPT_POSTFIELDS
, $fields);
311 if ($cookie === true) {
312 curl_setopt($ch, CURLOPT_COOKIE
, 'challengev='.$this->challenge
.'; '.$this->cookie
);
315 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
316 curl_setopt($ch, CURLOPT_HEADER
, true);
318 $result = curl_exec($ch);
320 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE
);
321 $header = substr($result, 0, $header_size);
322 $body = substr($result, $header_size);
325 // check if response is empty
327 throw new RouterException('empty response');
330 // check if body is encrypted (hex instead of json)
331 if (ctype_xdigit($body)) {
332 $body = $this->decrypt($body);
336 $body = preg_replace("/(\r\n)|(\r)/", "\n", $body);
337 $body = preg_replace('/\'/i', '"', $body);
338 $body = preg_replace("/\[\s+\]/i", '[ {} ]', $body);
339 $body = preg_replace("/},\s+]/", "}\n]", $body);
342 if (strpos($url, '.json') !== false) {
343 $body = json_decode($body, true);
346 return array('header' => $this->parse_headers($header), 'body' => $body);
354 private function getToken () {
357 $path = 'html/content/overview/index.html';
359 $data = $this->sentRequest($path, $fields, true);
361 $a = explode('csrf_token = "', $data['body']);
362 $a = explode('";', $a[1]);
364 if (isset($a[0]) && !empty($a[0])) {
368 throw new RouterException('unable to get csrf_token');
373 * calculate the derivedk
375 * @param string $password
378 private function getDerviedk ($password) {
381 // calculate derivedk
382 if (!function_exists('hash_pbkdf2')) {
383 $pbkdf2 = new CryptLib\Key\Derivation\PBKDF\
PBKDF2(array('hash' => 'sha1'));
384 $derivedk = bin2hex($pbkdf2->derive(hash('sha256', $password), substr($this->challenge
, 0, 16), 1000, 32));
385 $derivedk = substr($derivedk, 0, 32);
388 $derivedk = hash_pbkdf2('sha1', hash('sha256', $password), substr($this->challenge
, 0, 16), 1000, 32);
391 if (empty($derivedk)) {
392 throw new RouterException('unable to calculate derivedk');
399 * get cookie from header data
404 private function getCookie ($data) {
406 if (isset($data['header']['Set-Cookie']) && !empty($data['header']['Set-Cookie'])) {
407 preg_match('/^.*(SessionID_R3=[a-z0-9]*).*/i', $data['header']['Set-Cookie'], $match);
408 if (isset($match[1]) && !empty($match[1])) {
413 if (empty($cookie)) {
414 throw new RouterException('unable to get the session cookie from the router');
421 * parse the curl return header into an array
423 * @param string $response
426 private function parse_headers($response) {
428 $header_text = substr($response, 0, strpos($response, "\r\n\r\n"));
430 $header_text = explode("\r\n", $header_text);
431 foreach ($header_text as $i => $line) {
433 $headers['http_code'] = $line;
436 list ($key, $value) = explode(': ', $line);
437 $headers[$key] = $value;