/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2018 Simon Stürz * * * * This file is part of guh. * * * * Guh is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 2 of the License. * * * * Guh is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with guh. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "guhcore.h" #include "httpreply.h" #include "guhsettings.h" #include "httprequest.h" #include "loggingcategories.h" #include "debugserverhandler.h" #include #include namespace guhserver { DebugServerHandler::DebugServerHandler(QObject *parent) : QObject(parent) { } QByteArray DebugServerHandler::createDebugXmlDocument() { QByteArray data; QXmlStreamWriter writer(&data); writer.setAutoFormatting(true); writer.writeStartDocument("1.0"); writer.writeProcessingInstruction("DOCUMENT", "html"); writer.writeComment("Auto generated html page from nymea"); writer.writeStartElement("html"); writer.writeAttribute("lang", GuhCore::instance()->configuration()->locale().name()); writer.writeStartElement("head"); writer.writeEmptyElement("meta"); writer.writeAttribute("http-equiv", "Content-Type"); writer.writeAttribute("content", "text/html; charset=utf-8"); writer.writeTextElement("title", QCoreApplication::translate("main", "Debug nymea")); writer.writeEndElement(); // head writer.writeStartElement("body"); // Welcome section writer.writeTextElement("h1", QCoreApplication::translate("main", "nymea debug interface")); writer.writeEmptyElement("hr"); writer.writeTextElement("p", QCoreApplication::translate("main", "Welcome to the debug interface.")); writer.writeTextElement("p", QCoreApplication::translate("main", "This debug interface was designed to provide an easy possibility to get helpful information about the running nymea server.")); writer.writeEmptyElement("hr"); writer.writeTextElement("h3", QCoreApplication::translate("main", "Warning")); writer.writeTextElement("p", QCoreApplication::translate("main", "Be aware that this debug interface is a security breach and offers access to the system log and therefore to possibly sensible data.")); writer.writeTextElement("p", QCoreApplication::translate("main", "If you are not using this debug tools, than you should disable it.")); writer.writeEmptyElement("hr"); // System information section writer.writeTextElement("h2", QCoreApplication::translate("main", "Server information")); writer.writeEmptyElement("hr"); writer.writeStartElement("table"); //writer.writeAttribute("width", "100%"); writer.writeAttribute("border", "1"); writer.writeStartElement("col"); writer.writeAttribute("align", "left"); writer.writeEndElement(); // col writer.writeStartElement("col"); writer.writeAttribute("align", "left"); writer.writeEndElement(); // col QString userName = qgetenv("USER"); if (userName.isEmpty()) userName = qgetenv("USERNAME"); writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "User")); writer.writeTextElement("td", userName); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Compiled with Qt version")); writer.writeTextElement("td", QT_VERSION_STR); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Qt runtime version")); writer.writeTextElement("td", qVersion()); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Command")); writer.writeTextElement("td", QCoreApplication::arguments().join(' ')); writer.writeEndElement(); // tr if (!qgetenv("SNAP").isEmpty()) { // Note: http://snapcraft.io/docs/reference/env writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap name")); writer.writeTextElement("td", qgetenv("SNAP_NAME")); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap version")); writer.writeTextElement("td", qgetenv("SNAP_VERSION")); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap directory")); writer.writeTextElement("td", qgetenv("SNAP")); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap application data")); writer.writeTextElement("td", qgetenv("SNAP_DATA")); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap user data")); writer.writeTextElement("td", qgetenv("SNAP_USER_DATA")); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Snap common data")); writer.writeTextElement("td", qgetenv("SNAP_COMMON")); writer.writeEndElement(); // tr } writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Server name")); writer.writeTextElement("td", GuhCore::instance()->configuration()->serverName()); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Server version")); writer.writeTextElement("td", GUH_VERSION_STRING); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "JSON-RPC version")); writer.writeTextElement("td", JSON_PROTOCOL_VERSION); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Language")); writer.writeTextElement("td", GuhCore::instance()->configuration()->locale().name() + " (" + GuhCore::instance()->configuration()->locale().nativeCountryName() + " - " + GuhCore::instance()->configuration()->locale().nativeLanguageName() + ")"); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Timezone")); writer.writeTextElement("td", QString::fromUtf8(GuhCore::instance()->configuration()->timeZone())); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Server UUID")); writer.writeTextElement("td", GuhCore::instance()->configuration()->serverUuid().toString()); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Settings path")); writer.writeTextElement("td", GuhSettings::settingsPath()); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Translations path")); writer.writeTextElement("td", GuhSettings(GuhSettings::SettingsRoleGlobal).translationsPath()); writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Log database")); writer.writeTextElement("td", GuhSettings(GuhSettings::SettingsRoleGlobal).logPath()); writer.writeEndElement(); // tr for (int i = 0; i < GuhCore::instance()->deviceManager()->pluginSearchDirs().count(); i++) { writer.writeStartElement("tr"); writer.writeEndElement(); // tr if (i == 0) { writer.writeTextElement("th", QCoreApplication::translate("main", "Plugin paths")); } else { writer.writeTextElement("th", ""); } writer.writeTextElement("td", QFileInfo(GuhCore::instance()->deviceManager()->pluginSearchDirs().at(i)).absoluteFilePath()); } writer.writeEndElement(); // table // Downloads section writer.writeEmptyElement("hr"); writer.writeTextElement("h2", QCoreApplication::translate("main", "Downloads")); writer.writeEmptyElement("hr"); // Logs download section writer.writeTextElement("h3", QCoreApplication::translate("main", "Logs")); writer.writeStartElement("table"); writer.writeAttribute("border", "1"); writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Log database")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/logdb.sql"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Syslog")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/syslog"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr // TODO: offer logfile download if specified in the command line options writer.writeEndElement(); // table // Settings download section writer.writeTextElement("h3", QCoreApplication::translate("main", "Settings")); writer.writeStartElement("table"); writer.writeAttribute("border", "1"); writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Guhd settings")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/settings/guhd"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Device settings")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/settings/devices"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Device state settings")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/settings/devicestates"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr writer.writeStartElement("tr"); writer.writeTextElement("th", QCoreApplication::translate("main", "Rules settings")); writer.writeStartElement("td"); writer.writeStartElement("form"); writer.writeAttribute("method", "get"); writer.writeAttribute("action", "/debug/settings/rules"); writer.writeStartElement("button"); writer.writeAttribute("type", "submit"); writer.writeCharacters(QCoreApplication::translate("main", "Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // td writer.writeEndElement(); // tr writer.writeEndElement(); // table writer.writeEmptyElement("hr"); writer.writeStartElement("footer"); writer.writeTextElement("p", QString("Copyright %1 2018 guh GmbH.").arg(QChar(0xA9))); writer.writeTextElement("p", QCoreApplication::translate("main", "Released under the GNU GENERAL PUBLIC LICENSE Version 2.")); writer.writeEndElement(); // footer writer.writeEndElement(); // body writer.writeEndElement(); // html return data; } QByteArray DebugServerHandler::createErrorXmlDocument(HttpReply::HttpStatusCode statusCode, const QString &errorMessage) { QByteArray data; QXmlStreamWriter writer(&data); writer.setAutoFormatting(true); writer.writeStartDocument("1.0"); writer.writeComment("Live generated html page from nymea"); writer.writeStartElement("html"); writer.writeAttribute("lang", GuhCore::instance()->configuration()->locale().name()); writer.writeStartElement("head"); writer.writeEmptyElement("meta"); writer.writeAttribute("http-equiv", "Content-Type"); writer.writeAttribute("content", "text/html; charset=utf-8"); writer.writeTextElement("title", QCoreApplication::translate("main", "Debug nymea")); writer.writeEndElement(); // head writer.writeStartElement("body"); writer.writeTextElement("h1", QCoreApplication::translate("main", "Error") + QString(" %1").arg(static_cast(statusCode))); writer.writeEmptyElement("hr"); writer.writeTextElement("p", errorMessage); writer.writeEmptyElement("hr"); writer.writeStartElement("footer"); writer.writeTextElement("p", QString("Copyright %1 2018 guh GmbH.").arg(QChar(0xA9))); writer.writeTextElement("p", QCoreApplication::translate("main", "Released under the GNU GENERAL PUBLIC LICENSE Version 2.")); writer.writeEndElement(); // footer writer.writeEndElement(); // body writer.writeEndElement(); // html return data; } HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath) { qCDebug(dcWebServer()) << "Debug request for" << requestPath; // Check if this is a logdb requested if (requestPath.startsWith("/debug/logdb.sql")) { qCDebug(dcWebServer()) << "Loading" << GuhSettings::logPath(); QFile logDatabaseFile(GuhSettings::logPath()); if (!logDatabaseFile.exists()) { qCWarning(dcWebServer()) << "Could not read log database file for debug download" << GuhSettings::logPath() << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + logDatabaseFile.fileName())); return reply; } if (!logDatabaseFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read log database file for debug download" << GuhSettings::logPath(); HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + logDatabaseFile.fileName())); return reply; } QByteArray logDatabaseRawData = logDatabaseFile.readAll(); logDatabaseFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "application/sql"); reply->setPayload(logDatabaseRawData); return reply; } // Check if this is a syslog requested if (requestPath.startsWith("/debug/syslog")) { QString syslogFileName = "/var/log/syslog"; qCDebug(dcWebServer()) << "Loading" << syslogFileName; QFile syslogFile(syslogFileName); if (!syslogFile.exists()) { qCWarning(dcWebServer()) << "Could not read log database file for debug download" << syslogFileName << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + syslogFileName)); return reply; } if (!syslogFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read syslog file for debug download" << syslogFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + syslogFileName)); return reply; } QByteArray syslogFileData = syslogFile.readAll(); syslogFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(syslogFileData); return reply; } // Check if this is a settings request if (requestPath.startsWith("/debug/settings")) { if (requestPath.startsWith("/debug/settings/devices")) { QString settingsFileName = GuhSettings(GuhSettings::SettingsRoleDevices).fileName(); qCDebug(dcWebServer()) << "Loading" << settingsFileName; QFile settingsFile(settingsFileName); if (!settingsFile.exists()) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + settingsFileName)); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + settingsFileName)); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/rules")) { QString settingsFileName = GuhSettings(GuhSettings::SettingsRoleRules).fileName(); qCDebug(dcWebServer()) << "Loading" << settingsFileName; QFile settingsFile(settingsFileName); if (!settingsFile.exists()) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + settingsFileName)); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + settingsFileName)); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/guhd")) { QString settingsFileName = GuhSettings(GuhSettings::SettingsRoleGlobal).fileName(); qCDebug(dcWebServer()) << "Loading" << settingsFileName; QFile settingsFile(settingsFileName); if (!settingsFile.exists()) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + settingsFileName)); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + settingsFileName)); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/devicestates")) { QString settingsFileName = GuhSettings(GuhSettings::SettingsRoleDeviceStates).fileName(); qCDebug(dcWebServer()) << "Loading" << settingsFileName; QFile settingsFile(settingsFileName); if (!settingsFile.exists()) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist."; HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not find file") + " " + settingsFileName)); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, QCoreApplication::translate("main", "Could not open file") + " " + settingsFileName)); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } } qCDebug(dcWebServer()) << "Create debug interface page"; // Fallback default debug page HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createDebugXmlDocument()); return reply; } }