/* * Remote Laboratory Authentication 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) 2012 Timothy Pearson * Raptor Engineering * http://www.raptorengineeringinc.com */ #include #include #include #include "auth_conn.h" /* exception handling */ struct exit_exception { int c; exit_exception(int c):c(c) { } }; /* The AuthSocket 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. */ AuthSocket::AuthSocket(int sock, TQObject *parent, const char *name) : TDEKerberosServerSocket(parent, name), m_criticalSection(0), m_stationID(-1), m_bound(false), m_servActive(false), m_servState(0), m_servClientSocket(NULL), m_servClientTimeout(NULL), m_loopTimer(NULL), m_config(static_cast(parent)->m_config), m_database(NULL), m_databaseStationsCursor(NULL), m_databaseServicesCursor(NULL), m_databaseServiceTypesCursor(NULL), m_databasePermissionsCursor(NULL), m_databaseActivityCursor(NULL) { // Initialize timers m_kerberosInitTimer = new TQTimer(); connect(m_kerberosInitTimer, SIGNAL(timeout()), this, SLOT(finishKerberosHandshake())); setServiceName("remotefpga"); m_loopBuffer.resize(8192); // 8kB line = 0; connect(this, SIGNAL(connectionClosed()), SLOT(connectionClosedHandler())); setSocket(sock); if (connectToDatabase() != 0) { exit(1); } } AuthSocket::~AuthSocket() { if (m_kerberosInitTimer) { m_kerberosInitTimer->stop(); delete m_kerberosInitTimer; m_kerberosInitTimer = NULL; } if (m_loopTimer) { m_loopTimer->stop(); delete m_loopTimer; m_loopTimer = NULL; } if (m_databaseStationsCursor) { delete m_databaseStationsCursor; } if (m_databaseServicesCursor) { delete m_databaseServicesCursor; } if (m_databaseServiceTypesCursor) { delete m_databaseServiceTypesCursor; } if (m_databasePermissionsCursor) { delete m_databasePermissionsCursor; } if (m_databaseActivityCursor) { delete m_databaseActivityCursor; } if (m_servClientSocket) { delete m_servClientSocket; } } void AuthSocket::close() { if (state() == TQSocket::Connected) { TDEKerberosServerSocket::close(); connectionClosedHandler(); } } void AuthSocket::connectionClosedHandler() { printf("[DEBUG] Connection from %s closed\n\r", m_remoteHost.ascii()); if (m_bound) { // Update database m_databaseActivityCursor->select(TQString("station='%1' AND username='%2' AND realmname='%3'").arg(m_stationID).arg(m_authenticatedUserName).arg(m_authenticatedRealmName)); if (m_databaseActivityCursor->next()) { m_databaseActivityCursor->primeDelete(); m_databaseActivityCursor->del(true); } } if (m_criticalSection > 0) { throw exit_exception(-1); } } void AuthSocket::initiateKerberosHandshake() { setUsingKerberos(true); m_kerberosInitTimer->start(100, TRUE); } void AuthSocket::finishKerberosHandshake() { if (kerberosStatus() == TDEKerberosServerSocket::KerberosInitializing) { m_kerberosInitTimer->start(100, TRUE); return; } if (kerberosStatus() == TDEKerberosServerSocket::KerberosInUse) { TQ_UINT32 magicnum = MAGIC_NUMBER; TQ_UINT32 protover = PROTOCOL_VERSION; TQDataStream ds(this); ds.setPrintableData(true); ds << magicnum; ds << protover; writeEndOfFrame(); enterCommandLoop(); return; } else { printf("[DEBUG] Connection from %s closed due to Kerberos failure\n\r", m_remoteHost.ascii()); fflush(stdout); close(); return; } } int AuthSocket::servLoop() { bool transferred_data; transferred_data = false; if (m_servActive) { TQString command; TQDataStream ds(this); ds.setPrintableData(true); TDEKerberosClientSocket::KerberosStatus krbstat; switch (m_servState) { case 0: if (!m_servClientTimeout) { m_servClientTimeout = new TQTimer(); m_servClientTimeout->start(5000, TRUE); } if ((m_servClientSocket->state() == TQSocket::Connecting) || (m_servClientSocket->state() == TQSocket::HostLookup)) { if (!m_servClientTimeout->isActive()) { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } } else { if (m_servClientTimeout) { m_servClientTimeout->stop(); delete m_servClientTimeout; m_servClientTimeout = NULL; } transferred_data = true; m_servState = 1; } break; case 1: if (m_servClientSocket->state() == TQSocket::Connected) { m_servClientSocket->setUsingKerberos(true); m_servState = 2; } else { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } break; case 2: krbstat = m_servClientSocket->kerberosStatus(); if ((krbstat == TDEKerberosClientSocket::KerberosInitializing) || (krbstat == TDEKerberosClientSocket::KerberosInUse)) { if (krbstat == TDEKerberosClientSocket::KerberosInUse) { transferred_data = true; m_servState = 3; } } else { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s due to Kerberos failure\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } break; case 3: if (!m_servClientTimeout) { m_servClientTimeout = new TQTimer(); m_servClientTimeout->start(5000, TRUE); } if (m_servClientSocket->state() == TQSocket::Connected) { if (m_servClientSocket->canReadFrame()) { TQDataStream clientDS(m_servClientSocket); clientDS.setPrintableData(true); TQString server_reply; clientDS >> server_reply; m_servClientSocket->clearFrameTail(); if (server_reply == "OK") { ds << TQString("OK"); writeEndOfFrame(); transferred_data = true; m_servState = 4; } else { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s due to remote server returning %s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii(), server_reply.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } } else { if (!m_servClientTimeout->isActive()) { // Timeout! m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } } } else { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection failed to %s:%d for user %s@%s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; delete m_servClientTimeout; m_servClientTimeout = NULL; close(); } break; case 4: if (m_servClientSocket->state() == TQSocket::Connected) { TQ_ULONG reclen; if (canReadData()) { reclen = readBlock(m_loopBuffer.data(), m_loopBuffer.size()); m_servClientSocket->writeBlock(m_loopBuffer.data(), reclen); transferred_data = true; } if (m_servClientSocket->canReadData()) { reclen = m_servClientSocket->readBlock(m_loopBuffer.data(), m_loopBuffer.size()); writeBlock(m_loopBuffer.data(), reclen); transferred_data = true; } } else { m_servClientSocket->close(); ds << TQString("ERRNOTAVL"); writeEndOfFrame(); printf("[DEBUG] Connection terminated by remote host %s:%d for user %s@%s\n\r", m_srvServiceHostName.ascii(), m_srvServicePort, m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_servActive = false; close(); } break; } } if (transferred_data) { return 1; } else { return 0; } } void AuthSocket::commandLoop() { bool transferred_data; if (m_servActive) { transferred_data = false; if (servLoop() == 1) { transferred_data = true; } if (transferred_data) { if (m_loopTimer) m_loopTimer->start(0, TRUE); } else { if (m_loopTimer) m_loopTimer->start(100, TRUE); } return; } m_criticalSection++; try { transferred_data = false; if (state() == TQSocket::Connected) { if (canReadFrame()) { TQString command; TQDataStream ds(this); ds.setPrintableData(true); transferred_data = true; ds >> command; clearFrameTail(); if (command != "") { printf("[DEBUG] Got command %s from user %s@%s\n\r", command.ascii(), m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); if (command == "LIST") { // Send list of available servers... m_slist.clear(); // Get all stations from the database m_databaseStationsCursor->select(); while (m_databaseStationsCursor->next()) { bool authorized = false; bool in_use = false; m_databasePermissionsCursor->select(TQString("station=%1").arg(m_databaseStationsCursor->value("pk").toInt())); while (m_databasePermissionsCursor->next()) { if (m_databasePermissionsCursor->value("username").toString() == m_authenticatedUserName) { authorized = true; } } m_databaseActivityCursor->select(TQString("station=%1").arg(m_databaseStationsCursor->value("pk").toInt())); while (m_databaseActivityCursor->next()) { if (m_databaseActivityCursor->value("username").toString() != "") { in_use = true; } } if ((authorized) && (!in_use)) { StationType st; st.id = m_databaseStationsCursor->value("pk").toInt(); st.name = m_databaseStationsCursor->value("name").toString(); st.description = m_databaseStationsCursor->value("description").toString(); m_databaseServicesCursor->select(TQString("station=%1").arg(m_databaseStationsCursor->value("pk").toInt())); while (m_databaseServicesCursor->next()) { m_databaseServiceTypesCursor->select(TQString("serviceid=%1").arg(m_databaseServicesCursor->value("servicetype").toInt())); ServiceType svt; if (m_databaseServiceTypesCursor->next()) { svt.type = m_databaseServiceTypesCursor->value("serviceid").toInt(); svt.name = m_databaseServiceTypesCursor->value("name").toString(); svt.description = m_databaseServiceTypesCursor->value("description").toString(); svt.clientLibrary = m_databaseServiceTypesCursor->value("client_library").toString(); svt.version = m_databaseServiceTypesCursor->value("version").toInt(); char tempchar; tempchar = m_databaseServiceTypesCursor->value("single_instance").toInt(); svt.singleInstance = (tempchar != 0); } if (svt.name == "") { svt.name = i18n(""); } if (svt.description == "") { svt.description = i18n(""); } st.services.append(svt); } m_slist.append(st); } } ds << m_slist; writeEndOfFrame(); } else if (command == "BIND") { // Get desired Station Type from client StationType st; ds >> st; clearFrameTail(); // Attempt to bind to station matching desired Service Type list... m_stationID = -1; // Ensure that this user is not already connected int activeID = -1; m_databaseActivityCursor->select(TQString("username='%1' AND realmname='%2'").arg(m_authenticatedUserName).arg(m_authenticatedRealmName)); if (m_databaseActivityCursor->next()) { activeID = m_databaseActivityCursor->value("station").toInt(); } if (activeID < 0) { for (StationList::Iterator it(m_slist.begin()); it != m_slist.end(); ++it) { if ((*it).services == st.services) { m_stationID = (*it).id; break; } } if (m_stationID < 0) { ds << TQString("ERRUNAVAL"); writeEndOfFrame(); } else { m_bound = true; // Update database TQSqlRecord *buffer = m_databaseActivityCursor->primeInsert(); buffer->setValue("station", m_stationID); buffer->setValue("username", m_authenticatedUserName); buffer->setValue("realmname", m_authenticatedRealmName); buffer->setValue("logontime", TQDateTime::currentDateTime().toTime_t()); m_databaseActivityCursor->insert(); ds << TQString("OK"); writeEndOfFrame(); } } else { ds << TQString("ERRPREVCN"); writeEndOfFrame(); } } else if (command == "SERV") { // Get client library name from the client TQString libname; ds >> libname; clearFrameTail(); printf("[DEBUG] SERV command parameter was %s from user %s@%s\n\r", libname.ascii(), m_authenticatedUserName.ascii(), m_authenticatedRealmName.ascii()); fflush(stdout); m_databaseActivityCursor->select(TQString("username='%1' AND realmname='%2'").arg(m_authenticatedUserName).arg(m_authenticatedRealmName)); if (m_databaseActivityCursor->next()) { m_stationID = m_databaseActivityCursor->value("station").toInt(); } if (m_bound == true) { ds << TQString("ERRINVCMD"); writeEndOfFrame(); } else { if (m_stationID < 0) { ds << TQString("ERRNOCONN"); writeEndOfFrame(); } else { // Find the service ID for the specified client library name TQ_INT32 sid = -1; m_databaseServiceTypesCursor->select(TQString("client_library='%1'").arg(libname)); if (m_databaseServiceTypesCursor->next()) { sid = m_databaseServiceTypesCursor->value("serviceid").toInt(); } if (sid < 0) { ds << TQString("ERRNOSERV"); writeEndOfFrame(); } else { // Attempt to connect to the backend server m_databaseServicesCursor->select(TQString("servicetype=%1 AND station=%2").arg(sid).arg(m_stationID)); if (m_databaseServicesCursor->next()) { m_srvServiceHostName = m_databaseServicesCursor->value("hostname").toString(); m_srvServicePort = m_databaseServicesCursor->value("port").toInt(); if (!m_servClientSocket) m_servClientSocket = new TDEKerberosClientSocket; m_servClientSocket->setServiceName("remotefpga"); m_servClientSocket->setServerFQDN(m_srvServiceHostName); m_servClientSocket->connectToHost(m_srvServiceHostName, m_srvServicePort); m_servState = 0; m_servActive = true; } else { ds << TQString("ERRNOSERV"); writeEndOfFrame(); } } } } } else { ds << TQString("ERRINVCMD"); writeEndOfFrame(); } } } 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 AuthSocket::enterCommandLoop() { 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 AuthSocket::connectToDatabase() { if (m_database) { return -2; } m_database = TQSqlDatabase::database(); if (!m_database) { printf("[ERROR] Database was not constructed by the application\n\r"); fflush(stdout); return -1; } m_databaseStationsCursor = new TQSqlCursor("stations", TRUE, m_database); m_databaseServicesCursor = new TQSqlCursor("services", TRUE, m_database); m_databaseServiceTypesCursor = new TQSqlCursor("servicetypes", TRUE, m_database); m_databasePermissionsCursor = new TQSqlCursor("permissions", TRUE, m_database); m_databaseActivityCursor = new TQSqlCursor("activity", TRUE, m_database); return 0; } /* The AuthServer class handles new connections to the server. For every client that connects, it creates a new AuthSocket -- that instance is now responsible for the communication with that client. */ AuthServer::AuthServer(TQObject* parent) : TQServerSocket( 4004, 1, parent ), m_database(NULL) { m_config = new KSimpleConfig("remotefpga_authserver.conf", false); if (connectToDatabase() != 0) { exit(1); } if ( !ok() ) { printf("[ERROR] Failed to bind to port 4004\n\r"); exit(1); } printf("[INFO] Server started on port 4004\n\r"); fflush(stdout); } AuthServer::~AuthServer() { if (m_database) { TQSqlDatabase::removeDatabase(m_database); m_database = NULL; } delete m_config; } int AuthServer::connectToDatabase() { m_config->setGroup("Database"); m_database = TQSqlDatabase::addDatabase(m_config->readEntry("driver")); m_database->setDatabaseName(m_config->readEntry("database")); m_database->setUserName(m_config->readEntry("username")); m_database->setPassword(m_config->readEntry("password")); m_database->setHostName(m_config->readEntry("server")); if(!m_database->open()) { printf("[ERROR] Failed to connect to control database on server '%s' [%s]\n\r", m_database->hostName().ascii(), m_database->lastError().text().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } if (!m_database->tables().contains("stations")) { m_database->close(); printf("[ERROR] Control database '%s' on '%s' does not contain the required 'stations' table\n\r", m_database->databaseName().ascii(), m_database->hostName().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } if (!m_database->tables().contains("services")) { m_database->close(); printf("[ERROR] Control database '%s' on '%s' does not contain the required 'services' table\n\r", m_database->databaseName().ascii(), m_database->hostName().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } if (!m_database->tables().contains("servicetypes")) { m_database->close(); printf("[ERROR] Control database '%s' on '%s' does not contain the required 'servicetypes' table\n\r", m_database->databaseName().ascii(), m_database->hostName().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } if (!m_database->tables().contains("permissions")) { m_database->close(); printf("[ERROR] Control database '%s' on '%s' does not contain the required 'permissions' table\n\r", m_database->databaseName().ascii(), m_database->hostName().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } if (!m_database->tables().contains("activity")) { m_database->close(); printf("[ERROR] Control database '%s' on '%s' does not contain the required 'activity' table\n\r", m_database->databaseName().ascii(), m_database->hostName().ascii()); fflush(stdout); TQSqlDatabase::removeDatabase(m_database); m_database = NULL; return -1; } return 0; } void AuthServer::newConnection(int socket) { AuthSocket *s = new AuthSocket(socket, this); s->m_remoteHost = s->peerAddress().toString(); printf("[DEBUG] New connection from %s\n\r", s->m_remoteHost.ascii()); connect(s, SIGNAL(connectionClosed()), s, SLOT(deleteLater())); s->initiateKerberosHandshake(); emit newConnect(s); }