mirror of
https://github.com/binary-kitchen/doorlockd
synced 2024-12-22 02:14:26 +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:
parent
abf69d70fa
commit
49a5b88f6c
@ -67,6 +67,8 @@ set(LIBDOORLOCK_SRCS
|
||||
lib/logger.h
|
||||
lib/logic.cpp
|
||||
lib/logic.h
|
||||
lib/request.cpp
|
||||
lib/request.h
|
||||
lib/response.cpp
|
||||
lib/response.h
|
||||
lib/util.cpp
|
||||
|
@ -71,7 +71,6 @@ static int doorlock_client(const std::string &hostname,
|
||||
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
|
||||
tcp::resolver::iterator end;
|
||||
boost::system::error_code error = ba::error::host_not_found;
|
||||
size_t length;
|
||||
std::vector<char> data;
|
||||
|
||||
while (error && endpoint_iterator != end) {
|
||||
@ -97,7 +96,8 @@ static int doorlock_client(const std::string &hostname,
|
||||
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);
|
||||
|
||||
receiveMessage();
|
||||
@ -107,13 +107,15 @@ static int doorlock_client(const std::string &hostname,
|
||||
receiveMessage();
|
||||
io_service.run();
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
l(LogLevel::error, e.what());
|
||||
goto out;
|
||||
catch(const Response &err) {
|
||||
l(err.message, LogLevel::error);
|
||||
retval = -1;
|
||||
}
|
||||
catch(const std::exception &err) {
|
||||
l(LogLevel::error, err.what());
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -9,24 +9,22 @@
|
||||
|
||||
#include "daemon.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void daemonize(const string &dir,
|
||||
const string &stdinfile,
|
||||
const string &stdoutfile,
|
||||
const string &stderrfile)
|
||||
void daemonize(const std::string &dir,
|
||||
const std::string &stdinfile,
|
||||
const std::string &stdoutfile,
|
||||
const std::string &stderrfile)
|
||||
{
|
||||
umask(0);
|
||||
|
||||
rlimit rl;
|
||||
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)
|
||||
{
|
||||
throw runtime_error(strerror(errno));
|
||||
throw std::runtime_error(strerror(errno));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,5 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
@ -11,13 +7,13 @@
|
||||
#include <json/json.h>
|
||||
|
||||
#include "../lib/logic.h"
|
||||
#include "../lib/util.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "daemon.h"
|
||||
|
||||
namespace po = boost::program_options;
|
||||
using boost::asio::ip::tcp;
|
||||
namespace ba = boost::asio;
|
||||
using ba::ip::tcp;
|
||||
|
||||
// Info about doorlockd version
|
||||
const static std::string version =
|
||||
@ -31,7 +27,7 @@ const int constexpr SOCKET_BUFFERLENGTH = 2048;
|
||||
const static Logger &l = Logger::get();
|
||||
|
||||
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::condition_variable onClientMessage;
|
||||
@ -46,13 +42,26 @@ static void signal_handler(int signum)
|
||||
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)
|
||||
{
|
||||
boost::asio::ip::address remoteAddress;
|
||||
ba::ip::address remoteAddress;
|
||||
unsigned short remotePort = 0;
|
||||
Response response;
|
||||
|
||||
try {
|
||||
boost::system::error_code error;
|
||||
std::vector<char> data;
|
||||
data.resize(SOCKET_BUFFERLENGTH);
|
||||
|
||||
@ -63,79 +72,59 @@ static void session(tcp::socket &&sock)
|
||||
+ std::to_string(remotePort) + ")",
|
||||
LogLevel::notice);
|
||||
|
||||
size_t length = sock.read_some(boost::asio::buffer(data),
|
||||
error);
|
||||
if (error == boost::asio::error::eof)
|
||||
return;
|
||||
else if (error)
|
||||
throw boost::system::system_error(error);
|
||||
size_t length = sock.read_some(ba::buffer(data));
|
||||
|
||||
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;
|
||||
Json::Value root;
|
||||
Response response;
|
||||
std::string command;
|
||||
switch (request.command) {
|
||||
case Request::Command::Lock:
|
||||
case Request::Command::Unlock:
|
||||
response = logic->request(request);
|
||||
break;
|
||||
|
||||
if (!reader.parse(request, root, false))
|
||||
{
|
||||
response.message = "Request is no valid JSON";
|
||||
response.code = Response::Code::JsonError;
|
||||
l(response.message, LogLevel::warning);
|
||||
goto out;
|
||||
}
|
||||
|
||||
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()));
|
||||
case Request::Command::Subscribe:
|
||||
if (remoteAddress.is_loopback() == false) {
|
||||
response.code = Response::Code::AccessDenied;
|
||||
response.message = "Subscriptions are only allowed from localhost";
|
||||
} else {
|
||||
response = subscribe(sock);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Received unknown command " + command;
|
||||
l(response.message, LogLevel::warning);
|
||||
break;
|
||||
|
||||
case Request::Command::Unknown:
|
||||
default:
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Received unknown command ";
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
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);
|
||||
throw response;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
std::string message = "Exception in session " + remoteAddress.to_string()
|
||||
+ ":" + std::to_string(remotePort) + " : " + e.what();
|
||||
l(message, LogLevel::error);
|
||||
catch (const Response &err) {
|
||||
response = err;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@ -143,7 +132,7 @@ static void server(unsigned short port)
|
||||
{
|
||||
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::socket sock(io_service);
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include <json/json.h>
|
||||
|
||||
#include "clientmessage.h"
|
||||
#include "util.h"
|
||||
#include "response.h"
|
||||
|
||||
const std::string Clientmessage::_tokenKey = "token";
|
||||
const std::string Clientmessage::_unlockButtonKey = "unlockButton";
|
||||
@ -40,21 +39,33 @@ const Doormessage& Clientmessage::doormessage() const
|
||||
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;
|
||||
Doormessage doormessage;
|
||||
|
||||
if (reader.parse(json, root) == false)
|
||||
throw std::runtime_error("Error parsing JSON");
|
||||
|
||||
token = getJsonOrFail<std::string>(root, _tokenKey);
|
||||
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey);
|
||||
doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey);
|
||||
doormessage.isEmergencyUnlock = getJsonOrFail<bool>(root, _emergencyUnlockKey);
|
||||
doormessage.isOpen = getJsonOrFail<bool>(root, _isOpenKey);
|
||||
try {
|
||||
token = getJsonOrFail<std::string>(root, _tokenKey);
|
||||
doormessage.isLockButton = getJsonOrFail<bool>(root, _lockButtonKey);
|
||||
doormessage.isUnlockButton = getJsonOrFail<bool>(root, _unlockButtonKey);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define CLIENTMESSAGE_H
|
||||
|
||||
#include <string>
|
||||
#include <json/json.h>
|
||||
|
||||
#include "doormessage.h"
|
||||
|
||||
@ -13,7 +14,8 @@ public:
|
||||
Clientmessage(std::string token,
|
||||
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;
|
||||
|
||||
const std::string& token() const;
|
||||
|
@ -133,7 +133,7 @@ void Door::lock()
|
||||
_heartbeatCondition.notify_one();
|
||||
_heartbeatThread.join();
|
||||
|
||||
_logger(LogLevel::info, "Door closed");
|
||||
_logger(LogLevel::notice , "Door closed");
|
||||
|
||||
out:
|
||||
_logger(LogLevel::notice, "Executing Post Lock Script");
|
||||
@ -175,7 +175,7 @@ void Door::unlock()
|
||||
writeCMD(DOOR_CMD_LOCK);
|
||||
});
|
||||
|
||||
_logger(LogLevel::info, "Door opened");
|
||||
_logger(LogLevel::notice, "Door opened");
|
||||
|
||||
out:
|
||||
_logger(LogLevel::notice, "Executing Post Unlock Script");
|
||||
|
@ -1,11 +1,12 @@
|
||||
#ifndef DOOR_H
|
||||
#define DOOR_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/serial_port.hpp>
|
||||
|
@ -1,26 +1,17 @@
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <errno.h>
|
||||
#define LDAP_DEPRECATED 1
|
||||
#include <ldap.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "logic.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Logic::Logic(const chrono::seconds tokenTimeout,
|
||||
const string &ldapUri,
|
||||
const string &bindDN,
|
||||
const string &webPrefix,
|
||||
const string &serDev,
|
||||
Logic::Logic(const std::chrono::seconds tokenTimeout,
|
||||
const std::string &ldapUri,
|
||||
const std::string &bindDN,
|
||||
const std::string &webPrefix,
|
||||
const std::string &serDev,
|
||||
const unsigned int baudrate,
|
||||
condition_variable &onClientUpdate) :
|
||||
std::condition_variable &onClientUpdate) :
|
||||
_logger(Logger::get()),
|
||||
_door(serDev, baudrate),
|
||||
_tokenTimeout(tokenTimeout),
|
||||
@ -36,10 +27,10 @@ Logic::Logic(const chrono::seconds tokenTimeout,
|
||||
this,
|
||||
std::placeholders::_1));
|
||||
|
||||
_tokenUpdater = thread([this] () {
|
||||
_tokenUpdater = std::thread([this] () {
|
||||
while (_run)
|
||||
{
|
||||
unique_lock<mutex> l(_mutex);
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
_tokenCondition.wait_for(l, _tokenTimeout);
|
||||
if (_run == false)
|
||||
{
|
||||
@ -58,58 +49,60 @@ Logic::~Logic()
|
||||
_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;
|
||||
string command, user, password, ip, token;
|
||||
|
||||
try {
|
||||
command = getJsonOrFail<string>(root, "command");
|
||||
ip = getJsonOrFail<string>(root, "ip");
|
||||
user = getJsonOrFail<string>(root, "user");
|
||||
password = getJsonOrFail<string>(root, "password");
|
||||
token = getJsonOrFail<string>(root, "token");
|
||||
switch (doorCommand) {
|
||||
case DoorCommand::Lock:
|
||||
response = _lock();
|
||||
break;
|
||||
case DoorCommand::Unlock:
|
||||
response = _unlock();
|
||||
break;
|
||||
default:
|
||||
response.code = Response::Code::UnknownCommand;
|
||||
response.message = "Unknown DoorCommand";
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_logger(LogLevel::warning, "Error parsing JSON");
|
||||
response.code = Response::Code::JsonError;
|
||||
response.message = "Error parsing JSON";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
_logger(LogLevel::info, " -> Token check successful");
|
||||
|
||||
_logger(" User : " + user, LogLevel::notice);
|
||||
_logger(" IP : " + ip, LogLevel::notice);
|
||||
_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";
|
||||
response = _checkLDAP(request.user, request.password);
|
||||
if (!response) {
|
||||
goto out;
|
||||
}
|
||||
_logger(LogLevel::info, " -> LDAP check successful");
|
||||
|
||||
response = _checkLDAP(user,password);
|
||||
if (!response)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
response = processDoor(doorCommand);
|
||||
_logger(LogLevel::info, " -> Door Command successful");
|
||||
|
||||
out:
|
||||
return response;
|
||||
@ -122,14 +115,15 @@ Response Logic::_lock()
|
||||
{
|
||||
response.code = Response::Code::AlreadyLocked;
|
||||
response.message = "Unable to lock: already closed";
|
||||
_logger(response.message, LogLevel::warning);
|
||||
} else {
|
||||
_door.lock();
|
||||
_createNewToken(false);
|
||||
|
||||
response.code = Response::Code::Success;
|
||||
response.message = "Success";
|
||||
}
|
||||
|
||||
_door.lock();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -145,32 +139,38 @@ Response Logic::_unlock()
|
||||
{
|
||||
response.code = Response::Code::AlreadyUnlocked;
|
||||
response.message = "Unable to unlock: already unlocked";
|
||||
_logger(response.message, LogLevel::warning);
|
||||
_logger(response.message, LogLevel::info);
|
||||
} else {
|
||||
response.code = Response::Code::Success;
|
||||
response.message = "Success";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool Logic::_checkToken(const string &strToken)
|
||||
Response Logic::_checkToken(std::string token) const
|
||||
{
|
||||
try {
|
||||
uint64_t token = toUint64(strToken);
|
||||
if (token == _curToken || (_prevValid == true && token == _prevToken))
|
||||
{
|
||||
_logger(LogLevel::info, "Token check successful");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const char* const &ex)
|
||||
{
|
||||
_logger(LogLevel::error, "Check Token failed for token \"%s\" (expected %s): %s", strToken.c_str(), toHexString(_curToken).c_str(), ex);
|
||||
}
|
||||
return false;
|
||||
std::transform(token.begin(),
|
||||
token.end(),
|
||||
token.begin(),
|
||||
::toupper);
|
||||
|
||||
if (token == _curToken)
|
||||
return Response(Response::Code::Success);
|
||||
|
||||
if (_prevValid == true && token == _prevToken)
|
||||
return Response(Response::Code::Success);
|
||||
|
||||
_logger("Check Token failed: got \"" + token
|
||||
+ "\", 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;
|
||||
char buffer[BUFFERSIZE];
|
||||
@ -180,16 +180,15 @@ Response Logic::_checkLDAP(const string &user, const string &password)
|
||||
LDAP* ld = nullptr;
|
||||
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());
|
||||
|
||||
rc = ldap_initialize(&ld, _ldapUri.c_str());
|
||||
if(rc != LDAP_SUCCESS)
|
||||
{
|
||||
retval.message = (string)"LDAP initialize error: "
|
||||
+ ldap_err2string(rc);
|
||||
retval.message = (std::string)"LDAP initialize error: "
|
||||
+ ldap_err2string(rc);
|
||||
retval.code = Response::Code::LDAPInit;
|
||||
_logger(retval.message, LogLevel::error);
|
||||
goto out2;
|
||||
}
|
||||
|
||||
@ -200,7 +199,6 @@ Response Logic::_checkLDAP(const string &user, const string &password)
|
||||
{
|
||||
retval.code = Response::Code::LDAPInit;
|
||||
retval.message = "LDAP set version failed";
|
||||
_logger(retval.message, LogLevel::error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -210,12 +208,11 @@ Response Logic::_checkLDAP(const string &user, const string &password)
|
||||
retval = Response::Code::InvalidCredentials;
|
||||
retval.message = "Credential check for user \"" + user
|
||||
+ "\" failed: " + ldap_err2string(rc);
|
||||
_logger(retval.message, LogLevel::error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
_logger(LogLevel::notice, "user \"%s\" successfully authenticated", user.c_str());
|
||||
retval = Response::Code::Success;
|
||||
retval.code = Response::Code::Success;
|
||||
retval.message = "";
|
||||
|
||||
out:
|
||||
ldap_unbind(ld);
|
||||
@ -231,11 +228,13 @@ void Logic::_createNewToken(const bool stillValid)
|
||||
_prevToken = _curToken;
|
||||
_prevValid = stillValid;
|
||||
|
||||
_curToken = (((uint64_t)rand())<<32) | ((uint64_t)rand());
|
||||
_curToken = toHexString((((uint64_t)rand())<<32) | ((uint64_t)rand()));
|
||||
|
||||
ostringstream message;
|
||||
message << "New Token generated: " << toHexString(_curToken) << " old Token: " << toHexString(_prevToken) << " is " << (_prevValid?"still":"not") << " valid";
|
||||
_logger(message, LogLevel::info);
|
||||
std::ostringstream message;
|
||||
message << "New token: " << _curToken
|
||||
<< " old token: " << _prevToken << " is "
|
||||
<< (_prevValid?"still":"not") << " valid";
|
||||
_logger(message, LogLevel::notice);
|
||||
|
||||
_onClientUpdate.notify_all();
|
||||
}
|
||||
@ -243,7 +242,7 @@ void Logic::_createNewToken(const bool stillValid)
|
||||
Clientmessage Logic::getClientMessage()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
Clientmessage retval(_webPrefix + toHexString(_curToken),
|
||||
Clientmessage retval(_webPrefix + _curToken,
|
||||
_doormessage);
|
||||
|
||||
// Reset doormessage
|
||||
|
@ -1,23 +1,23 @@
|
||||
#ifndef LOGIC_H
|
||||
#define LOGIC_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "config.h"
|
||||
#include "clientmessage.h"
|
||||
#include "door.h"
|
||||
#include "logger.h"
|
||||
#include "request.h"
|
||||
#include "response.h"
|
||||
#include "clientmessage.h"
|
||||
|
||||
/* The "Logic" class
|
||||
*
|
||||
* 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
|
||||
{
|
||||
@ -33,7 +33,11 @@ public:
|
||||
~Logic();
|
||||
|
||||
// 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
|
||||
Clientmessage getClientMessage();
|
||||
@ -46,7 +50,7 @@ private:
|
||||
Response _unlock();
|
||||
|
||||
// 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
|
||||
Response _checkLDAP(const std::string &user,
|
||||
@ -63,13 +67,10 @@ private:
|
||||
// The door
|
||||
Door _door;
|
||||
|
||||
// Tokens are 64-bit hexadecimal values
|
||||
using Token = uint64_t;
|
||||
|
||||
// The current token
|
||||
Token _curToken = { 0x0000000000000000 };
|
||||
std::string _curToken = { "0000000000000000" };
|
||||
// The previous token
|
||||
Token _prevToken = { 0x0000000000000000 };
|
||||
std::string _prevToken = { "0000000000000000" };
|
||||
// Indicates whether the previous token is valid
|
||||
bool _prevValid = { false };
|
||||
|
||||
|
69
doorlockd/lib/request.cpp
Normal file
69
doorlockd/lib/request.cpp
Normal 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
29
doorlockd/lib/request.h
Normal 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
|
||||
|
@ -8,6 +8,18 @@
|
||||
const std::string Response::_codeKey = "code";
|
||||
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
|
||||
{
|
||||
return code == Response::Code::Success;
|
||||
@ -24,21 +36,34 @@ std::string Response::toJson() const
|
||||
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;
|
||||
|
||||
if (reader.parse(json, root) == false)
|
||||
throw std::runtime_error("Error parsing JSON");
|
||||
try {
|
||||
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);
|
||||
if (code > Code::RESPONSE_NUM_ITEMS)
|
||||
throw std::runtime_error("Error code out of range");
|
||||
retval.code = static_cast<Code>(code);
|
||||
retval.code = static_cast<Code>(code);
|
||||
}
|
||||
catch (const std::exception &ex) {
|
||||
throw Response(Response::Code::JsonError, ex.what());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -3,8 +3,11 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
struct Response
|
||||
#include <json/json.h>
|
||||
|
||||
class Response
|
||||
{
|
||||
public:
|
||||
enum Code {
|
||||
Success = 0, // Request successful
|
||||
Fail, // General non-specified error
|
||||
@ -22,19 +25,11 @@ struct Response
|
||||
} code;
|
||||
std::string message;
|
||||
|
||||
Response() :
|
||||
code(Fail),
|
||||
message("General failure")
|
||||
{
|
||||
}
|
||||
Response();
|
||||
Response(Response::Code code, const std::string &message = "");
|
||||
|
||||
Response(Response::Code code, const std::string &message = "") :
|
||||
code(code),
|
||||
message(message)
|
||||
{
|
||||
}
|
||||
|
||||
static Response fromJson(const std::string &json);
|
||||
static Response fromJson(const Json::Value &root);
|
||||
static Response fromString(const std::string &json);
|
||||
std::string toJson() const;
|
||||
|
||||
// Returns true if code is success
|
||||
|
@ -1,9 +1,7 @@
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
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());
|
||||
if (val.isInt())
|
||||
@ -12,7 +10,7 @@ int getJson(const Json::Value &root, const string &key)
|
||||
}
|
||||
|
||||
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());
|
||||
if (val.isString())
|
||||
@ -21,7 +19,7 @@ string getJson(const Json::Value &root, const string &key)
|
||||
}
|
||||
|
||||
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());
|
||||
if (val.isInt())
|
||||
@ -30,7 +28,7 @@ size_t getJson(const Json::Value &root, const string &key)
|
||||
}
|
||||
|
||||
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());
|
||||
if (val.isBool())
|
||||
@ -39,7 +37,7 @@ bool getJson(const Json::Value &root, const string &key)
|
||||
}
|
||||
|
||||
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());
|
||||
return val;
|
||||
@ -57,9 +55,9 @@ static char nibble2hex(unsigned char input)
|
||||
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>>56) & 0xF);
|
||||
@ -92,21 +90,3 @@ unsigned char hex2uchar(const char 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;
|
||||
}
|
||||
|
@ -14,13 +14,12 @@ T getJsonOrFail(const Json::Value &root, const std::string &key)
|
||||
const auto members = root.getMemberNames();
|
||||
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);
|
||||
}
|
||||
|
||||
std::string toHexString(uint64_t c);
|
||||
uint64_t toUint64(const std::string &s);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user