diff options
Diffstat (limited to 'ipc.c')
-rw-r--r-- | ipc.c | 993 |
1 files changed, 993 insertions, 0 deletions
@@ -0,0 +1,993 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* IPC - communication between BitlBee processes */ + +/* + 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 2 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 with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ipc.h" +#include "commands.h" +#ifndef _WIN32 +#include <sys/uio.h> +#include <sys/un.h> +#endif + +GSList *child_list = NULL; +static int ipc_child_recv_fd = -1; + +static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ); +static gboolean ipc_send_fd( int fd, int send_fd ); + +/* On Solaris and possibly other systems passing FDs between processes is + * not possible (or at least not using the method used in this file. + * Just disable that code, the functionality is not that important. */ +#if defined(NO_FD_PASSING) && !defined(CMSG_SPACE) +#define CMSG_SPACE(len) 1 +#endif + +static void ipc_master_cmd_client( irc_t *data, char **cmd ) +{ + /* Normally data points at an irc_t block, but for the IPC master + this is different. We think this scary cast is better than + creating a new command_t structure, just to make the compiler + happy. */ + struct bitlbee_child *child = (void*) data; + + if( child && cmd[1] ) + { + child->host = g_strdup( cmd[1] ); + child->nick = g_strdup( cmd[2] ); + child->realname = g_strdup( cmd[3] ); + } + + /* CLIENT == On initial connects, HELLO is after /RESTARTs. */ + if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 ) + ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n", + (int) ( child ? child->pid : -1 ), cmd[2], cmd[1], cmd[3] ); +} + +static void ipc_master_cmd_nick( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data; + + if( child && cmd[1] ) + { + g_free( child->nick ); + child->nick = g_strdup( cmd[1] ); + } +} + +static void ipc_master_cmd_die( irc_t *data, char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_to_children_str( "DIE\r\n" ); + + bitlbee_shutdown( NULL, -1, 0 ); +} + +static void ipc_master_cmd_deaf( irc_t *data, char **cmd ) +{ + if( global.conf->runmode == RUNMODE_DAEMON ) + { + b_event_remove( global.listen_watch_source_id ); + close( global.listen_socket ); + + global.listen_socket = global.listen_watch_source_id = -1; + + ipc_to_children_str( "OPERMSG :Closed listening socket, waiting " + "for all users to disconnect." ); + } + else + { + ipc_to_children_str( "OPERMSG :The DEAF command only works in " + "normal daemon mode. Try DIE instead." ); + } +} + +void ipc_master_cmd_rehash( irc_t *data, char **cmd ) +{ + runmode_t oldmode; + + oldmode = global.conf->runmode; + + g_free( global.conf ); + global.conf = conf_load( 0, NULL ); + + if( global.conf->runmode != oldmode ) + { + log_message( LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting" ); + global.conf->runmode = oldmode; + } + + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_to_children( cmd ); +} + +void ipc_master_cmd_restart( irc_t *data, char **cmd ) +{ + if( global.conf->runmode != RUNMODE_FORKDAEMON ) + { + /* Tell child that this is unsupported. */ + return; + } + + global.restart = -1; + bitlbee_shutdown( NULL, -1, 0 ); +} + +void ipc_master_cmd_identify( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data, *old = NULL; + char *resp; + GSList *l; + + if( !child || !child->nick || strcmp( child->nick, cmd[1] ) != 0 ) + return; + + g_free( child->password ); + child->password = g_strdup( cmd[2] ); + + for( l = child_list; l; l = l->next ) + { + old = l->data; + if( child != old && + old->nick && nick_cmp( old->nick, child->nick ) == 0 && + old->password && strcmp( old->password, child->password ) == 0 ) + break; + } + + if( l && !child->to_child && !old->to_child ) + { + resp = "TAKEOVER INIT\r\n"; + child->to_child = old; + old->to_child = child; + } + else + { + /* Won't need the fd since we can't send it anywhere. */ + closesocket( child->to_fd ); + child->to_fd = -1; + resp = "TAKEOVER NO\r\n"; + } + + if( write( child->ipc_fd, resp, strlen( resp ) ) != strlen( resp ) ) + ipc_master_free_one( child ); +} + + +void ipc_master_cmd_takeover( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data; + char *fwd = NULL; + + /* Normal daemon mode doesn't keep these and has simplified code for + takeovers. */ + if( child == NULL ) + return; + + if( child->to_child == NULL || + g_slist_find( child_list, child->to_child ) == NULL ) + return ipc_master_takeover_fail( child, FALSE ); + + if( strcmp( cmd[1], "AUTH" ) == 0 ) + { + /* New connection -> Master */ + if( child->to_child && + child->nick && child->to_child->nick && cmd[2] && + child->password && child->to_child->password && cmd[3] && + strcmp( child->nick, child->to_child->nick ) == 0 && + strcmp( child->nick, cmd[2] ) == 0 && + strcmp( child->password, child->to_child->password ) == 0 && + strcmp( child->password, cmd[3] ) == 0 ) + { + ipc_send_fd( child->to_child->ipc_fd, child->to_fd ); + + fwd = irc_build_line( cmd ); + if( write( child->to_child->ipc_fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) + ipc_master_free_one( child ); + g_free( fwd ); + } + else + return ipc_master_takeover_fail( child, TRUE ); + } + else if( strcmp( cmd[1], "DONE" ) == 0 || strcmp( cmd[1], "FAIL" ) == 0 ) + { + /* Old connection -> Master */ + int fd; + + /* The copy was successful (or not), we don't need it anymore. */ + closesocket( child->to_fd ); + child->to_fd = -1; + + /* Pass it through to the other party, and flush all state. */ + fwd = irc_build_line( cmd ); + fd = child->to_child->ipc_fd; + child->to_child->to_child = NULL; + child->to_child = NULL; + if( write( fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) + ipc_master_free_one( child ); + g_free( fwd ); + } +} + +static const command_t ipc_master_commands[] = { + { "client", 3, ipc_master_cmd_client, 0 }, + { "hello", 0, ipc_master_cmd_client, 0 }, + { "nick", 1, ipc_master_cmd_nick, 0 }, + { "die", 0, ipc_master_cmd_die, 0 }, + { "deaf", 0, ipc_master_cmd_deaf, 0 }, + { "wallops", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "wall", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "opermsg", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "rehash", 0, ipc_master_cmd_rehash, 0 }, + { "kill", 2, NULL, IPC_CMD_TO_CHILDREN }, + { "restart", 0, ipc_master_cmd_restart, 0 }, + { "identify", 2, ipc_master_cmd_identify, 0 }, + { "takeover", 1, ipc_master_cmd_takeover, 0 }, + { NULL } +}; + + +static void ipc_child_cmd_die( irc_t *irc, char **cmd ) +{ + irc_abort( irc, 0, "Shutdown requested by operator" ); +} + +static void ipc_child_cmd_wallops( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 'w' ) ) + irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] ); +} + +static void ipc_child_cmd_wall( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 's' ) ) + irc_write( irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1] ); +} + +static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 'o' ) ) + irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1] ); +} + +static void ipc_child_cmd_rehash( irc_t *irc, char **cmd ) +{ + runmode_t oldmode; + + oldmode = global.conf->runmode; + + g_free( global.conf ); + global.conf = conf_load( 0, NULL ); + + global.conf->runmode = oldmode; +} + +static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( nick_cmp( cmd[1], irc->user->nick ) != 0 ) + return; /* It's not for us. */ + + irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] ); + irc_abort( irc, 0, "Killed by operator: %s", cmd[2] ); +} + +static void ipc_child_cmd_hello( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + ipc_to_master_str( "HELLO\r\n" ); + else + ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); +} + +static void ipc_child_cmd_takeover_yes( void *data ); +static void ipc_child_cmd_takeover_no( void *data ); + +static void ipc_child_cmd_takeover( irc_t *irc, char **cmd ) +{ + if( strcmp( cmd[1], "NO" ) == 0 ) + { + /* Master->New connection */ + /* No takeover, finish the login. */ + } + else if( strcmp( cmd[1], "INIT" ) == 0 ) + { + /* Master->New connection */ + if( !set_getbool( &irc->b->set, "allow_takeover" ) ) + { + ipc_child_cmd_takeover_no( irc ); + return; + } + + /* Offer to take over the old session, unless for some reason + we're already logging into IM connections. */ + if( irc->login_source_id != -1 ) + query_add( irc, NULL, + "You're already connected to this server. " + "Would you like to take over this session?", + ipc_child_cmd_takeover_yes, + ipc_child_cmd_takeover_no, NULL, irc ); + + /* This one's going to connect to accounts, avoid that. */ + b_event_remove( irc->login_source_id ); + irc->login_source_id = -1; + } + else if( strcmp( cmd[1], "AUTH" ) == 0 ) + { + /* Master->Old connection */ + if( irc->password && cmd[2] && cmd[3] && + ipc_child_recv_fd != -1 && + strcmp( irc->user->nick, cmd[2] ) == 0 && + strcmp( irc->password, cmd[3] ) == 0 && + set_getbool( &irc->b->set, "allow_takeover" ) ) + { + irc_switch_fd( irc, ipc_child_recv_fd ); + irc_sync( irc ); + irc_usermsg( irc, "You've successfully taken over your old session" ); + ipc_child_recv_fd = -1; + + ipc_to_master_str( "TAKEOVER DONE\r\n" ); + } + else + { + ipc_to_master_str( "TAKEOVER FAIL\r\n" ); + } + } + else if( strcmp( cmd[1], "DONE" ) == 0 ) + { + /* Master->New connection (now taken over by old process) */ + irc_free( irc ); + } + else if( strcmp( cmd[1], "FAIL" ) == 0 ) + { + /* Master->New connection */ + irc_usermsg( irc, "Could not take over old session" ); + } +} + +static void ipc_child_cmd_takeover_yes( void *data ) +{ + irc_t *irc = data, *old = NULL; + char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL }; + + /* Master->New connection */ + ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n", + irc->user->nick, irc->password ); + + if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + + for( l = irc_connection_list; l; l = l->next ) + { + old = l->data; + + if( irc != old && + irc->user->nick && old->user->nick && + irc->password && old->password && + strcmp( irc->user->nick, old->user->nick ) == 0 && + strcmp( irc->password, old->password ) == 0 ) + break; + } + if( l == NULL ) + { + to_auth[1] = "FAIL"; + ipc_child_cmd_takeover( irc, to_auth ); + return; + } + } + + /* Drop credentials, we'll shut down soon and shouldn't overwrite + any settings. */ + irc_usermsg( irc, "Trying to take over existing session" ); + + irc_desync( irc ); + + if( old ) + { + ipc_child_recv_fd = dup( irc->fd ); + ipc_child_cmd_takeover( old, to_auth ); + } + + /* TODO: irc_setpass() should do all of this. */ + irc_setpass( irc, NULL ); + irc->status &= ~USTATUS_IDENTIFIED; + irc_umode_set( irc, "-R", 1 ); + + if( old ) + irc_abort( irc, FALSE, NULL ); +} + +static void ipc_child_cmd_takeover_no( void *data ) +{ + ipc_to_master_str( "TAKEOVER NO\r\n" ); + cmd_identify_finish( data, 0, 0 ); +} + +static const command_t ipc_child_commands[] = { + { "die", 0, ipc_child_cmd_die, 0 }, + { "wallops", 1, ipc_child_cmd_wallops, 0 }, + { "wall", 1, ipc_child_cmd_wall, 0 }, + { "opermsg", 1, ipc_child_cmd_opermsg, 0 }, + { "rehash", 0, ipc_child_cmd_rehash, 0 }, + { "kill", 2, ipc_child_cmd_kill, 0 }, + { "hello", 0, ipc_child_cmd_hello, 0 }, + { "takeover", 1, ipc_child_cmd_takeover, 0 }, + { NULL } +}; + +gboolean ipc_child_identify( irc_t *irc ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { +#ifndef NO_FD_PASSING + if( !ipc_send_fd( global.listen_socket, irc->fd ) ) + ipc_child_disable(); + + ipc_to_master_str( "IDENTIFY %s :%s\r\n", irc->user->nick, irc->password ); +#endif + + return TRUE; + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + irc_t *old; + char *to_init[] = { "TAKEOVER", "INIT", NULL }; + + for( l = irc_connection_list; l; l = l->next ) + { + old = l->data; + + if( irc != old && + irc->user->nick && old->user->nick && + irc->password && old->password && + strcmp( irc->user->nick, old->user->nick ) == 0 && + strcmp( irc->password, old->password ) == 0 ) + break; + } + if( l == NULL || + !set_getbool( &irc->b->set, "allow_takeover" ) || + !set_getbool( &old->b->set, "allow_takeover" ) ) + return FALSE; + + ipc_child_cmd_takeover( irc, to_init ); + + return TRUE; + } + else + return FALSE; +} + +static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ) +{ + if( child == NULL || g_slist_find( child_list, child ) == NULL ) + return; + + if( both && child->to_child != NULL ) + ipc_master_takeover_fail( child->to_child, FALSE ); + + if( child->to_fd > -1 ) + { + /* Send this error only to the new connection, which can be + recognised by to_fd being set. */ + if( write( child->ipc_fd, "TAKEOVER FAIL\r\n", 15 ) != 15 ) + { + ipc_master_free_one( child ); + return; + } + close( child->to_fd ); + child->to_fd = -1; + } + child->to_child = NULL; +} + +static void ipc_command_exec( void *data, char **cmd, const command_t *commands ) +{ + int i, j; + + if( !cmd[0] ) + return; + + for( i = 0; commands[i].command; i ++ ) + if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 ) + { + /* There is no typo in this line: */ + for( j = 1; cmd[j]; j ++ ); j --; + + if( j < commands[i].required_parameters ) + break; + + if( commands[i].flags & IPC_CMD_TO_CHILDREN ) + ipc_to_children( cmd ); + else + commands[i].execute( data, cmd ); + + break; + } +} + +/* Return just one line. Returns NULL if something broke, an empty string + on temporary "errors" (EAGAIN and friends). */ +static char *ipc_readline( int fd, int *recv_fd ) +{ + struct msghdr msg; + struct iovec iov; + char ccmsg[CMSG_SPACE(sizeof(recv_fd))]; + struct cmsghdr *cmsg; + char buf[513], *eol; + int size; + + /* Because this is internal communication, it should be pretty safe + to just peek at the message, find its length (by searching for the + end-of-line) and then just read that message. With internal + sockets and limites message length, messages should always be + complete. Saves us quite a lot of code and buffering. */ + size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK ); + if( size == 0 || ( size < 0 && !sockerr_again() ) ) + return NULL; + else if( size < 0 ) /* && sockerr_again() */ + return( g_strdup( "" ) ); + else + buf[size] = 0; + + if( ( eol = strstr( buf, "\r\n" ) ) == NULL ) + return NULL; + else + size = eol - buf + 2; + + iov.iov_base = buf; + iov.iov_len = size; + + memset( &msg, 0, sizeof( msg ) ); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; +#ifndef NO_FD_PASSING + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof( ccmsg ); +#endif + + if( recvmsg( fd, &msg, 0 ) != size ) + return NULL; + +#ifndef NO_FD_PASSING + if( recv_fd ) + for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) + if( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS ) + { + /* Getting more than one shouldn't happen but if it does, + make sure we don't leave them around. */ + if( *recv_fd != -1 ) + close( *recv_fd ); + + *recv_fd = *(int*) CMSG_DATA( cmsg ); + /* + fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd ); + */ + } +#endif + + /* + fprintf( stderr, "pid %d received: %s", (int) getpid(), buf ); + */ + return g_strndup( buf, size - 2 ); +} + +gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ) +{ + struct bitlbee_child *child = data; + char *buf, **cmd; + + if( ( buf = ipc_readline( source, &child->to_fd ) ) ) + { + cmd = irc_parse_line( buf ); + if( cmd ) + { + ipc_command_exec( child, cmd, ipc_master_commands ); + g_free( cmd ); + } + g_free( buf ); + } + else + { + ipc_master_free_fd( source ); + } + + return TRUE; +} + +gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ) +{ + char *buf, **cmd; + + if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) ) + { + cmd = irc_parse_line( buf ); + if( cmd ) + { + ipc_command_exec( data, cmd, ipc_child_commands ); + g_free( cmd ); + } + g_free( buf ); + } + else + { + ipc_child_disable(); + } + + return TRUE; +} + +void ipc_to_master( char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + char *s = irc_build_line( cmd ); + ipc_to_master_str( "%s", s ); + g_free( s ); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + ipc_command_exec( NULL, cmd, ipc_master_commands ); + } +} + +void ipc_to_master_str( char *format, ... ) +{ + char *msg_buf; + va_list params; + + va_start( params, format ); + msg_buf = g_strdup_vprintf( format, params ); + va_end( params ); + + if( strlen( msg_buf ) > 512 ) + { + /* Don't send it, it's too long... */ + } + else if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + if( global.listen_socket >= 0 ) + if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 ) + ipc_child_disable(); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + char **cmd, *s; + + if( ( s = strchr( msg_buf, '\r' ) ) ) + *s = 0; + + cmd = irc_parse_line( msg_buf ); + ipc_command_exec( NULL, cmd, ipc_master_commands ); + g_free( cmd ); + } + + g_free( msg_buf ); +} + +void ipc_to_children( char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + char *msg_buf = irc_build_line( cmd ); + ipc_to_children_str( "%s", msg_buf ); + g_free( msg_buf ); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + + for( l = irc_connection_list; l; l = l->next ) + ipc_command_exec( l->data, cmd, ipc_child_commands ); + } +} + +void ipc_to_children_str( char *format, ... ) +{ + char *msg_buf; + va_list params; + + va_start( params, format ); + msg_buf = g_strdup_vprintf( format, params ); + va_end( params ); + + if( strlen( msg_buf ) > 512 ) + { + /* Don't send it, it's too long... */ + } + else if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + int msg_len = strlen( msg_buf ); + GSList *l, *next; + + for( l = child_list; l; l = next ) + { + struct bitlbee_child *c = l->data; + + next = l->next; + if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 ) + ipc_master_free_one( c ); + } + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + char **cmd, *s; + + if( ( s = strchr( msg_buf, '\r' ) ) ) + *s = 0; + + cmd = irc_parse_line( msg_buf ); + ipc_to_children( cmd ); + g_free( cmd ); + } + + g_free( msg_buf ); +} + +static gboolean ipc_send_fd( int fd, int send_fd ) +{ + struct msghdr msg; + struct iovec iov; + char ccmsg[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr *cmsg; + + memset( &msg, 0, sizeof( msg ) ); + iov.iov_base = "0x90\r\n"; /* Ja, noppes */ + iov.iov_len = 6; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +#ifndef NO_FD_PASSING + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof( ccmsg ); + cmsg = CMSG_FIRSTHDR( &msg ); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN( sizeof( send_fd ) ); + *(int*)CMSG_DATA( cmsg ) = send_fd; + msg.msg_controllen = cmsg->cmsg_len; +#endif + + return sendmsg( fd, &msg, 0 ) == 6; +} + +void ipc_master_free_one( struct bitlbee_child *c ) +{ + GSList *l; + + b_event_remove( c->ipc_inpa ); + closesocket( c->ipc_fd ); + + if( c->to_fd != -1 ) + close( c->to_fd ); + + g_free( c->host ); + g_free( c->nick ); + g_free( c->realname ); + g_free( c->password ); + g_free( c ); + + child_list = g_slist_remove( child_list, c ); + + /* Also, if any child has a reference to this one, remove it. */ + for( l = child_list; l; l = l->next ) + { + struct bitlbee_child *oc = l->data; + + if( oc->to_child == c ) + ipc_master_takeover_fail( oc, FALSE ); + } +} + +void ipc_master_free_fd( int fd ) +{ + GSList *l; + struct bitlbee_child *c; + + for( l = child_list; l; l = l->next ) + { + c = l->data; + if( c->ipc_fd == fd ) + { + ipc_master_free_one( c ); + break; + } + } +} + +void ipc_master_free_all() +{ + while( child_list ) + ipc_master_free_one( child_list->data ); +} + +void ipc_child_disable() +{ + b_event_remove( global.listen_watch_source_id ); + close( global.listen_socket ); + + global.listen_socket = -1; +} + +#ifndef _WIN32 +char *ipc_master_save_state() +{ + char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" ); + int fd = mkstemp( fn ); + GSList *l; + FILE *fp; + int i; + + if( fd == -1 ) + { + log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) ); + g_free( fn ); + return NULL; + } + + /* This is more convenient now. */ + fp = fdopen( fd, "w" ); + + for( l = child_list, i = 0; l; l = l->next ) + i ++; + + /* Number of client processes. */ + fprintf( fp, "%d\n", i ); + + for( l = child_list; l; l = l->next ) + fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid, + ((struct bitlbee_child*)l->data)->ipc_fd ); + + if( fclose( fp ) == 0 ) + { + return fn; + } + else + { + unlink( fn ); + g_free( fn ); + return NULL; + } +} + + +static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond ) +{ + struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 ); + + child->to_fd = -1; + child->ipc_fd = accept( serversock, NULL, 0 ); + if( child->ipc_fd == -1 ) + { + log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) ); + return TRUE; + } + + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); + + child_list = g_slist_prepend( child_list, child ); + + return TRUE; +} + +int ipc_master_listen_socket() +{ + struct sockaddr_un un_addr; + int serversock; + + /* Clean up old socket files that were hanging around.. */ + if (unlink(IPCSOCKET) == -1 && errno != ENOENT) { + log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) ); + return 0; + } + + un_addr.sun_family = AF_UNIX; + strcpy(un_addr.sun_path, IPCSOCKET); + + serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX); + + if (serversock == -1) { + log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) ); + return 0; + } + + if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) { + log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) ); + return 0; + } + + if (listen(serversock, 5) == -1) { + log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) ); + return 0; + } + + b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL ); + + return 1; +} +#else +int ipc_master_listen_socket() +{ + /* FIXME: Open named pipe \\.\BITLBEE */ + return 0; +} +#endif + +int ipc_master_load_state( char *statefile ) +{ + struct bitlbee_child *child; + FILE *fp; + int i, n; + + if( statefile == NULL ) + return 0; + + fp = fopen( statefile, "r" ); + unlink( statefile ); /* Why do it later? :-) */ + if( fp == NULL ) + return 0; + + if( fscanf( fp, "%d", &n ) != 1 ) + { + log_message( LOGLVL_WARNING, "Could not import state information for child processes." ); + fclose( fp ); + return 0; + } + + log_message( LOGLVL_INFO, "Importing information for %d child processes.", n ); + for( i = 0; i < n; i ++ ) + { + child = g_new0( struct bitlbee_child, 1 ); + + if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 ) + { + log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i ); + g_free( child ); + fclose( fp ); + return 0; + } + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); + child->to_fd = -1; + + child_list = g_slist_prepend( child_list, child ); + } + + ipc_to_children_str( "HELLO\r\n" ); + ipc_to_children_str( "OPERMSG :New BitlBee master process started (version " BITLBEE_VERSION ")\r\n" ); + + fclose( fp ); + return 1; +} |