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; +} |