1
0
mirror of https://github.com/binary-kitchen/doorlockd synced 2024-10-31 22:47:05 +01:00

Big rewrite of several things

- Data type Token changed from uint64_t to std::string
- Added new class "Request" that describes a JSON TCP request
- Classes may now throw Responses for proper error handling
- Removed JSON parsing from Logic
- proper Error handling everywhere
- Many small fixes
- removed unnecessary includes
- removed using namespace std everywhere
This commit is contained in:
Ralf Ramsauer 2015-10-01 22:09:55 +02:00
parent abf69d70fa
commit 49a5b88f6c
16 changed files with 370 additions and 268 deletions

View File

@ -67,6 +67,8 @@ set(LIBDOORLOCK_SRCS
lib/logger.h lib/logger.h
lib/logic.cpp lib/logic.cpp
lib/logic.h lib/logic.h
lib/request.cpp
lib/request.h
lib/response.cpp lib/response.cpp
lib/response.h lib/response.h
lib/util.cpp lib/util.cpp

View File

@ -71,7 +71,6 @@ static int doorlock_client(const std::string &hostname,
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end; tcp::resolver::iterator end;
boost::system::error_code error = ba::error::host_not_found; boost::system::error_code error = ba::error::host_not_found;
size_t length;
std::vector<char> data; std::vector<char> data;
while (error && endpoint_iterator != end) { while (error && endpoint_iterator != end) {
@ -97,7 +96,8 @@ static int doorlock_client(const std::string &hostname,
throw boost::system::system_error(ec); throw boost::system::system_error(ec);
} }
const auto message = Clientmessage::fromJson(std::string(data.begin(), data.begin()+length)); const auto message = Clientmessage::fromString(
std::string(data.begin(), data.begin()+length));
onDoorlockUpdate(message); onDoorlockUpdate(message);
receiveMessage(); receiveMessage();
@ -107,13 +107,15 @@ static int doorlock_client(const std::string &hostname,
receiveMessage(); receiveMessage();
io_service.run(); io_service.run();
} }
catch(const std::exception &e) catch(const Response &err) {
{ l(err.message, LogLevel::error);
l(LogLevel::error, e.what()); retval = -1;
goto out; }
catch(const std::exception &err) {
l(LogLevel::error, err.what());
retval = -1;
} }
out:
return retval; return retval;
} }

View File

