add ACL for read/write/find/scan, add auth command, add -a and -s options to find command, include user and access level to info output, add user and secret arguments to HTTP port
This commit is contained in:
@@ -928,11 +928,11 @@ void BusHandler::receiveCompleted() {
|
||||
}
|
||||
}
|
||||
|
||||
result_t BusHandler::startScan(bool full) {
|
||||
result_t BusHandler::startScan(bool full, string levels) {
|
||||
if (m_runningScans > 0) {
|
||||
return RESULT_ERR_DUPLICATE;
|
||||
}
|
||||
deque<Message*> messages = m_messages->findAll("scan", "", true);
|
||||
deque<Message*> messages = m_messages->findAll("scan", "", levels, true);
|
||||
for (deque<Message*>::iterator it = messages.begin(); it < messages.end(); it++) {
|
||||
Message* message = *it;
|
||||
if (message->getPrimaryCommand() == 0x07 && message->getSecondaryCommand() == 0x04) {
|
||||
|
||||
@@ -420,9 +420,10 @@ class BusHandler : public WaitThread {
|
||||
/**
|
||||
* Initiate a scan of the slave addresses.
|
||||
* @param full true for a full scan (all slaves), false for scanning only already seen slaves.
|
||||
* @param level the current user's access levels.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t startScan(bool full = false);
|
||||
result_t startScan(bool full, string levels);
|
||||
|
||||
/**
|
||||
* Set the scan result @a string for a scanned slave address.
|
||||
|
||||
+26
-1
@@ -75,12 +75,14 @@ static struct options opt = {
|
||||
false, // readOnly
|
||||
false, // initialSend
|
||||
-1, // latency
|
||||
|
||||
CONFIG_PATH, // configPath
|
||||
false, // scanConfig
|
||||
BROADCAST, // initialScan
|
||||
false, // checkConfig
|
||||
false, // dumpConfig
|
||||
5, // pollInterval
|
||||
|
||||
0x31, // address
|
||||
false, // answer
|
||||
9400, // acquireTimeout
|
||||
@@ -89,6 +91,9 @@ static struct options opt = {
|
||||
SLAVE_RECV_TIMEOUT*5/3, // receiveTimeout
|
||||
0, // masterCount
|
||||
false, // generateSyn
|
||||
|
||||
"", // accessLevel
|
||||
"", // aclFile
|
||||
false, // foreground
|
||||
false, // enableHex
|
||||
PID_FILE_NAME, // pidFile
|
||||
@@ -96,10 +101,12 @@ static struct options opt = {
|
||||
false, // localOnly
|
||||
0, // httpPort
|
||||
"/var/" PACKAGE "/html", // htmlPath
|
||||
|
||||
PACKAGE_LOGFILE, // logFile
|
||||
false, // logRaw
|
||||
PACKAGE_LOGFILE, // logRawFile
|
||||
100, // logRawSize
|
||||
|
||||
false, // dump
|
||||
"/tmp/" PACKAGE "_dump.bin", // dumpFile
|
||||
100, // dumpSize
|
||||
@@ -127,7 +134,9 @@ static const char argpdoc[] =
|
||||
#define O_RCVTIM (O_SNDRET+1)
|
||||
#define O_MASCNT (O_RCVTIM+1)
|
||||
#define O_GENSYN (O_MASCNT+1)
|
||||
#define O_HEXCMD (O_GENSYN+1)
|
||||
#define O_ACLDEF (O_GENSYN+1)
|
||||
#define O_ACLFIL (O_ACLDEF+1)
|
||||
#define O_HEXCMD (O_ACLFIL+1)
|
||||
#define O_PIDFIL (O_HEXCMD+1)
|
||||
#define O_LOCAL (O_PIDFIL+1)
|
||||
#define O_HTTPPT (O_LOCAL+1)
|
||||
@@ -170,6 +179,8 @@ static const struct argp_option argpoptions[] = {
|
||||
{"generatesyn", O_GENSYN, NULL, 0, "Enable AUTO-SYN symbol generation", 0 },
|
||||
|
||||
{NULL, 0, NULL, 0, "Daemon options:", 4 },
|
||||
{"accesslevel", O_ACLDEF, "LEVEL", 0, "Set default access level to LEVEL (\"*\" for everything) [\"\"]", 0 },
|
||||
{"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE", 0 },
|
||||
{"foreground", 'f', NULL, 0, "Run in foreground", 0 },
|
||||
{"enablehex", O_HEXCMD, NULL, 0, "Enable hex command", 0 },
|
||||
{"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PID_FILE_NAME "]", 0 },
|
||||
@@ -360,6 +371,20 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
break;
|
||||
|
||||
// Daemon options:
|
||||
case O_ACLDEF: // --accesslevel=*
|
||||
if (arg == NULL) {
|
||||
argp_error(state, "invalid accesslevel");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->accessLevel = arg;
|
||||
break;
|
||||
case O_ACLFIL: // --aclfile=/etc/ebusd/acl
|
||||
if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid aclfile");
|
||||
return EINVAL;
|
||||
}
|
||||
opt->aclFile = arg;
|
||||
break;
|
||||
case 'f': // --foreground
|
||||
opt->foreground = true;
|
||||
break;
|
||||
|
||||
@@ -57,6 +57,8 @@ struct options {
|
||||
unsigned int masterCount; //!< expected number of masters for arbitration [0]
|
||||
bool generateSyn; //!< enable AUTO-SYN symbol generation
|
||||
|
||||
const char* accessLevel; //!< default access level
|
||||
const char* aclFile; //!< ACL file name
|
||||
bool foreground; //!< run in foreground
|
||||
bool enableHex; //!< enable hex command
|
||||
const char* pidFile; //!< PID file name [/var/run/ebusd.pid]
|
||||
|
||||
+204
-111
@@ -23,6 +23,7 @@
|
||||
#include "ebusd/mainloop.h"
|
||||
#include <iomanip>
|
||||
#include <deque>
|
||||
#include <algorithm>
|
||||
#include "ebusd/main.h"
|
||||
#include "lib/utils/log.h"
|
||||
#include "lib/ebus/data.h"
|
||||
@@ -41,6 +42,7 @@ using std::setw;
|
||||
static const char* columnNames[] = {
|
||||
"type", "t",
|
||||
"circuit", "c",
|
||||
"level", "l",
|
||||
"name", "n",
|
||||
"comment", "co",
|
||||
"qq", "q",
|
||||
@@ -51,9 +53,10 @@ static const char* columnNames[] = {
|
||||
};
|
||||
|
||||
/** the known column IDs according to @a columnNames. */
|
||||
static const size_t columnIds[] = {
|
||||
static const column_t columnIds[] = {
|
||||
COLUMN_TYPE, COLUMN_TYPE,
|
||||
COLUMN_CIRCUIT, COLUMN_CIRCUIT,
|
||||
COLUMN_LEVEL, COLUMN_LEVEL,
|
||||
COLUMN_NAME, COLUMN_NAME,
|
||||
COLUMN_COMMENT, COLUMN_COMMENT,
|
||||
COLUMN_QQ, COLUMN_QQ,
|
||||
@@ -67,8 +70,36 @@ static const size_t columnIds[] = {
|
||||
static const size_t columnCount = sizeof(columnNames) / sizeof(char*);
|
||||
|
||||
|
||||
result_t UserList::addFromFile(vector<string>::iterator& begin, const vector<string>::iterator end,
|
||||
vector< vector<string> >* defaults, const string& defaultDest, const string& defaultCircuit,
|
||||
const string& defaultSuffix, const string& filename, unsigned int lineNo) {
|
||||
// name,secret,level
|
||||
if (begin == end) {
|
||||
return RESULT_ERR_EOF;
|
||||
}
|
||||
string name = *begin++;
|
||||
if (begin == end) {
|
||||
return RESULT_ERR_EOF;
|
||||
}
|
||||
if (name.empty()) {
|
||||
return RESULT_ERR_INVALID_ARG;
|
||||
}
|
||||
if (name == "*") { // default levels
|
||||
name = "";
|
||||
}
|
||||
const string secret = *begin++;
|
||||
if (begin == end) {
|
||||
return RESULT_ERR_EOF;
|
||||
}
|
||||
const string levels = *begin++;
|
||||
m_userSecrets[name] = secret;
|
||||
m_userLevels[name] = levels;
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
|
||||
MainLoop::MainLoop(const struct options opt, Device *device, MessageMap* messages)
|
||||
: Thread(), m_device(device), m_reconnectCount(0), m_messages(messages),
|
||||
: Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages),
|
||||
m_address(opt.address), m_scanConfig(opt.scanConfig),
|
||||
m_initialScan(opt.initialScan), m_enableHex(opt.enableHex) {
|
||||
// open Device
|
||||
@@ -90,6 +121,12 @@ MainLoop::MainLoop(const struct options opt, Device *device, MessageMap* message
|
||||
m_logRawFile = NULL;
|
||||
}
|
||||
m_logRawEnabled = opt.logRaw;
|
||||
if (opt.aclFile[0]) {
|
||||
result_t result = m_userList.readFromFile(opt.aclFile);
|
||||
if (result != RESULT_OK) {
|
||||
logError(lf_main, "error reading ACL file \"%s\": %s", opt.aclFile, getResultCode(result));
|
||||
}
|
||||
}
|
||||
// create BusHandler
|
||||
unsigned int latency;
|
||||
if (opt.latency < 0) {
|
||||
@@ -109,7 +146,7 @@ MainLoop::MainLoop(const struct options opt, Device *device, MessageMap* message
|
||||
m_htmlPath = opt.htmlPath;
|
||||
m_network = new Network(opt.localOnly, opt.port, opt.httpPort, &m_netQueue);
|
||||
m_network->start("network");
|
||||
if (!datahandler_register(m_busHandler, messages, m_dataHandlers)) {
|
||||
if (!datahandler_register(&m_userList, m_busHandler, messages, m_dataHandlers)) {
|
||||
logError(lf_main, "error registering data handlers");
|
||||
}
|
||||
}
|
||||
@@ -187,7 +224,7 @@ void MainLoop::run() {
|
||||
result_t result = RESULT_ERR_NO_SIGNAL;
|
||||
if (m_initialScan == SYN) {
|
||||
logNotice(lf_main, "initiating full scan");
|
||||
result = m_busHandler->startScan(true);
|
||||
result = m_busHandler->startScan(true, "*");
|
||||
} else {
|
||||
logNotice(lf_main, "starting initial scan for %2.2x", m_initialScan);
|
||||
SymbolString slave(false);
|
||||
@@ -245,7 +282,7 @@ void MainLoop::run() {
|
||||
}
|
||||
time(&now);
|
||||
if (!dataSinks.empty()) {
|
||||
messages = m_messages->findAll("", "", false, true, true, true, false, true, sinkSince, now);
|
||||
messages = m_messages->findAll("", "", "*", false, true, true, true, true, sinkSince, now);
|
||||
for (deque<Message*>::iterator it = messages.begin(); it != messages.end(); it++) {
|
||||
Message* message = *it;
|
||||
for (list<DataSink*>::iterator it = dataSinks.begin(); it != dataSinks.end(); it++) {
|
||||
@@ -258,6 +295,7 @@ void MainLoop::run() {
|
||||
continue;
|
||||
}
|
||||
string request = netMessage->getRequest();
|
||||
string user = netMessage->getUser();
|
||||
bool listening = netMessage->isListening(&since);
|
||||
if (!listening) {
|
||||
since = now;
|
||||
@@ -266,7 +304,7 @@ void MainLoop::run() {
|
||||
bool connected = true;
|
||||
if (request.length() > 0) {
|
||||
logDebug(lf_main, ">>> %s", request.c_str());
|
||||
ostream << decodeMessage(request, netMessage->isHttp(), connected, listening, reload);
|
||||
ostream << decodeMessage(request, netMessage->isHttp(), connected, listening, user, reload);
|
||||
|
||||
if (ostream.tellp() == 0 && !netMessage->isHttp()) {
|
||||
ostream << getResultCode(RESULT_EMPTY);
|
||||
@@ -283,7 +321,8 @@ void MainLoop::run() {
|
||||
}
|
||||
}
|
||||
if (listening) {
|
||||
messages = m_messages->findAll("", "", false, true, true, true, false, true, since, now);
|
||||
string levels = getUserLevels(user);
|
||||
messages = m_messages->findAll("", "", levels, false, true, true, true, true, since, now);
|
||||
for (deque<Message*>::iterator it = messages.begin(); it != messages.end(); it++) {
|
||||
Message* message = *it;
|
||||
ostream << message->getCircuit() << " " << message->getName() << " = " << dec;
|
||||
@@ -292,7 +331,7 @@ void MainLoop::run() {
|
||||
}
|
||||
}
|
||||
// send result to client
|
||||
netMessage->setResult(ostream.str(), listening, now, !connected);
|
||||
netMessage->setResult(ostream.str(), user, listening, now, !connected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,8 +350,8 @@ void MainLoop::notifyDeviceData(const unsigned char byte, bool received) {
|
||||
}
|
||||
}
|
||||
|
||||
string MainLoop::decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening, bool& reload) {
|
||||
// prepare data
|
||||
string MainLoop::decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening,
|
||||
string& user, bool& reload) {
|
||||
string token, previous;
|
||||
istringstream stream(data);
|
||||
vector<string> args;
|
||||
@@ -358,65 +397,70 @@ string MainLoop::decodeMessage(const string& data, const bool isHttp, bool& conn
|
||||
if (args.size() == 0) {
|
||||
return executeHelp();
|
||||
}
|
||||
const char* str = args[0].c_str();
|
||||
string cmd = args[0];
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
if (args.size() == 2) {
|
||||
// check for "CMD -h"
|
||||
if (strcasecmp(args[1].c_str(), "-h") == 0 || strcasecmp(args[1].c_str(), "-?") == 0 ||
|
||||
strcasecmp(args[1].c_str(), "--help") == 0) {
|
||||
string arg = args[1];
|
||||
if (arg == "?" || arg == "-?" || arg == "--help") {
|
||||
// found "CMD HELP"
|
||||
args.clear(); // empty args is used as command help indicator
|
||||
} else if (strcasecmp(args[0].c_str(), "H") == 0 || strcasecmp(args[0].c_str(), "HELP") == 0) {
|
||||
// check for "HELP CMD"
|
||||
str = args[1].c_str();
|
||||
} else if (cmd == "?" || cmd == "H" || cmd == "HELP") {
|
||||
// found "HELP CMD"
|
||||
cmd = args[1];
|
||||
transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
args.clear(); // empty args is used as command help indicator
|
||||
}
|
||||
}
|
||||
if (strcasecmp(str, "R") == 0 || strcasecmp(str, "READ") == 0) {
|
||||
return executeRead(args);
|
||||
if (cmd == "AUTH" || cmd == "A") {
|
||||
return executeAuth(args, user);
|
||||
}
|
||||
if (strcasecmp(str, "W") == 0 || strcasecmp(str, "WRITE") == 0) {
|
||||
return executeWrite(args);
|
||||
if (cmd == "R" || cmd == "READ") {
|
||||
return executeRead(args, getUserLevels(user));
|
||||
}
|
||||
if (strcasecmp(str, "HEX") == 0) {
|
||||
if (cmd == "W" || cmd == "WRITE") {
|
||||
return executeWrite(args, getUserLevels(user));
|
||||
}
|
||||
if (cmd == "HEX") {
|
||||
if (m_enableHex) {
|
||||
return executeHex(args);
|
||||
}
|
||||
return "ERR: command not enabled";
|
||||
}
|
||||
if (strcasecmp(str, "F") == 0 || strcasecmp(str, "FIND") == 0) {
|
||||
return executeFind(args);
|
||||
if (cmd == "F" || cmd == "FIND") {
|
||||
return executeFind(args, getUserLevels(user));
|
||||
}
|
||||
if (strcasecmp(str, "L") == 0 || strcasecmp(str, "LISTEN") == 0) {
|
||||
if (cmd == "L" || cmd == "LISTEN") {
|
||||
return executeListen(args, listening);
|
||||
}
|
||||
if (strcasecmp(str, "S") == 0 || strcasecmp(str, "STATE") == 0) {
|
||||
if (cmd == "S" || cmd == "STATE") {
|
||||
return executeState(args);
|
||||
}
|
||||
if (strcasecmp(str, "G") == 0 || strcasecmp(str, "GRAB") == 0) {
|
||||
if (cmd == "G" || cmd == "GRAB") {
|
||||
return executeGrab(args);
|
||||
}
|
||||
if (strcasecmp(str, "SCAN") == 0) {
|
||||
return executeScan(args);
|
||||
if (cmd == "SCAN") {
|
||||
return executeScan(args, getUserLevels(user));
|
||||
}
|
||||
if (strcasecmp(str, "LOG") == 0) {
|
||||
if (cmd == "LOG") {
|
||||
return executeLog(args);
|
||||
}
|
||||
if (strcasecmp(str, "RAW") == 0) {
|
||||
if (cmd == "RAW") {
|
||||
return executeRaw(args);
|
||||
}
|
||||
if (strcasecmp(str, "DUMP") == 0) {
|
||||
if (cmd == "DUMP") {
|
||||
return executeDump(args);
|
||||
}
|
||||
if (strcasecmp(str, "RELOAD") == 0) {
|
||||
if (cmd == "RELOAD") {
|
||||
reload = true;
|
||||
return executeReload(args);
|
||||
}
|
||||
if (strcasecmp(str, "Q") == 0 || strcasecmp(str, "QUIT") == 0) {
|
||||
if (cmd == "Q" || cmd == "QUIT") {
|
||||
return executeQuit(args, connected);
|
||||
}
|
||||
if (strcasecmp(str, "I") == 0 || strcasecmp(str, "INFO") == 0) {
|
||||
return executeInfo(args);
|
||||
if (cmd == "I" || cmd == "INFO") {
|
||||
return executeInfo(args, user);
|
||||
}
|
||||
if (strcasecmp(str, "H") == 0 || strcasecmp(str, "HELP") == 0) {
|
||||
if (cmd == "?" || cmd == "H" || cmd == "HELP") {
|
||||
return executeHelp();
|
||||
}
|
||||
return "ERR: command not found";
|
||||
@@ -448,7 +492,21 @@ result_t MainLoop::parseHexMaster(vector<string> &args, size_t argPos, SymbolStr
|
||||
return ret;
|
||||
}
|
||||
|
||||
string MainLoop::executeRead(vector<string> &args) {
|
||||
string MainLoop::executeAuth(vector<string> &args, string &user) {
|
||||
if (args.size() != 3) {
|
||||
return "usage: auth USER SECRET\n"
|
||||
" Authorize with USER name and SECRET.\n"
|
||||
" USER the user name\n"
|
||||
" SECRET the secret string of the user";
|
||||
}
|
||||
if (m_userList.checkSecret(args[1], args[2])) {
|
||||
user = args[1];
|
||||
return getResultCode(RESULT_OK);
|
||||
}
|
||||
return "ERR: invalid user name or secret";
|
||||
}
|
||||
|
||||
string MainLoop::executeRead(vector<string> &args, const string levels) {
|
||||
size_t argPos = 1;
|
||||
bool hex = false, numeric = false;
|
||||
OutputFormat verbosity = 0;
|
||||
@@ -558,6 +616,9 @@ string MainLoop::executeRead(vector<string> &args) {
|
||||
if (message == NULL) {
|
||||
return getResultCode(RESULT_ERR_NOTFOUND);
|
||||
}
|
||||
if (!message->hasLevel(levels)) {
|
||||
return getResultCode(RESULT_ERR_NOTAUTHORIZED);
|
||||
}
|
||||
if (message->isWrite()) {
|
||||
return getResultCode(RESULT_ERR_INVALID_ARG);
|
||||
}
|
||||
@@ -596,27 +657,27 @@ string MainLoop::executeRead(vector<string> &args) {
|
||||
return getResultCode(ret);
|
||||
}
|
||||
if (argPos == 0 || args.size() < argPos + 1 || args.size() > argPos + 2) {
|
||||
return "usage: read [-f] [-m SECONDS] [-c CIRCUIT] [-d ZZ] [-p PRIO] [-v|-V] [-n] [-i VALUE[;VALUE]*] NAME "
|
||||
"[FIELD[.N]]\n"
|
||||
return "usage: read [-f] [-m SECONDS] [-c CIRCUIT] [-d ZZ] [-p PRIO] [-v|-V] [-n] [-i VALUE[;VALUE]*] NAME"
|
||||
" [FIELD[.N]]\n"
|
||||
" or: read [-f] [-m SECONDS] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Read value(s) or hex message.\n"
|
||||
" -f force reading from the bus (same as '-m 0')\n"
|
||||
" -m SECONDS only return cached value if age is less than SECONDS [300]\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -p PRIO set the message poll priority (1-9)\n"
|
||||
" -v increase verbosity (include names/units/comments)\n"
|
||||
" -V be very verbose (include names, units, and comments)\n"
|
||||
" -n use numeric value of value=name pairs\n"
|
||||
" -i VALUE read additional message parameters from VALUE\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" FIELD only retrieve the field named FIELD\n"
|
||||
" N only retrieve the N'th field named FIELD (0-based)\n"
|
||||
" -h send hex read message (or answer from cache):\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
" -f force reading from the bus (same as '-m 0')\n"
|
||||
" -m SECONDS only return cached value if age is less than SECONDS [300]\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -p PRIO set the message poll priority (1-9)\n"
|
||||
" -v increase verbosity (include names/units/comments)\n"
|
||||
" -V be very verbose (include names, units, and comments)\n"
|
||||
" -n use numeric value of value=name pairs\n"
|
||||
" -i VALUE read additional message parameters from VALUE\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" FIELD only retrieve the field named FIELD\n"
|
||||
" N only retrieve the N'th field named FIELD (0-based)\n"
|
||||
" -h send hex read message (or answer from cache):\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
}
|
||||
string fieldName;
|
||||
signed char fieldIndex = -2;
|
||||
@@ -634,15 +695,14 @@ string MainLoop::executeRead(vector<string> &args) {
|
||||
}
|
||||
|
||||
ostringstream result;
|
||||
Message* message = m_messages->find(circuit, args[argPos], false);
|
||||
|
||||
Message* message = m_messages->find(circuit, args[argPos], levels, false);
|
||||
// adjust poll priority
|
||||
if (message != NULL && pollPriority > 0 && message->setPollPriority(pollPriority)) {
|
||||
m_messages->addPollMessage(message);
|
||||
}
|
||||
|
||||
if (dstAddress == SYN && maxAge > 0 && params.length() == 0) {
|
||||
Message* cacheMessage = m_messages->find(circuit, args[argPos], false, true);
|
||||
Message* cacheMessage = m_messages->find(circuit, args[argPos], levels, false, true);
|
||||
bool hasCache = cacheMessage != NULL;
|
||||
if (!hasCache || (message != NULL && message->getLastUpdateTime() > cacheMessage->getLastUpdateTime())) {
|
||||
cacheMessage = message; // message is newer/better
|
||||
@@ -702,7 +762,7 @@ string MainLoop::executeRead(vector<string> &args) {
|
||||
return result.str();
|
||||
}
|
||||
|
||||
string MainLoop::executeWrite(vector<string> &args) {
|
||||
string MainLoop::executeWrite(vector<string> &args, const string levels) {
|
||||
size_t argPos = 1;
|
||||
bool hex = false;
|
||||
string circuit;
|
||||
@@ -753,6 +813,9 @@ string MainLoop::executeWrite(vector<string> &args) {
|
||||
if (message == NULL) {
|
||||
return getResultCode(RESULT_ERR_NOTFOUND);
|
||||
}
|
||||
if (!message->hasLevel(levels)) {
|
||||
return getResultCode(RESULT_ERR_NOTAUTHORIZED);
|
||||
}
|
||||
if (!message->isWrite()) {
|
||||
return getResultCode(RESULT_ERR_INVALID_ARG);
|
||||
}
|
||||
@@ -796,17 +859,17 @@ string MainLoop::executeWrite(vector<string> &args) {
|
||||
return "usage: write [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" or: write [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Write value(s) or hex message.\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -c CIRCUIT CIRCUIT of the message to send\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" VALUE a single field VALUE\n"
|
||||
" -h send hex write message:\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -c CIRCUIT CIRCUIT of the message to send\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" VALUE a single field VALUE\n"
|
||||
" -h send hex write message:\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
}
|
||||
Message* message = m_messages->find(circuit, args[argPos], true);
|
||||
Message* message = m_messages->find(circuit, args[argPos], levels, true);
|
||||
|
||||
if (message == NULL) {
|
||||
return getResultCode(RESULT_ERR_NOTFOUND);
|
||||
@@ -891,12 +954,12 @@ string MainLoop::executeHex(vector<string> &args) {
|
||||
" Dx data byte(s) to send";
|
||||
}
|
||||
|
||||
string MainLoop::executeFind(vector<string> &args) {
|
||||
string MainLoop::executeFind(vector<string> &args, string levels) {
|
||||
size_t argPos = 1;
|
||||
bool configFormat = false, exact = false, withRead = true, withWrite = false, withPassive = true, first = true,
|
||||
onlyWithData = false, hexFormat = false;
|
||||
OutputFormat verbosity = 0;
|
||||
vector<size_t> columns;
|
||||
vector<column_t> columns;
|
||||
string circuit;
|
||||
vector<unsigned char> id;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
@@ -968,6 +1031,8 @@ string MainLoop::executeFind(vector<string> &args) {
|
||||
withRead = withWrite = false;
|
||||
}
|
||||
withPassive = true;
|
||||
} else if (args[argPos] == "-a") {
|
||||
withRead = withWrite = withPassive = true;
|
||||
} else if (args[argPos] == "-d") {
|
||||
onlyWithData = true;
|
||||
} else if (args[argPos] == "-h") {
|
||||
@@ -997,6 +1062,13 @@ string MainLoop::executeFind(vector<string> &args) {
|
||||
break;
|
||||
}
|
||||
circuit = args[argPos];
|
||||
} else if (args[argPos] == "-s") {
|
||||
argPos++;
|
||||
if (argPos >= args.size()) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
levels = args[argPos];
|
||||
} else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
@@ -1004,25 +1076,28 @@ string MainLoop::executeFind(vector<string> &args) {
|
||||
argPos++;
|
||||
}
|
||||
if (argPos == 0 || args.size() < argPos || args.size() > argPos + 1) {
|
||||
return "usage: find [-v|-V] [-r] [-w] [-p] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e] [-c CIRCUIT] [NAME]\n"
|
||||
return "usage: find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e] [-c CIRCUIT]"
|
||||
" [-l LEVEL] [NAME]\n"
|
||||
" Find message(s).\n"
|
||||
" -v increase verbosity (include names/units/comments+destination address+update time)\n"
|
||||
" -V be very verbose (include everything)\n"
|
||||
" -r limit to active read messages (default: read + passive)\n"
|
||||
" -w limit to active write messages (default: read + passive)\n"
|
||||
" -p limit to passive messages (default: read + passive)\n"
|
||||
" -d only include messages with actual data\n"
|
||||
" -h show hex data instead of decoded values\n"
|
||||
" -i ID limit to messages with ID (in hex, PB, SB and further ID bytes)\n"
|
||||
" -f list messages in CSV configuration file format\n"
|
||||
" -F COL[,COL]* list messages in the specified format\n"
|
||||
" (COL: type,circuit,name,comment,qq,zz,pbsb,id,fields)\n"
|
||||
" -e match NAME and optional CIRCUIT exactly (ignoring case)\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT (or a part thereof without '-e')\n"
|
||||
" NAME NAME of the messages to find (or a part thereof without '-e')";
|
||||
" -v increase verbosity (include names/units/comments+destination address+update time)\n"
|
||||
" -V be very verbose (include everything)\n"
|
||||
" -r limit to active read messages (default: read + passive)\n"
|
||||
" -w limit to active write messages (default: read + passive)\n"
|
||||
" -p limit to passive messages (default: read + passive)\n"
|
||||
" -a include all message types (read, passive, and write)\n"
|
||||
" -d only include messages with actual data\n"
|
||||
" -h show hex data instead of decoded values\n"
|
||||
" -i ID limit to messages with ID (in hex, PB, SB and further ID bytes)\n"
|
||||
" -f list messages in CSV configuration file format\n"
|
||||
" -F COL[,COL]* list messages in the specified format\n"
|
||||
" (COL: type|circuit|level|name|comment|qq|zz|pbsb|id|fields)\n"
|
||||
" -e match NAME and optional CIRCUIT exactly (ignoring case)\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT (or a part thereof without '-e')\n"
|
||||
" -l LEVEL limit to messages with access LEVEL (\"*\" for any, default: current level)\n"
|
||||
" NAME NAME of the messages to find (or a part thereof without '-e')";
|
||||
}
|
||||
deque<Message*> messages = m_messages->findAll(
|
||||
circuit, args.size() == argPos ? "" : args[argPos], exact, withRead, withWrite, withPassive);
|
||||
circuit, args.size() == argPos ? "" : args[argPos], levels, exact, withRead, withWrite, withPassive);
|
||||
|
||||
bool found = false;
|
||||
ostringstream result;
|
||||
@@ -1154,9 +1229,9 @@ string MainLoop::executeGrab(vector<string> &args) {
|
||||
" Start or stop grabbing, or report unknown or all grabbed messages.";
|
||||
}
|
||||
|
||||
string MainLoop::executeScan(vector<string> &args) {
|
||||
string MainLoop::executeScan(vector<string> &args, string levels) {
|
||||
if (args.size() == 1) {
|
||||
result_t result = m_busHandler->startScan();
|
||||
result_t result = m_busHandler->startScan(false, levels);
|
||||
if (result == RESULT_ERR_DUPLICATE) {
|
||||
return "ERR: scan already running";
|
||||
}
|
||||
@@ -1168,7 +1243,7 @@ string MainLoop::executeScan(vector<string> &args) {
|
||||
|
||||
if (args.size() == 2) {
|
||||
if (strcasecmp(args[1].c_str(), "FULL") == 0) {
|
||||
result_t result = m_busHandler->startScan(true);
|
||||
result_t result = m_busHandler->startScan(true, levels);
|
||||
if (result != RESULT_OK) {
|
||||
logError(lf_main, "full scan: %s", getResultCode(result));
|
||||
}
|
||||
@@ -1281,13 +1356,20 @@ string MainLoop::executeReload(vector<string> &args) {
|
||||
return getResultCode(result);
|
||||
}
|
||||
|
||||
string MainLoop::executeInfo(vector<string> &args) {
|
||||
string MainLoop::executeInfo(vector<string> &args, const string user) {
|
||||
if (args.size() == 0) {
|
||||
return "usage: info\n"
|
||||
" Report information about the daemon, the configuration, and seen devices.";
|
||||
}
|
||||
ostringstream result;
|
||||
result << "version: " << PACKAGE_STRING "." REVISION "\n";
|
||||
if (!user.empty()) {
|
||||
result << "user: " << user << "\n";
|
||||
}
|
||||
string levels = getUserLevels(user);
|
||||
if (!user.empty() || !levels.empty()) {
|
||||
result << "access: " << levels << "\n";
|
||||
}
|
||||
if (m_busHandler->hasSignal()) {
|
||||
result << "signal: acquired\n";
|
||||
result << "symbol rate: " << m_busHandler->getSymbolRate() << "\n";
|
||||
@@ -1320,9 +1402,10 @@ string MainLoop::executeHelp() {
|
||||
" Read hex message: read [-f] [-m SECONDS] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" write|w Write value(s): write [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" Write hex message: write [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" auth|a Authorize user: auth USER SECRET\n"
|
||||
" hex Send hex data: hex ZZPBSBNNDx\n"
|
||||
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
|
||||
" [-c CIRCUIT] [NAME]\n"
|
||||
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
|
||||
" [-c CIRCUIT] [-l LEVEL] [NAME]\n"
|
||||
" listen|l Listen for updates: listen [stop]\n"
|
||||
" state|s Report bus state\n"
|
||||
" info|i Report information about the daemon, the configuration, and seen devices.\n"
|
||||
@@ -1331,13 +1414,11 @@ string MainLoop::executeHelp() {
|
||||
" scan Scan slaves: scan [full|ZZ]\n"
|
||||
" Report scan result: scan result\n"
|
||||
" log Set log area/level: log [AREA[,AREA]*] [LEVEL]\n"
|
||||
" AREA: main|network|bus|update|all\n"
|
||||
" LEVEL: error|notice|info|debug\n"
|
||||
" raw Toggle logging of each byte\n"
|
||||
" dump Toggle binary dump of received bytes\n"
|
||||
" reload Reload CSV config files\n"
|
||||
" quit|q Close connection\n"
|
||||
" help|h Print help help [COMMAND]";
|
||||
" help|? Print help help [COMMAND], COMMMAND ?";
|
||||
}
|
||||
|
||||
string MainLoop::executeGet(vector<string> &args, bool& connected) {
|
||||
@@ -1361,7 +1442,9 @@ string MainLoop::executeGet(vector<string> &args, bool& connected) {
|
||||
time_t since = 0;
|
||||
unsigned char pollPriority = 0;
|
||||
bool exact = false;
|
||||
string user = "";
|
||||
if (args.size() > argPos) {
|
||||
string secret;
|
||||
string query = args[argPos++];
|
||||
istringstream stream(query);
|
||||
string token;
|
||||
@@ -1374,36 +1457,43 @@ string MainLoop::executeGet(vector<string> &args, bool& connected) {
|
||||
} else {
|
||||
qname = token;
|
||||
}
|
||||
if (strcmp(qname.c_str(), "since") == 0) {
|
||||
if (qname == "since") {
|
||||
since = parseInt(value.c_str(), 10, 0, 0xffffffff, ret);
|
||||
} else if (strcmp(qname.c_str(), "poll") == 0) {
|
||||
} else if (qname == "poll") {
|
||||
pollPriority = (unsigned char)parseInt(value.c_str(), 10, 1, 9, ret);
|
||||
} else if (strcmp(qname.c_str(), "exact") == 0) {
|
||||
exact = value.length() == 0 || strcmp(value.c_str(), "1") == 0;
|
||||
} else if (strcmp(qname.c_str(), "verbose") == 0) {
|
||||
if (value.length() == 0 || strcmp(value.c_str(), "1") == 0) {
|
||||
} else if (qname == "exact") {
|
||||
exact = value.length() == 0 || value == "1";
|
||||
} else if (qname == "verbose") {
|
||||
if (value.length() == 0 || value == "1") {
|
||||
verbosity |= OF_UNITS | OF_COMMENTS;
|
||||
}
|
||||
} else if (strcmp(qname.c_str(), "indexed") == 0) {
|
||||
if (value.length() == 0 || strcmp(value.c_str(), "1") == 0) {
|
||||
} else if (qname == "indexed") {
|
||||
if (value.length() == 0 || value == "1") {
|
||||
verbosity &= ~OF_NAMES;
|
||||
}
|
||||
} else if (strcmp(qname.c_str(), "numeric") == 0) {
|
||||
numeric = value.length() == 0 || strcmp(value.c_str(), "1") == 0;
|
||||
} else if (strcmp(qname.c_str(), "required") == 0) {
|
||||
required = value.length() == 0 || strcmp(value.c_str(), "1") == 0;
|
||||
} else if (qname == "numeric") {
|
||||
numeric = value.length() == 0 || value == "1";
|
||||
} else if (qname == "required") {
|
||||
required = value.length() == 0 || value == "1";
|
||||
} else if (qname == "user") {
|
||||
user = value;
|
||||
} else if (qname == "secret") {
|
||||
secret = value;
|
||||
}
|
||||
if (ret != RESULT_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((!user.empty() || !secret.empty()) && !m_userList.checkSecret(user, secret)) {
|
||||
ret = RESULT_ERR_NOTAUTHORIZED;
|
||||
}
|
||||
}
|
||||
|
||||
result << "{";
|
||||
string lastCircuit = "";
|
||||
time_t maxLastUp = 0;
|
||||
if (ret == RESULT_OK) {
|
||||
deque<Message *> messages = m_messages->findAll(circuit, name, exact, true, false, true);
|
||||
deque<Message *> messages = m_messages->findAll(circuit, name, getUserLevels(user), exact, true, false, true);
|
||||
|
||||
bool first = true;
|
||||
for (deque<Message*>::iterator it = messages.begin(); it != messages.end();) {
|
||||
@@ -1565,6 +1655,9 @@ string MainLoop::formatHttpResult(result_t ret, ostringstream& result, int type)
|
||||
case RESULT_ERR_OUT_OF_RANGE:
|
||||
result << "400 Bad Request";
|
||||
break;
|
||||
case RESULT_ERR_NOTAUTHORIZED:
|
||||
result << "403 Forbidden";
|
||||
break;
|
||||
default:
|
||||
result << "500 Internal Server Error";
|
||||
break;
|
||||
|
||||
+78
-7
@@ -22,9 +22,11 @@
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ebusd/bushandler.h"
|
||||
#include "ebusd/datahandler.h"
|
||||
#include "ebusd/network.h"
|
||||
#include "lib/ebus/filereader.h"
|
||||
#include "lib/ebus/message.h"
|
||||
#include "lib/utils/rotatefile.h"
|
||||
|
||||
@@ -34,6 +36,51 @@
|
||||
|
||||
namespace ebusd {
|
||||
|
||||
/**
|
||||
* Helper class for user authentication.
|
||||
*/
|
||||
class UserList : public UserInfo, public FileReader {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* @param defaultLevels the default access levels.
|
||||
*/
|
||||
explicit UserList(const string defaultLevels) : FileReader::FileReader(false) {
|
||||
m_userLevels[""] = defaultLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~UserList() {}
|
||||
|
||||
// @copydoc
|
||||
virtual result_t addFromFile(vector<string>::iterator& begin, const vector<string>::iterator end,
|
||||
vector< vector<string> >* defaults, const string& defaultDest, const string& defaultCircuit,
|
||||
const string& defaultSuffix, const string& filename, unsigned int lineNo);
|
||||
|
||||
// @copydoc
|
||||
virtual bool hasUser(const string user) {
|
||||
return m_userLevels.find(user) != m_userLevels.end();
|
||||
}
|
||||
|
||||
// @copydoc
|
||||
virtual bool checkSecret(const string user, const string secret) {
|
||||
return m_userSecrets.find(user) != m_userSecrets.end() && m_userSecrets[user] == secret;
|
||||
}
|
||||
|
||||
// @copydoc
|
||||
virtual string getLevels(const string user) { return m_userLevels[user]; }
|
||||
|
||||
private:
|
||||
/** the secret string by user name. */
|
||||
map<string, string> m_userSecrets;
|
||||
|
||||
/** the access levels by user name (separated by semicolon, empty name for default levels). */
|
||||
map<string, string> m_userLevels;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The main loop handling requests from connected clients.
|
||||
*/
|
||||
@@ -83,7 +130,8 @@ class MainLoop : public Thread, DeviceListener {
|
||||
* @param reload set to true when the configuration files were reloaded.
|
||||
* @return result string to send back to the client.
|
||||
*/
|
||||
string decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening, bool& reload);
|
||||
string decodeMessage(const string& data, const bool isHttp, bool& connected, bool& listening,
|
||||
string& user, bool& reload);
|
||||
|
||||
/**
|
||||
* Parse the hex master message from the remaining arguments.
|
||||
@@ -95,18 +143,35 @@ class MainLoop : public Thread, DeviceListener {
|
||||
result_t parseHexMaster(vector<string> &args, size_t argPos, SymbolString& master);
|
||||
|
||||
/**
|
||||
* Execute the read command.
|
||||
* Get the access levels associated with the specified user name.
|
||||
* @param user the user name, or empty for default levels.
|
||||
* @return the access levels separated by semicolon.
|
||||
*/
|
||||
string getUserLevels(const string user) { return m_userList.getLevels(user); }
|
||||
|
||||
/**
|
||||
* Execute the auth command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param user the current user name to set to the new user name on success.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeRead(vector<string> &args);
|
||||
string executeAuth(vector<string> &args, string &user);
|
||||
|
||||
/**
|
||||
* Execute the read command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param level the current user's access levels.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeRead(vector<string> &args, const string levels);
|
||||
|
||||
/**
|
||||
* Execute the write command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param level the current user's access levels.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeWrite(vector<string> &args);
|
||||
string executeWrite(vector<string> &args, const string levels);
|
||||
|
||||
/**
|
||||
* Execute the hex command.
|
||||
@@ -118,9 +183,10 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/**
|
||||
* Execute the find command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param level the current user's access levels.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeFind(vector<string> &args);
|
||||
string executeFind(vector<string> &args, string levels);
|
||||
|
||||
/**
|
||||
* Execute the listen command.
|
||||
@@ -147,9 +213,10 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/**
|
||||
* Execute the scan command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param level the current user's access levels.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeScan(vector<string> &args);
|
||||
string executeScan(vector<string> &args, const string levels);
|
||||
|
||||
/**
|
||||
* Execute the log command.
|
||||
@@ -182,9 +249,10 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/**
|
||||
* Execute the info command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param user the current user name.
|
||||
* @return the result string.
|
||||
*/
|
||||
string executeInfo(vector<string> &args);
|
||||
string executeInfo(vector<string> &args, const string user);
|
||||
|
||||
/**
|
||||
* Execute the quit command.
|
||||
@@ -232,6 +300,9 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/** the @a RotateFile for dumping received data, or NULL. */
|
||||
RotateFile* m_dumpFile;
|
||||
|
||||
/** the @a UserList instance. */
|
||||
UserList m_userList;
|
||||
|
||||
/** the @a MessageMap instance. */
|
||||
MessageMap* m_messages;
|
||||
|
||||
|
||||
+13
-1
@@ -119,6 +119,12 @@ class NetMessage {
|
||||
*/
|
||||
string getRequest() const { return m_request; }
|
||||
|
||||
/**
|
||||
* Return the current user name.
|
||||
* @return the current user name.
|
||||
*/
|
||||
string getUser() const { return m_user; }
|
||||
|
||||
/**
|
||||
* Wait for the result being set and return the result string.
|
||||
* @return the result string.
|
||||
@@ -141,13 +147,16 @@ class NetMessage {
|
||||
/**
|
||||
* Set the result string and notify the waiting thread.
|
||||
* @param result the result string.
|
||||
* @param user the new user name.
|
||||
* @param listening whether the client is in listening mode.
|
||||
* @param listenUntil the end time to which to updates were added (exclusive).
|
||||
* @param disconnect true when the client shall be disconnected.
|
||||
*/
|
||||
void setResult(const string result, const bool listening, const time_t listenUntil, const bool disconnect) {
|
||||
void setResult(const string result, const string user, const bool listening, const time_t listenUntil,
|
||||
const bool disconnect) {
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
m_result = result;
|
||||
m_user = user;
|
||||
m_disconnect = disconnect;
|
||||
m_listening = listening;
|
||||
m_listenSince = listenUntil;
|
||||
@@ -177,6 +186,9 @@ class NetMessage {
|
||||
/** the request string. */
|
||||
string m_request;
|
||||
|
||||
/** the current user name. */
|
||||
string m_user;
|
||||
|
||||
/** whether the result was already set. */
|
||||
bool m_resultSet;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user