diff options
Diffstat (limited to 'bitlbee.c')
| -rw-r--r-- | bitlbee.c | 386 | 
1 files changed, 386 insertions, 0 deletions
| diff --git a/bitlbee.c b/bitlbee.c new file mode 100644 index 00000000..0e1415e8 --- /dev/null +++ b/bitlbee.c @@ -0,0 +1,386 @@ +  /********************************************************************\ +  * BitlBee -- An IRC to other IM-networks gateway                     * +  *                                                                    * +  * Copyright 2002-2004 Wilmer van der Gaast and others                * +  \********************************************************************/ + +/* Main file                                                            */ + +/* +  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 "commands.h" +#include "protocols/nogaim.h" +#include "help.h" +#include "ipc.h" +#include <signal.h> +#include <stdio.h> +#include <errno.h> + +static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ); + +static gboolean try_listen( struct addrinfo *res ) +{ +	int i; +	 +	global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); +	if( global.listen_socket < 0 ) +	{ +		log_error( "socket" ); +		return FALSE; +	} + +#ifdef IPV6_V6ONLY		 +	if( res->ai_family == AF_INET6 ) +	{ +		i = 0; +		setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, +		            (char *) &i, sizeof( i ) ); +	} +#endif + +	/* TIME_WAIT (?) sucks.. */ +	i = 1; +	setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) ); + +	i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen ); +	if( i == -1 ) +	{ +		closesocket( global.listen_socket ); +		global.listen_socket = -1; +		 +		log_error( "bind" ); +		return FALSE; +	} +	 +	return TRUE; +} + +int bitlbee_daemon_init() +{ +	struct addrinfo *res, hints, *addrinfo_bind; +	int i; +	FILE *fp; +	 +	log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); +	log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); +	 +	memset( &hints, 0, sizeof( hints ) ); +	hints.ai_family = PF_UNSPEC; +	hints.ai_socktype = SOCK_STREAM; +	hints.ai_flags = AI_PASSIVE +#ifdef AI_ADDRCONFIG +	/* Disabled as it may be doing more harm than good: this flag +	   ignores IPv6 addresses on lo (which seems reasonable), but +	   the result is that some clients (including irssi) try to +	   connect to ::1 and fail. +	               | AI_ADDRCONFIG */ +#endif +	; + +	i = getaddrinfo( global.conf->iface_in, global.conf->port, &hints, &addrinfo_bind ); +	if( i ) +	{ +		log_message( LOGLVL_ERROR, "Couldn't parse address `%s': %s", +		                           global.conf->iface_in, gai_strerror(i) ); +		return -1; +	} + +	global.listen_socket = -1; +	 +	/* Try IPv6 first (which will become an IPv6+IPv4 socket). */ +	for( res = addrinfo_bind; res; res = res->ai_next ) +		if( res->ai_family == AF_INET6 && try_listen( res ) ) +			break; +	 +	/* The rest (so IPv4, I guess). */ +	if( res == NULL ) +		for( res = addrinfo_bind; res; res = res->ai_next ) +			if( res->ai_family != AF_INET6 && try_listen( res ) ) +				break; +	 +	freeaddrinfo( addrinfo_bind ); + +	i = listen( global.listen_socket, 10 ); +	if( i == -1 ) +	{ +		log_error( "listen" ); +		return( -1 ); +	} +	 +	global.listen_watch_source_id = b_input_add( global.listen_socket, B_EV_IO_READ, bitlbee_io_new_client, NULL ); +	 +#ifndef _WIN32 +	if( !global.conf->nofork ) +	{ +		i = fork(); +		if( i == -1 ) +		{ +			log_error( "fork" ); +			return( -1 ); +		} +		else if( i != 0 )  +			exit( 0 ); +		 +		setsid(); +		i = chdir( "/" ); +		/* Don't use i, just make gcc happy. :-/ */ +		 +		if( getenv( "_BITLBEE_RESTART_STATE" ) == NULL ) +			for( i = 0; i < 3; i ++ ) +				if( close( i ) == 0 ) +				{ +					/* Keep something bogus on those fd's just in case. */ +					open( "/dev/null", O_WRONLY ); +				} +	} +#endif +	 +	if( global.conf->runmode == RUNMODE_FORKDAEMON ) +		ipc_master_load_state( getenv( "_BITLBEE_RESTART_STATE" ) ); + +	if( global.conf->runmode == RUNMODE_DAEMON || global.conf->runmode == RUNMODE_FORKDAEMON ) +		ipc_master_listen_socket(); +	 +#ifndef _WIN32 +	if( ( fp = fopen( global.conf->pidfile, "w" ) ) ) +	{ +		fprintf( fp, "%d\n", (int) getpid() ); +		fclose( fp ); +	} +	else +	{ +		log_message( LOGLVL_WARNING, "Warning: Couldn't write PID to `%s'", global.conf->pidfile ); +	} +#endif +	 +	if( !global.conf->nofork ) +	{ +		log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG ); +		log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG ); +	} +	 +	return( 0 ); +} +  +int bitlbee_inetd_init() +{ +	if( !irc_new( 0 ) ) +		return( 1 ); +	 +	return( 0 ); +} + +gboolean bitlbee_io_current_client_read( gpointer data, gint fd, b_input_condition cond ) +{ +	irc_t *irc = data; +	char line[513]; +	int st; +	 +	st = read( irc->fd, line, sizeof( line ) - 1 ); +	if( st == 0 ) +	{ +		irc_abort( irc, 1, "Connection reset by peer" ); +		return FALSE; +	} +	else if( st < 0 ) +	{ +		if( sockerr_again() ) +		{ +			return TRUE; +		} +		else +		{ +			irc_abort( irc, 1, "Read error: %s", strerror( errno ) ); +			return FALSE; +		} +	} +	 +	line[st] = '\0'; +	if( irc->readbuffer == NULL )  +	{ +		irc->readbuffer = g_strdup( line ); +	} +	else  +	{ +		irc->readbuffer = g_renew( char, irc->readbuffer, strlen( irc->readbuffer ) + strlen ( line ) + 1 ); +		strcpy( ( irc->readbuffer + strlen( irc->readbuffer ) ), line ); +	} +	 +	irc_process( irc ); +	 +	/* Normally, irc_process() shouldn't call irc_free() but irc_abort(). Just in case: */ +	if( !g_slist_find( irc_connection_list, irc ) ) +	{ +		log_message( LOGLVL_WARNING, "Abnormal termination of connection with fd %d.", fd ); +		return FALSE; +	}  +	 +	/* Very naughty, go read the RFCs! >:) */ +	if( irc->readbuffer && ( strlen( irc->readbuffer ) > 1024 ) ) +	{ +		irc_abort( irc, 0, "Maximum line length exceeded" ); +		return FALSE; +	} +	 +	return TRUE; +} + +gboolean bitlbee_io_current_client_write( gpointer data, gint fd, b_input_condition cond ) +{ +	irc_t *irc = data; +	int st, size; +	char *temp; + +	if( irc->sendbuffer == NULL ) +		return FALSE; +	 +	size = strlen( irc->sendbuffer ); +	st = write( irc->fd, irc->sendbuffer, size ); +	 +	if( st == 0 || ( st < 0 && !sockerr_again() ) ) +	{ +		irc_abort( irc, 1, "Write error: %s", strerror( errno ) ); +		return FALSE; +	} +	else if( st < 0 ) /* && sockerr_again() */ +	{ +		return TRUE; +	} +	 +	if( st == size ) +	{ +		if( irc->status & USTATUS_SHUTDOWN ) +		{ +			irc_free( irc ); +		} +		else +		{ +			g_free( irc->sendbuffer ); +			irc->sendbuffer = NULL; +			irc->w_watch_source_id = 0; +		} +		 +		return FALSE; +	} +	else +	{ +		temp = g_strdup( irc->sendbuffer + st ); +		g_free( irc->sendbuffer ); +		irc->sendbuffer = temp; +		 +		return TRUE; +	} +} + +static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ) +{ +	socklen_t size = sizeof( struct sockaddr_in ); +	struct sockaddr_in conn_info; +	int new_socket = accept( global.listen_socket, (struct sockaddr *) &conn_info, &size ); +	 +	if( new_socket == -1 ) +	{ +		log_message( LOGLVL_WARNING, "Could not accept new connection: %s", strerror( errno ) ); +		return TRUE; +	} +	 +#ifndef _WIN32 +	if( global.conf->runmode == RUNMODE_FORKDAEMON ) +	{ +		pid_t client_pid = 0; +		int fds[2]; +		 +		if( socketpair( AF_UNIX, SOCK_STREAM, 0, fds ) == -1 ) +		{ +			log_message( LOGLVL_WARNING, "Could not create IPC socket for client: %s", strerror( errno ) ); +			fds[0] = fds[1] = -1; +		} +		 +		sock_make_nonblocking( fds[0] ); +		sock_make_nonblocking( fds[1] ); +		 +		client_pid = fork(); +		 +		if( client_pid > 0 && fds[0] != -1 ) +		{ +			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 ); +			 +			/* Close some things we don't need in the parent process. */ +			close( new_socket ); +			close( fds[1] ); +		} +		else if( client_pid == 0 ) +		{ +			irc_t *irc; +			 +			/* Since we're fork()ing here, let's make sure we won't +			   get the same random numbers as the parent/siblings. */ +			srand( time( NULL ) ^ getpid() ); +			 +			b_main_init(); +			 +			/* Close the listening socket, we're a client. */ +			close( global.listen_socket ); +			b_event_remove( global.listen_watch_source_id ); +			 +			/* Make the connection. */ +			irc = irc_new( new_socket ); +			 +			/* We can store the IPC fd there now. */ +			global.listen_socket = fds[1]; +			global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc ); +			 +			close( fds[0] ); +			 +			ipc_master_free_all(); +		} +	} +	else +#endif +	{ +		log_message( LOGLVL_INFO, "Creating new connection with fd %d.", new_socket ); +		irc_new( new_socket ); +	} +	 +	return TRUE; +} + +gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ) +{ +	/* Try to save data for all active connections (if desired). */ +	while( irc_connection_list != NULL ) +		irc_abort( irc_connection_list->data, TRUE, +		           "BitlBee server shutting down" ); +	 +	/* We'll only reach this point when not running in inetd mode: */ +	b_main_quit(); +	 +	return FALSE; +} | 
