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