397 lines
12 KiB
C++
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";
|
|
}
|
|
}
|