diff options
| author | ulim <a.sporto+bee@gmail.com> | 2007-12-03 15:28:45 +0100 | 
|---|---|---|
| committer | ulim <a.sporto+bee@gmail.com> | 2007-12-03 15:28:45 +0100 | 
| commit | 2ff20765990c756533957e8da9c7c29dd3102e79 (patch) | |
| tree | 8d19ceb1490866feee355ba9a098d7e4be6eea53 /dcc.c | |
| parent | 2c2df7dd91930345a9b22a8bb61327d1dcc7e3d5 (diff) | |
Intermediate commit. Sending seems to work. TODOs:
* move from out_of_data to is_writable, eliminate buffers
* implement "transfers reject [id]"
* documentation in commands.xml
* implement throughput and cummulative throughput boundaries
* feature discovery before sending
* implement sending over a proxy
  (proxy discovery, socks5 client handshake for sending, activate message)
* integrate toxik-mek-ft
Diffstat (limited to 'dcc.c')
| -rw-r--r-- | dcc.c | 312 | 
1 files changed, 270 insertions, 42 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: @@ -75,6 +76,9 @@ 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); +void dccs_recv_out_of_data( 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 ) @@ -103,31 +107,44 @@ gboolean imcb_file_write( file_transfer_t *file, gpointer data, size_t data_size  	return dccs_send_write( file, data, data_size );  } +/* As defined in ft.h */ +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 ) +{ +	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... */  file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size )  {  	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; +  	/* 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 ); @@ -260,25 +277,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 +296,44 @@ 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; +} + +gboolean  dcc_check_maxseg( dcc_file_transfer_t *df, int 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 +	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,21 +346,12 @@ 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 +		if ( !dcc_check_maxseg( df, fd ) ) +			return FALSE; +  		/* IM protocol callback */  		if( file->accept ) @@ -452,6 +485,113 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )  	return TRUE;  } +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" ); + +	sock_make_nonblocking( fd ); + +	if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && +	    ( errno != EINPROGRESS ) ) +		return dcc_abort( df, "Connecting" ); + +	ft->status = FT_STATUS_CONNECTING; + +	/* watch */ +	df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df ); +	ft->out_of_data = dccs_recv_out_of_data; + +	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; + +		//df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); + +		df->watch_out = 0; +		return FALSE; +	} + +	if( revents & POLLIN ) +	{ +		char *buffer = g_malloc( 65536 ); +		int ret, done; + +		ASSERTSOCKOP( ret = recv( fd, buffer, 65536, 0 ), "Receiving" ); + +		if( ret == 0 ) +			return dcc_abort( df, "Remote end closed connection" ); + +		buffer = g_realloc( buffer, ret ); + +		df->bytes_sent += ret; + +		done = df->bytes_sent >= ft->file_size; + +		if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || +		    done ) +		{ +			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", ret ); +		} +		 +		if( !ft->write( df->ft, buffer, ret ) && !done ) +		{ +			df->watch_in = 0; +			return FALSE; +		} + +		if( done ) +		{ +			closesocket( fd ); +			dcc_finish( ft ); + +			df->watch_in = 0; +			return FALSE; +		} + +		return TRUE; +	} + +	return TRUE; +} + +void dccs_recv_out_of_data( file_transfer_t *ft ) +{ +	dcc_file_transfer_t *df = ft->priv; + +	if( !df->watch_in ) +		df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df ); +} +  /*    * 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. @@ -471,7 +611,7 @@ gboolean dccs_send_write( file_transfer_t *file, gpointer data, unsigned int dat  	df->queued_bytes += data_size; -	if( ( file->status & FT_STATUS_TRANSFERING ) &&  +	if( ( file->status & FT_STATUS_TRANSFERRING ) &&   #ifndef DCC_SEND_AHEAD  	    ( file->bytes_transferred >= df->bytes_sent ) &&   #endif @@ -532,3 +672,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; +} +  | 
