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 | |
55 | typedef struct { |
56 | int socket; |
57 | FILE* out; |
58 | } ClientInfo; |
59 | |
60 | static int ServerSocket; |
61 | static ClientInfo ClientList[ MAX_CLIENTS ]; |
62 | static int SocketPort = -1; |
63 | static unsigned char BindToAllInterfaces = 0; |
64 | static int CurrentSocket; |
65 | static const char LockFile[] = "/var/run/ksysguardd.pid" ; |
66 | static const char *ConfigFile = KSYSGUARDDRCFILE; |
67 | |
68 | void signalHandler( int sig ); |
69 | void makeDaemon( void ); |
70 | void resetClientList( void ); |
71 | int addClient( int client ); |
72 | int delClient( int client ); |
73 | int createServerSocket( void ); |
74 | |
75 | /** |
76 | This variable is set to 1 if a module requests that the daemon should |
77 | be terminated. |
78 | */ |
79 | int 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 | */ |
85 | int 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 | */ |
91 | FILE* CurrentClient = 0; |
92 | |
93 | static 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 | |
124 | static 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 | |
136 | static 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 | |
180 | static 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 | |
200 | void 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 | |
232 | static 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 | |
259 | void 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 | */ |
272 | int 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 | */ |
301 | int 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 | |
318 | int 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 | |
368 | static 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 | |
396 | static 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 | |
405 | static 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 | |
457 | static 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 | |
474 | static 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 | */ |
496 | char* 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 |
538 | static 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 |
547 | int 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 | |