/** *************************************************************************** * @file Server.cpp * * @brief * * Copyright (C) 2013 * * @author Francois Ferrand * @date 4/2013 *************************************************************************** */ #include "Server.h" #include #include "Protocol.h" #include "HttpSettings.h" #include "crypto/Crypto.h" #include #include #include #include using namespace KeepassHttpProtocol; using namespace KeepassHttpProtocol; //////////////////////////////////////////////////////////////////////////////////////////////////// /// Request //////////////////////////////////////////////////////////////////////////////////////////////////// Server::Server(QObject *parent) : QObject(parent), m_started(false) { connect(this, SIGNAL(emitRequest(const QByteArray, QByteArray*)), this, SLOT(handleRequest(const QByteArray, QByteArray*))); connect(this, SIGNAL(emitOpenDatabase(bool*)), this, SLOT(handleOpenDatabase(bool*))); } void Server::testAssociate(const Request& r, Response * protocolResp) { if (r.id().isEmpty()) return; //ping QString key = getKey(r.id()); if (key.isEmpty() || !r.CheckVerifier(key)) return; protocolResp->setSuccess(); protocolResp->setId(r.id()); protocolResp->setVerifier(key); } void Server::associate(const Request& r, Response * protocolResp) { if (!r.CheckVerifier(r.key())) return; QString id = storeKey(r.key()); if (id.isEmpty()) return; protocolResp->setSuccess(); protocolResp->setId(id); protocolResp->setVerifier(r.key()); } void Server::getLogins(const Request &r, Response *protocolResp) { QString key = getKey(r.id()); if (!r.CheckVerifier(key)) return; protocolResp->setId(r.id()); protocolResp->setVerifier(key); QList entries = findMatchingEntries(r.id(), r.url(), r.submitUrl(), r.realm()); //TODO: filtering, request confirmation [in db adaptation layer?] if (entries.count() == 0) { protocolResp->setError("url not found!"); } else { if (r.sortSelection()) { //TODO: sorting (in db adaptation layer? here?) } protocolResp->setSuccess(); protocolResp->setEntries(entries); } } void Server::getLoginsCount(const Request &r, Response *protocolResp) { QString key = getKey(r.id()); if (!r.CheckVerifier(key)) return; protocolResp->setSuccess(); protocolResp->setId(r.id()); protocolResp->setVerifier(key); protocolResp->setCount(countMatchingEntries(r.id(), r.url(), r.submitUrl(), r.realm())); } void Server::getAllLogins(const Request &r, Response *protocolResp) { QString key = getKey(r.id()); if (!r.CheckVerifier(key)) return; protocolResp->setSuccess(); protocolResp->setId(r.id()); protocolResp->setVerifier(key); protocolResp->setEntries(searchAllEntries(r.id())); //TODO: ensure there is no password --> change API? } void Server::setLogin(const Request &r, Response *protocolResp) { QString key = getKey(r.id()); if (!r.CheckVerifier(key)) return; QString uuid = r.uuid(); if (uuid.isEmpty()) addEntry(r.id(), r.login(), r.password(), r.url(), r.submitUrl(), r.realm()); else updateEntry(r.id(), r.uuid(), r.login(), r.password(), r.url()); protocolResp->setSuccess(); protocolResp->setId(r.id()); 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()); if (!r.CheckVerifier(key)) return; QString password = generatePassword(); QString bits = QString::number(HttpSettings::getbits()); protocolResp->setSuccess(); protocolResp->setId(r.id()); protocolResp->setVerifier(key); protocolResp->setEntries(QList() << Entry("generate-password", bits, password, "generate-password")); 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 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 ASSOCIATE: associate(r, &protocolResp); break; case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break; case GENERATE_PASSWORD: generatePassword(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; }