@ -9,24 +9,22 @@
#include "daemon.h" #include "daemon.h"
using namespace std; void daemonize(const std::string &dir,
const std::string &stdinfile,
void daemonize(const string &dir, const std::string &stdoutfile,
const string &stdinfile, const std::string &stderrfile)
const string &stdoutfile,
const string &stderrfile)
{ {
umask(0); umask(0);
rlimit rl; rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{ {
throw runtime_error(strerror(errno)); throw std::runtime_error(strerror(errno));
} }
if (!dir.empty() && chdir(dir.c_str()) < 0) if (!dir.empty() && chdir(dir.c_str()) < 0)
{ {
throw runtime_error(strerror(errno)); throw std::runtime_error(strerror(errno));
} }
if (rl.rlim_max == RLIM_INFINITY) if (rl.rlim_max == RLIM_INFINITY)
@ -47,6 +45,6 @@ void daemonize(const string &dir,
if (fd0 != STDIN_FILENO || fd1 != STDOUT_FILENO || fd2 != STDERR_FILENO) if (fd0 != STDIN_FILENO || fd1 != STDOUT_FILENO || fd2 != STDERR_FILENO)
{ {
throw runtime_error("new standard file descriptors were not opened as expected"); throw std::runtime_error("new standard file descriptors were not opened as expected");
} }
} }

View File

@ -1,9 +1,5 @@
#include <iostream>
#include <string>
#include <cstdlib>
#include <memory>
#include <utility>
#include <csignal> #include <csignal>
#include <iostream>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@ -11,13 +7,13 @@
#include <json/json.h> #include <json/json.h>
#include "../lib/logic.h" #include "../lib/logic.h"
#include "../lib/util.h"
#include "config.h" #include "config.h"
#include "daemon.h" #include "daemon.h"
namespace po = boost::program_options; namespace po = boost::program_options;
using boost::asio::ip::tcp; namespace ba = boost::asio;
using ba::ip::tcp;
// Info about doorlockd version // Info about doorlockd version
const static std::string version = const static std::string version =
@ -31,7 +27,7 @@ const int constexpr SOCKET_BUFFERLENGTH = 2048;
const static Logger &l = Logger::get(); const static Logger &l = Logger::get();
static std::unique_ptr<Logic> logic = nullptr; static std::unique_ptr<Logic> logic = nullptr;
static boost::asio::io_service io_service; static ba::io_service io_service;
static std::mutex mutex; static std::mutex mutex;
static std::condition_variable onClientMessage; static std::condition_variable onClientMessage;
@ -46,13 +42,26 @@ static void signal_handler(int signum)
onClientMessage.notify_all(); onClientMessage.notify_all();
} }
static Response subscribe(tcp::socket &sock)
{
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
while (run) {
std::unique_lock<std::mutex> lock(mutex);
onClientMessage.wait(lock);
if (run) {
sock.write_some(ba::buffer(logic->getClientMessage().toJson()));
}
};
return Response(Response::Code::Success);
}
static void session(tcp::socket &&sock) static void session(tcp::socket &&sock)
{ {
boost::asio::ip::address remoteAddress; ba::ip::address remoteAddress;
unsigned short remotePort = 0; unsigned short remotePort = 0;
Response response;
try { try {
boost::system::error_code error;
std::vector<char> data; std::vector<char> data;
data.resize(SOCKET_BUFFERLENGTH); data.resize(SOCKET_BUFFERLENGTH);
@ -63,79 +72,59 @@ static void session(tcp::socket &&sock)
+ std::to_string(remotePort) + ")", + std::to_string(remotePort) + ")",
LogLevel::notice); LogLevel::notice);
size_t length = sock.read_some(boost::asio::buffer(data), size_t length = sock.read_some(ba::buffer(data));
error);
if (error == boost::asio::error::eof)
return;
else if (error)
throw boost::system::system_error(error);
const std::string request(data.begin(), data.begin()+length); // Get Request
const std::string requestString(data.begin(), data.begin()+length);
l(" Parsing request...", LogLevel::info);
Request request = Request::fromString(requestString);
Json::Reader reader; switch (request.command) {
Json::Value root; case Request::Command::Lock:
Response response; case Request::Command::Unlock:
std::string command; response = logic->request(request);
break;
if (!reader.parse(request, root, false)) case Request::Command::Subscribe:
{ if (remoteAddress.is_loopback() == false) {
response.message = "Request is no valid JSON"; response.code = Response::Code::AccessDenied;
response.code = Response::Code::JsonError; response.message = "Subscriptions are only allowed from localhost";
l(response.message, LogLevel::warning); } else {
goto out; response = subscribe(sock);
}
try {
command = getJsonOrFail<std::string>(root, "command");
}
catch (...)
{
response.code = Response::Code::JsonError;
response.message = "Error parsing JSON";
l(response.message, LogLevel::warning);
goto out;
}
l(" Command: " + command, LogLevel::notice);
if (command == "lock" || command == "unlock") {
response = logic->parseRequest(root);
} else if (command == "subscribe") {
if (remoteAddress.is_loopback() == false) {
response.code = Response::Code::AccessDenied;
response.message = "Subscriptions are only allowed from localhost";
l(response.message, LogLevel::warning);
goto out;
}
sock.write_some(boost::asio::buffer(logic->getClientMessage().toJson()));
while (run) {
std::unique_lock<std::mutex> lock(mutex);
onClientMessage.wait(lock);
if (run) {
sock.write_some(boost::asio::buffer(logic->getClientMessage().toJson()));
} }
}; break;
} else {
response.code = Response::Code::UnknownCommand; case Request::Command::Unknown:
response.message = "Received unknown command " + command; default:
l(response.message, LogLevel::warning); response.code = Response::Code::UnknownCommand;
response.message = "Received unknown command ";
break;
} }
out: throw response;
sock.write_some(boost::asio::buffer(response.toJson()),
error);
if (error == boost::asio::error::eof)
return;
else if (error)
throw boost::system::system_error(error);
} }
catch (const std::exception &e) { catch (const Response &err) {
std::string message = "Exception in session " + remoteAddress.to_string() response = err;
+ ":" + std::to_string(remotePort) + " : " + e.what();
l(message, LogLevel::error);
} }
catch (const std::exception &err) {
response.code = Response::Code::Fail;
response.message = "Exception in session " + remoteAddress.to_string()
+ ":" + std::to_string(remotePort) + " : " + err.what();
}
catch (...) {
response.code = Response::Code::Fail;
response.message = "Unhandled doorlockd error";
}
if (!response) {
l(response.message, LogLevel::warning);
}
if (sock.is_open()) {
boost::system::error_code ec;
sock.write_some(ba::buffer(response.toJson()), ec);
}
l("Closing TCP connection from " + remoteAddress.to_string(), LogLevel::notice); l("Closing TCP connection from " + remoteAddress.to_string(), LogLevel::notice);
} }
@ -143,7 +132,7 @@ static void server(unsigned short port)
{ {
l(LogLevel::info, "Starting TCP Server"); l(LogLevel::info, "Starting TCP Server");
const auto endpoint = tcp::endpoint(boost::asio::ip::address::from_string("127.0.0.1"), port); const auto endpoint = tcp::endpoint(ba::ip::address::from_string("127.0.0.1"), port);
tcp::acceptor a(io_service, endpoint); tcp::acceptor a(io_service, endpoint);
tcp::socket sock(io_service); tcp::socket sock(io_service);

View File

@ -1,7 +1,6 @@
#include <json/json.h>
#include "clientmessage.h" #include "clientmessage.h"
#include "util.h" #include "util.h"
#include "response.h"
const std::string Clientmessage::_tokenKey = "token"; const std::string Clientmessage::_tokenKey = "token";
const std::string Clientmessage::_unlockButtonKey = "unlockButton"; const std::string Clientmessage::_unlockButtonKey = "unlockButton";
@ -40,21 +39,33 @@ const Doormessage& Clientmessage::doormessage() const
return _doormessage; return _doormessage;
} }
Clientmessage Clientmessage::fromJson(const std::string &json) Clientmessage Clientmessage::fromJson(const Json::Value &root)
{ {
Json::Reader reader;
Json::Value root;
std::string token; std::string token;
Doormessage doormessage; Doormessage doormessage;
if (reader.parse(json, root) == false) try {
throw std::runtime_error("Error parsing JSON"); token = getJsonOrFail<std::string>(root, _tokenKey);
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey);
token = getJsonOrFail<std::string>(root, _tokenKey); doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey);
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey); doormessage.isEmergencyUnlock = getJsonOrFail<bool>(root, _emergencyUnlockKey);
doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey); doormessage.isOpen = getJsonOrFail<bool>(root, _isOpenKey);
doormessage.isEmergencyUnlock = getJsonOrFail<bool>(root, _emergencyUnlockKey); }
doormessage.isOpen = getJsonOrFail<bool>(root, _isOpenKey); catch (const std::exception &ex) {
throw Response(Response::Code::JsonError, ex.what());
}
return Clientmessage(token, doormessage); return Clientmessage(token, doormessage);
} }
Clientmessage Clientmessage::fromString(const std::string &string)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(string, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}

