diff --git a/CMakeLists.txt b/CMakeLists.txt index f0c2d025b..92ade41df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,8 @@ if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0")) set(GCRYPT_HAS_SALSA20 1) endif() +find_package(LibMicroHTTPD REQUIRED) + find_package(ZLIB REQUIRED) check_cxx_source_compiles(" diff --git a/cmake/FindLibMicroHTTPD.cmake b/cmake/FindLibMicroHTTPD.cmake new file mode 100644 index 000000000..f31928043 --- /dev/null +++ b/cmake/FindLibMicroHTTPD.cmake @@ -0,0 +1,9 @@ + +find_path(MHD_INCLUDE_DIR microhttpd.h) + +find_library(MHD_LIBRARIES microhttpd) + +mark_as_advanced(MHD_LIBRARIES MHD_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibMicroHTTPD DEFAULT_MSG MHD_LIBRARIES MHD_INCLUDE_DIR) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 35bc888cc..84cc6a48d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -237,7 +237,6 @@ add_library(keepassx_core STATIC ${keepassx_SOURCES}) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) add_subdirectory(gui/qocoa) -add_subdirectory(http/qhttpserver) add_subdirectory(http/qjson) add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE}) @@ -245,7 +244,7 @@ target_link_libraries(${PROGNAME} keepassx_core Qocoa qjson - qhttpserver + ${MHD_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTNETWORK_LIBRARY} diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2e2827401..7c11d5b13 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -42,17 +42,19 @@ class HttpPlugin: public ISettingsPage { public: - HttpPlugin(DatabaseTabWidget * tabWidget): m_service(new Service(tabWidget)) { + HttpPlugin(DatabaseTabWidget * tabWidget) { + m_service = new Service(tabWidget); } virtual ~HttpPlugin() { + //delete m_service; } virtual QString name() { return QObject::tr("Http"); } virtual QWidget * createWidget() { OptionDialog * dlg = new OptionDialog(); - QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service.data(), SLOT(removeSharedEncryptionKeys())); - QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service.data(), SLOT(removeStoredPermissions())); + QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys())); + QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions())); return dlg; } virtual void loadSettings(QWidget * widget) { @@ -66,7 +68,7 @@ public: m_service->stop(); } private: - QScopedPointer m_service; + Service *m_service; }; const QString MainWindow::BaseWindowTitle = "KeePassX"; diff --git a/src/http/Server.cpp b/src/http/Server.cpp index 9725e56f3..dedbd221c 100644 --- a/src/http/Server.cpp +++ b/src/http/Server.cpp @@ -12,37 +12,17 @@ */ #include "Server.h" -#include "qhttpserver/qhttpserver.h" -#include "qhttpserver/qhttprequest.h" -#include "qhttpserver/qhttpresponse.h" +#include #include "Protocol.h" #include "crypto/Crypto.h" #include #include #include +#include using namespace KeepassHttpProtocol; -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// Request -//////////////////////////////////////////////////////////////////////////////////////////////////// - -RequestHandler::RequestHandler(QHttpRequest *request, QHttpResponse *response): m_request(request), m_response(response) -{ - m_request->storeBody(); - connect(m_request, SIGNAL(end()), this, SLOT(onEnd())); - connect(m_response, SIGNAL(done()), this, SLOT(deleteLater())); -} - -RequestHandler::~RequestHandler() -{ - delete m_request; -} - -void RequestHandler::onEnd() -{ - Q_EMIT requestComplete(m_request, m_response); -} +using namespace KeepassHttpProtocol; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -51,71 +31,14 @@ void RequestHandler::onEnd() Server::Server(QObject *parent) : QObject(parent), - m_httpServer(new QHttpServer(this)), m_started(false) { - connect(m_httpServer, SIGNAL(newRequest(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequest(QHttpRequest*,QHttpResponse*))); + connect(this, SIGNAL(emitRequest(const QByteArray, QByteArray*)), + this, SLOT(handleRequest(const QByteArray, QByteArray*))); + connect(this, SIGNAL(emitOpenDatabase(bool*)), + this, SLOT(handleOpenDatabase(bool*))); } -void Server::start() -{ - if (m_started) - return; - - static const int PORT = 19455; - m_started = m_httpServer->listen(QHostAddress::LocalHost, PORT) - || m_httpServer->listen(QHostAddress::LocalHostIPv6, PORT); -} - -void Server::stop() -{ - if (!m_started) - return; - m_httpServer->close(); - m_started = false; -} - -void Server::handleRequest(QHttpRequest *request, QHttpResponse *response) -{ - RequestHandler * h = new RequestHandler(request, response); - connect(h, SIGNAL(requestComplete(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequestComplete(QHttpRequest*,QHttpResponse*))); -} - -void Server::handleRequestComplete(QHttpRequest *request, QHttpResponse *response) -{ - Request r; - if (!isDatabaseOpened() && !openDatabase()) { - response->writeHead(QHttpResponse::STATUS_SERVICE_UNAVAILABLE); - response->end(); - } else if (request->header("content-type").compare("application/json", Qt::CaseInsensitive) == 0 && - r.fromJson(request->body())) { - - QByteArray hash = QCryptographicHash::hash((getDatabaseRootUuid() + getDatabaseRecycleBinUuid()).toUtf8(), - QCryptographicHash::Sha1).toHex(); - - Response protocolResp(r, QString::fromAscii(hash)); - switch(r.requestType()) { - case INVALID: break; - case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break; - case ASSOCIATE: associate(r, &protocolResp); break; - case GET_LOGINS: getLogins(r, &protocolResp); break; - case GET_LOGINS_COUNT: getLoginsCount(r, &protocolResp); break; - case GET_ALL_LOGINS: getAllLogins(r, &protocolResp); break; - case SET_LOGIN: setLogin(r, &protocolResp); break; - case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break; - } - - QByteArray s = protocolResp.toJson().toUtf8(); - response->setHeader("Content-Type", "application/json"); - response->setHeader("Content-Length", QString::number(s.size())); - response->writeHead(QHttpResponse::STATUS_OK); - response->write(s); - response->end(); - } else { - response->writeHead(QHttpResponse::STATUS_BAD_REQUEST); - response->end(); - } -} void Server::testAssociate(const Request& r, Response * protocolResp) { @@ -202,6 +125,39 @@ void Server::setLogin(const Request &r, Response *protocolResp) protocolResp->setVerifier(key); } + +int Server::send_response(struct MHD_Connection *connection, const char *page) +{ + int ret; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer( + strlen(page), static_cast(const_cast(page)), + MHD_RESPMEM_PERSISTENT); + if (!response) return MHD_NO; + + MHD_add_response_header (response, "Content-Type", "application/json"); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + + return ret; +} + + +int Server::send_unavailable(struct MHD_Connection *connection) +{ + int ret; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT); + if (!response) return MHD_NO; + + ret = MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE, response); + MHD_destroy_response (response); + + return ret; +} + void Server::generatePassword(const Request &r, Response *protocolResp) { QString key = getKey(r.id()); @@ -217,3 +173,153 @@ void Server::generatePassword(const Request &r, Response *protocolResp) memset(password.data(), 0, password.length()); } + + +int Server::request_handler_wrapper(void *me, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, size_t *upload_data_size, void **con_cls) +{ + Server *myself = static_cast(me); + + if (myself) + return myself->request_handler(connection, url, method, version, + upload_data, upload_data_size, con_cls); + else + return MHD_NO; +} + + +void Server::handleRequest(const QByteArray in, QByteArray *out) +{ + *out = QByteArray(); + + Request r; + if (!r.fromJson(in)) + return; + + QByteArray hash = QCryptographicHash::hash( + (getDatabaseRootUuid() + getDatabaseRecycleBinUuid()).toUtf8(), + QCryptographicHash::Sha1).toHex(); + + Response protocolResp(r, QString::fromAscii(hash)); + switch(r.requestType()) { + case INVALID: break; + case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break; + case ASSOCIATE: associate(r, &protocolResp); break; + case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break; + case GET_LOGINS: getLogins(r, &protocolResp); break; + case GET_LOGINS_COUNT: getLoginsCount(r, &protocolResp); break; + case GET_ALL_LOGINS: getAllLogins(r, &protocolResp); break; + case SET_LOGIN: setLogin(r, &protocolResp); break; + } + + *out = protocolResp.toJson().toUtf8(); + Q_EMIT donewrk(); +} + + +void Server::handleOpenDatabase(bool *success) +{ + *success = openDatabase(); + Q_EMIT donewrk(); +} + + +int Server::request_handler(struct MHD_Connection *connection, + const char *, const char *method, const char *, + const char *upload_data, size_t *upload_data_size, void **con_cls) +{ + struct Server::connection_info_struct *con_info = + static_cast(*con_cls); + + if (!isDatabaseOpened()) { + bool success; + QEventLoop loop1; + loop1.connect(this, SIGNAL(donewrk()), SLOT(quit())); + Q_EMIT emitOpenDatabase(&success); + loop1.exec(); + + if (!success) + return send_unavailable(connection); + } + + if (con_info == NULL) { + *con_cls = calloc(1, sizeof(*con_info)); + return MHD_YES; + } + + if (strcmp (method, MHD_HTTP_METHOD_POST) != 0) + return MHD_NO; + + if (*upload_data_size == 0) { + if (con_info && con_info->response) + return send_response(connection, con_info->response); + else + return MHD_NO; + } + + QString type = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, "Content-Type"); + if (!type.contains("application/json", Qt::CaseInsensitive)) + return MHD_NO; + + // Now process the POST request + + QByteArray post = QByteArray(upload_data, *upload_data_size); + + QByteArray s; + QEventLoop loop; + loop.connect(this, SIGNAL(donewrk()), SLOT(quit())); + Q_EMIT emitRequest(post, &s); + loop.exec(); + + if (s.size() == 0) + return MHD_NO; + + con_info->response = static_cast(calloc(1, s.size()+1)); + memcpy(con_info->response, s.data(), s.size()); + + *upload_data_size = 0; + + return MHD_YES; +} + + +void Server::request_completed(void *, struct MHD_Connection *, + void **con_cls, enum MHD_RequestTerminationCode) +{ + struct Server::connection_info_struct *con_info = + static_cast(*con_cls); + + if (con_info == NULL) + return; + + if (con_info->response) free(con_info->response); + free(con_info); + *con_cls = NULL; +} + + +void Server::start(void) +{ + if (m_started) return; + + static const int PORT = 19455; + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &this->request_handler_wrapper, this, + MHD_OPTION_NOTIFY_COMPLETED, + this->request_completed, NULL, + MHD_OPTION_END); + m_started = true; +} + + +void Server::stop(void) +{ + if (!m_started) + return; + + MHD_stop_daemon(daemon); + m_started = false; +} diff --git a/src/http/Server.h b/src/http/Server.h index 563ecae07..c093556a5 100644 --- a/src/http/Server.h +++ b/src/http/Server.h @@ -16,10 +16,8 @@ #include #include +#include -class QHttpServer; -class QHttpRequest; -class QHttpResponse; namespace KeepassHttpProtocol { @@ -27,24 +25,6 @@ class Request; class Response; class Entry; -class RequestHandler: public QObject { - Q_OBJECT - -public: - RequestHandler(QHttpRequest *request, QHttpResponse *response); - ~RequestHandler(); - -private Q_SLOTS: - void onEnd(); - -Q_SIGNALS: - void requestComplete(QHttpRequest *request, QHttpResponse *response); - -private: - QHttpRequest * m_request; - QHttpResponse *m_response; -}; - class Server : public QObject { Q_OBJECT @@ -70,8 +50,13 @@ public Q_SLOTS: void stop(); private Q_SLOTS: - void handleRequest(QHttpRequest * request, QHttpResponse* response); - void handleRequestComplete(QHttpRequest * request, QHttpResponse* response); + void handleRequest(const QByteArray in, QByteArray *out); + void handleOpenDatabase(bool *success); + +Q_SIGNALS: + void emitRequest(const QByteArray in, QByteArray *out); + void emitOpenDatabase(bool *success); + void donewrk(); private: void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); @@ -82,8 +67,25 @@ private: void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); - QHttpServer * const m_httpServer; + static int request_handler_wrapper(void *me, + struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, size_t *upload_data_size, void **con_cls); + static void request_completed(void *, struct MHD_Connection *, + void **con_cls, enum MHD_RequestTerminationCode); + + int request_handler(struct MHD_Connection *connection, + const char *, const char *method, const char *, + const char *upload_data, size_t *upload_data_size, void **con_cls); + int send_response(struct MHD_Connection *connection, const char *page); + int send_unavailable(struct MHD_Connection *connection); + bool m_started; + struct MHD_Daemon *daemon; + + struct connection_info_struct { + char *response; + }; }; } /*namespace KeepassHttpProtocol*/