diff options
Diffstat (limited to 'ipc.c')
| -rw-r--r-- | ipc.c | 358 | 
1 files changed, 337 insertions, 21 deletions
| @@ -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" ); | 
