Index: bitlbee.c
===================================================================
--- bitlbee.c	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ bitlbee.c	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -318,8 +318,10 @@
 			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 );
 			
Index: bitlbee.h
===================================================================
--- bitlbee.h	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ bitlbee.h	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -163,4 +163,5 @@
 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 );
 
Index: ipc.c
===================================================================
--- ipc.c	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ ipc.c	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -33,4 +33,8 @@
 
 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 )
@@ -49,4 +53,5 @@
 	}
 	
+	/* 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",
@@ -54,4 +59,15 @@
 }
 
+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 )
 {
@@ -110,4 +126,93 @@
 	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( 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 );
+	}
 }
 
@@ -115,4 +220,5 @@
 	{ "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 },
@@ -123,4 +229,6 @@
 	{ "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 }
 };
@@ -189,4 +297,112 @@
 	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 */
+		/* 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 );
 }
 
@@ -199,7 +415,45 @@
 	{ "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 )
@@ -230,6 +484,10 @@
 /* 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;
@@ -253,20 +511,44 @@
 		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 );
 		}
@@ -285,5 +567,5 @@
 	char *buf, **cmd;
 	
-	if( ( buf = ipc_readline( source ) ) )
+	if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) )
 	{
 		cmd = irc_parse_line( buf );
@@ -392,8 +674,5 @@
 			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 );
-			}
 		}
 	}
@@ -413,13 +692,55 @@
 }
 
+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 );
+	}
 }
 
@@ -435,5 +756,4 @@
 		{
 			ipc_master_free_one( c );
-			child_list = g_slist_remove( child_list, c );
 			break;
 		}
@@ -443,11 +763,6 @@
 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 );
 }
 
@@ -506,6 +821,6 @@
 	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 )
 	{
@@ -516,5 +831,5 @@
 	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;
@@ -598,6 +913,7 @@
 		}
 		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->to_fd = -1;
+		
+		child_list = g_slist_prepend( child_list, child );
 	}
 	
Index: ipc.h
===================================================================
--- ipc.h	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ ipc.h	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -37,4 +37,10 @@
 	char *nick;
 	char *realname;
+	
+	char *password;
+	
+	/* For takeovers: */
+	struct bitlbee_child *to_child;
+	int to_fd;
 };
 
Index: irc.h
===================================================================
--- irc.h	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ irc.h	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -87,4 +87,5 @@
 	gint w_watch_source_id;
 	gint ping_source_id;
+	gint login_source_id; /* To slightly delay some events at login time. */
 	
 	struct bee *b;
Index: irc_user.c
===================================================================
--- irc_user.c	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ irc_user.c	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -25,4 +25,5 @@
 
 #include "bitlbee.h"
+#include "ipc.h"
 
 irc_user_t *irc_user_new( irc_t *irc, const char *nick )
@@ -160,4 +161,7 @@
 	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;
 }
Index: root_commands.c
===================================================================
--- root_commands.c	(revision 9a9b520df6044cfc034f9736fb97660a46e879b9)
+++ root_commands.c	(revision f3b6764f269490f11a8adbddeeb06964331917d0)
@@ -105,5 +105,4 @@
 {
 	storage_status_t status;
-	char *account_on[] = { "account", "on", NULL };
 	gboolean load = TRUE;
 	char *password = cmd[1];
@@ -158,6 +157,14 @@
 		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:
@@ -166,4 +173,15 @@
 		break;
 	}
+}
+
+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;
 }
 
@@ -672,5 +690,5 @@
 	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] ) )
