source: ipc.c @ bce78c8

Last change on this file since bce78c8 was 3ddb7477, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-26T12:14:37Z

One total mess that doesn't do much yet, but reorganised some stuff and
untying the IRC and the core parts a little bit. Lots of work left to do.

  • Property mode set to 100644
File size: 14.5 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2006 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* IPC - communication between BitlBee processes                        */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "ipc.h"
29#include "commands.h"
30#ifndef _WIN32
31#include <sys/un.h>
32#endif
33
34GSList *child_list = NULL;
35
36static void ipc_master_cmd_client( irc_t *data, char **cmd )
37{
38        /* Normally data points at an irc_t block, but for the IPC master
39           this is different. We think this scary cast is better than
40           creating a new command_t structure, just to make the compiler
41           happy. */
42        struct bitlbee_child *child = (void*) data;
43       
44        if( child && cmd[1] )
45        {
46                child->host = g_strdup( cmd[1] );
47                child->nick = g_strdup( cmd[2] );
48                child->realname = g_strdup( cmd[3] );
49        }
50       
51        if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 )
52                ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n",
53                                     (int) ( child ? child->pid : -1 ), cmd[2], cmd[1], cmd[3] );
54}
55
56static void ipc_master_cmd_die( irc_t *data, char **cmd )
57{
58        if( global.conf->runmode == RUNMODE_FORKDAEMON )
59                ipc_to_children_str( "DIE\r\n" );
60       
61        bitlbee_shutdown( NULL, -1, 0 );
62}
63
64static void ipc_master_cmd_deaf( irc_t *data, char **cmd )
65{
66        if( global.conf->runmode == RUNMODE_DAEMON )
67        {
68                b_event_remove( global.listen_watch_source_id );
69                close( global.listen_socket );
70               
71                global.listen_socket = global.listen_watch_source_id = -1;
72       
73                ipc_to_children_str( "OPERMSG :Closed listening socket, waiting "
74                                     "for all users to disconnect." );
75        }
76        else
77        {
78                ipc_to_children_str( "OPERMSG :The DEAF command only works in "
79                                     "normal daemon mode. Try DIE instead." );
80        }
81}
82
83void ipc_master_cmd_rehash( irc_t *data, char **cmd )
84{
85        runmode_t oldmode;
86       
87        oldmode = global.conf->runmode;
88       
89        g_free( global.conf );
90        global.conf = conf_load( 0, NULL );
91       
92        if( global.conf->runmode != oldmode )
93        {
94                log_message( LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting" );
95                global.conf->runmode = oldmode;
96        }
97       
98        if( global.conf->runmode == RUNMODE_FORKDAEMON )
99                ipc_to_children( cmd );
100}
101
102void ipc_master_cmd_restart( irc_t *data, char **cmd )
103{
104        if( global.conf->runmode != RUNMODE_FORKDAEMON )
105        {
106                /* Tell child that this is unsupported. */
107                return;
108        }
109       
110        global.restart = -1;
111        bitlbee_shutdown( NULL, -1, 0 );
112}
113
114static const command_t ipc_master_commands[] = {
115        { "client",     3, ipc_master_cmd_client,     0 },
116        { "hello",      0, ipc_master_cmd_client,     0 },
117        { "die",        0, ipc_master_cmd_die,        0 },
118        { "deaf",       0, ipc_master_cmd_deaf,       0 },
119        { "wallops",    1, NULL,                      IPC_CMD_TO_CHILDREN },
120        { "wall",       1, NULL,                      IPC_CMD_TO_CHILDREN },
121        { "opermsg",    1, NULL,                      IPC_CMD_TO_CHILDREN },
122        { "rehash",     0, ipc_master_cmd_rehash,     0 },
123        { "kill",       2, NULL,                      IPC_CMD_TO_CHILDREN },
124        { "restart",    0, ipc_master_cmd_restart,    0 },
125        { NULL }
126};
127
128
129static void ipc_child_cmd_die( irc_t *irc, char **cmd )
130{
131        irc_abort( irc, 0, "Shutdown requested by operator" );
132}
133
134static void ipc_child_cmd_wallops( irc_t *irc, char **cmd )
135{
136        if( !( irc->status & USTATUS_LOGGED_IN ) )
137                return;
138       
139        if( strchr( irc->umode, 'w' ) )
140                irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] );
141}
142
143static void ipc_child_cmd_wall( irc_t *irc, char **cmd )
144{
145        if( !( irc->status & USTATUS_LOGGED_IN ) )
146                return;
147       
148        if( strchr( irc->umode, 's' ) )
149                irc_write( irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1] );
150}
151
152static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd )
153{
154        if( !( irc->status & USTATUS_LOGGED_IN ) )
155                return;
156       
157        if( strchr( irc->umode, 'o' ) )
158                irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1] );
159}
160
161static void ipc_child_cmd_rehash( irc_t *irc, char **cmd )
162{
163        runmode_t oldmode;
164       
165        oldmode = global.conf->runmode;
166       
167        g_free( global.conf );
168        global.conf = conf_load( 0, NULL );
169       
170        global.conf->runmode = oldmode;
171}
172
173static void ipc_child_cmd_kill( irc_t *irc, char **cmd )
174{
175        if( !( irc->status & USTATUS_LOGGED_IN ) )
176                return;
177       
178        if( nick_cmp( cmd[1], irc->user->nick ) != 0 )
179                return;         /* It's not for us. */
180       
181        irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] );
182        irc_abort( irc, 0, "Killed by operator: %s", cmd[2] );
183}
184
185static void ipc_child_cmd_hello( irc_t *irc, char **cmd )
186{
187        if( !( irc->status & USTATUS_LOGGED_IN ) )
188                ipc_to_master_str( "HELLO\r\n" );
189        else
190                ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
191}
192
193static const command_t ipc_child_commands[] = {
194        { "die",        0, ipc_child_cmd_die,         0 },
195        { "wallops",    1, ipc_child_cmd_wallops,     0 },
196        { "wall",       1, ipc_child_cmd_wall,        0 },
197        { "opermsg",    1, ipc_child_cmd_opermsg,     0 },
198        { "rehash",     0, ipc_child_cmd_rehash,      0 },
199        { "kill",       2, ipc_child_cmd_kill,        0 },
200        { "hello",      0, ipc_child_cmd_hello,       0 },
201        { NULL }
202};
203
204
205static void ipc_command_exec( void *data, char **cmd, const command_t *commands )
206{
207        int i, j;
208       
209        if( !cmd[0] )
210                return;
211       
212        for( i = 0; commands[i].command; i ++ )
213                if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 )
214                {
215                        /* There is no typo in this line: */
216                        for( j = 1; cmd[j]; j ++ ); j --;
217                       
218                        if( j < commands[i].required_parameters )
219                                break;
220                       
221                        if( commands[i].flags & IPC_CMD_TO_CHILDREN )
222                                ipc_to_children( cmd );
223                        else
224                                commands[i].execute( data, cmd );
225                       
226                        break;
227                }
228}
229
230/* Return just one line. Returns NULL if something broke, an empty string
231   on temporary "errors" (EAGAIN and friends). */
232static char *ipc_readline( int fd )
233{
234        char buf[513], *eol;
235        int size;
236       
237        /* Because this is internal communication, it should be pretty safe
238           to just peek at the message, find its length (by searching for the
239           end-of-line) and then just read that message. With internal
240           sockets and limites message length, messages should always be
241           complete. Saves us quite a lot of code and buffering. */
242        size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK );
243        if( size == 0 || ( size < 0 && !sockerr_again() ) )
244                return NULL;
245        else if( size < 0 ) /* && sockerr_again() */
246                return( g_strdup( "" ) );
247        else
248                buf[size] = 0;
249       
250        if( ( eol = strstr( buf, "\r\n" ) ) == NULL )
251                return NULL;
252        else
253                size = eol - buf + 2;
254       
255        if( recv( fd, buf, size, 0 ) != size )
256                return NULL;
257        else
258                return g_strndup( buf, size - 2 );
259}
260
261gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond )
262{
263        char *buf, **cmd;
264       
265        if( ( buf = ipc_readline( source ) ) )
266        {
267                cmd = irc_parse_line( buf );
268                if( cmd )
269                {
270                        ipc_command_exec( data, cmd, ipc_master_commands );
271                        g_free( cmd );
272                }
273                g_free( buf );
274        }
275        else
276        {
277                ipc_master_free_fd( source );
278        }
279       
280        return TRUE;
281}
282
283gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond )
284{
285        char *buf, **cmd;
286       
287        if( ( buf = ipc_readline( source ) ) )
288        {
289                cmd = irc_parse_line( buf );
290                if( cmd )
291                {
292                        ipc_command_exec( data, cmd, ipc_child_commands );
293                        g_free( cmd );
294                }
295                g_free( buf );
296        }
297        else
298        {
299                ipc_child_disable();
300        }
301       
302        return TRUE;
303}
304
305void ipc_to_master( char **cmd )
306{
307        if( global.conf->runmode == RUNMODE_FORKDAEMON )
308        {
309                char *s = irc_build_line( cmd );
310                ipc_to_master_str( "%s", s );
311                g_free( s );
312        }
313        else if( global.conf->runmode == RUNMODE_DAEMON )
314        {
315                ipc_command_exec( NULL, cmd, ipc_master_commands );
316        }
317}
318
319void ipc_to_master_str( char *format, ... )
320{
321        char *msg_buf;
322        va_list params;
323
324        va_start( params, format );
325        msg_buf = g_strdup_vprintf( format, params );
326        va_end( params );
327       
328        if( strlen( msg_buf ) > 512 )
329        {
330                /* Don't send it, it's too long... */
331        }
332        else if( global.conf->runmode == RUNMODE_FORKDAEMON )
333        {
334                if( global.listen_socket >= 0 )
335                        if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 )
336                                ipc_child_disable();
337        }
338        else if( global.conf->runmode == RUNMODE_DAEMON )
339        {
340                char **cmd, *s;
341               
342                if( ( s = strchr( msg_buf, '\r' ) ) )
343                        *s = 0;
344               
345                cmd = irc_parse_line( msg_buf );
346                ipc_command_exec( NULL, cmd, ipc_master_commands );
347                g_free( cmd );
348        }
349       
350        g_free( msg_buf );
351}
352
353void ipc_to_children( char **cmd )
354{
355        if( global.conf->runmode == RUNMODE_FORKDAEMON )
356        {
357                char *msg_buf = irc_build_line( cmd );
358                ipc_to_children_str( "%s", msg_buf );
359                g_free( msg_buf );
360        }
361        else if( global.conf->runmode == RUNMODE_DAEMON )
362        {
363                GSList *l;
364               
365                for( l = irc_connection_list; l; l = l->next )
366                        ipc_command_exec( l->data, cmd, ipc_child_commands );
367        }
368}
369
370void ipc_to_children_str( char *format, ... )
371{
372        char *msg_buf;
373        va_list params;
374
375        va_start( params, format );
376        msg_buf = g_strdup_vprintf( format, params );
377        va_end( params );
378       
379        if( strlen( msg_buf ) > 512 )
380        {
381                /* Don't send it, it's too long... */
382        }
383        else if( global.conf->runmode == RUNMODE_FORKDAEMON )
384        {
385                int msg_len = strlen( msg_buf );
386                GSList *l, *next;
387               
388                for( l = child_list; l; l = next )
389                {
390                        struct bitlbee_child *c = l->data;
391                       
392                        next = l->next;
393                        if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 )
394                        {
395                                ipc_master_free_one( c );
396                                child_list = g_slist_remove( child_list, c );
397                        }
398                }
399        }
400        else if( global.conf->runmode == RUNMODE_DAEMON )
401        {
402                char **cmd, *s;
403               
404                if( ( s = strchr( msg_buf, '\r' ) ) )
405                        *s = 0;
406               
407                cmd = irc_parse_line( msg_buf );
408                ipc_to_children( cmd );
409                g_free( cmd );
410        }
411       
412        g_free( msg_buf );
413}
414
415void ipc_master_free_one( struct bitlbee_child *c )
416{
417        b_event_remove( c->ipc_inpa );
418        closesocket( c->ipc_fd );
419       
420        g_free( c->host );
421        g_free( c->nick );
422        g_free( c->realname );
423        g_free( c );
424}
425
426void ipc_master_free_fd( int fd )
427{
428        GSList *l;
429        struct bitlbee_child *c;
430       
431        for( l = child_list; l; l = l->next )
432        {
433                c = l->data;
434                if( c->ipc_fd == fd )
435                {
436                        ipc_master_free_one( c );
437                        child_list = g_slist_remove( child_list, c );
438                        break;
439                }
440        }
441}
442
443void ipc_master_free_all()
444{
445        GSList *l;
446       
447        for( l = child_list; l; l = l->next )
448                ipc_master_free_one( l->data );
449       
450        g_slist_free( child_list );
451        child_list = NULL;
452}
453
454void ipc_child_disable()
455{
456        b_event_remove( global.listen_watch_source_id );
457        close( global.listen_socket );
458       
459        global.listen_socket = -1;
460}
461
462#ifndef _WIN32
463char *ipc_master_save_state()
464{
465        char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" );
466        int fd = mkstemp( fn );
467        GSList *l;
468        FILE *fp;
469        int i;
470       
471        if( fd == -1 )
472        {
473                log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) );
474                g_free( fn );
475                return NULL;
476        }
477       
478        /* This is more convenient now. */
479        fp = fdopen( fd, "w" );
480       
481        for( l = child_list, i = 0; l; l = l->next )
482                i ++;
483       
484        /* Number of client processes. */
485        fprintf( fp, "%d\n", i );
486       
487        for( l = child_list; l; l = l->next )
488                fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid,
489                                        ((struct bitlbee_child*)l->data)->ipc_fd );
490       
491        if( fclose( fp ) == 0 )
492        {
493                return fn;
494        }
495        else
496        {
497                unlink( fn );
498                g_free( fn );
499                return NULL;
500        }
501}
502
503
504static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond )
505{
506        struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 );
507       
508        child->ipc_fd = accept( serversock, NULL, 0 );
509       
510        if( child->ipc_fd == -1 )
511        {
512                log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) );
513                return TRUE;
514        }
515               
516        child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
517       
518        child_list = g_slist_append( child_list, child );
519       
520        return TRUE;
521}
522
523int ipc_master_listen_socket()
524{
525        struct sockaddr_un un_addr;
526        int serversock;
527
528        /* Clean up old socket files that were hanging around.. */
529        if (unlink(IPCSOCKET) == -1 && errno != ENOENT) {
530                log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) );
531                return 0;
532        }
533
534        un_addr.sun_family = AF_UNIX;
535        strcpy(un_addr.sun_path, IPCSOCKET);
536
537        serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
538
539        if (serversock == -1) {
540                log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) );
541                return 0;
542        }
543
544        if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) {
545                log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) );
546                return 0;
547        }
548
549        if (listen(serversock, 5) == -1) {
550                log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) );
551                return 0;
552        }
553       
554        b_input_add( serversock, GAIM_INPUT_READ, new_ipc_client, NULL );
555       
556        return 1;
557}
558#else
559int ipc_master_listen_socket()
560{
561        /* FIXME: Open named pipe \\.\BITLBEE */
562        return 0;
563}
564#endif
565
566int ipc_master_load_state( char *statefile )
567{
568        struct bitlbee_child *child;
569        FILE *fp;
570        int i, n;
571       
572        if( statefile == NULL )
573                return 0;
574       
575        fp = fopen( statefile, "r" );
576        unlink( statefile );    /* Why do it later? :-) */
577        if( fp == NULL )
578                return 0;
579       
580        if( fscanf( fp, "%d", &n ) != 1 )
581        {
582                log_message( LOGLVL_WARNING, "Could not import state information for child processes." );
583                fclose( fp );
584                return 0;
585        }
586       
587        log_message( LOGLVL_INFO, "Importing information for %d child processes.", n );
588        for( i = 0; i < n; i ++ )
589        {
590                child = g_new0( struct bitlbee_child, 1 );
591               
592                if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 )
593                {
594                        log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i );
595                        g_free( child );
596                        fclose( fp );
597                        return 0;
598                }
599                child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
600               
601                child_list = g_slist_append( child_list, child );
602        }
603       
604        ipc_to_children_str( "HELLO\r\n" );
605        ipc_to_children_str( "OPERMSG :New BitlBee master process started (version " BITLBEE_VERSION ")\r\n" );
606       
607        fclose( fp );
608        return 1;
609}
Note: See TracBrowser for help on using the repository browser.