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