mario-dns/resolver.cpp

331 lines
9.9 KiB
C++

#include "cppcodec/base32_rfc4648.hpp"
#include "resolver.h"
#include "funcs.h"
#include "HTTPheaders.h"
#include <QTcpSocket>
#include <QDateTime>
#include <QHostInfo>
#include <QDebug>
#include <QFile>
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
Resolver::Resolver(const QString& addr, quint16 port, QObject* parent) :
QObject(parent),
m_tcpServer(new QTcpServer),
m_address(addr),
m_port(port)
{
if (!m_tcpServer->listen(m_address, m_port)) {
throw "Server not binded";
}
qDebug().noquote() << "<-" << m_tcpServer->serverAddress().toString() + " : " +
QString::number(m_tcpServer->serverPort()) << "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + "]";
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(slotNewUser()));
}
void Resolver::closeSocketViaDescriptor(int id)
{
if(m_sockets.contains(id)) {
m_sockets.value(id)->close();
}
}
QSharedPointer<QTcpSocket> Resolver::getSocketViaDescriptor(int id)
{
if (!m_sockets.contains(id)) {
qDebug() << "Resolver::getSocketViaDescriptor: Requested socket not found";
}
return m_sockets.value(id);
}
void Resolver::slotNewUser()
{
if (!m_tcpServer->isListening()) return;
QTcpSocket* clientSocket = m_tcpServer->nextPendingConnection();
int idUserSock = clientSocket->socketDescriptor();
m_sockets.insert(idUserSock, QSharedPointer<QTcpSocket>(clientSocket));
connect (m_sockets.value(idUserSock).data(), SIGNAL(readyRead()), this, SLOT(slotReadyClient()));
connect (m_sockets.value(idUserSock).data(), SIGNAL(disconnected()), this, SLOT(slotClientDisconnected()));
}
void Resolver::slotReadyClient()
{
QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
QString req(clientSocket->readAll());
qDebug().noquote() << "\n->" << clientSocket->peerAddress().toString()
<< "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + "]";
bool web = req.startsWith("GET /");
QSharedPointer<QTextStream> ts(new QTextStream(clientSocket));
if (web) {
qDebug().noquote() << " └ Via web browser";
if (req.contains("toConverting=")) {
processPage(req, ts, web);
} else {
startPage(ts, web);
}
} else {
req.remove('\r');
req.remove('\n');
qDebug().noquote() << " └ Without web browser";
if (req.contains(":") or req.contains(".")) {
processPage(req, ts, web);
} else {
startPage(ts, web);
}
}
}
void Resolver::slotClientDisconnected()
{
QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
auto idUserSock = clientSocket->socketDescriptor();
m_sockets.remove(idUserSock);
}
void Resolver::convertStrToRaw(const QString &str, Address &array)
{
inet_pton(AF_INET6, str.toUtf8(), (void*)array.data());
}
QString Resolver::getBase32(const Address &rawAddr)
{
return cppcodec::base32_rfc4648::encode(rawAddr.data(), ADDRIPV6_SIZE).c_str();
}
QString Resolver::decodeMeshToIP(const QString &meshname)
{
std::string mesh = pickupStringForMeshname(meshname.toStdString()) + "======"; // 6 паддингов - норма для IPv6 адреса
std::vector<uint8_t> raw;
try {
raw = cppcodec::base32_rfc4648::decode(mesh);
} catch (cppcodec::padding_error) {
return QString();
} catch (cppcodec::symbol_error) {
return QString();
}
Address rawAddr;
for(int i = 0; i < 16; ++i)
rawAddr[i] = raw[i];
return getAddress(rawAddr);
}
std::string Resolver::pickupStringForMeshname(std::string str)
{
bool dot = false;
std::string::iterator delend;
for (auto it = str.begin(); it != str.end(); it++)
{
*it = toupper(*it); // делаем все буквы заглавными для обработки
if(*it == '.') {
delend = it;
dot = true;
}
}
if (dot)
for (auto it = str.end(); it != delend; it--)
str.pop_back(); // удаляем доменную зону
return str;
}
QString Resolver::getAddress(const Address& rawAddr)
{
char ipStrBuf[46];
inet_ntop(AF_INET6, rawAddr.data(), ipStrBuf, 46);
return QString(ipStrBuf);
}
void Resolver::startPage(QSharedPointer<QTextStream> ts, bool web)
{
QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
if (web) {
qDebug().noquote() << " └ Start page";
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "gray");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", "<p>Input any domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>"
"<p>Also you can use Mario DNS tool via command line (telnet or netcat for example) to get result in JSON. Just pass the value for processing.");
*ts << page;
}
else {
qDebug().noquote() << " └ Incorrect request";
*ts << "{\n"
" \"status\": false,\n"
" \"answer\": \"Push any domain to resolve or IPv6 to convert to meship\"\n"
"}\n";
}
clientSocket->close();
}
void Resolver::startPageWithMessage(const QString & value, QSharedPointer<QTextStream> ts, const QString & msg, bool web)
{
qDebug().noquote() << "" + msg;
if (web) {
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "red");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", msg);
if (!value.isEmpty()) {
page.replace("placeholder=\"IPv6 address or domain\"", "value=\"" + value + "\"");
}
*ts << page;
}
else {
*ts << "{\n"
" \"status\": false,\n"
" \"answer\": \"" + msg + "\"\n"
"}\n";
}
}
void Resolver::processPage(const QString& req, QSharedPointer<QTextStream> ts, bool web)
{
QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
QString value;
if (web) {
value = funcs::getValue(req, "toConverting");
} else {
value = req;
}
value.remove('+');
value = QByteArray::fromPercentEncoding(value.toUtf8());
qDebug().noquote() << "" + value;
const uint8_t MAX_LEN = 60;
if (value.size() > MAX_LEN) {
startPageWithMessage("", ts, "Maximum input value length = " + QString::number(MAX_LEN), web);
}
else if (value.contains(":")) {
toMeship(value, ts, web);
}
else if (value.endsWith(".meship")) {
toIp(value, ts, web);
}
else if (value.contains(".")) {
toRealResolv(value, ts, web);
}
else {
startPageWithMessage(value, ts, "Input value must contains domain or IPv6", web);
}
clientSocket->close();
}
void Resolver::toMeship(const QString & value, QSharedPointer<QTextStream> ts, bool web)
{
Address rawAddr;
convertStrToRaw(value, rawAddr);
QString base32 = getBase32(rawAddr);
base32 = base32.toLower();
base32.remove('=');
base32 += ".meship";
if (web) {
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "green");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", base32);
page.replace("placeholder=\"IPv6 address or domain\"", "value=\"" + value + "\"");
*ts << page;
}
else {
*ts << "{\n"
" \"status\": true,\n"
" \"answer\": \"" + base32 + "\"\n"
"}\n";
}
}
void Resolver::toIp(const QString & value, QSharedPointer<QTextStream> ts, bool web)
{
QString result = decodeMeshToIP(value);
if (result.isEmpty()) {
startPageWithMessage(value, ts, "Failed: meship domain must have 26 base32 (RFC4648) characters only", web);
return;
}
if (web) {
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "green");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", result);
page.replace("placeholder=\"IPv6 address or domain\"", "value=\"" + value + "\"");
*ts << page;
}
else {
*ts << "{\n"
" \"status\": true,\n"
" \"answer\": \"" + result + "\"\n"
"}\n";
}
}
void Resolver::toRealResolv(const QString & value, QSharedPointer<QTextStream> ts, bool web)
{
QHostInfo host = QHostInfo::fromName(value);
if (host.error() == QHostInfo::NoError) {
auto addrs = host.addresses();
if (web) {
QString addresses;
for (auto a: addrs) {
addresses += a.toString() + "<br>";
}
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "green");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", addresses);
page.replace("placeholder=\"IPv6 address or domain\"", "value=\"" + value + "\"");
*ts << page;
}
else {
*ts << "{\n"
" \"status\": true,\n"
" \"answer\": [\n";
for (auto it = addrs.begin(); it != addrs.end(); it++) {
*ts << " \"" + it->toString() + "\"";
if (it+1 != addrs.end()) {
*ts << ",";
}
*ts << "\n";
}
*ts << " ]\n"
"}\n";
}
} else {
QString msg;
if (value.endsWith(".meshname")) {
msg = "Domain not resolved. Try \".meship\" instead \".meshname\" for direct translation to address";
} else {
msg = "Failed: " + host.errorString();
}
startPageWithMessage(value, ts, msg, web);
}
}