/*
* Remote Laboratory FPGA 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 <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 < SysCtlServer * > ( 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 ( ) , masterUser . ascii ( ) , masterRealm . 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 ( ) ;
TQSqlCursor databaseActivityCursor ( " sessions " , TRUE , m_terminals_database ) ;
databaseActivityCursor . select ( ) ;
while ( databaseActivityCursor . next ( ) ) {
TQ_UINT32 protocolVersion = 1 ;
TQDateTime loginStamp ;
TQDateTime activityStamp ;
loginStamp . setTime_t ( databaseActivityCursor . value ( " stamp_start " ) . toInt ( ) ) ;
activityStamp . setTime_t ( databaseActivityCursor . value ( " stamp_statechange " ) . toInt ( ) ) ;
ds < < protocolVersion ;
ds < < databaseActivityCursor . value ( " pk " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " username " ) . toString ( ) ;
ds < < databaseActivityCursor . value ( " servername " ) . toString ( ) ;
ds < < databaseActivityCursor . value ( " server_pid " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " wm_pid " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " state " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " display " ) . toInt ( ) ;
ds < < loginStamp ;
ds < < activityStamp ;
}
writeEndOfFrame ( ) ;
}
else if ( subCommand = = " WORKSPACES " ) {
clearFrameTail ( ) ;
TQSqlCursor databaseActivityCursor ( " activity " , TRUE , m_workspaces_database ) ;
databaseActivityCursor . select ( ) ;
while ( databaseActivityCursor . next ( ) ) {
TQ_UINT32 protocolVersion = 1 ;
TQDateTime loginStamp ;
loginStamp . setTime_t ( databaseActivityCursor . value ( " logontime " ) . toInt ( ) ) ;
ds < < protocolVersion ;
ds < < databaseActivityCursor . value ( " pk " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " station " ) . toInt ( ) ;
ds < < databaseActivityCursor . value ( " username " ) . toString ( ) ;
ds < < databaseActivityCursor . value ( " realmname " ) . toString ( ) ;
ds < < databaseActivityCursor . value ( " serverid " ) . toInt ( ) ;
ds < < loginStamp ;
}
writeEndOfFrame ( ) ;
}
else {
clearFrameTail ( ) ;
}
}
else if ( command = = " SESSION " ) {
TQString subCommand ;
TQString sessionID ;
ds > > subCommand ;
ds > > sessionID ;
if ( subCommand = = " LOGOFF_TERMINAL " ) {
TQ_UINT32 delay ;
ds > > delay ;
clearFrameTail ( ) ;
// FIXME
}
else if ( subCommand = = " CANCEL_LOGOFF_TERMINAL " ) {
clearFrameTail ( ) ;
// FIXME
}
else if ( subCommand = = " KILL_TERMINAL " ) {
clearFrameTail ( ) ;
// FIXME
}
else if ( subCommand = = " KILL_WORKSPACE " ) {
clearFrameTail ( ) ;
// FIXME
}
else {
clearFrameTail ( ) ;
}
}
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 SysCtlServer 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 .
*/
SysCtlServer : : SysCtlServer ( 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 ) ;
}
SysCtlServer : : ~ SysCtlServer ( ) {
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 SysCtlServer : : 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 SysCtlServer : : 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 SysCtlServer : : 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 SysCtlServer : : remoteConnectionClosed ( ) {
m_numberOfConnections - - ;
}