"Fossies" - the Fresh Open Source Software Archive

Member "zebedee-2.5.3/ftpgw.tcl" (13 Apr 2001, 12440 Bytes) of package /linux/privat/old/zebedee-2.5.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Tcl/Tk source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ftpgw.tcl" see the Fossies "Dox" file reference documentation.

    1 #!/bin/sh
    2 #
    3 # ftpgw.tcl -- FTP gateway to permit tunnelling
    4 #
    5 # Usage: ftpgw.tcl [-p port-range] [-v] [listen-port [ftpd-host [ftpd-port]]]
    6 #
    7 # This program is a simple FTP gateway that intercepts PORT commands and
    8 # passive-mode responses so that it can set up handlers for the data
    9 # streams associated with them. It then rewites these control lines so
   10 # that traffic to an FTP server apparently comes from the gateway process.
   11 # Similarly a client sees a remote port that it can access for passive
   12 # mode transfers. This allows the FTP control (but not data) channel to
   13 # be tunnelled and encrypted.
   14 #
   15 # By default the program listens on port 2121 and will redirect traffic
   16 # to a local FTP server on port 21. These values can be overridden on the
   17 # command line. The -v option turns on verbose logging to stderr.
   18 #
   19 # If the -p option is specified then the argument is a range of port
   20 # numbers in the form xxx-yyy. All passive-mode data ports will be in
   21 # the range xxx to yyy and the response lines will be re-written to
   22 # redirect a client to the corresponding port on 127.0.0.1 (localhost).
   23 # This means that passive-mode data connections can be tunnelled in
   24 # addition to the control connection.
   25 #
   26 #
   27 # This file is part of "zebedee".
   28 #
   29 # Copyright 2000, 2001 by Neil Winton. All rights reserved.
   30 # 
   31 # This program is free software; you can redistribute it and/or modify
   32 # it under the terms of the GNU General Public License as published by
   33 # the Free Software Foundation; either version 2 of the License, or
   34 # (at your option) any later version.
   35 #
   36 # This program is distributed in the hope that it will be useful,
   37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   39 # GNU General Public License for more details.
   40 #
   41 # You should have received a copy of the GNU General Public License
   42 # along with this program; if not, write to the Free Software
   43 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   44 #
   45 # For further details on "zebedee" see http://www.winton.org.uk/zebedee/
   46 #
   47 #
   48 # $Id: ftpgw.tcl,v 1.2 2001/04/13 17:42:30 ndwinton Exp $
   49 #
   50 # Restart using tclsh. Do not delete this backslash -> \
   51     exec tclsh $0 ${1+"$@"}
   52 
   53 set ListenPort 2121     ;# Port on which to listen
   54 set FtpdHost localhost      ;# Host on which ftpd is running
   55 set FtpdPort 21         ;# Port on which ftpd is listening
   56 set FtpdAddr 127.0.0.1      ;# Address of host on which ftpd is running
   57 set Verbose 0           ;# Verbose mode -- log messages to stderr
   58 set Initialised 0       ;# Flag to indicate initialisation complete
   59 set MinPasvPort 0       ;# Minimum value for passive data port
   60 set MaxPasvPort 0       ;# Maximum value for passive data port
   61 
   62 
   63 # log
   64 #
   65 # Log a message in verbose mode
   66 
   67 proc log {msg} {
   68     global Verbose
   69 
   70     if {$Verbose} {puts stderr $msg}
   71 }
   72 
   73 # acceptCtrlConn
   74 #
   75 # Accept a new control connection and create a socket to the real ftpd.
   76 # Traffic on either connection is handled by the handleCtrl routine.
   77 
   78 proc acceptCtrlConn {mySock ipAddr port} {
   79     global FtpdHost FtpdPort Initialised
   80 
   81     if {!$Initialised} {
   82     # First connection received will be a dummy to determine the host
   83     # address -- ignore it
   84     set Initialised 1
   85     close $mySock
   86     return
   87     }
   88 
   89     log "$mySock: new client from $ipAddr/$port"
   90 
   91     if {[catch {socket $FtpdHost $FtpdPort} ftpdSock]} {
   92     close $mySock
   93     error "can't create forwarding control connection to $FtpdHost/$FtpdPort: $ftpdSock"
   94     }
   95 
   96     log "$mySock: connected to $FtpdHost/$FtpdPort via $ftpdSock"
   97 
   98     fconfigure $mySock -blocking false
   99     fconfigure $ftpdSock -blocking false
  100 
  101     fileevent $mySock readable [list handleCtrl $mySock $ftpdSock]
  102     fileevent $ftpdSock readable [list handleCtrl $ftpdSock $mySock]
  103 }
  104 
  105 # handleCtrl
  106 #
  107 # Handle a control connection. This is used for both traffic from and to
  108 # the server. Data is read from fromSock and written to toSock. It may
  109 # be transformed before being written. Specifically, PORT commands from
  110 # a client result and passive-mode replies (227) from a server result in
  111 # a new local data socket and handler being created and the address details
  112 # being rewritten.
  113 
  114 proc handleCtrl {fromSock toSock} {
  115     global HostAddr FtpdHost FtpdAddr MaxPasvPort MinPasvPort DataSock
  116 
  117     # Check for EOF and close connections if necessary
  118 
  119     if {[gets $fromSock line] < 0} {
  120     close $fromSock
  121     close $toSock
  122     } {
  123     # Make sure we do not show passwords in verbose output
  124 
  125     if {[string match "PASS *" $line]} {
  126         log "$fromSock -> $toSock: PASS <password>"
  127     } {
  128         log "$fromSock -> $toSock: $line"
  129     }
  130 
  131     # Re-write PORT command lines from the client.
  132 
  133     if {[regexp -nocase {^PORT ([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $line dummy a1 a2 a3 a4 p1 p2]} {
  134         log "$fromSock -> $toSock: Rewriting $line"
  135 
  136         set clientAddr "$a1.$a2.$a3.$a4"
  137         set clientPort [expr {$p1 * 256 + $p2}]
  138         set handler [list acceptDataConn [list $FtpdAddr 127.0.0.1] $clientAddr $clientPort]
  139 
  140         if {[catch {createDataConn $handler 1024 65535} connInfo]} {
  141         close $fromSock
  142         close $toSock
  143         log "$fromSock -> $toSock: Error creating data connection: $connInfo"
  144         return
  145         }
  146 
  147         # Note the socket handle used for this address/port combination
  148         # so that we can close it after a connection has been accepted.
  149 
  150         set DataSock($clientAddr,$clientPort) [lindex $connInfo 0]
  151 
  152         # Construct new PORT command referring to the new local
  153         # data socket.
  154 
  155         set port [lindex $connInfo 1]
  156         set port [expr {$port / 256}],[expr {$port % 256}]
  157 
  158         # If the ftpd is running locally then we need to supply
  159         # the localhost address otherwise the full machine IP
  160         # address is needed for the data connection to appear to
  161         # come from the same place as the control connection.
  162 
  163         if {$FtpdAddr == "127.0.0.1"} {
  164         set myAddr "127.0.0.1"
  165         } {
  166         set myAddr $HostAddr
  167         }
  168         set myAddr [join [split $myAddr .] ,]
  169 
  170         set line "PORT $myAddr,$port"
  171 
  172         log "$fromSock -> $toSock: Rewritten to $line"
  173     }
  174 
  175     # Rewrite passive mode lines response lines from server
  176 
  177     if {[regexp {^227 .*[^0-9]([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $line dummy a1 a2 a3 a4 p1 p2]} {
  178         log "$fromSock -> $toSock: Rewriting $line"
  179 
  180         set serverAddr "$a1.$a2.$a3.$a4"
  181         set serverPort [expr {$p1 * 256 + $p2}]
  182 
  183         if {$MinPasvPort} {
  184         set allowed 127.0.0.1
  185         } {
  186         set allowed {}
  187         }
  188         set handler [list acceptDataConn $allowed $serverAddr $serverPort]
  189 
  190         if {[catch {createDataConn $handler $MinPasvPort $MaxPasvPort} connInfo]} {
  191         close $fromSock
  192         close $toSock
  193         log "$fromSock -> $toSock: Error creating data connection: $connInfo"
  194         return
  195         }
  196 
  197         # Note the socket handle used for this address/port combination
  198         # so that we can close it after a connection has been accepted.
  199 
  200         set DataSock($serverAddr,$serverPort) [lindex $connInfo 0]
  201 
  202         # Construct a 227 response line referring to the new local
  203         # data socket.
  204 
  205         set port [lindex $connInfo 1]
  206         set port [expr {$port / 256}],[expr {$port % 256}]
  207 
  208         # If a port range has been specified then we must only supply
  209         # the localhost address because this is being used for
  210         # tunnelling and for this to work the client must connect to
  211         # its matching local port.
  212 
  213         if {$MinPasvPort} {
  214         set myAddr "127.0.0.1"
  215         } {
  216         set myAddr $HostAddr
  217         }
  218         set myAddr [join [split $myAddr .] ,]
  219         set line "227 Entering Passive Mode ($myAddr,$port)"
  220 
  221         log "$fromSock -> $toSock: Rewritten to $line"
  222     }
  223 
  224     puts $toSock $line
  225     flush $toSock
  226     }
  227 }
  228 
  229 # createDataConn
  230 #
  231 # Create a new data connection listener socket with handler command "cmd".
  232 # The function returns the port number. The "loPort" and "hiPort" parameters
  233 # give the range in which the port should lie. If loPort is zero then any
  234 # port (>= 1024) is acceptable. Within the range we try to pick a port at
  235 # random (sort of :-) to avoid the worst excesses of passive port stealing.
  236 #
  237 # Returns a list of the socket handle and port number
  238 
  239 proc createDataConn {cmd loPort hiPort} {
  240 
  241     if {$loPort < 1024} {
  242     set loPort 1024
  243     }
  244     if {$hiPort > 65535 || $hiPort < 1024} {
  245     set hiPort 65535
  246     }
  247 
  248     set count [expr {$hiPort - $loPort + 1}]
  249 
  250     # Pick a random starting point
  251 
  252     set start [expr {int(rand() * $count)}]
  253 
  254     for {set i 0} {$i < $count} {incr i} {
  255 
  256     set port [expr {(($i + $start) % $count) + $loPort}]
  257 
  258     if {![catch {socket -server $cmd $port} sock]} {
  259         break
  260     }
  261     }
  262 
  263     if {$i >= $count} {
  264     error "can't find free data port socket"
  265     }
  266 
  267     return [list $sock $port]
  268 }
  269 
  270 # acceptDataConn
  271 #
  272 # Accept a new data connection and set up forwarding data channel handlers
  273 # (in binary data mode) to the address and port in toAddr/toPort. The
  274 # connection will be rejected unless it comes from an address named in
  275 # allowFrom, if set, to avoid port "theft".
  276 
  277 proc acceptDataConn {allowFrom toAddr toPort mySock ipAddr port} {
  278     global DataSock
  279 
  280     log "$mySock: new data connection from $ipAddr/$port"
  281 
  282     if {$allowFrom != {} && [lsearch -exact $allowFrom $ipAddr] == -1} {
  283     log "$mySock: WARNING: rejected connection from $ipAddr"
  284     close $mySock
  285     return
  286     }
  287 
  288     # Once a data connection has been accepted we can close the listening
  289     # socket. It will only be used once.
  290 
  291     if {[info exists DataSock($toAddr,$toPort)]} {
  292     close $DataSock($toAddr,$toPort)
  293     unset DataSock($toAddr,$toPort)
  294     }
  295 
  296     # Open a connection to the real destination
  297 
  298     if {[catch {socket $toAddr $toPort} toSock]} {
  299     close $mySock
  300     error "can't make forwarding data connection to $toAddr/$toPort: $toSock"
  301     }
  302 
  303     log "$mySock: forwards to $toSock"
  304 
  305     # Make the channels handle binary data
  306 
  307     fconfigure $toSock -translation binary
  308     fconfigure $mySock -translation binary
  309 
  310     # Set up data transfer based on which end of the pipe becomes readable
  311 
  312     fileevent $mySock readable [list startCopy $mySock $toSock]
  313     fileevent $toSock readable [list startCopy $toSock $mySock]
  314 }
  315 
  316 # startCopy
  317 #
  318 # Set up fcopy to handle copying the data in the background from fromSock to
  319 # toSock.
  320 
  321 proc startCopy {fromSock toSock} {
  322 
  323     fileevent $toSock readable {}
  324     fileevent $fromSock readable {}
  325 
  326     log "$fromSock -> $toSock: starting data copy"
  327     fcopy $fromSock $toSock -command [list finishCopy $fromSock $toSock]
  328 }
  329 
  330 # finishCopy
  331 #
  332 # Handler routine for end of fcopy
  333 
  334 proc finishCopy {fromSock toSock bytes {error {}}} {
  335     if {"$error" != {}} {
  336     log "$fromSock -> $toSock: error copying data: $error"
  337     }
  338 
  339     log "$fromSock -> $toSock: copy finished, $bytes bytes transferred"
  340 
  341     catch {
  342     close $fromSock
  343     close $toSock
  344     }
  345 }
  346 
  347 # bgerror
  348 #
  349 # Handle an error -- just print a message in verbose mode
  350 
  351 proc bgerror {args} {
  352     log "ERROR: $args"
  353 }
  354 
  355 ###
  356 ### Main Code
  357 ###
  358 
  359 for {set i 0} {$i < [llength $argv]} {incr i} {
  360     switch -exact -- [lindex $argv $i] {
  361     {-v} {
  362         incr Verbose
  363     }
  364 
  365     {-p} {
  366         incr i
  367         if {[scan [lindex $argv $i] "%hu-%hu" MinPasvPort MaxPasvPort] != 2} {
  368         error "$argv0: invalid range to -r: [lindex $argv $i]"
  369         }
  370         if {$MinPasvPort < 1024} {
  371         error "$argv0: minimum passive data port must be >= 1024"
  372         }
  373         if {$MinPasvPort > $MaxPasvPort} {
  374         error "$argv0: minimum passive data port must be <= maximum"
  375         }
  376     }
  377 
  378     default {
  379         break
  380     }
  381     }
  382 }
  383 
  384 set argv [lrange $argv $i end]
  385 
  386 if {[lindex $argv 0] != {}} {
  387     set ListenPort [lindex $argv 0]
  388 }
  389 if {[lindex $argv 1] != {}} {
  390     set FtpdHost [lindex $argv 1]
  391 }
  392 if {[lindex $argv 2] != {}} {
  393     set FtpdPort [lindex $argv 2]
  394 }
  395 
  396 # Try connecting to the ftpd server both to validate the date and to
  397 # get its IP address
  398 
  399 log "Contacting FTP server on $FtpdHost/$FtpdPort"
  400 
  401 if {[catch {socket $FtpdHost $FtpdPort} s]} {
  402     error "$argv0: can't contact FTP server on $FtpdHost/$FtdPort"
  403 }
  404 set FtpdAddr [lindex [fconfigure $s -peername] 0]
  405 close $s
  406 
  407 # Start the local listener
  408 
  409 set Listener [socket -server acceptCtrlConn $ListenPort]
  410 
  411 log "Listening on port $ListenPort"
  412 
  413 # Get the local IP address. We do this by making a connection to the
  414 # port we have just set up for listening and then using fconfigure. Note
  415 # that on Windows systems fconfigure can take an unexpectedly long time.
  416 
  417 log "Determining the host address ..."
  418 
  419 set s [socket [info hostname] $ListenPort]
  420 set HostAddr [lindex [fconfigure $s -sockname] 0]
  421 close $s
  422 
  423 log "Host [info hostname], address $HostAddr"
  424 
  425 # Enter the Tcl event loop ...
  426 
  427 vwait forever