diff options
Diffstat (limited to 'dcc.c')
| -rw-r--r-- | dcc.c | 427 | 
1 files changed, 274 insertions, 153 deletions
| @@ -27,6 +27,7 @@  #include "dcc.h"  #include <poll.h>  #include <netinet/tcp.h> +#include <regex.h>  /*    * Since that might be confusing a note on naming: @@ -59,22 +60,16 @@ unsigned int local_transfer_id=1;   */  unsigned int receivedchunks=0, receiveddata=0; -/*  - * If using DCC SEND AHEAD this value will be set before the first transfer starts. - * Not that in this case it degenerates to the maximum message size to send() and - * has nothing to do with packets. - */ -#ifdef DCC_SEND_AHEAD -int max_packet_size = DCC_PACKET_SIZE; -#else  int max_packet_size = 0; -#endif  static void dcc_finish( file_transfer_t *file );  static void dcc_close( file_transfer_t *file );  gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond );  gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_ptr );  int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct sockaddr_storage *saddr ); +gboolean dccs_recv_start( file_transfer_t *ft ); +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); +gboolean dccs_recv_write_request( file_transfer_t *ft );  /* As defined in ft.h */  file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) @@ -98,9 +93,22 @@ void imcb_file_canceled( file_transfer_t *file, char *reason )  }  /* As defined in ft.h */ -gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size ) +gboolean imcb_file_recv_start( file_transfer_t *ft ) +{ +	return dccs_recv_start( ft ); +} + +dcc_file_transfer_t *dcc_alloc_transfer( char *file_name, size_t file_size, struct im_connection *ic )  { -	return dccs_send_write( file, data, data_size ); +	file_transfer_t *file = g_new0( file_transfer_t, 1 ); +	dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1); +	file->file_size = file_size; +	file->file_name = g_strdup( file_name ); +	file->local_id = local_transfer_id++; +	df->ic = ic; +	df->ft = file; +	 +	return df;  }  /* This is where the sending magic starts... */ @@ -108,26 +116,22 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha  {  	file_transfer_t *file;  	dcc_file_transfer_t *df; -	struct sockaddr_storage **saddr; +	struct sockaddr_storage *saddr;  	if( file_size > global.conf->max_filetransfer_size )  		return NULL; -	/* alloc stuff */ -	file = g_new0( file_transfer_t, 1 ); -	file->priv = df = g_new0( dcc_file_transfer_t, 1); -	file->file_size = file_size; -	file->file_name = g_strdup( file_name ); -	file->local_id = local_transfer_id++; -	df->ic = ic; -	df->ft = file; -	 +	df = dcc_alloc_transfer( file_name, file_size, ic ); +	file = df->ft; +	file->write = dccs_send_write; +	file->sending = TRUE; +  	/* listen and request */ -	if( !dcc_listen( df, saddr ) || -	    !dccs_send_request( df, user_nick, *saddr ) ) +	if( !dcc_listen( df, &saddr ) || +	    !dccs_send_request( df, user_nick, saddr ) )  		return NULL; -	g_free( *saddr ); +	g_free( saddr );  	/* watch */  	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df ); @@ -177,13 +181,8 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc  	{  		struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr; -		/*  -		 * this is so ridiculous. We're supposed to convert the address to -		 * host byte order!!! Let's exclude anyone running big endian just -		 * for the fun of it... -		 */  		sprintf( ipaddr, "%d",  -			 htonl( saddr_ipv4->sin_addr.s_addr ) ); +			 ntohl( saddr_ipv4->sin_addr.s_addr ) );  		port = saddr_ipv4->sin_port;  	} else   	{ @@ -209,9 +208,6 @@ int dccs_send_request( struct dcc_file_transfer *df, char *user_nick, struct soc  	g_free( cmd ); -	/* message is sortof redundant cause the users client probably informs him about that. remove? */ -	imcb_log( df->ic, "Transferring file %s: Chose local address %s for DCC connection", df->ft->file_name, ipaddr ); -  	return TRUE;  } @@ -260,25 +256,15 @@ gboolean dcc_listen( dcc_file_transfer_t *df, struct sockaddr_storage **saddr_pt  }  /* - * After setup, the transfer itself is handled entirely by this function. - * There are basically four things to handle: connect, receive, send, and error. + * Checks poll(), same for receiving and sending   */ -gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +gboolean dcc_poll( dcc_file_transfer_t *df, int fd, short *revents )  { -	dcc_file_transfer_t *df = data; -	file_transfer_t *file = df->ft;  	struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR|POLLIN|POLLOUT }; -	short revents; -	 -	if ( poll( &pfd, 1, 0 ) == -1 ) -	{ -		imcb_log( df->ic, "poll() failed, weird!" ); -		revents = 0; -	}; -	revents = pfd.revents; +	ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) -	if( revents & POLLERR ) +	if( pfd.revents & POLLERR )  	{  		int sockerror;  		socklen_t errlen = sizeof( sockerror ); @@ -289,9 +275,45 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  		return dcc_abort( df, "Socket error: %s", strerror( sockerror ) );  	} -	if( revents & POLLHUP )  +	if( pfd.revents & POLLHUP )   		return dcc_abort( df, "Remote end closed connection" ); +	*revents = pfd.revents; + +	return TRUE; +} + +/* + * fills max_packet_size with twice the TCP maximum segment size + */ +gboolean  dcc_check_maxseg( dcc_file_transfer_t *df, int fd ) +{ +	/*  +	 * use twice the maximum segment size as a maximum for calls to send(). +	 */ +	if( max_packet_size == 0 ) +	{ +		unsigned int mpslen = sizeof( max_packet_size ); +		if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) +			return dcc_abort( df, "getsockopt() failed" ); +		max_packet_size *= 2; +	} +	return TRUE; +} + +/* + * After setup, the transfer itself is handled entirely by this function. + * There are basically four things to handle: connect, receive, send, and error. + */ +gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +{ +	dcc_file_transfer_t *df = data; +	file_transfer_t *file = df->ft; +	short revents; +	 +	if( !dcc_poll( df, fd, &revents) ) +		return FALSE; +  	if( ( revents & POLLIN ) &&  	    ( file->status & FT_STATUS_LISTENING ) )  	{ 	 @@ -304,25 +326,16 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  		closesocket( fd );  		fd = df->fd; -		file->status = FT_STATUS_TRANSFERING; +		file->status = FT_STATUS_TRANSFERRING;  		sock_make_nonblocking( fd ); -#ifdef DCC_SEND_AHEAD -		/*  -		 * use twice the maximum segment size as a maximum for calls to send(). -		 */ -		if( max_packet_size == 0 ) -		{ -			unsigned int mpslen = sizeof( max_packet_size ); -			if( getsockopt( fd, IPPROTO_TCP, TCP_MAXSEG, &max_packet_size, &mpslen ) ) -				return dcc_abort( df, "getsockopt() failed" ); -			max_packet_size *= 2; -		} -#endif -		/* IM protocol callback */ +		if ( !dcc_check_maxseg( df, fd ) ) +			return FALSE; +		/* IM protocol callback */  		if( file->accept )  			file->accept( file ); +  		/* reschedule for reading on new fd */  		df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df ); @@ -366,122 +379,156 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  			return FALSE;  		} -#ifndef DCC_SEND_AHEAD -		/* reschedule writer if neccessary */ -		if( file->bytes_transferred >= df->bytes_sent &&  -		    df->watch_out == 0 &&  -		    df->queued_bytes > 0 ) { -			df->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, dcc_send_proto, df ); -		} -#endif  		return TRUE;  	} -	if( revents & POLLOUT ) -	{ -		struct dcc_buffer *dccb; -		int ret; -		char *msg; +	return TRUE; +} -		if( df->queued_bytes == 0 ) -		{ -			/* shouldn't happen */ -			imcb_log( df->ic, "WARNING: DCC SEND: write called with empty queue" ); +gboolean dccs_recv_start( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; +	struct sockaddr_storage *saddr = &df->saddr; +	int fd; +	socklen_t sa_len = saddr->ss_family == AF_INET ?  +		sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); +	 +	if( !ft->write ) +		return dcc_abort( df, "Protocol didn't register write()" ); +	 +	ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ) , "Opening Socket" ); -			df->watch_out = 0; -			return FALSE; -		} +	sock_make_nonblocking( fd ); -		/* start where we left off */ - 		if( !( df->queued_buffers ) || -		    !( dccb = df->queued_buffers->data ) ) -			return dcc_abort( df, "BUG in DCC SEND: queued data but no buffers" ); +	if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && +	    ( errno != EINPROGRESS ) ) +		return dcc_abort( df, "Connecting" ); -		msg = dccb->b + df->buffer_pos; +	ft->status = FT_STATUS_CONNECTING; -		int msgsize = MIN(  -#ifndef DCC_SEND_AHEAD -				  file->bytes_transferred + MAX_PACKET_SIZE - df->bytes_sent, -#else -				  max_packet_size, -#endif -				  dccb->len - df->buffer_pos ); +	/* watch */ +	df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); +	ft->write_request = dccs_recv_write_request; -		if ( msgsize == 0 ) -		{ -			df->watch_out = 0; +	return TRUE; +} + +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond) +{ +	dcc_file_transfer_t *df = data; +	file_transfer_t *ft = df->ft; +	short revents; + +	if( !dcc_poll( df, fd, &revents ) ) +		return FALSE; +	 +	if( ( revents & POLLOUT ) && +	    ( ft->status & FT_STATUS_CONNECTING ) ) +	{ +		ft->status = FT_STATUS_TRANSFERRING; +		if ( !dcc_check_maxseg( df, fd ) )  			return FALSE; -		} -		ASSERTSOCKOP( ret = send( fd, msg, msgsize, 0 ), "Sending data" ); +		//df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + +		df->watch_out = 0; +		return FALSE; +	} + +	if( revents & POLLIN ) +	{ +		int ret, done; + +		ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" );  		if( ret == 0 )  			return dcc_abort( df, "Remote end closed connection" );  		df->bytes_sent += ret; -		df->queued_bytes -= ret; -		df->buffer_pos += ret; -		if( df->buffer_pos == dccb->len ) +		done = df->bytes_sent >= ft->file_size; + +		if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || +		    done )  		{ -			df->buffer_pos = 0; -			df->queued_buffers = g_slist_remove( df->queued_buffers, dccb ); -			g_free( dccb->b ); -			g_free( dccb ); +			int ack, ackret; +			ack = htonl( ft->bytes_transferred = df->bytes_sent ); + +			ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); +			 +			if ( ackret != 4 ) +				return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret );  		} +		 +		if( !ft->write( df->ft, ft->buffer, ret ) ) +			return FALSE; -		if( ( df->queued_bytes < DCC_QUEUE_THRESHOLD_LOW ) && file->out_of_data ) -			file->out_of_data( file ); -	 -		if( df->queued_bytes > 0 ) +		if( done )  		{ -			/* Who knows how long the event loop cycle will take,  -			 * let's just try to send some more now. */ -#ifndef DCC_SEND_AHEAD -			if( df->bytes_sent < ( file->bytes_transferred + max_packet_size ) ) -#endif -				return dccs_send_proto( df, fd, cond ); +			closesocket( fd ); +			dcc_finish( ft ); + +			df->watch_in = 0; +			return FALSE;  		} -		df->watch_out = 0; +		df->watch_in = 0;  		return FALSE;  	} -	/* Send buffer full, come back later */ +	return TRUE; +} + +gboolean dccs_recv_write_request( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; + +	if( df->watch_in ) +		return dcc_abort( df, "BUG: write_request() called while watching" ); + +	df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );  	return TRUE;  } +gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ +	struct dcc_file_transfer *df = data; +	df->watch_out = 0; + +	df->ft->write_request( df->ft ); +	return FALSE; +} +  /*  - * Incoming data. Note that the buffer MUST NOT be freed by the caller! - * We don't copy the buffer but put it in our queue. + * Incoming data.   *  - * */ -gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int data_size ) + */ +gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len )  {  	dcc_file_transfer_t *df = file->priv; -	struct dcc_buffer *dccb = g_new0( struct dcc_buffer, 1 ); +	int ret; -	receivedchunks++; receiveddata += data_size; +	receivedchunks++; receiveddata += data_len; -	dccb->b = data; -	dccb->len = data_size; +	if( df->watch_out ) +		return dcc_abort( df, "BUG: write() called while watching" ); -	df->queued_buffers = g_slist_append( df->queued_buffers, dccb ); +	ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" ); -	df->queued_bytes += data_size; +	if( ret == 0 ) +		return dcc_abort( df, "Remote end closed connection" ); -	if( ( file->status & FT_STATUS_TRANSFERING ) &&  -#ifndef DCC_SEND_AHEAD -	    ( file->bytes_transferred >= df->bytes_sent ) &&  -#endif -	    ( df->watch_out == 0 ) &&  -	    ( df->queued_bytes > 0 ) ) -	{ -		df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_proto, df ); -	} -	 -	return df->queued_bytes > DCC_QUEUE_THRESHOLD_HIGH; +	/* TODO: this should really not be fatal */ +	if( ret < data_len ) +		return dcc_abort( df, "send() sent %d instead of %d", ret, data_len ); + +	df->bytes_sent += ret; + +	if( df->bytes_sent < df->ft->file_size ) +		df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df ); + +	return TRUE;  }  /* @@ -502,20 +549,6 @@ static void dcc_close( file_transfer_t *file )  	if( df->watch_out )  		b_event_remove( df->watch_out ); -	if( df->queued_buffers ) -	{ -		struct dcc_buffer *dccb; -		GSList *gslist = df->queued_buffers; - -		for( ; gslist ; gslist = g_slist_next( gslist ) ) -		{ -			dccb = gslist->data; -			g_free( dccb->b ); -			g_free( dccb ); -		} -		g_slist_free( df->queued_buffers ); -	} -  	df->ic->irc->file_transfers = g_slist_remove( df->ic->irc->file_transfers, file );  	g_free( df ); @@ -532,3 +565,91 @@ void dcc_finish( file_transfer_t *file )  	dcc_close( file );  } + +/*  + * DCC SEND <filename> <IP> <port> <filesize> + * + * filename can be in "" or not. If it is, " can probably be escaped... + * IP can be an unsigned int (IPV4) or something else (IPV6) + *  + */ +file_transfer_t *dcc_request( struct im_connection *ic, char *line ) +{ +	char *pattern = "SEND" +		" (([^\"][^ ]*)|\"([^\"]|\\\")*\")" +		" (([0-9]*)|([^ ]*))" +		" ([0-9]*)" +		" ([0-9]*)\001"; +	regmatch_t pmatch[9]; +	regex_t re; +	file_transfer_t *ft; +	dcc_file_transfer_t *df; + +	if( regcomp( &re, pattern, REG_EXTENDED ) ) +		return NULL; +	if( regexec( &re, line, 9, pmatch, 0 ) ) +		return NULL; + +	if( ( pmatch[1].rm_so > 0 ) && +	    ( pmatch[4].rm_so > 0 ) && +	    ( pmatch[7].rm_so > 0 ) && +	    ( pmatch[8].rm_so > 0 ) ) +	{ +		char *input = g_strdup( line ); +		char *filename, *host, *port; +		size_t filesize; +		struct addrinfo hints, *rp; + +		/* "filename" or filename */ +		if ( pmatch[2].rm_so > 0 ) +		{ +			input[pmatch[2].rm_eo] = '\0'; +			filename = input + pmatch[2].rm_so; +		} else +		{ +			input[pmatch[3].rm_eo] = '\0'; +			filename = input + pmatch[3].rm_so; +		} +			 +		input[pmatch[4].rm_eo] = '\0'; + +		/* number means ipv4, something else means ipv6 */ +		if ( pmatch[5].rm_so > 0 ) +		{ +			struct in_addr ipaddr = { htonl( atoi( input + pmatch[5].rm_so ) ) }; +			host = inet_ntoa( ipaddr ); +		} else +		{ +			/* Contains non-numbers, hopefully an IPV6 address */ +			host = input + pmatch[6].rm_so; +		} + +		input[pmatch[7].rm_eo] = '\0'; +		input[pmatch[8].rm_eo] = '\0'; + +		port = input + pmatch[7].rm_so; +		filesize = atoll( input + pmatch[8].rm_so ); + +		memset( &hints, 0, sizeof ( struct addrinfo ) ); +		if ( getaddrinfo( host, port, &hints, &rp ) ) +		{ +			g_free( input ); +			return NULL; +		} + +		df = dcc_alloc_transfer( filename, filesize, ic ); +		ft = df->ft; +		ft->sending = TRUE; +		memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen ); + +		freeaddrinfo( rp ); +		g_free( input ); + +		df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, ft ); + +		return ft; +	} + +	return NULL; +} + | 
