Completely finish up the raw changes to the sysadm-server so it can operator over a bridge connection (untested).

This also adds some stand-alone CLI options to the sysadm-binary utility:
"bridge_list": List any bridge connections in the settings file. Output Format: "name (url)"
"bridge_add <name> <url>": Add a bridge connection to the settings with the given name. (if websocket server is running, this change will take effect within 5 minutes).
"bridge_remove <name>": Remove a bridge connection from the settings. If a websocket server is running, this change will take effect within 5 minutes (closing the connection to the removed bridge as needed).

There is also a new option in the global server config file:
BRIDGE_CONNECTIONS_ONLY=[true/false]
If true, this will allow the websocket server to run without listening on any ports, and instead force all traffic through the existing bridge connections.
This commit is contained in:
Ken Moore
2016-05-12 10:25:16 -04:00
parent 916069ce69
commit 8a16f9a4d0
7 changed files with 123 additions and 17 deletions

View File

@@ -233,9 +233,12 @@ QString AuthorizationManager::GenerateEncCheckString(){
QString AuthorizationManager::GenerateEncString_bridge(QString str){
//Get the private key
return str; //NOT IMPLEMENTED YET
QByteArray privkey = "";//SSL_cfg.privateKey().toPem();
QFile keyfile("/usr/local/etc/sysadm/ws_bridge.key");
keyfile.open(QIODevice::ReadOnly);
QSslKey key(&keyfile, QSsl::Rsa);
QByteArray privkey = key.toPem();
keyfile.close();
//Now use this private key to encode the given string
unsigned char encode[4098] = {};
RSA *rsa= NULL;

View File

@@ -33,7 +33,8 @@ bool WebServer::startServer(quint16 port, bool websocket){
qDebug() << " - Version:" << QSslSocket::sslLibraryVersionString();
}
bool ok = false;
if(websocket){ ok = setupWebSocket(port); }
if(websocket && BRIDGE_ONLY){ ok = true; }
else if(websocket){ ok = setupWebSocket(port); }
else{ ok = setupTcp(port); }
if(ok){
@@ -124,9 +125,11 @@ bool WebServer::allowConnection(QHostAddress addr){
}
QString WebServer::generateID(){
int id = 0;
int id = 1; //start integer ID's with 1
for(int i=0; i<OpenSockets.length(); i++){
if(OpenSockets[i]->ID().toInt()>=id){ id = OpenSockets[i]->ID().toInt()+1; }
bool ok=false;
int num = OpenSockets[i]->ID().toInt(&ok); //some ID's will not be a simple number (bridge connections)
if(ok && num>=id){ id = OpenSockets[i]->ID().toInt()+1; }
}
return QString::number(id);
}
@@ -216,5 +219,25 @@ void WebServer::SocketClosed(QString ID){
// BRIDGE Connection checks
void WebServer::checkBridges(){
if(!WS_MODE){ return; }
//Get all the unique bridge URL's we need connections to
QStringList bridgeKeys = CONFIG->allKeys().filter("bridge_connections/");
for(int i=0; i<bridgeKeys.length(); i++){
bridgeKeys[i] = CONFIG->value(bridgeKeys[i]).toString(); //turn the key into the URL for the bridge (unique ID)
}
//Now browse through all the current connections and see if any are already active
for(int i=0; i<OpenSockets.length(); i++){
if( bridgeKeys.contains( OpenSockets[i]->ID() ) ){
bridgeKeys.removeAll( OpenSockets[i]->ID() ); //already running - remove from the temporary list
}else if( OpenSockets[i]->ID().toInt()==0 ){
//non-integer ID - another bridge connection which must have been removed from the server settings
OpenSockets[i]->closeConnection();
}
}
//Now startup any connections which are missing
for(int i=0; i<bridgeKeys.length(); i++){
WebSocket *sock = new WebSocket(bridgeKeys[i], bridgeKeys[i], AUTH);
connect(sock, SIGNAL(SocketClosed(QString)), this, SLOT(SocketClosed(QString)) );
OpenSockets << sock;
}
}

View File

@@ -59,7 +59,7 @@ WebSocket::WebSocket(QSslSocket *sock, QString ID, AuthorizationManager *auth){
QTimer::singleShot(30000, this, SLOT(checkAuth()));
}
WebSocket::WebSocket(QUrl url, QString ID, AuthorizationManager *auth){
WebSocket::WebSocket(QString url, QString ID, AuthorizationManager *auth){
//sets up a bridge connection (websocket only)
SockID = ID;
isBridge = true;
@@ -81,7 +81,12 @@ WebSocket::WebSocket(QUrl url, QString ID, AuthorizationManager *auth){
connect(SOCKET, SIGNAL(connected()), this, SLOT(startBridgeAuth()) );
//idletimer->start(); //do not idle out on a bridge connection
/*QTimer::singleShot(30000, this, SLOT(checkAuth()));*/
SOCKET->open(url);
//Assemble the URL as needed
if(!url.startsWith("wss://")){ url.prepend("wss://"); }
bool hasport = false;
url.section(":",-1).toInt(&hasport); //check if the last piece of the url is a valid number
if(!hasport){ url.append(":"+QString::number(BRIDGEPORTNUMBER)); }
SOCKET->open(QUrl(url));
}
WebSocket::~WebSocket(){
@@ -101,6 +106,15 @@ QString WebSocket::ID(){
return SockID;
}
void WebSocket::closeConnection(){
if(SOCKET!=0 && SOCKET->isValid()){
SOCKET->close();
}
if(TSOCKET!=0 && TSOCKET->isValid()){
TSOCKET->close();
}
}
//=======================
// PRIVATE
//=======================
@@ -285,6 +299,10 @@ if(out.in_struct.namesp.toLower() == "rpc"){
for(int i=0; i<evlist.length(); i++){
EventWatcher::EVENT_TYPE type = EventWatcher::typeFromString(evlist[i]);
//qDebug() << " - type:" << type;
if(isBridge){
ForwardEvents.clear();
if(!REQ.bridgeID.isEmpty()){ ForwardEvents = BRIDGE[REQ.bridgeID].sendEvents; }
}
if(type==EventWatcher::BADEVENT){ continue; }
outargs.insert(out.in_struct.name,QJsonValue(evlist[i]));
if(sub==1){
@@ -293,6 +311,7 @@ if(out.in_struct.namesp.toLower() == "rpc"){
}else{
ForwardEvents.removeAll(type);
}
if(isBridge && !REQ.bridgeID.isEmpty()){ BRIDGE[REQ.bridgeID].sendEvents = ForwardEvents; }
}
out.out_args = outargs;
out.CODE = RestOutputStruct::OK;
@@ -542,14 +561,29 @@ void WebSocket::EventUpdate(EventWatcher::EVENT_TYPE evtype, QJsonValue msg){
//qDebug() << "Got Socket Event Update:" << msg;
if(msg.isNull()){ msg = EVENTS->lastEvent(evtype); }
if(msg.isNull()){ return; } //nothing to send
if( !ForwardEvents.contains(evtype) ){ return; }
if( !ForwardEvents.contains(evtype) && !isBridge ){ return; }
RestOutputStruct out;
out.CODE = RestOutputStruct::OK;
out.in_struct.namesp = "events";
out.out_args = msg;
out.Header << "Content-Type: text/json; charset=utf-8"; //REST header info
//out.Header << "Content-Type: text/json; charset=utf-8"; //REST header info
out.in_struct.name = EventWatcher::typeToString(evtype);
//qDebug() << "Send Event:" << out.assembleMessage();
//Now send the message back through the socket
this->emit SendMessage(out.assembleMessage());
if(isBridge){
QString raw = out.assembleMessage();
QStringList conns = BRIDGE.keys();
for(int i=0; i<conns.length(); i++){
if( !BRIDGE[conns[i]].sendEvents.contains(evtype) ){ continue; }
//Encrypt the data with the proper key
QString key = BRIDGE[conns[i]].enc_key;
QString enc_data = raw;
if(!key.isEmpty()){ enc_data = AUTHSYSTEM->encryptString(raw, key); }
//Now add the destination ID
enc_data.prepend( conns[i]+"\n");
this->emit SendMessage(enc_data);
}
}else{
//NON-BRIDGE: Now send the message back through the socket
this->emit SendMessage(out.assembleMessage());
}
}

View File

@@ -13,6 +13,7 @@
struct bridge_data{
QString enc_key, auth_tok;
QList<EventWatcher::EVENT_TYPE> sendEvents;
};
class WebSocket : public QObject{
@@ -20,10 +21,11 @@ class WebSocket : public QObject{
public:
WebSocket(QWebSocket*, QString ID, AuthorizationManager *auth);
WebSocket(QSslSocket*, QString ID, AuthorizationManager *auth);
WebSocket(QUrl, QString ID, AuthorizationManager *auth); //sets up a bridge connection (websocket only)
WebSocket(QString url, QString ID, AuthorizationManager *auth); //sets up a bridge connection (websocket only)
~WebSocket();
QString ID();
void closeConnection();
private:
QTimer *idletimer;

View File

@@ -20,6 +20,7 @@ extern EventWatcher *EVENTS;
extern Dispatcher *DISPATCHER;
//Special defines
#define BRIDGEPORTNUMBER 12149 //Default port for a sysadm-bridge
#define WSPORTNUMBER 12150 // WebSocket server default port
#define PORTNUMBER 12151 // TCP server default port
@@ -27,5 +28,6 @@ extern Dispatcher *DISPATCHER;
extern int BlackList_BlockMinutes;
extern int BlackList_AuthFailsToBlock;
extern int BlackList_AuthFailResetMinutes;
extern bool BRIDGE_ONLY; //bridge-only mode (no listening on a socket)
#endif

View File

@@ -25,6 +25,7 @@ bool WS_MODE = false;
int BlackList_BlockMinutes = 60;
int BlackList_AuthFailsToBlock = 5;
int BlackList_AuthFailResetMinutes = 10;
bool BRIDGE_ONLY = false;
//Create the default logfile
QFile logfile;
@@ -64,7 +65,7 @@ inline QString ReadFile(QString path){
int main( int argc, char ** argv )
{
QCoreApplication a(argc, argv);
//Check whether running as root
if( getuid() != 0){
qDebug() << "sysadm-server must be started as root!";
@@ -73,13 +74,44 @@ int main( int argc, char ** argv )
//Evaluate input arguments
bool websocket = true;
bool setonly = false;
quint16 port = 0;
for(int i=1; i<argc; i++){
if( QString(argv[i])=="-rest" ){ websocket = false;}
else if( QString(argv[i])=="-p" && (i+1<argc) ){ i++; port = QString(argv[i]).toUInt(); }
else if( QString(argv[i]).startsWith("bridge_") ){
setonly = true;
QString opt = QString(argv[i]).section("_",1,-1);
if(opt=="list"){
QStringList bridges = CONFIG->allKeys().filter("bridge_connections/");
qDebug() << "Current Bridges:";
for(int i=0; i<bridges.length(); i++){
qDebug() << bridges[i].section("/",1,-1) + " ("+CONFIG->value(bridges[i]).toString()+")";
}
}else if(opt=="add" && argc > i+2){
QString name = QString(argv[i+1]);
QString url = QString(argv[i+2]);
CONFIG->setValue("bridge_connections/"+name, url);
qDebug() << "New Bridge Added:" << name+" ("+url+")";
i=i+2;
}else if(opt=="remove" && argc>i+1){
QString name = QString(argv[i+1]);
CONFIG->remove("bridge_connections/"+name);
qDebug() << "Bridge Removed:" << name;
i=i+1;
}else{
qDebug() << "Unknown option:" << argv[i];
return 1;
}
}else{
qDebug() << "Unknown option:" << argv[1];
return 1;
}
}
if(setonly){ CONFIG->sync(); return 0; }
WS_MODE = websocket; //set the global variable too
QCoreApplication a(argc, argv);
//Now load the config file
QStringList conf = ReadFile(CONFFILE).split("\n");
if(!conf.filter("[internal]").isEmpty()){
@@ -123,7 +155,11 @@ int main( int argc, char ** argv )
int tmp = conf.filter(rg).first().section("=",1,1).simplified().toInt(&ok);
if(ok){ BlackList_AuthFailResetMinutes = tmp; }
}
rg = QRegExp("BRIDGE_CONNECTIONS_ONLY=*",Qt::CaseSensitive,QRegExp::Wildcard);
if(!conf.filter(rg).isEmpty()){
BRIDGE_ONLY = conf.filter(rg).first().section("=",1,1).simplified().toLower()=="true";
}
//Setup the log file
LogManager::checkLogDir(); //ensure the logging directory exists
if(!websocket){ logfile.setFileName("/var/log/sysadm-server-tcp.log"); }

View File

@@ -17,6 +17,12 @@ ssl_keygen()
-keyout /usr/local/etc/sysadm/wsserver.key \
-out /usr/local/etc/sysadm/wsserver.crt -days 1024 \
-subj "/C=US/ST=MY/L=NULL/O=SysAdm/OU=SysAdm/CN=SysAdm/emailAddress=none@example.org" 2>/dev/null
if [ -ne "/usr/local/etc/sysadm/ws_bridge.key" ]; then
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout /usr/local/etc/sysadm/ws_bridge.key \
-out /usr/local/etc/sysadm/ws_bridge.crt -days 102400 \
-subj "/C=US/ST=MY/L=NULL/O=SysAdm/OU=SysAdm/CN=SysAdm/emailAddress=none@example.org" 2>/dev/null
fi
fi
}