This is the new home of the egghelp.org community forum.
All data has been migrated (including user logins/passwords) to a new phpBB version.


For more information, see this announcement post. Click the X in the top right-corner of this box to dismiss this message.

Accepting DCC Files, without filesys.mod

Help for those learning Tcl or writing their own scripts.
User avatar
user
 
Posts: 1452
Joined: Tue Mar 18, 2003 9:58 pm
Location: Norway

Post by user »

It seems like fcopy will prevent you from being able to write to the socket using puts, so you'll have to do what fcopy does yourself. (like nml375 suggested)

Here's some (untested & written in a hurry) code to get you started:

Code: Select all

namespace eval ::dccrecv {

	# change this and make sure the directory exists
	variable dir "~/received";

	bind ctcp - DCC ::dccrecv::ctcp
	
	proc ctcp {n u h t a} {
		variable dir
		if {![isbotnick $t]} {return 0}
		if {[scan $a %s]=="SEND"} {
			set user $n!$u
			if {![matchattr $h X]} {
				# access denied (replace this with your own user validation)
				log "Refused SEND from $user"
				return 0
			}
			if {[scan $a "%s%s%u%u%u" k file ip port size]==5} {
				# valid ctcp?
				if {[catch {valid_file $file} err]} {
					log "SEND request from $user failed: $err"
					return 0
				}
				if {[catch {valid_ip $ip} err]} {
					log "SEND request from $user failed: $err"
					return 0
				}
				if {$port<1024||$port>65535} {
					log "SEND request from $user failed: Invalid port"
					return 0
				}
				if {$size<1} {
					log "SEND request from $user failed: Invalid size"
					return 0
				}
				log "Connecting to [dec2dot $ip]:$port to receive $file from $user..."
				# merge dir & file name
				set file [file join $dir $file]
				# make sure no file by that name exists...
				# (remove this if you implement RESUME, of course :)
				if {[file exists $file]} {
					set i 0
					set root [file rootname $file]
					set ext [file extension $file]
					set file $root.$i$ext
					while {[file exists $file]} {
						set file $root.[incr i]$ext
					}
					log "(new local file name: $file)"
				}
				# create socket and wait for connection to complete
				if {[catch {socket -async $ip $port} sock]} {
					log "Failed creating socket to receive $file from $user"
					return 0
				}
				fconfigure $sock -blocking 0 -buffering none -buffersize 1000000 -translation binary
				fileevent $sock writable [list ::dccrecv::conn $sock $n!$u $file $size]
				return 1
			} else {
				log "Malformed SEND request from $user"
				return 0
			}
		} else {
			# not DCC SEND
			return 0
		}
	}
	
	# Connected?
	proc conn {sock user file size} {
		if {[set err [fconfigure $sock -error]]!=""} {
			log "Failed connecting to $user: $err"
			close $sock
			return
		}
		fileevent $sock writable {}
		fileevent $sock readable [list ::dccrecv::read_first $sock $user $file $size]
	}
	
	# The first packet... create the file if any data is received
	proc read_first {sock user file size} {
		if {[set err [fconfigure $sock -error]]!=""} {
			log "Failed reading first packet from $user: $err"
			close $sock
			return
		}
		if {[eof $sock]} {# i'm not sure this will ever happen
			log "EOF on connect to $user - no data received"
			close $sock
			return
		}
		if {[catch {
			set fchan [open $file w]
			fconfigure $fchan -translation binary
			puts -nonewline $fchan [read $sock]
			puts -nonewline $sock [binary format I [tell $fchan]]
		} err]} {
			log "Error reading first packet from $user: $err"
			close $sock
			return
		}
		if {[tell $fchan]>=$size} {
			close $fchan
			close $sock
			log "Received $file from $user (all in the first packet)"
		} else {
			fileevent $sock readable [list ::dccrecv::read_more $sock $user $file $fchan $size]
		}
	}

	# The rest of the file... keep reading and reporting the total size
	# and check the size to see if we're done.
	proc read_more {sock user file fchan size} {
		if {[set err [fconfigure $sock -error]]!=""} {
			log "Lost connection to $user: $error"
			close $fchan
			close $sock
			# you might want to [file delete $file] here
			return
		} elseif {[eof $sock]} {
			log "DCC SEND from $user incomplete (unexpected EOF)"
			close $fchan
			close $sock
			# you might want to [file delete $file] here
			return
		}
		if {[catch {
			puts -nonewline $fchan [read $sock]
			puts -nonewline $sock [binary format I [tell $fchan]]
		} err]} {
			log "Error recieving $file from $user: $err"
			close $fchan
			close $sock
			# you might want to [file delete $file] here
			return
		}
		if {[tell $fchan]>=$size} {
			# you might want to check if the file size > $size (indicating a possible dos attempt)
			close $fchan
			close $sock
			log "Received $file from $user"
		}
	}

