mario-dns/resolver.cpp

397 lines
12 KiB
C++

#include "cppcodec/base32_rfc4648.hpp"
#include "resolver.h"
#include "funcs.h"
#include "HTTPheaders.h"
#include <QTcpSocket>
#include <QRegularExpression>
#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),
m_NoApi(false),
m_NoHttp(false),
m_NoResolve(false)
{
if (!m_tcpServer->listen(m_address, m_port)) {
throw "Server not binded";
}
qInfo().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::disableAPI()
{
m_NoApi = true;
}
void Resolver::disableWebPage()
{
m_NoHttp = true;
}
void Resolver::disableResolv()
{
m_NoResolve = true;
http::HTML_PAGE.replace("IPv6 address or domain", "IPv6 address or meship domain");
}
void Resolver::slotNewUser()
{
if (!m_tcpServer->isListening()) return;
QTcpSocket* clientSocket = m_tcpServer->nextPendingConnection();
connect (clientSocket, SIGNAL(readyRead()), this, SLOT(slotReadyClient()));
connect (clientSocket, SIGNAL(disconnected()), clientSocket, SLOT(deleteLater()));
}
void Resolver::slotReadyClient()
{
QTcpSocket* clientSocket = static_cast<QTcpSocket*>(sender());
QString req(clientSocket->readAll());
qInfo().noquote() << "\n->" << clientSocket->peerAddress().toString()
<< "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + "]";
QSharedPointer<QTextStream> ts(new QTextStream(clientSocket));
if (m_NoHttp) {
if (req.startsWith("POST /") or req.startsWith("HEAD /") or req.startsWith("GET /")) {
qInfo().noquote() << " └ HTTP (dropped)";
*ts << http::HEADER_REJECT;
clientSocket->close();
return;
}
}
if (req.startsWith("POST /")) {
qInfo().noquote() << " └ POST request (dropped)";
clientSocket->close();
return;
}
if (req.startsWith("HEAD /")) {
qInfo().noquote() << " └ HEAD request";
*ts << http::HEADER_OK;
clientSocket->close();
return;
}
bool web = req.startsWith("GET /");
if (web) {
qInfo().noquote() << " └ HTTP";
if (req.contains("toConverting=")) {
processPage(req, ts, web);
} else {
startPage(ts, web);
}
} else if (not m_NoApi) {
req.remove('\r');
req.remove('\n');
qInfo().noquote() << " └ API";
if (req.contains(":") or req.contains(".") or req.contains("version")) {
processPage(req, ts, web);
} else {
startPage(ts, web);
}
}
else {
qInfo().noquote() << " └ API (dropped)";
clientSocket->close();
}
}
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 const&) {
return QString();
} catch (cppcodec::symbol_error const&) {
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) {
qInfo().noquote() << " └ Start page";
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
QString text;
if (m_NoApi and not m_NoResolve) {
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>";
} else if (m_NoApi and m_NoResolve) {
text = "<p>Input meship domain to resolve or IPv6 to convert to <a href=\"https://github.com/zhoreeq/meshname\" style=\"text-decoration: none\">meship</a>.</p>";
} else if (not m_NoResolve) {
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.";
} else { // no resolve
text = "<p>Input meship 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.";
}
css.replace("{{COLOR}}", "gray");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", text);
*ts << page;
}
else {
QString msg;
if (m_NoResolve) {
msg = "Push meship domain to resolve or IPv6 to convert to meship";
} else {
msg = "Push any domain to resolve or IPv6 to convert to meship";
}
qInfo().noquote() << " └ Incorrect request";
*ts << "{\n"
" \"status\": false,\n"
" \"answer\": \"" + msg + "\"\n"
"}\n";
}
clientSocket->close();
}
void Resolver::startPageWithMessage(const QString & value, QSharedPointer<QTextStream> ts, const QString & msg, bool web)
{
qInfo().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(QRegularExpression("placeholder=\".*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");
value.remove('+');
value = QByteArray::fromPercentEncoding(value.toUtf8());
} else {
value = req;
}
qInfo().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 == "version") {
toVersion(value, ts, web);
}
else if (value.contains(":")) {
toMeship(value, ts, web);
}
else if (value.endsWith(".meship")) {
toIp(value, ts, web);
}
else if (value.contains(".") and not m_NoResolve) {
toRealResolv(value, ts, web);
}
else {
QString text;
m_NoResolve
? text ="Input value must contains meship domain or IPv6"
: text = "Input value must contains domain or IPv6";
startPageWithMessage(value, ts, text, 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(QRegularExpression("placeholder=\".*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(QRegularExpression("placeholder=\".*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(QRegularExpression("placeholder=\".*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);
}
}
void Resolver::toVersion(const QString &value , QSharedPointer<QTextStream> ts, bool web)
{
if (web) {
QString page = http::HTML_PAGE;
QString css = http::CSS_DEFAULT;
css.replace("{{COLOR}}", "gray");
page.replace("{{STYLES}}", css);
page.replace("{{TEXT}}", VERSION);
page.replace(QRegularExpression("placeholder=\".*domain\""), "value=\"" + value + "\"");
*ts << page;
}
else {
*ts << "{\n"
" \"status\": true,\n"
" \"answer\": \"" + VERSION + "\"\n"
"}\n";
}
}