aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2010-07-10 00:39:25 +0100
committerWilmer van der Gaast <wilmer@gaast.net>2010-07-10 00:39:25 +0100
commitf3b6764f269490f11a8adbddeeb06964331917d0 (patch)
tree503b810a5e9322b444dc7e84e4d8535dc0f4807e
parent9a9b520df6044cfc034f9736fb97660a46e879b9 (diff)
parentb556e46f9bfeeb630b45a3c0f0951110ac3de0f2 (diff)
Only available in ForkDaemon mode for now: If a user connects (and
identifies) while he's already logged in, offer to take over the old connection.
-rw-r--r--bitlbee.c2
-rw-r--r--bitlbee.h1
-rw-r--r--ipc.c358
-rw-r--r--ipc.h6
-rw-r--r--irc.h1
-rw-r--r--irc_user.c4
-rw-r--r--root_commands.c26
7 files changed, 373 insertions, 25 deletions
diff --git a/bitlbee.c b/bitlbee.c
index 5e93f733..5ced6469 100644
--- a/bitlbee.c
+++ b/bitlbee.c
@@ -317,10 +317,12 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition
{
struct bitlbee_child *child;
+ /* TODO: Stuff like this belongs in ipc.c. */
child = g_new0( struct bitlbee_child, 1 );
child->pid = client_pid;
child->ipc_fd = fds[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_append( child_list, child );
log_message( LOGLVL_INFO, "Creating new subprocess with pid %d.", (int) client_pid );
diff --git a/bitlbee.h b/bitlbee.h
index 5610d95b..9b35810f 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -162,6 +162,7 @@ gboolean bitlbee_io_current_client_write( gpointer data, gint source, b_input_co
void root_command_string( irc_t *irc, char *command );
void root_command( irc_t *irc, char *command[] );
+gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond );
gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond );
char *set_eval_root_nick( set_t *set, char *new_nick );
diff --git a/ipc.c b/ipc.c
index 81c5b8b4..b4b31da1 100644
--- a/ipc.c
+++ b/ipc.c
@@ -32,6 +32,10 @@
#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 );
static void ipc_master_cmd_client( irc_t *data, char **cmd )
{
@@ -48,11 +52,23 @@ static void ipc_master_cmd_client( irc_t *data, char **cmd )
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 )
@@ -111,9 +127,99 @@ void ipc_master_cmd_restart( irc_t *data, char **cmd )
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( 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( nick_cmp( old->nick, child->nick ) == 0 && child != old &&
+ 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;
+
+ 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 },
@@ -122,6 +228,8 @@ static const command_t ipc_master_commands[] = {
{ "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 }
};
@@ -190,6 +298,114 @@ static void ipc_child_cmd_hello( irc_t *irc, char **cmd )
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 */
+ /* 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, 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 )
+ {
+ GSList *l;
+
+ /* TODO: Move this all into irc_switch_fd() or so and
+ irc_sync() */
+ b_event_remove( irc->r_watch_source_id );
+ closesocket( irc->fd );
+ irc->fd = ipc_child_recv_fd;
+ irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
+ ipc_child_recv_fd = -1;
+
+ irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
+ irc->user->user, irc->user->host, irc->user->nick,
+ irc->umode );
+
+ for( l = irc->channels; l; l = l->next )
+ {
+ irc_channel_t *ic = l->data;
+ if( ic->flags & IRC_CHANNEL_JOINED )
+ irc_send_join( ic, irc->user );
+ }
+ irc_usermsg( irc, "You've successfully taken over your old session" );
+
+ 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;
+ GSList *l;
+
+ /* Master->New connection */
+ ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n",
+ irc->user->nick, irc->password );
+
+ /* Drop credentials, we'll shut down soon and shouldn't overwrite
+ any settings. */
+ irc_usermsg( irc, "Trying to take over existing session" );
+
+ /* TODO: irc_setpass() should do all of this. */
+ irc_setpass( irc, NULL );
+ irc->status &= ~USTATUS_IDENTIFIED;
+ irc_umode_set( irc, "-R", 1 );
+
+ for( l = irc->channels; l; l = l->next )
+ irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK,
+ "Switching to old session" );
+
+ irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
+ irc->user->user, irc->user->host, irc->user->nick,
+ irc->umode );
+}
+
+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 },
@@ -198,9 +414,47 @@ static const command_t ipc_child_commands[] = {
{ "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 )
+ {
+ 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 );
+
+ 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 )
{
@@ -229,8 +483,12 @@ static void ipc_command_exec( void *data, char **cmd, const command_t *commands
/* Return just one line. Returns NULL if something broke, an empty string
on temporary "errors" (EAGAIN and friends). */
-static char *ipc_readline( int fd )
+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;
@@ -252,22 +510,46 @@ static char *ipc_readline( int fd )
else
size = eol - buf + 2;
- if( recv( fd, buf, size, 0 ) != size )
+ iov.iov_base = buf;
+ iov.iov_len = size;
+
+ memset( &msg, 0, sizeof( msg ) );
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = ccmsg;
+ msg.msg_controllen = sizeof( ccmsg );
+
+ if( recvmsg( fd, &msg, 0 ) != size )
return NULL;
- else
- return g_strndup( buf, size - 2 );
+
+ 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 );
+ }
+
+ 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 ) ) )
+ if( ( buf = ipc_readline( source, &child->to_fd ) ) )
{
cmd = irc_parse_line( buf );
if( cmd )
{
- ipc_command_exec( data, cmd, ipc_master_commands );
+ ipc_command_exec( child, cmd, ipc_master_commands );
g_free( cmd );
}
g_free( buf );
@@ -284,7 +566,7 @@ gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond )
{
char *buf, **cmd;
- if( ( buf = ipc_readline( source ) ) )
+ if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) )
{
cmd = irc_parse_line( buf );
if( cmd )
@@ -391,10 +673,7 @@ void ipc_to_children_str( char *format, ... )
next = l->next;
if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 )
- {
ipc_master_free_one( c );
- child_list = g_slist_remove( child_list, c );
- }
}
}
else if( global.conf->runmode == RUNMODE_DAEMON )
@@ -412,15 +691,57 @@ void ipc_to_children_str( char *format, ... )
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;
+
+ 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;
+
+ 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 )
@@ -434,7 +755,6 @@ void ipc_master_free_fd( int fd )
if( c->ipc_fd == fd )
{
ipc_master_free_one( c );
- child_list = g_slist_remove( child_list, c );
break;
}
}
@@ -442,13 +762,8 @@ void ipc_master_free_fd( int fd )
void ipc_master_free_all()
{
- GSList *l;
-
- for( l = child_list; l; l = l->next )
- ipc_master_free_one( l->data );
-
- g_slist_free( child_list );
- child_list = NULL;
+ while( child_list )
+ ipc_master_free_one( child_list->data );
}
void ipc_child_disable()
@@ -505,8 +820,8 @@ static gboolean new_ipc_client( gpointer data, gint serversock, b_input_conditio
{
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) );
@@ -515,7 +830,7 @@ static gboolean new_ipc_client( gpointer data, gint serversock, b_input_conditio
child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
- child_list = g_slist_append( child_list, child );
+ child_list = g_slist_prepend( child_list, child );
return TRUE;
}
@@ -597,8 +912,9 @@ int ipc_master_load_state( char *statefile )
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_append( child_list, child );
+ child_list = g_slist_prepend( child_list, child );
}
ipc_to_children_str( "HELLO\r\n" );
diff --git a/ipc.h b/ipc.h
index 0e71c520..bf5f0454 100644
--- a/ipc.h
+++ b/ipc.h
@@ -36,6 +36,12 @@ struct bitlbee_child
char *host;
char *nick;
char *realname;
+
+ char *password;
+
+ /* For takeovers: */
+ struct bitlbee_child *to_child;
+ int to_fd;
};
diff --git a/irc.h b/irc.h
index 94738832..3b5e244f 100644
--- a/irc.h
+++ b/irc.h
@@ -86,6 +86,7 @@ typedef struct irc
gint r_watch_source_id;
gint w_watch_source_id;
gint ping_source_id;
+ gint login_source_id; /* To slightly delay some events at login time. */
struct bee *b;
} irc_t;
diff --git a/irc_user.c b/irc_user.c
index 7d29cae0..8db1de28 100644
--- a/irc_user.c
+++ b/irc_user.c
@@ -24,6 +24,7 @@
*/
#include "bitlbee.h"
+#include "ipc.h"
irc_user_t *irc_user_new( irc_t *irc, const char *nick )
{
@@ -159,6 +160,9 @@ int irc_user_set_nick( irc_user_t *iu, const char *new )
g_hash_table_insert( irc->nick_user_hash, iu->key, iu );
irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp );
+ if( iu == irc->user )
+ ipc_to_master_str( "NICK :%s\r\n", new );
+
return 1;
}
diff --git a/root_commands.c b/root_commands.c
index 5c5ead7b..cf448e8d 100644
--- a/root_commands.c
+++ b/root_commands.c
@@ -104,7 +104,6 @@ static void cmd_account( irc_t *irc, char **cmd );
static void cmd_identify( irc_t *irc, char **cmd )
{
storage_status_t status;
- char *account_on[] = { "account", "on", NULL };
gboolean load = TRUE;
char *password = cmd[1];
@@ -157,8 +156,16 @@ static void cmd_identify( irc_t *irc, char **cmd )
irc->status |= USTATUS_IDENTIFIED;
irc_umode_set( irc, "+R", 1 );
irc_channel_auto_joins( irc, NULL );
- if( load && set_getbool( &irc->b->set, "auto_connect" ) )
- cmd_account( irc, account_on );
+
+ if( ipc_child_identify( irc ) )
+ {
+ if( load && set_getbool( &irc->b->set, "auto_connect" ) )
+ irc->login_source_id = b_timeout_add( 200,
+ cmd_identify_finish, irc );
+ }
+ else if( load && set_getbool( &irc->b->set, "auto_connect" ) )
+ cmd_identify_finish( irc, 0, 0 );
+
break;
case STORAGE_OTHER_ERROR:
default:
@@ -167,6 +174,17 @@ static void cmd_identify( irc_t *irc, char **cmd )
}
}
+gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond )
+{
+ char *account_on[] = { "account", "on", NULL };
+ irc_t *irc = data;
+
+ cmd_account( irc, account_on );
+
+ irc->login_source_id = -1;
+ return FALSE;
+}
+
static void cmd_register( irc_t *irc, char **cmd )
{
if( global.conf->authmode == AUTHMODE_REGISTERED )
@@ -671,7 +689,7 @@ static void cmd_rename( irc_t *irc, char **cmd )
}
else if( iu == irc->user )
{
- irc_usermsg( irc, "Nick `%s' can't be changed", cmd[1] );
+ irc_usermsg( irc, "Use /nick to change your own nickname" );
}
else if( !nick_ok( cmd[2] ) )
{