View File

@ -2,6 +2,7 @@
#define CLIENTMESSAGE_H #define CLIENTMESSAGE_H
#include <string> #include <string>
#include <json/json.h>
#include "doormessage.h" #include "doormessage.h"
@ -13,7 +14,8 @@ public:
Clientmessage(std::string token, Clientmessage(std::string token,
Doormessage doormessage); Doormessage doormessage);
static Clientmessage fromJson(const std::string &json); static Clientmessage fromJson(const Json::Value &root);
static Clientmessage fromString(const std::string &json);
std::string toJson() const; std::string toJson() const;
const std::string& token() const; const std::string& token() const;

View File

@ -133,7 +133,7 @@ void Door::lock()
_heartbeatCondition.notify_one(); _heartbeatCondition.notify_one();
_heartbeatThread.join(); _heartbeatThread.join();
_logger(LogLevel::info, "Door closed"); _logger(LogLevel::notice , "Door closed");
out: out:
_logger(LogLevel::notice, "Executing Post Lock Script"); _logger(LogLevel::notice, "Executing Post Lock Script");
@ -175,7 +175,7 @@ void Door::unlock()
writeCMD(DOOR_CMD_LOCK); writeCMD(DOOR_CMD_LOCK);
}); });
_logger(LogLevel::info, "Door opened"); _logger(LogLevel::notice, "Door opened");
out: out:
_logger(LogLevel::notice, "Executing Post Unlock Script"); _logger(LogLevel::notice, "Executing Post Unlock Script");