	proc log msg {
		putlog "\[dccrecv\] $msg"
	}
	
	proc dec2dot ip {
		join [scan [format %08x $ip] %2x%2x%2x%2x] .
	}

	proc valid_ip ip {
		if {![string is integer -strict $ip]||($ip<0)||($ip>4294967295)} {
			error "not a valid ipv4 address: $ip"
		}
		if {($ip>=2130706432)&&($ip<2147483648)} {
			error "loopback address: [dec2dot $ip]"
		}
		if {(($ip>=167772160)&&($ip<184549376))||\
		(($ip>=2886729728)&&($ip<2887778304))||\
		(($ip>=3232235520)&&($ip<3232301056))} {
			error "private network address: [dec2dot $ip]"
		}
		if {($ip>=2851995648)&&($ip<2852061184)} {
			error "link-local address: [dec2dot $ip]"
		}
	}

	proc valid_file {file {dir {}} {
		if {[string match {*[/|@&<>]*} $file]} {
			error "invalid filename (possible exploit attempt?): $file"
		}
		if {$dir!={}&&[file isdir [file join $dir $file]]} {
			error "file is directory: $file
		}
	}


}
EDIT: Spelling ;p
Last edited by user on Tue Mar 04, 2008 2:08 am, edited 1 time in total.
Have you ever read "The Manual"?
E
Empus
Voice
Posts: 13
Joined: Thu Feb 28, 2008 5:50 am

Post by Empus »

Thanks user and nml375, your help is much appreciated.

The code is working. I'm currently sending a 700mb file to the bot from a local client, but the transfer speed is only about 70kB/s

Any ideas on how to improve this? I would expect the transfer to be a lot faster than that...
User avatar
strikelight
Owner
Posts: 708
Joined: Mon Oct 07, 2002 10:39 am
Contact:

Post by strikelight »

Just wanted to say nice work as usual, user. (Btw, it's "receive" not "recieve" ;p)
User avatar
user
&nbsp;
Posts: 1452
Joined: Tue Mar 18, 2003 9:58 pm
Location: Norway

Post by user »

Empus wrote:I would expect the transfer to be a lot faster than that...
Try adding a loop that keeps reading while {![fblocked $sock]} {...} (in the read_more proc)
strikelight wrote:Btw, it's "receive" not "recieve" ;p
Corrected :)
Have you ever read "The Manual"?
E
Empus
Voice
Posts: 13
Joined: Thu Feb 28, 2008 5:50 am

Post by Empus »

Unfortunately that made it much much slower :<
User avatar
user
&nbsp;
Posts: 1452
Joined: Tue Mar 18, 2003 9:58 pm
Location: Norway

Post by user »

Empus wrote:Unfortunately that made it much much slower :<
That's weird. How about doing a fcopy with -size == file size? That way, when the callback is invoked, you'll either have the entire file or an error. (or SHOULD have - I didn't quite understand what went wrong in your previous fcopy experiment)
Have you ever read "The Manual"?
E
Empus
Voice
Posts: 13
Joined: Thu Feb 28, 2008 5:50 am

Post by Empus »

Forgive me, wasn't the point of this new method, given the prior results, to not use fcopy?
User avatar
user
&nbsp;
Posts: 1452
Joined: Tue Mar 18, 2003 9:58 pm
Location: Norway

Post by user »

Empus wrote:Forgive me, wasn't the point of this new method, given the prior results, to not use fcopy?
Yes, because fcopy didn't SEEM to work, but I re-read your comment and this:
Empus wrote:the file is transferring, and CopyMore is being ran, but it seems to lock the bot, and I don't get an acknowledgement that the transfer completed.
leads me to believe fcopy could work. I'd also like to see your code using "while {![fblocked" as I don't see how it could become alot slower that way.
Have you ever read "The Manual"?
E
Empus
Voice
Posts: 13
Joined: Thu Feb 28, 2008 5:50 am

Post by Empus »

To be honest, I believe it wasn't being placed in the right area of the code.

Can you show me what you mean?

I'm a complete noob when it comes to this, I've never had to deal with TCP sockets like this -reading and writing a file is as close as I've gotten to this :>
n
nml375
Revered One
Posts: 2860
Joined: Fri Aug 04, 2006 2:09 pm

Post by nml375 »

The problem with using fcopy is that it does not provide any means to identify individual packets, which is required for the ack. It will allow you to read a predefined number of bytes, but there's no guarantee that the sending party is using this packet size as there is no enforced size in the dcc implementation.

fileevent is, unfortunately, not ideal in this matter either, as if there is too much time in between calls to Tcl_DoOneEvent(), multiple packets might have arrived - being read as one chunk of data upon next event run.

Have you compared the performance of user's script with the filesys/transfer module (as a benchmark)?

It is unfortunate that the transfer module, although capable of outbound dcc transfers, cannot accept inbound transfers without depending on a separate module (such as filesys).
NML_375
Post Reply