You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
634 lines
15 KiB
634 lines
15 KiB
/*
|
|
KSysGuard, the KDE System Guard
|
|
|
|
Copyright (c) 1999 - 2003 Chris Schlaeger <cs@kde.org>
|
|
Tobias Koenig <tokoe@kde.org>
|
|
|
|
Solaris support by Torsten Kasch <tk@Genetik.Uni-Bielefeld.DE>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of version 2 of the GNU General Public
|
|
License as published by the Free Software Foundation.
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/file.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <../version.h>
|
|
#ifdef HAVE_DNSSD
|
|
#include <dns_sd.h>
|
|
#endif
|
|
#include "modules.h"
|
|
|
|
#include "ksysguardd.h"
|
|
|
|
#define CMDBUFSIZE 128
|
|
#define MAX_CLIENTS 100
|
|
|
|
typedef struct {
|
|
int socket;
|
|
FILE* out;
|
|
} ClientInfo;
|
|
|
|
static int ServerSocket;
|
|
static ClientInfo ClientList[ MAX_CLIENTS ];
|
|
static int SocketPort = -1;
|
|
static unsigned char BindToAllInterfaces = 0;
|
|
static int CurrentSocket;
|
|
static const char *LockFile = "/var/run/ksysguardd.pid";
|
|
static const char *ConfigFile = KSYSGUARDDRCFILE;
|
|
#ifdef HAVE_DNSSD
|
|
static int ServiceSocket = -1;
|
|
static DNSServiceRef Ref;
|
|
#endif
|
|
|
|
void signalHandler( int sig );
|
|
void makeDaemon( void );
|
|
void resetClientList( void );
|
|
int addClient( int client );
|
|
int delClient( int client );
|
|
int createServerSocket( void );
|
|
#ifdef HAVE_DNSSD
|
|
void publish_callback (DNSServiceRef, DNSServiceFlags, DNSServiceErrorType errorCode, const char *name,
|
|
const char*, const char*, void *context);
|
|
#endif
|
|
|
|
/**
|
|
This variable is set to 1 if a module requests that the daemon should
|
|
be terminated.
|
|
*/
|
|
int QuitApp = 0;
|
|
|
|
/**
|
|
This variable indicates whether we are running as daemon or (1) or if
|
|
we were have a controlling shell.
|
|
*/
|
|
int RunAsDaemon = 0;
|
|
|
|
/**
|
|
This pointer is used by all modules. It tqcontains the file pointer of
|
|
the currently served client. This is stdout for non-daemon mode.
|
|
*/
|
|
FILE* CurrentClient = 0;
|
|
|
|
static int processArguments( int argc, char* argv[] )
|
|
{
|
|
int option;
|
|
|
|
opterr = 0;
|
|
while ( ( option = getopt( argc, argv, "-p:f:dih" ) ) != EOF ) {
|
|
switch ( tolower( option ) ) {
|
|
case 'p':
|
|
SocketPort = atoi( optarg );
|
|
break;
|
|
case 'f':
|
|
ConfigFile = strdup( optarg );
|
|
break;
|
|
case 'd':
|
|
RunAsDaemon = 1;
|
|
break;
|
|
case 'i':
|
|
BindToAllInterfaces = 1;
|
|
break;
|
|
case '?':
|
|
case 'h':
|
|
default:
|
|
fprintf(stderr, "Usage: %s [-d] [-i] [-p port]\n", argv[ 0 ] );
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void printWelcome( FILE* out )
|
|
{
|
|
fprintf( out, "ksysguardd %s\n"
|
|
"(c) 1999, 2000, 2001, 2002 Chris Schlaeger <cs@kde.org> and\n"
|
|
"(c) 2001 Tobias Koenig <tokoe@kde.org>\n"
|
|
"This program is part of the KDE Project and licensed under\n"
|
|
"the GNU GPL version 2. See http://www.kde.org for details.\n",
|
|
KSYSGUARD_VERSION );
|
|
|
|
fflush( out );
|
|
}
|
|
|
|
static int createLockFile()
|
|
{
|
|
FILE *file;
|
|
|
|
if ( ( file = fopen( LockFile, "w+" ) ) != NULL ) {
|
|
struct flock lock;
|
|
lock.l_type = F_WRLCK;
|
|
lock.l_whence = 0;
|
|
lock.l_start = 0;
|
|
lock.l_len = 0;
|
|
lock.l_pid = -1;
|
|
if ( fcntl( fileno( file ), F_SETLK, &lock ) < 0 ) {
|
|
if ( ( errno == EACCES ) || ( errno == EAGAIN ) ) {
|
|
log_error( "ksysguardd is running already" );
|
|
fprintf( stderr, "ksysguardd is running already\n" );
|
|
fclose( file );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
fseek( file, 0, SEEK_SET );
|
|
fprintf( file, "%d\n", getpid() );
|
|
fflush( file );
|
|
ftruncate( fileno( file ), ftell( file ) );
|
|
} else {
|
|
log_error( "Cannot create lockfile '%s'", LockFile );
|
|
fprintf( stderr, "Cannot create lockfile '%s'\n", LockFile );
|
|
return -2;
|
|
}
|
|
|
|
/**
|
|
We abandon 'file' here on purpose. It's needed nowhere else, but we
|
|
have to keep the file open and locked. The kernel will remove the
|
|
lock when the process terminates and the runlevel scripts has to
|
|
remove the pid file.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
void signalHandler( int sig )
|
|
{
|
|
switch ( sig ) {
|
|
case SIGQUIT:
|
|
case SIGTERM:
|
|
#ifdef HAVE_DNSSD
|
|
if ( ServiceSocket != -1 ) DNSServiceRefDeallocate(Ref);
|
|
#endif
|
|
exit( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void installSignalHandler( void )
|
|
{
|
|
struct sigaction Action;
|
|
|
|
Action.sa_handler = signalHandler;
|
|
sigemptyset( &Action.sa_mask );
|
|
/* make sure that interrupted system calls are restarted. */
|
|
Action.sa_flags = SA_RESTART;
|
|
sigaction( SIGTERM, &Action, 0 );
|
|
sigaction( SIGQUIT, &Action, 0 );
|
|
}
|
|
|
|
static void dropPrivileges( void )
|
|
{
|
|
struct passwd *pwd;
|
|
|
|
if ( ( pwd = getpwnam( "nobody" ) ) != NULL ) {
|
|
if ( !setgid(pwd->pw_gid) )
|
|
setuid(pwd->pw_uid);
|
|
if (!geteuid() && getuid() != pwd->pw_uid)
|
|
_exit(1);
|
|
}
|
|
else {
|
|
log_error( "User 'nobody' does not exist." );
|
|
/**
|
|
We exit here to avoid becoming vulnerable just because
|
|
user nobody does not exist.
|
|
*/
|
|
_exit(1);
|
|
}
|
|
}
|
|
|
|
void makeDaemon( void )
|
|
{
|
|
int fd = -1;
|
|
switch ( fork() ) {
|
|
case -1:
|
|
log_error( "fork() failed" );
|
|
break;
|
|
case 0:
|
|
setsid();
|
|
chdir( "/" );
|
|
umask( 0 );
|
|
if ( createLockFile() < 0 )
|
|
_exit( 1 );
|
|
|
|
dropPrivileges();
|
|
installSignalHandler();
|
|
|
|
fd = open("/dev/null", O_RDWR, 0);
|
|
if (fd != -1) {
|
|
dup2(fd, STDIN_FILENO);
|
|
dup2(fd, STDOUT_FILENO);
|
|
dup2(fd, STDERR_FILENO);
|
|
close (fd);
|
|
}
|
|
break;
|
|
default:
|
|
exit( 0 );
|
|
}
|
|
}
|
|
|
|
static int readCommand( int fd, char* cmdBuf, size_t len )
|
|
{
|
|
unsigned int i;
|
|
char c;
|
|
for ( i = 0; i < len; ++i )
|
|
{
|
|
int result = read( fd, &c, 1 );
|
|
if (result < 0)
|
|
return -1; /* Error */
|
|
|
|
if (result == 0) {
|
|
if (i == 0)
|
|
return -1; /* Connection lost */
|
|
|
|
break; /* End of data */
|
|
}
|
|
|
|
if (c == '\n')
|
|
break; /* End of line */
|
|
|
|
cmdBuf[ i ] = c;
|
|
}
|
|
cmdBuf[i] = '\0';
|
|
|
|
return i;
|
|
}
|
|
|
|
void resetClientList( void )
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
ClientList[ i ].socket = -1;
|
|
ClientList[ i ].out = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
addClient adds a new client to the ClientList.
|
|
*/
|
|
int addClient( int client )
|
|
{
|
|
int i;
|
|
FILE* out;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( ClientList[ i ].socket == -1 ) {
|
|
ClientList[ i ].socket = client;
|
|
if ( ( out = fdopen( client, "w+" ) ) == NULL ) {
|
|
log_error( "fdopen()" );
|
|
return -1;
|
|
}
|
|
/* We use unbuffered IO */
|
|
fcntl( fileno( out ), F_SETFL, O_NDELAY );
|
|
ClientList[ i ].out = out;
|
|
printWelcome( out );
|
|
fprintf( out, "ksysguardd> " );
|
|
fflush( out );
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
delClient removes a client from the ClientList.
|
|
*/
|
|
int delClient( int client )
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( ClientList[i].socket == client ) {
|
|
fclose( ClientList[ i ].out );
|
|
ClientList[ i ].out = 0;
|
|
close( ClientList[ i ].socket );
|
|
ClientList[ i ].socket = -1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_DNSSD
|
|
void publish_callback (DNSServiceRef ref, DNSServiceFlags f, DNSServiceErrorType errorCode, const char *name,
|
|
const char* type, const char* domain, void *context)
|
|
{
|
|
if (errorCode != kDNSServiceErr_NoError) log_error("Publishing DNS-SD service failed with error %i",errorCode);
|
|
}
|
|
#endif
|
|
|
|
|
|
int createServerSocket()
|
|
{
|
|
int i = 1;
|
|
int newSocket;
|
|
struct sockaddr_in s_in;
|
|
struct servent *service;
|
|
|
|
if ( ( newSocket = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 ) {
|
|
log_error( "socket()" );
|
|
return -1;
|
|
}
|
|
|
|
setsockopt( newSocket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) );
|
|
|
|
/**
|
|
The -p command line option always overrides the default or the
|
|
service entry.
|
|
*/
|
|
if ( SocketPort == -1 ) {
|
|
if ( ( service = getservbyname( "ksysguardd", "tcp" ) ) == NULL ) {
|
|
/**
|
|
No entry in service directory and no command line request,
|
|
so we take the build-in default (the offical IANA port).
|
|
*/
|
|
SocketPort = PORT_NUMBER;
|
|
} else
|
|
SocketPort = htons( service->s_port );
|
|
}
|
|
|
|
memset( &s_in, 0, sizeof( struct sockaddr_in ) );
|
|
s_in.sin_family = AF_INET;
|
|
if ( BindToAllInterfaces )
|
|
s_in.sin_addr.s_addr = htonl( INADDR_ANY );
|
|
else
|
|
s_in.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
|
|
s_in.sin_port = htons( SocketPort );
|
|
|
|
if ( bind( newSocket, (struct sockaddr*)&s_in, sizeof( s_in ) ) < 0 ) {
|
|
log_error( "Cannot bind to port %d", SocketPort );
|
|
return -1;
|
|
}
|
|
|
|
if ( listen( newSocket, 5 ) < 0 ) {
|
|
log_error( "listen()" );
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_DNSSD
|
|
if ( BindToAllInterfaces )
|
|
if (DNSServiceRegister(&Ref, 0, 0, 0, "_ksysguard._tcp", RegisterDomain ?
|
|
RegisterDomain : "local.",NULL, htons(SocketPort), 0, 0, publish_callback, 0) == kDNSServiceErr_NoError)
|
|
ServiceSocket = DNSServiceRefSockFD(Ref);
|
|
#endif
|
|
|
|
return newSocket;
|
|
}
|
|
|
|
static int setupSelect( fd_set* fds )
|
|
{
|
|
int highestFD = ServerSocket;
|
|
FD_ZERO( fds );
|
|
/**
|
|
Fill the filedescriptor array with all relevant descriptors. If we
|
|
not in daemon mode we only need to watch stdin.
|
|
*/
|
|
if ( RunAsDaemon ) {
|
|
int i;
|
|
FD_SET( ServerSocket, fds );
|
|
#ifdef HAVE_DNSSD
|
|
if ( ServiceSocket != -1 ) {
|
|
FD_SET( ServiceSocket, fds );
|
|
if ( highestFD < ServiceSocket) highestFD = ServiceSocket;
|
|
}
|
|
#endif
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( ClientList[ i ].socket != -1 ) {
|
|
FD_SET( ClientList[ i ].socket, fds );
|
|
if ( highestFD < ClientList[ i ].socket )
|
|
highestFD = ClientList[ i ].socket;
|
|
}
|
|
}
|
|
} else {
|
|
FD_SET( STDIN_FILENO, fds );
|
|
if ( highestFD < STDIN_FILENO )
|
|
highestFD = STDIN_FILENO;
|
|
}
|
|
|
|
return highestFD;
|
|
}
|
|
|
|
static void checkModules()
|
|
{
|
|
struct SensorModul *entry;
|
|
|
|
for ( entry = SensorModulList; entry->configName != NULL; entry++ )
|
|
if ( entry->checkCommand != NULL && entry->available )
|
|
entry->checkCommand();
|
|
}
|
|
|
|
static void handleTimerEvent( struct timeval* tv, struct timeval* last )
|
|
{
|
|
struct timeval now;
|
|
gettimeofday( &now, NULL );
|
|
/* Check if the last event was really TIMERINTERVAL seconds ago */
|
|
if ( now.tv_sec - last->tv_sec >= TIMERINTERVAL ) {
|
|
/* If so, update all sensors and save current time to last. */
|
|
checkModules();
|
|
*last = now;
|
|
}
|
|
/**
|
|
Set tv so that the next timer event will be generated in
|
|
TIMERINTERVAL seconds.
|
|
*/
|
|
tv->tv_usec = last->tv_usec - now.tv_usec;
|
|
if ( tv->tv_usec < 0 ) {
|
|
tv->tv_usec += 1000000;
|
|
tv->tv_sec = last->tv_sec + TIMERINTERVAL - 1 - now.tv_sec;
|
|
} else
|
|
tv->tv_sec = last->tv_sec + TIMERINTERVAL - now.tv_sec;
|
|
}
|
|
|
|
static void handleSocketTraffic( int socketNo, const fd_set* fds )
|
|
{
|
|
char cmdBuf[ CMDBUFSIZE ];
|
|
|
|
if ( RunAsDaemon ) {
|
|
int i;
|
|
|
|
if ( FD_ISSET( socketNo, fds ) ) {
|
|
int clientsocket;
|
|
struct sockaddr addr;
|
|
kde_socklen_t addr_len = sizeof( struct sockaddr );
|
|
|
|
/* a new connection */
|
|
if ( ( clientsocket = accept( socketNo, &addr, &addr_len ) ) < 0 ) {
|
|
log_error( "accept()" );
|
|
exit( 1 );
|
|
} else
|
|
addClient( clientsocket );
|
|
}
|
|
|
|
#ifdef HAVE_DNSSD
|
|
if ( ServiceSocket != -1 && FD_ISSET( ServiceSocket, fds )) DNSServiceProcessResult(Ref);
|
|
#endif
|
|
|
|
for ( i = 0; i < MAX_CLIENTS; i++ ) {
|
|
if ( ClientList[ i ].socket != -1 ) {
|
|
CurrentSocket = ClientList[ i ].socket;
|
|
if ( FD_ISSET( ClientList[ i ].socket, fds ) ) {
|
|
ssize_t cnt;
|
|
if ( ( cnt = readCommand( CurrentSocket, cmdBuf, sizeof( cmdBuf ) - 1 ) ) <= 0 )
|
|
delClient( CurrentSocket );
|
|
else {
|
|
cmdBuf[ cnt ] = '\0';
|
|
if ( strncmp( cmdBuf, "quit", 4 ) == 0 )
|
|
delClient( CurrentSocket );
|
|
else {
|
|
CurrentClient = ClientList[ i ].out;
|
|
fflush( stdout );
|
|
executeCommand( cmdBuf );
|
|
fprintf( CurrentClient, "ksysguardd> " );
|
|
fflush( CurrentClient );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if ( FD_ISSET( STDIN_FILENO, fds ) ) {
|
|
if (readCommand( STDIN_FILENO, cmdBuf, sizeof( cmdBuf ) ) < 0) {
|
|
exit(0);
|
|
}
|
|
executeCommand( cmdBuf );
|
|
printf( "ksysguardd> " );
|
|
fflush( stdout );
|
|
}
|
|
}
|
|
|
|
static void initModules()
|
|
{
|
|
struct SensorModul *entry;
|
|
|
|
/* initialize all sensors */
|
|
initCommand();
|
|
|
|
for ( entry = SensorModulList; entry->configName != NULL; entry++ ) {
|
|
if ( entry->initCommand != NULL && sensorAvailable( entry->configName ) ) {
|
|
entry->available = 1;
|
|
entry->initCommand( entry );
|
|
}
|
|
}
|
|
|
|
ReconfigureFlag = 0;
|
|
}
|
|
|
|
static void exitModules()
|
|
{
|
|
struct SensorModul *entry;
|
|
|
|
for ( entry = SensorModulList; entry->configName != NULL; entry++ ) {
|
|
if ( entry->exitCommand != NULL && entry->available )
|
|
entry->exitCommand();
|
|
}
|
|
|
|
exitCommand();
|
|
}
|
|
|
|
/*
|
|
================================ public part =================================
|
|
*/
|
|
|
|
int main( int argc, char* argv[] )
|
|
{
|
|
fd_set fds;
|
|
struct timeval tv;
|
|
struct timeval last;
|
|
|
|
#ifdef OSTYPE_FreeBSD
|
|
/**
|
|
If we are not root or the executable does not belong to the
|
|
kmem group, ksysguardd will crash because of permission problems
|
|
for opening /dev/kmem
|
|
*/
|
|
struct group* grentry = NULL;
|
|
|
|
if ( geteuid() != 0 ) {
|
|
grentry = getgrnam( "kmem" );
|
|
if ( grentry == NULL ) {
|
|
fprintf( stderr, "the group kmem is missing on your system\n" );
|
|
return -1;
|
|
}
|
|
|
|
if ( getegid() != grentry->gr_gid ) {
|
|
fprintf( stderr, "ksysguardd can't be started because of permission conflicts!\n"
|
|
"Start the program as user 'root' or change its group to 'kmem' and set the sgid-bit\n" );
|
|
return -1;
|
|
}
|
|
|
|
endgrent();
|
|
}
|
|
#endif
|
|
|
|
printWelcome( stdout );
|
|
|
|
if ( processArguments( argc, argv ) < 0 )
|
|
return -1;
|
|
|
|
parseConfigFile( ConfigFile );
|
|
|
|
initModules();
|
|
|
|
if ( RunAsDaemon ) {
|
|
makeDaemon();
|
|
|
|
if ( ( ServerSocket = createServerSocket() ) < 0 )
|
|
return -1;
|
|
resetClientList();
|
|
} else {
|
|
fprintf( stdout, "ksysguardd> " );
|
|
fflush( stdout );
|
|
CurrentClient = stdout;
|
|
ServerSocket = 0;
|
|
}
|
|
|
|
tv.tv_sec = TIMERINTERVAL;
|
|
tv.tv_usec = 0;
|
|
gettimeofday( &last, NULL );
|
|
|
|
while ( !QuitApp ) {
|
|
int highestFD = setupSelect( &fds );
|
|
/* wait for communication or timeouts */
|
|
if ( select( highestFD + 1, &fds, NULL, NULL, &tv ) >= 0 ) {
|
|
handleTimerEvent( &tv, &last );
|
|
handleSocketTraffic( ServerSocket, &fds );
|
|
}
|
|
}
|
|
|
|
exitModules();
|
|
|
|
freeConfigFile();
|
|
|
|
return 0;
|
|
}
|