1/*
2 KSysGuard, the KDE System Guard
3
4 Copyright (c) 1999 - 2003 Chris Schlaeger <cs@kde.org>
5 Tobias Koenig <tokoe@kde.org>
6 2006 Greg Martyn <greg.martyn@gmail.com>
7
8 Solaris support by Torsten Kasch <tk@Genetik.Uni-Bielefeld.DE>
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of version 2 of the GNU General Public
12 License as published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23*/
24
25#include <config-workspace.h>
26#include <ctype.h>
27#include <fcntl.h>
28#include <netdb.h>
29#include <netinet/in.h>
30#include <pwd.h>
31#include <signal.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/file.h>
36#include <sys/socket.h>
37#include <sys/stat.h>
38#include <sys/time.h>
39#include <sys/types.h>
40#include <sys/wait.h>
41#include <unistd.h>
42#include <errno.h>
43
44#include "modules.h"
45
46#include "ksysguardd.h"
47
48#ifdef HAVE_SYS_INOTIFY_H
49#include <sys/inotify.h>
50#endif
51
52#define CMDBUFSIZE 128
53#define MAX_CLIENTS 100
54
55typedef struct {
56 int socket;
57 FILE* out;
58} ClientInfo;
59
60static int ServerSocket;
61static ClientInfo ClientList[ MAX_CLIENTS ];
62static int SocketPort = -1;
63static unsigned char BindToAllInterfaces = 0;
64static int CurrentSocket;
65static const char LockFile[] = "/var/run/ksysguardd.pid";
66static const char *ConfigFile = KSYSGUARDDRCFILE;
67
68void signalHandler( int sig );
69void makeDaemon( void );
70void resetClientList( void );
71int addClient( int client );
72int delClient( int client );
73int createServerSocket( void );
74
75/**
76 This variable is set to 1 if a module requests that the daemon should
77 be terminated.
78 */
79int QuitApp = 0;
80
81/**
82 This variable indicates whether we are running as daemon or (1) or if
83 we were have a controlling shell.
84 */
85int RunAsDaemon = 0;
86
87/**
88 This pointer is used by all modules. It contains the file pointer of
89 the currently served client. This is stdout for non-daemon mode.
90 */
91FILE* CurrentClient = 0;
92
93static int processArguments( int argc, char* argv[] )
94{
95 int option;
96
97 opterr = 0;
98 while ( ( option = getopt( argc, argv, "-p:f:dih" ) ) != EOF ) {
99 switch ( tolower( option ) ) {
100 case 'p':
101 SocketPort = atoi( optarg );
102 break;
103 case 'f':
104 ConfigFile = strdup( optarg );
105 break;
106 case 'd':
107 RunAsDaemon = 1;
108 break;
109 case 'i':
110 BindToAllInterfaces = 1;
111 break;
112 case '?':
113 case 'h':
114 default:
115 fprintf(stderr, "Usage: %s [-d] [-i] [-p port]\n", argv[ 0 ] );
116 return -1;
117 break;
118 }
119 }
120
121 return 0;
122}
123
124static void printWelcome( FILE* out )
125{
126 fprintf( out, "ksysguardd 4\n"
127 "(c) 1999, 2000, 2001, 2002 Chris Schlaeger <cs@kde.org>\n"
128 "(c) 2001 Tobias Koenig <tokoe@kde.org>\n"
129 "(c) 2006-2008 Greg Martyn <greg.martyn@gmail.com>\n"
130 "This program is part of the KDE Project and licensed under\n"
131 "the GNU GPL version 2. See http://www.kde.org for details.\n");
132
133 fflush( out );
134}
135
136static int createLockFile()
137{
138 FILE *file;
139
140 if ( ( file = fopen( LockFile, "w+" ) ) != NULL ) {
141 struct flock lock;
142 lock.l_type = F_WRLCK;
143 lock.l_whence = 0;
144 lock.l_start = 0;
145 lock.l_len = 0;
146 lock.l_pid = -1;
147 if ( fcntl( fileno( file ), F_SETLK, &lock ) < 0 ) {
148 if ( ( errno == EACCES ) || ( errno == EAGAIN ) ) {
149 log_error( "ksysguardd is running already" );
150 fprintf( stderr, "ksysguardd is running already\n" );
151 fclose( file );
152 return -1;
153 }
154 }
155
156 fseek( file, 0, SEEK_SET );
157 fprintf( file, "%d\n", getpid() );
158 fflush( file );
159 if (ftruncate( fileno( file ), ftell( file ) ) == -1) {
160 log_error( "Cannot set size of lockfile '%s'", LockFile );
161 fprintf( stderr, "Cannot set size of lockfile '%s'\n", LockFile );
162 fclose( file );
163 return -2;
164 }
165 } else {
166 log_error( "Cannot create lockfile '%s'", LockFile );
167 fprintf( stderr, "Cannot create lockfile '%s'\n", LockFile );
168 return -2;
169 }
170
171 /**
172 We abandon 'file' here on purpose. It's needed nowhere else, but we
173 have to keep the file open and locked. The kernel will remove the
174 lock when the process terminates and the runlevel scripts has to
175 remove the pid file.
176 */
177 return 0;
178}
179
180static void dropPrivileges( void )
181{
182 struct passwd *pwd;
183
184 if ( ( pwd = getpwnam( "nobody" ) ) != NULL ) {
185 if ( !setgid(pwd->pw_gid) )
186 setuid(pwd->pw_uid);
187 if (!geteuid() && getuid() != pwd->pw_uid)
188 _exit(1);
189 }
190 else {
191 log_error( "User 'nobody' does not exist." );
192 /**
193 We exit here to avoid becoming vulnerable just because
194 user nobody does not exist.
195 */
196 _exit(1);
197 }
198}
199
200void makeDaemon( void )
201{
202 int fd = -1;
203 switch ( fork() ) {
204 case -1:
205 log_error( "fork() failed" );
206 break;
207 case 0:
208 setsid();
209 if( chdir( "/" ) == -1) {
210 log_error("chdir(\"/\") failed");
211 _exit(1);
212 }
213 umask( 0 );
214 if ( createLockFile() < 0 )
215 _exit( 1 );
216
217 dropPrivileges();
218
219 fd = open("/dev/null", O_RDWR, 0);
220 if (fd != -1) {
221 dup2(fd, STDIN_FILENO);
222 dup2(fd, STDOUT_FILENO);
223 dup2(fd, STDERR_FILENO);
224 close (fd);
225 }
226 break;
227 default:
228 exit( 0 );
229 }
230}
231
232static int readCommand( int fd, char* cmdBuf, size_t len )
233{
234 unsigned int i;
235 char c;
236 for ( i = 0; i < len; ++i )
237 {
238 int result = read( fd, &c, 1 );
239 if (result < 0)
240 return -1; /* Error */
241
242 if (result == 0) {
243 if (i == 0)
244 return -1; /* Connection lost */
245
246 break; /* End of data */
247 }
248
249 if (c == '\n')
250 break; /* End of line */
251
252 cmdBuf[ i ] = c;
253 }
254 cmdBuf[i] = '\0';
255
256 return i;
257}
258
259void resetClientList( void )
260{
261 int i;
262
263 for ( i = 0; i < MAX_CLIENTS; i++ ) {
264 ClientList[ i ].socket = -1;
265 ClientList[ i ].out = 0;
266 }
267}
268
269/**
270 addClient adds a new client to the ClientList.
271 */
272int addClient( int client )
273{
274 int i;
275 FILE* out;
276
277 for ( i = 0; i < MAX_CLIENTS; i++ ) {
278 if ( ClientList[ i ].socket == -1 ) {
279 ClientList[ i ].socket = client;
280 if ( ( out = fdopen( client, "w+" ) ) == NULL ) {
281 log_error( "fdopen()" );
282 return -1;
283 }
284 /* We use unbuffered IO */
285 fcntl( fileno( out ), F_SETFL, O_NDELAY );
286 ClientList[ i ].out = out;
287 printWelcome( out );
288 fprintf( out, "ksysguardd> " );
289 fflush( out );
290
291 return 0;
292 }
293 }
294
295 return -1;
296}
297
298/**
299 delClient removes a client from the ClientList.
300 */
301int delClient( int client )
302{
303 int i;
304
305 for ( i = 0; i < MAX_CLIENTS; i++ ) {
306 if ( ClientList[i].socket == client ) {
307 fclose( ClientList[ i ].out );
308 ClientList[ i ].out = 0;
309 close( ClientList[ i ].socket );
310 ClientList[ i ].socket = -1;
311 return 0;
312 }
313 }
314
315 return -1;
316}
317
318int createServerSocket()
319{
320 int i = 1;
321 int newSocket;
322 struct sockaddr_in s_in;
323 struct servent *service;
324
325 if ( ( newSocket = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 ) {
326 log_error( "socket()" );
327 return -1;
328 }
329
330 setsockopt( newSocket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) );
331
332 /**
333 The -p command line option always overrides the default or the
334 service entry.
335 */
336 if ( SocketPort == -1 ) {
337 if ( ( service = getservbyname( "ksysguardd", "tcp" ) ) == NULL ) {
338 /**
339 No entry in service directory and no command line request,
340 so we take the build-in default (the official IANA port).
341 */
342 SocketPort = PORT_NUMBER;
343 } else
344 SocketPort = htons( service->s_port );
345 }
346
347 memset( &s_in, 0, sizeof( struct sockaddr_in ) );
348 s_in.sin_family = AF_INET;
349 if ( BindToAllInterfaces )
350 s_in.sin_addr.s_addr = htonl( INADDR_ANY );
351 else
352 s_in.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
353 s_in.sin_port = htons( SocketPort );
354
355 if ( bind( newSocket, (struct sockaddr*)&s_in, sizeof( s_in ) ) < 0 ) {
356 log_error( "Cannot bind to port %d", SocketPort );
357 return -1;
358 }
359
360 if ( listen( newSocket, 5 ) < 0 ) {
361 log_error( "listen()" );
362 return -1;
363 }
364
365 return newSocket;
366}
367
368static int setupSelect( fd_set* fds )
369{
370 int highestFD = ServerSocket;
371 FD_ZERO( fds );
372 /**
373 Fill the filedescriptor array with all relevant descriptors. If we
374 not in daemon mode we only need to watch stdin.
375 */
376 if ( RunAsDaemon ) {
377 int i;
378 FD_SET( ServerSocket, fds );
379
380 for ( i = 0; i < MAX_CLIENTS; i++ ) {
381 if ( ClientList[ i ].socket != -1 ) {
382 FD_SET( ClientList[ i ].socket, fds );
383 if ( highestFD < ClientList[ i ].socket )
384 highestFD = ClientList[ i ].socket;
385 }
386 }
387 } else {
388 FD_SET( STDIN_FILENO, fds );
389 if ( highestFD < STDIN_FILENO )
390 highestFD = STDIN_FILENO;
391 }
392
393 return highestFD;
394}
395
396static void checkModules()
397{
398 struct SensorModul *entry;
399
400 for ( entry = SensorModulList; entry->configName != NULL; entry++ )
401 if ( entry->checkCommand != NULL && entry->available )
402 entry->checkCommand();
403}
404
405static void handleSocketTraffic( int socketNo, const fd_set* fds )
406{
407 char cmdBuf[ CMDBUFSIZE ];
408
409 if ( RunAsDaemon ) {
410 int i;
411
412 if ( FD_ISSET( socketNo, fds ) ) {
413 int clientsocket;
414 struct sockaddr addr;
415 kde_socklen_t addr_len = sizeof( struct sockaddr );
416
417 /* a new connection */
418 if ( ( clientsocket = accept( socketNo, &addr, &addr_len ) ) < 0 ) {
419 log_error( "accept()" );
420 exit( 1 );
421 } else
422 addClient( clientsocket );
423 }
424
425 for ( i = 0; i < MAX_CLIENTS; i++ ) {
426 if ( ClientList[ i ].socket != -1 ) {
427 CurrentSocket = ClientList[ i ].socket;
428 if ( FD_ISSET( ClientList[ i ].socket, fds ) ) {
429 ssize_t cnt;
430 if ( ( cnt = readCommand( CurrentSocket, cmdBuf, sizeof( cmdBuf ) - 1 ) ) <= 0 )
431 delClient( CurrentSocket );
432 else {
433 cmdBuf[ cnt ] = '\0';
434 if ( strncmp( cmdBuf, "quit", 4 ) == 0 )
435 delClient( CurrentSocket );
436 else {
437 CurrentClient = ClientList[ i ].out;
438 fflush( stdout );
439 executeCommand( cmdBuf );
440 output( "ksysguardd> " );
441 fflush( CurrentClient );
442 }
443 }
444 }
445 }
446 }
447 } else if ( FD_ISSET( STDIN_FILENO, fds ) ) {
448 if (readCommand( STDIN_FILENO, cmdBuf, sizeof( cmdBuf ) ) < 0) {
449 exit(0);
450 }
451 executeCommand( cmdBuf );
452 printf( "ksysguardd> " );
453 fflush( stdout );
454 }
455}
456
457static void initModules()
458{
459 struct SensorModul *entry;
460
461 /* initialize all sensors */
462 initCommand();
463
464 for ( entry = SensorModulList; entry->configName != NULL; entry++ ) {
465 if ( entry->initCommand != NULL && sensorAvailable( entry->configName ) ) {
466 entry->available = 1;
467 entry->initCommand( entry );
468 }
469 }
470
471 ReconfigureFlag = 0;
472}
473
474static void exitModules()
475{
476 struct SensorModul *entry;
477
478 for ( entry = SensorModulList; entry->configName != NULL; entry++ ) {
479 if ( entry->exitCommand != NULL && entry->available )
480 entry->exitCommand();
481 }
482
483 exitCommand();
484}
485
486
487
488/*
489================================ public part =================================
490*/
491
492/*
493 * Will replace a "/" with "\/"
494 * Allocates a new string, so when calling this make sure to free the original.
495 */
496char* escapeString( char* string ) {
497 int i, length;
498 char* result;
499 char* resultP;
500 int charsToEscape = 0;
501
502 /* Count how many characters we need to escape so that we know how much memory we'll have to allocate */
503 i = 0;
504 while (string[i] != '\0') {
505 if( string[i] == '/' ) {
506 ++charsToEscape;
507 }
508
509 ++i;
510 }
511
512 /* Note: length doesn't count the \0 at the end of the string */
513 length = i;
514
515 /* Allocate a new string, result, with enough room for the escaped characters */
516 if(! (result = (char *)malloc( length + charsToEscape + 1 )) ) {
517 print_error("Malloc failed - out of memory");
518 exit(1);
519 }
520 resultP = result;
521 /* Fill result with an escaped version of string */
522 i = 0;
523 while (string[i] != '\0') {
524 if( string[i] == '/' ) {
525 resultP[0] = '\\';
526 resultP++;
527 }
528 resultP[0] = string[i];
529 resultP++;
530
531 ++i;
532 }
533 resultP[0] = '\0';
534 return result;
535}
536
537#ifdef HAVE_SYS_INOTIFY_H
538static void setupInotify(int *mtabfd) {
539 (*mtabfd) = inotify_init ();
540 if ((*mtabfd) >= 0) {
541 int wd = inotify_add_watch ((*mtabfd), "/etc/mtab", IN_MODIFY | IN_CREATE | IN_DELETE);
542 if(wd < 0) (*mtabfd) = -1; /* error setting up inotify watch */
543 }
544
545}
546#endif
547int main( int argc, char* argv[] )
548{
549 fd_set fds;
550
551 printWelcome( stdout );
552
553 if ( processArguments( argc, argv ) < 0 )
554 return -1;
555
556 parseConfigFile( ConfigFile );
557
558 initModules();
559
560 if ( RunAsDaemon ) {
561 makeDaemon();
562
563 if ( ( ServerSocket = createServerSocket() ) < 0 )
564 return -1;
565 resetClientList();
566 } else {
567 fprintf( stdout, "ksysguardd> " );
568 fflush( stdout );
569 CurrentClient = stdout;
570 ServerSocket = 0;
571 }
572
573#ifdef HAVE_SYS_INOTIFY_H
574 /* Monitor mtab for changes */
575 int mtabfd = 0;
576 setupInotify(&mtabfd);
577#endif
578
579 struct timeval now;
580 struct timeval last;
581 gettimeofday( &last, NULL );
582
583 while ( !QuitApp ) {
584 int highestFD = setupSelect( &fds );
585#ifdef HAVE_SYS_INOTIFY_H
586 if(mtabfd >= 0)
587 FD_SET( mtabfd, &fds);
588 if(mtabfd > highestFD) highestFD = mtabfd;
589#endif
590
591 /* wait for communication or timeouts */
592 int ret = select( highestFD + 1, &fds, NULL, NULL, NULL );
593 if(ret >= 0) {
594 gettimeofday( &now, NULL );
595 if ( now.tv_sec - last.tv_sec >= 5 ) { /* 5 second intervals */
596 /* If so, update all sensors and save current time to last. */
597 checkModules();
598 last = now;
599 }
600#ifdef HAVE_SYS_INOTIFY_H
601 if(mtabfd >= 0 && FD_ISSET(mtabfd, &fds)) {
602 close(mtabfd);
603 setupInotify(&mtabfd);
604 }
605#endif
606 handleSocketTraffic( ServerSocket, &fds );
607 }
608 }
609
610 exitModules();
611
612 freeConfigFile();
613
614 return 0;
615}
616