View File

@ -1,11 +1,12 @@
#ifndef DOOR_H #ifndef DOOR_H
#define DOOR_H #define DOOR_H
#include <string> #include <chrono>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable> #include <condition_variable>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp> #include <boost/asio/serial_port.hpp>

View File

@ -1,26 +1,17 @@
#include <chrono> #include <errno.h>
#include <functional>
#include <cstdlib>
#include <json/json.h>
#define LDAP_DEPRECATED 1 #define LDAP_DEPRECATED 1
#include <ldap.h> #include <ldap.h>
#include <errno.h>
#include "util.h"
#include "logic.h" #include "logic.h"
#include "util.h"
using namespace std; Logic::Logic(const std::chrono::seconds tokenTimeout,
const std::string &ldapUri,
Logic::Logic(const chrono::seconds tokenTimeout, const std::string &bindDN,
const string &ldapUri, const std::string &webPrefix,
const string &bindDN, const std::string &serDev,
const string &webPrefix,
const string &serDev,
const unsigned int baudrate, const unsigned int baudrate,
condition_variable &onClientUpdate) : std::condition_variable &onClientUpdate) :
_logger(Logger::get()), _logger(Logger::get()),
_door(serDev, baudrate), _door(serDev, baudrate),
_tokenTimeout(tokenTimeout), _tokenTimeout(tokenTimeout),
@ -36,10 +27,10 @@ Logic::Logic(const chrono::seconds tokenTimeout,
this, this,
std::placeholders::_1)); std::placeholders::_1));
_tokenUpdater = thread([this] () { _tokenUpdater = std::thread([this] () {
while (_run) while (_run)
{ {
unique_lock<mutex> l(_mutex); std::unique_lock<std::mutex> l(_mutex);
_tokenCondition.wait_for(l, _tokenTimeout); _tokenCondition.wait_for(l, _tokenTimeout);
if (_run == false) if (_run == false)
{ {
@ -58,58 +49,60 @@ Logic::~Logic()
_tokenUpdater.join(); _tokenUpdater.join();
} }
Response Logic::parseRequest(const Json::Value &root) Response Logic::processDoor(const DoorCommand &doorCommand)
{ {
unique_lock<mutex> l(_mutex);
_logger(LogLevel::info, "Incoming request...");
Response response; Response response;
string command, user, password, ip, token;
try { switch (doorCommand) {
command = getJsonOrFail<string>(root, "command"); case DoorCommand::Lock:
ip = getJsonOrFail<string>(root, "ip"); response = _lock();
user = getJsonOrFail<string>(root, "user"); break;
password = getJsonOrFail<string>(root, "password"); case DoorCommand::Unlock:
token = getJsonOrFail<string>(root, "token"); response = _unlock();
break;
default:
response.code = Response::Code::UnknownCommand;
response.message = "Unknown DoorCommand";
break;
} }
catch (...)
{ return response;
_logger(LogLevel::warning, "Error parsing JSON"); }
response.code = Response::Code::JsonError;
response.message = "Error parsing JSON"; Response Logic::request(const Request &request)
{
std::unique_lock<std::mutex> l(_mutex);
Response response;
DoorCommand doorCommand;
switch (request.command) {
case Request::Command::Lock:
doorCommand = DoorCommand::Lock;
break;
case Request::Command::Unlock:
doorCommand = DoorCommand::Unlock;
break;
default:
response.code = Response::Code::UnknownCommand;
response.message = "Unknown Command";
goto out;
}
response = _checkToken(request.token);
if (!response) {
goto out; goto out;
} }
_logger(LogLevel::info, " -> Token check successful");
_logger(" User : " + user, LogLevel::notice); response = _checkLDAP(request.user, request.password);
_logger(" IP : " + ip, LogLevel::notice); if (!response) {
_logger(" Token : " + token, LogLevel::notice);
if (_checkToken(token) == false)
{
_logger(LogLevel::error, "User provided invalid token");
response.code = Response::Code::InvalidToken;
response.message = "User provided invalid token";
goto out; goto out;
} }
_logger(LogLevel::info, " -> LDAP check successful");
response = _checkLDAP(user,password); response = processDoor(doorCommand);
if (!response) _logger(LogLevel::info, " -> Door Command successful");
{
_logger(LogLevel::error, "Ldap error");
goto out;
}
if (command == "lock")
{
response = _lock();
} else if (command == "unlock") {
response = _unlock();
} else {
response.code = Response::Code::UnknownCommand;
response.message = "Unknown Command: " + command;
_logger(response.message, LogLevel::error);
}
out: out:
return response; return response;
@ -122,14 +115,15 @@ Response Logic::_lock()
{ {
response.code = Response::Code::AlreadyLocked; response.code = Response::Code::AlreadyLocked;
response.message = "Unable to lock: already closed"; response.message = "Unable to lock: already closed";
_logger(response.message, LogLevel::warning);
} else { } else {
_door.lock();
_createNewToken(false); _createNewToken(false);
response.code = Response::Code::Success; response.code = Response::Code::Success;
response.message = "Success";
} }
_door.lock();
return response; return response;
} }
@ -145,32 +139,38 @@ Response Logic::_unlock()
{ {
response.code = Response::Code::AlreadyUnlocked; response.code = Response::Code::AlreadyUnlocked;
response.message = "Unable to unlock: already unlocked"; response.message = "Unable to unlock: already unlocked";
_logger(response.message, LogLevel::warning); _logger(response.message, LogLevel::info);
} else { } else {
response.code = Response::Code::Success; response.code = Response::Code::Success;
response.message = "Success";
} }
return response; return response;
} }
bool Logic::_checkToken(const string &strToken) Response Logic::_checkToken(std::string token) const
{ {
try { std::transform(token.begin(),
uint64_t token = toUint64(strToken); token.end(),
if (token == _curToken || (_prevValid == true && token == _prevToken)) token.begin(),
{ ::toupper);
_logger(LogLevel::info, "Token check successful");
return true; if (token == _curToken)
} return Response(Response::Code::Success);
}
catch (const char* const &ex) if (_prevValid == true && token == _prevToken)
{ return Response(Response::Code::Success);
_logger(LogLevel::error, "Check Token failed for token \"%s\" (expected %s): %s", strToken.c_str(), toHexString(_curToken).c_str(), ex);
} _logger("Check Token failed: got \"" + token
return false; + "\", expected \"" + _curToken +"\"",
LogLevel::error);
return Response(Response::InvalidToken,
"User provided invalid token");
} }
Response Logic::_checkLDAP(const string &user, const string &password) Response Logic::_checkLDAP(const std::string &user,
const std::string &password)
{ {
constexpr int BUFFERSIZE = 1024; constexpr int BUFFERSIZE = 1024;
char buffer[BUFFERSIZE]; char buffer[BUFFERSIZE];
@ -180,16 +180,15 @@ Response Logic::_checkLDAP(const string &user, const string &password)
LDAP* ld = nullptr; LDAP* ld = nullptr;
unsigned long version = LDAP_VERSION3; unsigned long version = LDAP_VERSION3;
_logger(LogLevel::notice, "Trying to authenticate as user \"%s\"", user.c_str()); _logger(LogLevel::info, " Trying to authenticate as user \"%s\"", user.c_str());
snprintf(buffer, BUFFERSIZE, _bindDN.c_str(), user.c_str()); snprintf(buffer, BUFFERSIZE, _bindDN.c_str(), user.c_str());
rc = ldap_initialize(&ld, _ldapUri.c_str()); rc = ldap_initialize(&ld, _ldapUri.c_str());
if(rc != LDAP_SUCCESS) if(rc != LDAP_SUCCESS)
{ {
retval.message = (string)"LDAP initialize error: " retval.message = (std::string)"LDAP initialize error: "
+ ldap_err2string(rc); + ldap_err2string(rc);
retval.code = Response::Code::LDAPInit; retval.code = Response::Code::LDAPInit;
_logger(retval.message, LogLevel::error);
goto out2; goto out2;
} }
@ -200,7 +199,6 @@ Response Logic::_checkLDAP(const string &user, const string &password)
{ {
retval.code = Response::Code::LDAPInit; retval.code = Response::Code::LDAPInit;
retval.message = "LDAP set version failed"; retval.message = "LDAP set version failed";
_logger(retval.message, LogLevel::error);
goto out; goto out;
} }
@ -210,12 +208,11 @@ Response Logic::_checkLDAP(const string &user, const string &password)
retval = Response::Code::InvalidCredentials; retval = Response::Code::InvalidCredentials;
retval.message = "Credential check for user \"" + user retval.message = "Credential check for user \"" + user
+ "\" failed: " + ldap_err2string(rc); + "\" failed: " + ldap_err2string(rc);
_logger(retval.message, LogLevel::error);
goto out; goto out;
} }
_logger(LogLevel::notice, "user \"%s\" successfully authenticated", user.c_str()); retval.code = Response::Code::Success;
retval = Response::Code::Success; retval.message = "";
out: out:
ldap_unbind(ld); ldap_unbind(ld);
@ -231,11 +228,13 @@ void Logic::_createNewToken(const bool stillValid)
_prevToken = _curToken; _prevToken = _curToken;
_prevValid = stillValid; _prevValid = stillValid;
_curToken = (((uint64_t)rand())<<32) | ((uint64_t)rand()); _curToken = toHexString((((uint64_t)rand())<<32) | ((uint64_t)rand()));
ostringstream message; std::ostringstream message;
message << "New Token generated: " << toHexString(_curToken) << " old Token: " << toHexString(_prevToken) << " is " << (_prevValid?"still":"not") << " valid"; message << "New token: " << _curToken
_logger(message, LogLevel::info); << " old token: " << _prevToken << " is "
<< (_prevValid?"still":"not") << " valid";
_logger(message, LogLevel::notice);
_onClientUpdate.notify_all(); _onClientUpdate.notify_all();
} }
@ -243,7 +242,7 @@ void Logic::_createNewToken(const bool stillValid)
Clientmessage Logic::getClientMessage() Clientmessage Logic::getClientMessage()
{ {
std::lock_guard<std::mutex> l(_mutex); std::lock_guard<std::mutex> l(_mutex);
Clientmessage retval(_webPrefix + toHexString(_curToken), Clientmessage retval(_webPrefix + _curToken,
_doormessage); _doormessage);
// Reset doormessage // Reset doormessage

View File

@ -1,23 +1,23 @@
#ifndef LOGIC_H #ifndef LOGIC_H
#define LOGIC_H #define LOGIC_H
#include <cstdint>
#include <string>
#include <thread>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <string>
#include <thread>
#include "config.h" #include "config.h"
#include "clientmessage.h"
#include "door.h" #include "door.h"
#include "logger.h" #include "logger.h"
#include "request.h"
#include "response.h" #include "response.h"
#include "clientmessage.h"
/* The "Logic" class /* The "Logic" class
* *
* This class is initilized by all settings. * This class is initilized by all settings.
* *
* It parses incoming JSON-Requests and returns the Response Code. * It handles incoming requests and allows modifications of the door
*/ */
class Logic class Logic
{ {
@ -33,7 +33,11 @@ public:
~Logic(); ~Logic();
// Parse incoming JSON Requests // Parse incoming JSON Requests
Response parseRequest(const Json::Value &root); Response request(const Request &request);
// Send direct command to door without credential checks
enum class DoorCommand { Lock, Unlock };
Response processDoor(const DoorCommand &doorCommand);
// Returns the current Token // Returns the current Token
Clientmessage getClientMessage(); Clientmessage getClientMessage();
@ -46,7 +50,7 @@ private:
Response _unlock(); Response _unlock();
// Checks if the incoming token is valid // Checks if the incoming token is valid
bool _checkToken(const std::string &token); Response _checkToken(std::string token) const;
// Checks if incoming credentials against LDAP // Checks if incoming credentials against LDAP
Response _checkLDAP(const std::string &user, Response _checkLDAP(const std::string &user,
@ -63,13 +67,10 @@ private:
// The door // The door
Door _door; Door _door;
// Tokens are 64-bit hexadecimal values
using Token = uint64_t;
// The current token // The current token
Token _curToken = { 0x0000000000000000 }; std::string _curToken = { "0000000000000000" };
// The previous token // The previous token
Token _prevToken = { 0x0000000000000000 }; std::string _prevToken = { "0000000000000000" };
// Indicates whether the previous token is valid // Indicates whether the previous token is valid
bool _prevValid = { false }; bool _prevValid = { false };

69
doorlockd/lib/request.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "request.h"
#include "util.h"
#include "logger.h"
const std::string Request::_commandKey = "command";
Request::Command Request::_commandFromString(const std::string &command)
{
Command retval = Command::Unknown;
if (command == "lock")
retval = Command::Lock;
else if (command == "unlock")
retval = Command::Unlock;
else if (command == "subscribe")
retval = Command::Subscribe;
else
retval = Command::Unknown;
return retval;
}
Request Request::fromJson(const Json::Value &root)
{
Request retval;
const auto &l = Logger::get();
try {
const auto commandStr =
getJsonOrFail<std::string>(root, _commandKey);
l(" command: " + commandStr, LogLevel::info);
retval.command = _commandFromString(commandStr);
// Stop parsing, if command is unknown
if (retval.command == Command::Unknown)
return retval;
if (retval.command == Command::Lock ||
retval.command == Command::Unlock) {
retval.user = getJsonOrFail<std::string>(root, "user");
l(" user: " + retval.user, LogLevel::info);
retval.password = getJsonOrFail<std::string>(root, "password");
l(" password: XXX", LogLevel::info);
retval.token = getJsonOrFail<std::string>(root, "token");
l(" token: " + retval.token, LogLevel::info);
}
if (retval.command == Command::Subscribe) {
// Nothing to do in this case
}
}
catch (const std::exception &ex) {
throw Response(Response::Code::JsonError, ex.what());
}
return retval;
}
Request Request::fromString(const std::string &string)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(string, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}

29
doorlockd/lib/request.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef REQUEST_H
#define REQUEST_H
#include <string>
#include <json/json.h>
#include "response.h"
class Request
{
public:
static Request fromJson(const Json::Value &root);
static Request fromString(const std::string &string);
enum class Command { Lock, Unlock, Subscribe, Unknown }
command = { Command::Unknown };
std::string user = { };
std::string password = { };
std::string token = { };
private:
static Command _commandFromString(const std::string &command);
const static std::string _commandKey;
};
#endif // REQUEST_H

View File

@ -8,6 +8,18 @@
const std::string Response::_codeKey = "code"; const std::string Response::_codeKey = "code";
const std::string Response::_messageKey = "message"; const std::string Response::_messageKey = "message";
Response::Response():
code(Fail),
message("General failure")
{
}
Response::Response(Response::Code code, const std::string &what) :
code(code),
message(what)
{
}
Response::operator bool() const Response::operator bool() const
{ {
return code == Response::Code::Success; return code == Response::Code::Success;
@ -24,21 +36,34 @@ std::string Response::toJson() const
return writer.write(response); return writer.write(response);
} }
Response Response::fromJson(const std::string &json) Response Response::fromJson(const Json::Value &root)
{ {
Json::Reader reader;
Json::Value root;
Response retval; Response retval;
if (reader.parse(json, root) == false) try {
throw std::runtime_error("Error parsing JSON"); retval.message = getJsonOrFail<std::string>(root, _messageKey);
retval.message = getJsonOrFail<std::string>(root, _messageKey); const auto code = getJsonOrFail<int>(root, _codeKey);
if (code > Code::RESPONSE_NUM_ITEMS)
throw std::runtime_error("Error code out of range");
const auto code = getJsonOrFail<int>(root, _codeKey); retval.code = static_cast<Code>(code);
if (code > Code::RESPONSE_NUM_ITEMS) }
throw std::runtime_error("Error code out of range"); catch (const std::exception &ex) {
retval.code = static_cast<Code>(code); throw Response(Response::Code::JsonError, ex.what());
}
return retval; return retval;
} }
Response Response::fromString(const std::string &json)
{
Json::Reader reader;
Json::Value root;
if (reader.parse(json, root) == false)
throw Response(Response::Code::NotJson,
"No valid JSON");
return fromJson(root);
}

View File

@ -3,8 +3,11 @@
#include <string> #include <string>
struct Response #include <json/json.h>
class Response
{ {
public:
enum Code { enum Code {
Success = 0, // Request successful Success = 0, // Request successful
Fail, // General non-specified error Fail, // General non-specified error
@ -22,19 +25,11 @@ struct Response
} code; } code;
std::string message; std::string message;
Response() : Response();
code(Fail), Response(Response::Code code, const std::string &message = "");
message("General failure")
{
}
Response(Response::Code code, const std::string &message = "") : static Response fromJson(const Json::Value &root);
code(code), static Response fromString(const std::string &json);
message(message)
{
}
static Response fromJson(const std::string &json);
std::string toJson() const; std::string toJson() const;
// Returns true if code is success // Returns true if code is success

View File

@ -1,9 +1,7 @@
#include "util.h" #include "util.h"
using namespace std;
template <> template <>
int getJson(const Json::Value &root, const string &key) int getJson(const Json::Value &root, const std::string &key)
{ {
auto val = root.get(key, Json::Value()); auto val = root.get(key, Json::Value());
if (val.isInt()) if (val.isInt())
@ -12,7 +10,7 @@ int getJson(const Json::Value &root, const string &key)
} }
template <> template <>
string getJson(const Json::Value &root, const string &key) std::string getJson(const Json::Value &root, const std::string &key)
{ {
auto val = root.get(key, Json::Value()); auto val = root.get(key, Json::Value());
if (val.isString()) if (val.isString())
@ -21,7 +19,7 @@ string getJson(const Json::Value &root, const string &key)
} }
template <> template <>
size_t getJson(const Json::Value &root, const string &key) size_t getJson(const Json::Value &root, const std::string &key)
{ {
auto val = root.get(key, Json::Value()); auto val = root.get(key, Json::Value());
if (val.isInt()) if (val.isInt())
@ -30,7 +28,7 @@ size_t getJson(const Json::Value &root, const string &key)
} }
template <> template <>
bool getJson(const Json::Value &root, const string &key) bool getJson(const Json::Value &root, const std::string &key)
{ {
auto val = root.get(key, Json::Value()); auto val = root.get(key, Json::Value());
if (val.isBool()) if (val.isBool())
@ -39,7 +37,7 @@ bool getJson(const Json::Value &root, const string &key)
} }
template <> template <>
Json::Value getJson(const Json::Value &root, const string &key) Json::Value getJson(const Json::Value &root, const std::string &key)
{ {
auto val = root.get(key, Json::Value()); auto val = root.get(key, Json::Value());
return val; return val;
@ -57,9 +55,9 @@ static char nibble2hex(unsigned char input)
return input - 0xA + 'A'; return input - 0xA + 'A';
} }
string toHexString(const uint64_t c) std::string toHexString(const uint64_t c)
{ {
string retval; std::string retval;
retval = nibble2hex((c>>60) & 0xF); retval = nibble2hex((c>>60) & 0xF);
retval += nibble2hex((c>>56) & 0xF); retval += nibble2hex((c>>56) & 0xF);
@ -92,21 +90,3 @@ unsigned char hex2uchar(const char input)
} }
throw std::runtime_error("Malformed hexadecimal input"); throw std::runtime_error("Malformed hexadecimal input");
} }
uint64_t toUint64(const string &s)
{
if (s.length() != (64/4))
{
throw std::runtime_error("Hex string has invalid length");
}
uint64_t retval = 0;
for (int i = 0 ; i < (64/4) ; i++)
{
retval <<= 4;
retval |= hex2uchar(s.at(i))&0xf;
}
return retval;
}

View File

@ -14,13 +14,12 @@ T getJsonOrFail(const Json::Value &root, const std::string &key)
const auto members = root.getMemberNames(); const auto members = root.getMemberNames();
if (std::find(members.begin(), members.end(), key) == members.end()) if (std::find(members.begin(), members.end(), key) == members.end())
{ {
throw std::runtime_error("Json key not existing"); throw std::runtime_error("Json key \"" + key + "\" not existing");
} }
return getJson<T>(root, key); return getJson<T>(root, key);
} }
std::string toHexString(uint64_t c); std::string toHexString(uint64_t c);
uint64_t toUint64(const std::string &s);
#endif #endif