summaryrefslogtreecommitdiffstats
path: root/servers/admin_user_mgmt_server_lin/src
diff options
context:
space:
mode:
authorTimothy Pearson <kb9vqf@pearsoncomputing.net>2013-01-20 13:33:42 -0600
committerTimothy Pearson <kb9vqf@pearsoncomputing.net>2013-01-20 13:33:42 -0600
commit941d8c3b969eae32b0945987f1d849930736f5bb (patch)
tree5d979620ea6e26056d7dfb2e05e6b8260e272591 /servers/admin_user_mgmt_server_lin/src
parent7d92c9326f68ed879bf34aa5be0507d7a18cc8d6 (diff)
downloadulab-941d8c3b969eae32b0945987f1d849930736f5bb.tar.gz
ulab-941d8c3b969eae32b0945987f1d849930736f5bb.zip
Add inital copies of user management server and client for future development
Diffstat (limited to 'servers/admin_user_mgmt_server_lin/src')
-rw-r--r--servers/admin_user_mgmt_server_lin/src/Makefile.am11
-rw-r--r--servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.cpp486
-rw-r--r--servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.h106
-rw-r--r--servers/admin_user_mgmt_server_lin/src/main.cpp64
4 files changed, 667 insertions, 0 deletions
diff --git a/servers/admin_user_mgmt_server_lin/src/Makefile.am b/servers/admin_user_mgmt_server_lin/src/Makefile.am
new file mode 100644
index 0000000..6e2c003
--- /dev/null
+++ b/servers/admin_user_mgmt_server_lin/src/Makefile.am
@@ -0,0 +1,11 @@
+INCLUDES= $(all_includes) $(KDE_INCLUDES)/tde -I/usr/include/sasl
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+
+bin_PROGRAMS = remotefpga_adminsysctlserver
+
+remotefpga_adminsysctlserver_SOURCES = main.cpp admin_sys_ctl.cpp
+
+remotefpga_adminsysctlserver_METASOURCES = AUTO
+remotefpga_adminsysctlserver_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -lktexteditor -ltdekrbsocket -ltqtrla
+
+KDE_OPTIONS = nofinal
diff --git a/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.cpp b/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.cpp
new file mode 100644
index 0000000..bfb83bc
--- /dev/null
+++ b/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.cpp
@@ -0,0 +1,486 @@
+/*
+ * Remote Laboratory User Management Server
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (c) 2013 Timothy Pearson
+ * Raptor Engineering
+ * http://www.raptorengineeringinc.com
+ */
+
+#include <stdio.h> /* perror() */
+#include <stdlib.h> /* atoi() */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h> /* read() */
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+
+#include <tqtimer.h>
+#include <tqfile.h>
+
+#include <klocale.h>
+
+#include "admin_sys_ctl.h"
+
+#define ABORT_SOCKET(s) s->close(); \
+ s->disconnect(); \
+ delete s; \
+ s = NULL;
+
+#define NETWORK_COMM_TIMEOUT_MS 5000
+
+/* exception handling */
+struct exit_exception {
+ int c;
+ exit_exception(int c):c(c) { }
+};
+
+enum connectionStates {
+ StateIdle = 0
+};
+
+/*
+ The SysCtlSocket class provides a socket that is connected with a client.
+ For every client that connects to the server, the server creates a new
+ instance of this class.
+*/
+SysCtlSocket::SysCtlSocket(int sock, TQObject *parent, const char *name) :
+ TDEKerberosServerSocket(parent, name), m_criticalSection(0), m_loopTimer(NULL), m_config(static_cast<UserMgmtServer*>(parent)->m_config), m_terminals_database(NULL), m_workspaces_database(NULL), m_commandLoopState(StateIdle)
+{
+
+ // Initialize timers
+ m_kerberosInitTimer = new TQTimer();
+ connect(m_kerberosInitTimer, SIGNAL(timeout()), this, SLOT(finishKerberosHandshake()));
+ m_servClientTimeout = new TQTimer();
+
+ setServiceName("remotefpga");
+
+ line = 0;
+ connect(this, SIGNAL(connectionClosed()), SLOT(connectionClosedHandler()));
+ connect(this, SIGNAL(connectionClosed()), parent, SLOT(remoteConnectionClosed()));
+ setSocket(sock);
+
+ if (connectToDatabase() != 0) {
+ exit(1);
+ }
+}
+
+SysCtlSocket::~SysCtlSocket() {
+ if (m_servClientTimeout) {
+ m_servClientTimeout->stop();
+ delete m_servClientTimeout;
+ m_servClientTimeout = NULL;
+ }
+ if (m_kerberosInitTimer) {
+ m_kerberosInitTimer->stop();
+ delete m_kerberosInitTimer;
+ m_kerberosInitTimer = NULL;
+ }
+ if (m_loopTimer) {
+ m_loopTimer->stop();
+ delete m_loopTimer;
+ m_loopTimer = NULL;
+ }
+}
+
+void SysCtlSocket::close() {
+ if (state() == TQSocket::Connected) {
+ TDEKerberosServerSocket::close();
+ connectionClosedHandler();
+ TQTimer::singleShot(0, parent(), SLOT(remoteConnectionClosed()));
+ }
+}
+
+void SysCtlSocket::connectionClosedHandler() {
+ printf("[DEBUG] Connection from %s closed\n\r", m_remoteHost.ascii());
+
+ if (m_criticalSection > 0) {
+ throw exit_exception(-1);
+ }
+}
+
+void SysCtlSocket::initiateKerberosHandshake() {
+ setUsingKerberos(true);
+ m_kerberosInitTimer->start(100, TRUE);
+}
+
+void SysCtlSocket::finishKerberosHandshake() {
+ if (kerberosStatus() == TDEKerberosServerSocket::KerberosInitializing) {
+ m_kerberosInitTimer->start(100, TRUE);
+ return;
+ }
+ if (kerberosStatus() == TDEKerberosServerSocket::KerberosInUse) {
+ m_config->setGroup("Security");
+ TQString masterUser = m_config->readEntry("masteruser");
+ TQString masterRealm = m_config->readEntry("masterrealm");
+ if (masterRealm == "") {
+ masterRealm = "(NULL)";
+ }
+ if ((m_authenticatedUserName != masterUser) || (m_authenticatedRealmName != masterRealm)) {
+ printf("[DEBUG] Connection from %s closed due to authentication failure (attempted connection as user %s@%s)\n\r", m_remoteHost.ascii(), m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii());
+ close();
+ return;
+ }
+
+ setDataTimeout(NETWORK_COMM_TIMEOUT_MS);
+
+ TQDataStream ds(this);
+ ds.setPrintableData(true);
+ ds << TQString("OK");
+ writeEndOfFrame();
+
+ enterCommandLoop();
+ return;
+ }
+ else {
+ printf("[DEBUG] Connection from %s closed due to Kerberos failure\n\r", m_remoteHost.ascii()); fflush(stdout);
+ close();
+ return;
+ }
+}
+
+void SysCtlSocket::commandLoop() {
+ bool transferred_data;
+
+ m_criticalSection++;
+ try {
+ transferred_data = false;
+ if (state() == TQSocket::Connected) {
+ if (m_commandLoopState == StateIdle) {
+ // Certain commands can come in at any time during some operations
+ if (canReadLine()) {
+ processPendingData();
+ }
+ if (canReadFrame()) {
+ TQDataStream ds(this);
+ ds.setPrintableData(true);
+ TQString command;
+ ds >> command;
+ if (command == "USERS") {
+ TQString subCommand;
+ ds >> subCommand;
+ if (subCommand == "TERMINALS") {
+ clearFrameTail();
+ ds << TQString("OK");
+ TerminalServiceStatusList list;
+ TQSqlCursor databaseActivityCursor("sessions", TRUE, m_terminals_database);
+ databaseActivityCursor.select();
+ while (databaseActivityCursor.next()) {
+ TerminalServiceStatusType status;
+
+ status.protocolVersion = 1;
+ status.sessionID = databaseActivityCursor.value("pk").toInt();
+ status.username = databaseActivityCursor.value("username").toString();
+ status.serverName = databaseActivityCursor.value("servername").toString();
+ status.serverPID = databaseActivityCursor.value("server_pid").toInt();
+ status.wmPID = databaseActivityCursor.value("wm_pid").toInt();
+ status.state = databaseActivityCursor.value("state").toInt();
+ status.display = databaseActivityCursor.value("display").toInt();
+ status.loginStamp.setTime_t(databaseActivityCursor.value("stamp_start").toLongLong());
+ status.activityStamp.setTime_t(databaseActivityCursor.value("stamp_statechange").toLongLong());
+
+ list.append(status);
+ }
+ ds << list;
+ writeEndOfFrame();
+ }
+ else if (subCommand == "WORKSPACES") {
+ clearFrameTail();
+ ds << TQString("OK");
+ WorkspaceServiceStatusList list;
+ TQSqlCursor databaseActivityCursor("activity", TRUE, m_workspaces_database);
+ databaseActivityCursor.select();
+ while (databaseActivityCursor.next()) {
+ WorkspaceServiceStatusType status;
+
+ status.protocolVersion = 1;
+ status.sessionID = databaseActivityCursor.value("pk").toInt();
+ status.stationID = databaseActivityCursor.value("station").toInt();
+ status.username = databaseActivityCursor.value("username").toString();
+ status.realmname = databaseActivityCursor.value("realmname").toString();
+ status.serverID = databaseActivityCursor.value("serverid").toInt();
+ status.serviceID = databaseActivityCursor.value("serviceid").toInt();
+ status.stationID = databaseActivityCursor.value("station").toInt();
+ TQSqlCursor databaseStationsCursor("stations", TRUE, m_workspaces_database);
+ databaseStationsCursor.select(TQString("pk=%1").arg(status.stationID));
+ if (databaseStationsCursor.next()) {
+ status.stationName = databaseStationsCursor.value("name").toString();
+ }
+ status.loginStamp.setTime_t(databaseActivityCursor.value("logontime").toLongLong());
+ status.terminateStamp.setTime_t(databaseActivityCursor.value("terminate").toLongLong());
+
+ list.append(status);
+ }
+ ds << list;
+ writeEndOfFrame();
+ }
+ else {
+ clearFrameTail();
+ ds << TQString("ERRINVCMD");
+ writeEndOfFrame();
+ }
+ }
+ else if (command == "SESSION") {
+ TQString subCommand;
+ TQString sessionID;
+ ds >> subCommand;
+ ds >> sessionID;
+ if (subCommand == "LOGOFF_TERMINAL") {
+ TQ_UINT32 delay;
+ ds >> delay;
+ clearFrameTail();
+ // FIXME UNIMPLEMENTED
+ ds << TQString("ERRINVCMD");
+ writeEndOfFrame();
+ }
+ else if (subCommand == "CANCEL_LOGOFF_TERMINAL") {
+ clearFrameTail();
+ // FIXME UNIMPLEMENTED
+ ds << TQString("ERRINVCMD");
+ writeEndOfFrame();
+ }
+ else if (subCommand == "KILL_TERMINAL") {
+ clearFrameTail();
+ TQSqlCursor databaseActivityCursor("sessions", TRUE, m_terminals_database);
+ databaseActivityCursor.select(TQString("pk=%1").arg(sessionID));
+ if (databaseActivityCursor.next()) {
+ // Gather server information
+ TQString server_name = databaseActivityCursor.value("servername").toString();
+ int server_pid = databaseActivityCursor.value("server_pid").toInt();
+ // Kill server process
+ TQString command = TQString("ssh root@%1 'kill -9 %2'").arg(server_name).arg(server_pid);
+ if (system(command.ascii()) == 0) {
+ // Remove database entry
+ databaseActivityCursor.select(TQString("pk=%1").arg(sessionID));
+ if (databaseActivityCursor.next()) {
+ databaseActivityCursor.primeDelete();
+ databaseActivityCursor.del(true);
+ }
+ ds << TQString("OK");
+ }
+ else {
+ ds << TQString("ERRFAILED");
+ }
+ writeEndOfFrame();
+ }
+ else {
+ ds << TQString("ERRINVCMD");
+ }
+ writeEndOfFrame();
+ }
+ else if (subCommand == "KILL_WORKSPACE") {
+ TQ_INT32 terminationOffset;
+ ds >> terminationOffset;
+ clearFrameTail();
+ TQDateTime terminationTime = TQDateTime::currentDateTime();
+ terminationTime = terminationTime.addSecs(terminationOffset*60);
+ TQSqlCursor databaseActivityCursor("activity", TRUE, m_workspaces_database);
+ databaseActivityCursor.select(TQString("pk=%1").arg(sessionID));
+ if (databaseActivityCursor.next()) {
+ TQSqlRecord *buffer = databaseActivityCursor.primeUpdate();
+ if (terminationOffset > -2) {
+ buffer->setValue("terminate", terminationTime.toTime_t());
+ }
+ else {
+ buffer->setValue("terminate", 0);
+ }
+ databaseActivityCursor.update();
+
+ ds << TQString("OK");
+ }
+ else {
+ ds << TQString("ERRINVCMD");
+ }
+ writeEndOfFrame();
+ }
+ else if (subCommand == "CANCEL_KILL_WORKSPACE") {
+ clearFrameTail();
+ TQSqlCursor databaseActivityCursor("activity", TRUE, m_workspaces_database);
+ databaseActivityCursor.select(TQString("pk=%1").arg(sessionID));
+ if (databaseActivityCursor.next()) {
+ TQSqlRecord *buffer = databaseActivityCursor.primeUpdate();
+ buffer->setValue("terminate", 0);
+ databaseActivityCursor.update();
+
+ ds << TQString("OK");
+ }
+ else {
+ ds << TQString("ERRINVCMD");
+ }
+ writeEndOfFrame();
+ }
+ else {
+ clearFrameTail();
+ ds << TQString("ERRINVCMD");
+ writeEndOfFrame();
+ }
+ }
+ else {
+ clearFrameTail();
+ }
+ transferred_data = true;
+ }
+ }
+ }
+ m_criticalSection--;
+ if (transferred_data) {
+ if (m_loopTimer) m_loopTimer->start(0, TRUE);
+ }
+ else {
+ if (m_loopTimer) m_loopTimer->start(100, TRUE);
+ }
+ return;
+ }
+ catch (...) {
+ m_criticalSection--;
+ return;
+ }
+}
+
+int SysCtlSocket::enterCommandLoop() {
+ m_commandLoopState = StateIdle;
+ if (!m_loopTimer) {
+ m_loopTimer = new TQTimer();
+ connect(m_loopTimer, SIGNAL(timeout()), this, SLOT(commandLoop()));
+ }
+ if (m_loopTimer) m_loopTimer->start(0, TRUE);
+ return 0;
+}
+
+int SysCtlSocket::connectToDatabase() {
+ if ((m_terminals_database) && (m_workspaces_database)) {
+ return -2;
+ }
+
+ m_terminals_database = TQSqlDatabase::database("terminals");
+ m_workspaces_database = TQSqlDatabase::database("workspaces");
+ if ((!m_terminals_database) || (!m_workspaces_database)) {
+ printf("[ERROR] Databases were not constructed by the application\n\r"); fflush(stdout);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ The UserMgmtServer class handles new connections to the server. For every
+ client that connects, it creates a new SysCtlSocket -- that instance is now
+ responsible for the communication with that client.
+*/
+UserMgmtServer::UserMgmtServer(TQObject* parent, int port, KSimpleConfig* config) :
+ TQServerSocket( port, 1, parent ), m_config(config), m_numberOfConnections(0), m_terminals_database(NULL), m_workspaces_database(NULL), m_sqlPingTimer(NULL) {
+
+ if (connectToDatabase() != 0) {
+ exit(1);
+ }
+
+ if ( !ok() ) {
+ printf("[ERROR] Failed to bind to port %d\n\r", port);
+ exit(1);
+ }
+
+ printf("[INFO] Server started on port %d\n\r", port); fflush(stdout);
+}
+
+UserMgmtServer::~UserMgmtServer() {
+ if (m_sqlPingTimer) {
+ m_sqlPingTimer->stop();
+ delete m_sqlPingTimer;
+ m_sqlPingTimer = NULL;
+ }
+ if (m_terminals_database) {
+ TQSqlDatabase::removeDatabase(m_terminals_database);
+ m_terminals_database = NULL;
+ }
+ if (m_workspaces_database) {
+ TQSqlDatabase::removeDatabase(m_workspaces_database);
+ m_workspaces_database = NULL;
+ }
+}
+
+int UserMgmtServer::connectToDatabase() {
+ m_config->setGroup("Terminals Database");
+
+ m_terminals_database = TQSqlDatabase::addDatabase(m_config->readEntry("driver"), "terminals");
+ m_terminals_database->setDatabaseName(m_config->readEntry("database"));
+ m_terminals_database->setUserName(m_config->readEntry("username"));
+ m_terminals_database->setPassword(m_config->readEntry("password"));
+ m_terminals_database->setHostName(m_config->readEntry("server"));
+
+ if(!m_terminals_database->open()) {
+ printf("[ERROR] Failed to connect to control database on server '%s' [%s]\n\r", m_terminals_database->hostName().ascii(), m_terminals_database->lastError().text().ascii()); fflush(stdout);
+ TQSqlDatabase::removeDatabase(m_terminals_database);
+ m_terminals_database = NULL;
+ return -1;
+ }
+
+ m_config->setGroup("Workspaces Database");
+
+ m_workspaces_database = TQSqlDatabase::addDatabase(m_config->readEntry("driver"), "workspaces");
+ m_workspaces_database->setDatabaseName(m_config->readEntry("database"));
+ m_workspaces_database->setUserName(m_config->readEntry("username"));
+ m_workspaces_database->setPassword(m_config->readEntry("password"));
+ m_workspaces_database->setHostName(m_config->readEntry("server"));
+
+ if(!m_workspaces_database->open()) {
+ printf("[ERROR] Failed to connect to control database on server '%s' [%s]\n\r", m_workspaces_database->hostName().ascii(), m_workspaces_database->lastError().text().ascii()); fflush(stdout);
+ TQSqlDatabase::removeDatabase(m_workspaces_database);
+ m_workspaces_database = NULL;
+ return -1;
+ }
+
+ // FIXME
+ // We currently have no way to handle something as simple as the database server going offline!
+
+ // Start database ping process
+ m_sqlPingTimer = new TQTimer();
+ connect(m_sqlPingTimer, SIGNAL(timeout()), this, SLOT(pingSQLServer()));
+ m_sqlPingTimer->start(60*1000);
+
+ return 0;
+}
+
+void UserMgmtServer::pingSQLServer() {
+ TQSqlQuery terminals_query(TQString::null, m_terminals_database);
+ terminals_query.exec("SELECT * FROM sessions");
+ TQSqlQuery workspaces_query(TQString::null, m_workspaces_database);
+ workspaces_query.exec("SELECT * FROM activity");
+}
+
+void UserMgmtServer::newConnection(int socket) {
+ SysCtlSocket *s = new SysCtlSocket(socket, this);
+ s->m_remoteHost = s->peerAddress().toString();
+ printf("[DEBUG] New connection from %s\n\r", s->m_remoteHost.ascii());
+ if (m_numberOfConnections > 0) {
+ printf("[DEBUG] Connection from %s closed due to multiple access attempt\n\r", s->m_remoteHost.ascii());
+ ABORT_SOCKET(s)
+ return;
+ }
+ connect(s, SIGNAL(connectionClosed()), s, SLOT(deleteLater()));
+ s->initiateKerberosHandshake();
+ emit newConnect(s);
+}
+
+void UserMgmtServer::remoteConnectionClosed() {
+ m_numberOfConnections--;
+}
diff --git a/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.h b/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.h
new file mode 100644
index 0000000..4812424
--- /dev/null
+++ b/servers/admin_user_mgmt_server_lin/src/admin_sys_ctl.h
@@ -0,0 +1,106 @@
+/*
+ * Remote Laboratory User Management Server
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (c) 2013 Timothy Pearson
+ * Raptor Engineering
+ * http://www.raptorengineeringinc.com
+ */
+
+#include <tqsocket.h>
+#include <tqserversocket.h>
+#include <tqapplication.h>
+#include <tqvbox.h>
+#include <tqtextview.h>
+#include <tqlabel.h>
+#include <tqpushbutton.h>
+#include <tqtextstream.h>
+#include <tqsqldatabase.h>
+#include <tqsqlcursor.h>
+
+#include <ksimpleconfig.h>
+
+#include <tdekrbserversocket.h>
+
+#include <tqtrla.h>
+
+#define MAGIC_NUMBER 1
+#define PROTOCOL_VERSION 1
+
+class SysCtlSocket : public TDEKerberosServerSocket
+{
+ Q_OBJECT
+
+ public:
+ SysCtlSocket(int sock, TQObject *parent=0, const char *name=0);
+ ~SysCtlSocket();
+
+ public:
+ void close();
+ void initiateKerberosHandshake();
+ int enterCommandLoop();
+
+ private slots:
+ void finishKerberosHandshake();
+ void connectionClosedHandler();
+ void commandLoop();
+ int connectToDatabase();
+
+ private:
+ int line;
+ int m_criticalSection;
+ TQString m_remoteHost;
+
+ TQTimer* m_kerberosInitTimer;
+ TQTimer* m_loopTimer;
+ TQTimer* m_servClientTimeout;
+
+ KSimpleConfig* m_config;
+ TQSqlDatabase* m_terminals_database;
+ TQSqlDatabase* m_workspaces_database;
+ int m_commandLoopState;
+
+ friend class UserMgmtServer;
+};
+
+class UserMgmtServer : public TQServerSocket
+{
+ Q_OBJECT
+
+ public:
+ UserMgmtServer(TQObject* parent=0, int port=0, KSimpleConfig* config=0);
+ ~UserMgmtServer();
+
+ void newConnection(int socket);
+
+ private slots:
+ void remoteConnectionClosed();
+ int connectToDatabase();
+ void pingSQLServer();
+
+ signals:
+ void newConnect(SysCtlSocket*);
+
+ private:
+ KSimpleConfig* m_config;
+ int m_numberOfConnections;
+ TQSqlDatabase* m_terminals_database;
+ TQSqlDatabase* m_workspaces_database;
+ TQTimer* m_sqlPingTimer;
+
+ friend class SysCtlSocket;
+
+}; \ No newline at end of file
diff --git a/servers/admin_user_mgmt_server_lin/src/main.cpp b/servers/admin_user_mgmt_server_lin/src/main.cpp
new file mode 100644
index 0000000..b216a9b
--- /dev/null
+++ b/servers/admin_user_mgmt_server_lin/src/main.cpp
@@ -0,0 +1,64 @@
+/***************************************************************************
+ * Copyright (C) 2013 by Timothy Pearson *
+ * kb9vqf@pearsoncomputing.net *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <tqdatetime.h>
+#include <tqfile.h>
+#include <tqdir.h>
+
+#include <kapplication.h>
+#include <kstartupinfo.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <ksimpleconfig.h>
+
+#include "admin_sys_ctl.h"
+
+static const char description[] = I18N_NOOP("RemoteFPGA System Administration User Management Server");
+
+static const char version[] = "v0.0.1";
+
+int main(int argc, char *argv[])
+{
+ KAboutData aboutData( "remotefpga_admin_sys_ctl_server", I18N_NOOP("RemoteFPGA System Administration User Management Server"),
+ version, description, KAboutData::License_GPL,
+ "(c) 2013, Timothy Pearson");
+ aboutData.addAuthor("Timothy Pearson",0, "kb9vqf@pearsoncomputing.net");
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ KApplication::disableAutoDcopRegistration();
+
+ KApplication app(false, false);
+
+ KStartupInfo::appStarted();
+
+ KSimpleConfig config("remotefpga_adminusermgmtserver.conf", false);
+ config.setGroup("Server");
+ SysCtlServer fpgasvr(0, config.readNumEntry("port", 4016), &config);
+ return app.exec();
+
+}