-
-
Save ancientGlider/e72cdaa2daf0af5f8d80f53fea4666be to your computer and use it in GitHub Desktop.
| # -*- coding: utf-8 -*- | |
| """ | |
| Данные с адресом, логином, паролем хранятся в конфигурационном файле следующего вида: | |
| [Router] | |
| ip_addr = 192.168.1.1:8080 | |
| login = admin | |
| passw = anyPassword | |
| """ | |
| CONFIG_FILE_NAME = "keenetic.conf" # имя конфигурационного файла | |
| import configparser | |
| import requests | |
| import hashlib | |
| cookies_current = None | |
| session = requests.session() # заводим сессию глобально чтобы отрабатывались куки | |
| def keen_auth(login, passw): # авторизация на роутере | |
| response = keen_request(ip_addr, "auth") | |
| if response.status_code == 401: | |
| md5 = login + ":" + response.headers["X-NDM-Realm"] + ":" + passw | |
| md5 = hashlib.md5(md5.encode('utf-8')) | |
| sha = response.headers["X-NDM-Challenge"] + md5.hexdigest() | |
| sha = hashlib.sha256(sha.encode('utf-8')) | |
| response = keen_request(ip_addr, "auth", {"login": login, "password": sha.hexdigest()}) | |
| if response.status_code == 200: | |
| return True | |
| elif response.status_code == 200: | |
| return True | |
| else: | |
| return False | |
| def keen_request(ip_addr, query, post = None): # отправка запросов на роутер | |
| global session | |
| # конструируем url | |
| url = "http://" + ip_addr + "/" + query | |
| # если есть данные для запроса POST, делаем POST, иначе GET | |
| if post: | |
| return session.post(url, json=post) | |
| else: | |
| return session.get(url) | |
| config = configparser.ConfigParser() # создаём объекта парсера | |
| config.read(CONFIG_FILE_NAME) # читаем конфиг | |
| ip_addr = config["Router"]["ip_addr"] | |
| login = config["Router"]["login"] | |
| passw = config["Router"]["passw"] | |
| # тестируем | |
| if keen_auth(login, passw): | |
| response = keen_request(ip_addr, 'rci/show/interface/WifiMaster0'); | |
| print(response.text) |
Переписал тоже самое на PHP
<?php class KeeneticAPI { private string $ip_addr; private string $login; private string $passw; private CurlHandle $session; private string $cookieJar; public function __construct(string $ip_addr, string $login, string $passw) { $this->ip_addr = $ip_addr; $this->login = $login; $this->passw = $passw; // Инициализация cURL сессии и файла для хранения cookie $this->cookieJar = tempnam(sys_get_temp_dir(), 'cookies_'); $this->session = curl_init(); $this->aut
Привет! Скажи куда вписывать свои данные от роутера. Спасибо
Использую для того чтобы через Алису выключать\включать VPN для телевизора
Скажите, а как Вы это сделали?
Я столкнулся с парой проблем:
-
Я так понял, что надо это скрипт оформить в виде, например, serverless функции в яндекс клауде. Ок, сделано. Но надо же, чтобы эта функция ходила на роутер, а значит надо разрешить к роутеру доступ извне - можно по белому IP, но тогда не работает HTTPS, а можно с помощью KeenDNS, но тогда не работает авторизация в приницпе, т.к. не приходят хедеры типа X-NDM-Challenge и X-NDM-Realm. Не понимаю как это победить.
-
Даже если победить пункт 1, как заставить Алису запускать эту функцию? Я создал навык, но не понимаю как заставить Алису его выполнять.
- Я так понял, что надо это скрипт оформить в виде, например, serverless функции в яндекс клауде. Ок, сделано. Но надо же, чтобы эта функция ходила на роутер, а значит надо разрешить к роутеру доступ извне - можно по белому IP, но тогда не работает HTTPS, а можно с помощью KeenDNS, но тогда не работает авторизация в приницпе, т.к. не приходят хедеры типа X-NDM-Challenge и X-NDM-Realm. Не понимаю как это победить.
У меня получилась по такой схеме. Посылаем целевой GET\POST запрос, НЕ на /auth, а в нужный нам эндпоинт. В ответе придёт статус 401, со стандартным заголовком для Digest auth схемы, например:
WWW-Authenticate: Digest realm="Keenetic Giga", nonce="y08bAAAAAAAbcGJz80k0UwbEDjHr+/xQDLL5Y2YxOTA=", qop="auth".
Никаких X-NDM-Challenge и X-NDM-Realm.
Дальше вычисляем хеш для ответа, по схеме описанной в примерах выше или с привлечением библиотек. Realm есть, nonce это Challenge
Добавляем в повторный запрос хедер
Authorization: Digest username="userrci", realm="Keenetic Giga", nonce="dFMbAAAAAACt2JFXg3Qs/c1qdue+l3flW4RkVDRlZTQ=", uri="/rci/show/interface/", algorithm="MD5", qop=auth, nc=00000001, cnonce="4nYeHdGL", response="988ea3592ff8584e0d22d246eea32f1c"
где response это то наш хеш.
После этого в успешном ответе от роутера будет хедер Authentication-Info: nextnonce="dFMbAAAAAACt2JFXg3Qs/c1qdue+l3flW4RkVDRlZTQ=", qop=auth, rspauth="36732f6344cac459257be90d1961a660", cnonce="4nYeHdGL", nc=00000001
nextnonce можно использовать для повторного вычисления хеша перед следующим запросом.
Пример кода у меня на джаве с использованием либы Apache httpclient5. Тут без вычисления nextnonce, каждый запрос по новой авторизуется. В итоге получилось сделать serverless функцию в Яндекс.Облаке, связать с телеграм ботом и стрелять на роутер через KeenDNS
private String keenRequest(ClassicHttpRequest request) {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
Credentials creds = new UsernamePasswordCredentials(USERNAME, PASSWORD.toCharArray());
DigestScheme digestScheme = new DigestScheme();
digestScheme.initPreemptive(creds, null, null);
// Первый запрос, получаем хедер для авторизации
String wwwHeader = httpclient.execute(request, response -> {
if (response.getCode() == 401 && response.getHeader("WWW-Authenticate") != null) {
return response.getHeader("WWW-Authenticate").getValue();
} else {
System.err.println("Unexpected return code: " + response.getCode() + "\nheaders: " + Arrays.toString(response.getHeaders()));
return "";
}
});
if (wwwHeader.isBlank()) {
System.err.println("No WWW-Authenticate header found!");
return null;
}
// Парсим хедер, в теории может быть несколько challenges, но у нас всегда один
List<AuthChallenge> parsedChallenges = AuthChallengeParser.INSTANCE.parse(ChallengeType.TARGET, wwwHeader, new ParserCursor(0, wwwHeader.length()));
if (parsedChallenges.isEmpty()) {
System.err.println("Could not parse WWW-Authenticate header!");
return null;
}
// Передаём челледж в digestScheme
AuthChallenge authChallenge = parsedChallenges.get(0);
digestScheme.processChallenge(authChallenge, null);
// Тут происходит магия вычисления хедера Authorization по схеме Digest
String authResponse = digestScheme.generateAuthResponse(null, request, null);
request.setHeader("Authorization", authResponse);
// Повторно отправляем запрос
return httpclient.execute(request, response -> {
String responseBody = EntityUtils.toString(response.getEntity());
if (response.getCode() == 200) {
return responseBody;
}
return null;
});
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Пример вызова
HttpGet httpGet = new HttpGet(String.format("%s/rci/show/interface/", HOST));
String result = this.keenRequest(httpGet);
@alekssamos
Увы, домены по типу
rr2---sn-oan31-5a5e.googlevideo.comпериодически меняются. Потому приходится собирать велосипеды из костылей.pc. Хмм, надо помониторить, вдруг и правда пул ip просто переименовывают...