"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