1#
2#   tcl FTP library package --
3#
4#   required:   tcl8.0
5#
6#   created:	12/96
7#   changed:    04/99
8#   version:    1.2
9#
10#   core ftp support: 	FTP::Open <server> <user> <passwd> <?options?>
11#			FTP::Close
12#		    	FTP::Cd <directory>
13#			FTP::Pwd
14#			FTP::Type <?ascii|binary?>
15#			FTP::List <?directory?>
16#			FTP::NList <?directory?>
17#			FTP::FileSize <file>
18#			FTP::ModTime <from> <to>
19#			FTP::Delete <file>
20#			FTP::Rename <from> <to>
21#			FTP::Put <local> <?remote?>
22#			FTP::Append <local> <?remote?>
23#			FTP::Get <remote> <?local?>
24#			FTP::Reget <remote> <?local?>
25#			FTP::Newer <remote> <?local?>
26#			FTP::MkDir <directory>
27#			FTP::RmDir <directory>
28#			FTP::Quote <arg1> <arg2> ...
29#
30#   Copyright (C) 1996-1999 Steffen Traeger
31#
32#   This program is free software; you can redistribute it and/or modify
33#   it under the terms of the GNU General Public License as published by
34#   the Free Software Foundation; either version 2 of the License, or
35#   (at your option) any later version.
36#
37#   This program is distributed in the hope that it will be useful,
38#   but WITHOUT ANY WARRANTY; without even the implied warranty of
39#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
40#   GNU General Public License for more details.
41#
42#   You should have received a copy of the GNU General Public License
43#   along with this program; if not, write to the Free Software
44#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
45#
46#   contact:
47#	email:	Steffen.Traeger@t-online.de
48#	url:	http://home.t-online.de/home/Steffen.Traeger
49#
50########################################################################
51
52package provide FTP 1.2
53
54namespace eval FTP {
55
56namespace export DisplayMsg Open Close Cd Pwd Type List NList FileSize ModTime\
57		 Delete Rename Put Append Get Reget Newer Quote MkDir RmDir
58
59set VERBOSE 1
60set DEBUG 1
61
62#############################################################################
63#
64# DisplayMsg --
65#
66# This is a simple procedure to display any messages on screen.
67# It must be overwritten by users source code in the form:
68# (exported)
69#
70#	namespace FTP {
71#		proc DisplayMsg {msg} {
72#			......
73#		}
74#	}
75#
76# Arguments:
77# msg - 		message string
78# state -		different states {normal, data, control, error}
79#
80proc DisplayMsg {msg {state ""}} {
81variable VERBOSE
82
83	switch $state {
84	  data		{if {$VERBOSE} {puts $msg}}
85	  control	{if {$VERBOSE} {puts $msg}}
86	  error		{puts stderr "ERROR: $msg"}
87	  default 	{if {$VERBOSE} {puts $msg}}
88	}
89}
90
91#############################################################################
92#
93# Timeout --
94#
95# Handle timeouts
96#
97# Arguments:
98#  -
99#
100proc Timeout {} {
101variable ftp
102upvar #0 finished state
103
104	after cancel $ftp(Wait)
105	set state(control) 1
106
107	DisplayMsg "Timeout of control connection after $ftp(Timeout) sec.!" error
108
109}
110
111#############################################################################
112#
113# WaitOrTimeout --
114#
115# Blocks the running procedure and waits for a variable of the transaction
116# to complete. It continues processing procedure until a procedure or
117# StateHandler sets the value of variable "finished".
118# If a connection hangs the variable is setting instead of by this procedure after
119# specified seconds in $ftp(Timeout).
120#
121#
122# Arguments:
123#  -
124#
125
126proc WaitOrTimeout {} {
127variable ftp
128upvar #0 finished state
129
130	set retvar 1
131
132	if {[info exists state(control)]} {
133
134		set ftp(Wait) [after [expr $ftp(Timeout) * 1000] [namespace current]::Timeout]
135
136		vwait finished(control)
137		set retvar $state(control)
138	}
139
140	return $retvar
141}
142
143#############################################################################
144#
145# WaitComplete --
146#
147# Transaction completed.
148# Cancel execution of the delayed command declared in procedure WaitOrTimeout.
149#
150# Arguments:
151# value -	result of the transaction
152#			0 ... Error
153#			1 ... OK
154#
155
156proc WaitComplete {value} {
157variable ftp
158upvar #0 finished state
159
160	if {[info exists state(data)]} {
161		vwait finished(data)
162	}
163
164	after cancel $ftp(Wait)
165	set state(control) $value
166}
167
168#############################################################################
169#
170# PutsCtrlSocket --
171#
172# Puts then specified command to control socket,
173# if DEBUG is set than it logs via DisplayMsg
174#
175# Arguments:
176# command - 		ftp command
177#
178
179proc PutsCtrlSock {{command ""}} {
180variable ftp
181variable DEBUG
182
183	if {$DEBUG} {
184		DisplayMsg "---> $command"
185	}
186
187	puts $ftp(CtrlSock) $command
188	flush $ftp(CtrlSock)
189
190
191}
192
193#############################################################################
194#
195# StateHandler --
196#
197# Implements a finite state handler and a fileevent handler
198# for the control channel
199#
200# Arguments:
201# sock - 		socket name
202#			If called from a procedure than this argument is empty.
203# 			If called from a fileevent than this argument contains
204#			the socket channel identifier.
205
206proc StateHandler {{sock ""}} {
207upvar #0 finished state
208variable ftp
209variable DEBUG
210variable VERBOSE
211
212	# disable fileevent on control socket, enable it at the and of the state machine
213        # fileevent $ftp(CtrlSock) readable {}
214
215	# there is no socket (and no channel to get) if called from a procedure
216	set rc "   "
217
218	if { $sock != "" } {
219
220		set number [gets $sock bufline]
221
222		if { $number > 0 } {
223
224			# get return code, check for multi-line text
225			regexp "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line msgtext
226
227			set buffer $bufline
228
229			# multi-line format detected ("-"), get all the lines
230			# until the real return code
231			while { $multi_line == "-"  } {
232				set number [gets $sock bufline]
233				if { $number > 0 } {
234					append buffer \n "$bufline"
235					regexp "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line
236				}
237			}
238		} elseif [eof $ftp(CtrlSock)] {
239			# remote server has closed control connection
240			# kill control socket, unset State to disable all following command
241			set rc 421
242			if {$VERBOSE} {
243				DisplayMsg "C: 421 Service not available, closing control connection." control
244			}
245			DisplayMsg "Service not available!" error
246			CloseDataConn
247			WaitComplete 0
248			catch {unset ftp(State)}
249			catch {close $ftp(CtrlSock); unset ftp(CtrlSock)}
250			return
251		}
252
253	}
254
255	if {$DEBUG} {
256		DisplayMsg "-> rc=\"$rc\"\n-> state=\"$ftp(State)\""
257	}
258
259	# system status replay
260	if {$rc == "211"} {return}
261
262	# use only the first digit
263	regexp "^\[0-9\]?" $rc rc
264
265 	switch -- $ftp(State) {
266
267		user	{
268			  switch $rc {
269			    2       {
270			    	       PutsCtrlSock "USER $ftp(User)"
271			               set ftp(State) passwd
272			            }
273			    default {
274				       set errmsg "Error connecting! $msgtext"
275				       set complete_with 0
276			            }
277			  }
278			}
279
280		passwd	{
281			  switch $rc {
282			    2       {
283				       set complete_with 1
284			            }
285			    3       {
286			  	       PutsCtrlSock "PASS $ftp(Passwd)"
287		  	       	       set ftp(State) connect
288			            }
289			    default {
290				       set errmsg "Error connecting! $msgtext"
291				       set complete_with 0
292			            }
293			  }
294			}
295
296		connect	{
297			  switch $rc {
298			    2       {
299				       set complete_with 1
300			            }
301			    default {
302				       set errmsg "Error connecting! $msgtext"
303				       set complete_with 0
304			            }
305			  }
306			}
307
308		quit	{
309		    	   PutsCtrlSock "QUIT"
310			   set ftp(State) quit_sent
311			}
312
313		quit_sent {
314			  switch $rc {
315			    2       {
316			               set complete_with 1
317			            }
318			    default {
319				       set errmsg "Error disconnecting! $msgtext"
320				       set complete_with 0
321			            }
322			  }
323			}
324
325		quote	{
326		    	   PutsCtrlSock $ftp(Cmd)
327			   set ftp(State) quote_sent
328			}
329
330		quote_sent {
331	                   set complete_with 1
332                           set ftp(Quote) $buffer
333			}
334
335		type	{
336		  	  if { $ftp(Type) == "ascii" } {
337			  	PutsCtrlSock "TYPE A"
338			  } else {
339			  	PutsCtrlSock "TYPE I"
340			  }
341  		  	  set ftp(State) type_sent
342			}
343
344		type_sent {
345			  switch $rc {
346			    2       {
347				       set complete_with 1
348			            }
349			    default {
350				       set errmsg "Error setting type \"$ftp(Type)\"!"
351				       set complete_with 0
352			            }
353			  }
354			}
355
356		nlist_active {
357			  if {[OpenActiveConn]} {
358			    	PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
359			  	set ftp(State) nlist_open
360			  } else {
361				set errmsg "Error setting port!"
362			  }
363
364		}
365
366		nlist_passive {
367		    PutsCtrlSock "PASV"
368		    set ftp(State) nlist_open
369		}
370
371		nlist_open {
372			  switch $rc {
373			    2 {
374			         if {$ftp(Mode) == "passive"} {
375				     if ![OpenPassiveConn $buffer] {
376				         set errmsg "Error setting PASSIVE mode!"
377				       	 set complete_with 0
378				     }
379				 }
380			         PutsCtrlSock "NLST$ftp(Dir)"
381			  	 set ftp(State) list_sent
382			      }
383			    default {
384			              if {$ftp(Mode) == "passive"} {
385				          set errmsg "Error setting PASSIVE mode!"
386				      } else {
387				          set errmsg "Error setting port!"
388				      }
389			       	      set complete_with 0
390			            }
391			  }
392		}
393
394		list_active	{
395			  if {[OpenActiveConn]} {
396				PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
397		  		set ftp(State) list_open
398			  } else {
399				set errmsg "Error setting port!"
400			  }
401
402		}
403
404		list_passive	{
405		    PutsCtrlSock "PASV"
406		    set ftp(State) list_open
407		}
408
409		list_open {
410			  switch $rc {
411			    2  {
412			         if {$ftp(Mode) == "passive"} {
413				     if {![OpenPassiveConn $buffer]} {
414				         set errmsg "Error setting PASSIVE mode!"
415				       	 set complete_with 0
416				     }
417				 }
418			         PutsCtrlSock "LIST$ftp(Dir)"
419			  	 set ftp(State) list_sent
420			       }
421			    default {
422			              if {$ftp(Mode) == "passive"} {
423				          set errmsg "Error setting PASSIVE mode!"
424				      } else {
425				          set errmsg "Error setting port!"
426				      }
427				      set complete_with 0
428			            }
429			  }
430		}
431
432		list_sent	{
433			  switch $rc {
434			    1       {
435			               set ftp(State) list_close
436			            }
437			    default {
438			               if { $ftp(Mode) == "passive" } {
439			    	           unset state(data)
440				       }
441				       set errmsg "Error getting directory listing!"
442				       set complete_with 0
443			            }
444			  }
445		}
446
447		list_close {
448			  switch $rc {
449			    2     {
450			               set complete_with 1
451			            }
452			    default {
453				       set errmsg "Error receiving list!"
454				       set complete_with 0
455			            }
456			  }
457			}
458
459		size {
460			  PutsCtrlSock "SIZE $ftp(File)"
461  		  	  set ftp(State) size_sent
462			}
463
464		size_sent {
465			  switch $rc {
466			    2       {
467            			       regexp "^\[0-9\]+ (.*)$" $buffer all ftp(FileSize)
468			               set complete_with 1
469			            }
470			    default {
471				       set errmsg "Error getting file size!"
472				       set complete_with 0
473			            }
474			  }
475			}
476
477		modtime {
478			  PutsCtrlSock "MDTM $ftp(File)"
479  		  	  set ftp(State) modtime_sent
480			}
481
482		modtime_sent {
483			  switch $rc {
484			    2       {
485            			       regexp "^\[0-9\]+ (.*)$" $buffer all ftp(DateTime)
486			               set complete_with 1
487			            }
488			    default {
489				       set errmsg "Error getting modification time!"
490				       set complete_with 0
491			            }
492			  }
493			}
494
495		pwd	{
496			   PutsCtrlSock "PWD"
497  		  	   set ftp(State) pwd_sent
498			}
499
500		pwd_sent {
501			  switch $rc {
502			    2       {
503            			       regexp "^.*\"(.*)\"" $buffer temp ftp(Dir)
504			               set complete_with 1
505			            }
506			    default {
507				       set errmsg "Error getting working dir!"
508				       set complete_with 0
509			            }
510			  }
511			}
512
513		cd	{
514			   PutsCtrlSock "CWD$ftp(Dir)"
515  		  	   set ftp(State) cd_sent
516			}
517
518		cd_sent {
519			  switch $rc {
520			    2       {
521			               set complete_with 1
522			            }
523			    default {
524				       set errmsg "Error changing directory!"
525				       set complete_with 0
526				     }
527			  }
528			}
529
530		mkdir	{
531			  PutsCtrlSock "MKD $ftp(Dir)"
532  		  	  set ftp(State) mkdir_sent
533			}
534
535		mkdir_sent {
536			  switch $rc {
537			    2       {
538			               set complete_with 1
539			            }
540			    default {
541				       set errmsg "Error making dir \"$ftp(Dir)\"!"
542				       set complete_with 0
543				     }
544			  }
545			}
546
547		rmdir	{
548			  PutsCtrlSock "RMD $ftp(Dir)"
549  		  	  set ftp(State) rmdir_sent
550			}
551
552		rmdir_sent {
553			  switch $rc {
554			    2       {
555			               set complete_with 1
556			            }
557			    default {
558				       set errmsg "Error removing directory!"
559				       set complete_with 0
560				     }
561			  }
562			}
563
564		delete	{
565			  PutsCtrlSock "DELE $ftp(File)"
566  		  	  set ftp(State) delete_sent
567			}
568
569		delete_sent {
570			  switch $rc {
571			    2       {
572			               set complete_with 1
573			            }
574			    default {
575				       set errmsg "Error deleting file \"$ftp(File)\"!"
576				       set complete_with 0
577				     }
578			  }
579			}
580
581		rename	{
582			  PutsCtrlSock "RNFR $ftp(RenameFrom)"
583  		  	  set ftp(State) rename_to
584			}
585
586		rename_to {
587			  switch $rc {
588			    3       {
589			  	       PutsCtrlSock "RNTO $ftp(RenameTo)"
590  		  	  	       set ftp(State) rename_sent
591			            }
592			    default {
593				       set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
594				       set complete_with 0
595				     }
596			  }
597			}
598
599		rename_sent {
600			  switch $rc {
601			    2     {
602			               set complete_with 1
603			            }
604			    default {
605				       set errmsg "Error renaming file \"$ftp(RenameFrom)\"!"
606				       set complete_with 0
607				     }
608			  }
609			}
610
611		put_active 	{
612			  if {[OpenActiveConn]} {
613			    	PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
614			  	set ftp(State) put_open
615			  } else {
616				set errmsg "Error setting port!"
617			  }
618			}
619
620
621		put_passive	{
622			               PutsCtrlSock "PASV"
623			  	       set ftp(State) put_open
624			}
625
626		put_open {
627			  switch $rc {
628			    2  {
629			         if {$ftp(Mode) == "passive"} {
630				     if {![OpenPassiveConn $buffer]} {
631				         set errmsg "Error setting PASSIVE mode!"
632				       	 set complete_with 0
633				     }
634				 }
635			         PutsCtrlSock "STOR $ftp(RemoteFilename)"
636			         set ftp(State) put_sent
637			       }
638			    default {
639			              if {$ftp(Mode) == "passive"} {
640				          set errmsg "Error setting PASSIVE mode!"
641				      } else {
642				          set errmsg "Error setting port!"
643				      }
644				      set complete_with 0
645				    }
646			  }
647		}
648
649		put_sent	{
650			  switch $rc {
651			    1       {
652			               set ftp(State) put_close
653			            }
654			    default {
655			              if {$ftp(Mode) == "passive"} {
656			    	         # close already opened DataConnection
657			    	         unset state(data)
658				      }
659				       set errmsg "Error opening connection!"
660				       set complete_with 0
661				     }
662			  }
663		}
664
665		put_close	{
666			  switch $rc {
667			    2       {
668			  	       set complete_with 1
669			            }
670			    default {
671				       set errmsg "Error storing file \"$ftp(RemoteFilename)\"!"
672				       set complete_with 0
673				     }
674			  }
675		}
676
677		append_active 	{
678			  if {[OpenActiveConn]} {
679			    	PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
680			  	set ftp(State) append_open
681			  } else {
682				set errmsg "Error setting port!"
683			  }
684			}
685
686
687		append_passive	{
688			               PutsCtrlSock "PASV"
689			  	       set ftp(State) append_open
690			}
691
692		append_open {
693			  switch $rc {
694			    2  {
695			         if {$ftp(Mode) == "passive"} {
696				     if {![OpenPassiveConn $buffer]} {
697				         set errmsg "Error setting PASSIVE mode!"
698				       	 set complete_with 0
699				     }
700				 }
701			         PutsCtrlSock "APPE $ftp(RemoteFilename)"
702			         set ftp(State) append_sent
703			       }
704			    default {
705			              if {$ftp(Mode) == "passive"} {
706				          set errmsg "Error setting PASSIVE mode!"
707				      } else {
708				          set errmsg "Error setting port!"
709				      }
710				      set complete_with 0
711				    }
712			  }
713		}
714
715		append_sent	{
716			  switch $rc {
717			    1       {
718			               set ftp(State) append_close
719			            }
720			    default {
721			              if {$ftp(Mode) == "passive"} {
722			    	         # close already opened DataConnection
723			    	         unset state(data)
724				      }
725				       set errmsg "Error opening connection!"
726				       set complete_with 0
727				     }
728			  }
729		}
730
731		append_close	{
732			  switch $rc {
733			    2       {
734			  	       set complete_with 1
735			            }
736			    default {
737				       set errmsg "Error storing file \"$ftp(RemoteFilename)\"!"
738				       set complete_with 0
739				     }
740			  }
741		}
742
743		reget_active 	{
744			  if {[OpenActiveConn]} {
745			    	PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
746			  	set ftp(State) reget_restart
747			  } else {
748				set errmsg "Error setting port!"
749			  }
750		}
751
752		reget_passive	{
753			               PutsCtrlSock "PASV"
754			  	       set ftp(State) reget_restart
755		}
756
757		reget_restart {
758			  switch $rc {
759			    2 {
760			         if {$ftp(Mode) == "passive"} {
761				     if {![OpenPassiveConn $buffer]} {
762				         set errmsg "Error setting PASSIVE mode!"
763				       	 set complete_with 0
764				     }
765				 }
766			         if {$ftp(FileSize) != 0} {
767				    PutsCtrlSock "REST $ftp(FileSize)"
768	               		    set ftp(State) reget_open
769				 } else {
770			            PutsCtrlSock "RETR $ftp(RemoteFilename)"
771			           set ftp(State) reget_sent
772				 }
773			       }
774			    default {
775				       set errmsg "Error restarting filetransfer of \"$ftp(RemoteFilename)\"!"
776				       set complete_with 0
777				     }
778			  }
779			}
780
781		reget_open {
782			  switch $rc {
783			    2  -
784			    3  {
785			         PutsCtrlSock "RETR $ftp(RemoteFilename)"
786			         set ftp(State) reget_sent
787			       }
788			    default {
789			              if {$ftp(Mode) == "passive"} {
790				          set errmsg "Error setting PASSIVE mode!"
791				      } else {
792				          set errmsg "Error setting port!"
793				      }
794				      set complete_with 0
795				    }
796			   }
797			 }
798
799
800		reget_sent	{
801			  switch $rc {
802			    1 {
803			         set ftp(State) reget_close
804			       }
805			    default {
806			              if {$ftp(Mode) == "passive"} {
807			    	         # close already opened DataConnection
808			    	         unset state(data)
809				      }
810				      set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
811				      set complete_with 0
812				    }
813			   }
814		}
815
816		reget_close	{
817			  switch $rc {
818			    2       {
819			  	       set complete_with 1
820			            }
821			    default {
822				       set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
823				       set complete_with 0
824				     }
825			  }
826		}
827		get_active 	{
828			  if {[OpenActiveConn]} {
829			    	PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)"
830			  	set ftp(State) get_open
831			  } else {
832				set errmsg "Error setting port!"
833			  }
834			}
835
836		get_passive {
837			        PutsCtrlSock "PASV"
838			  	set ftp(State) get_open
839			    }
840
841		get_open {
842			  switch $rc {
843			    2  -
844			    3  {
845			         if {$ftp(Mode) == "passive"} {
846				     if {![OpenPassiveConn $buffer]} {
847				         set errmsg "Error setting PASSIVE mode!"
848				       	 set complete_with 0
849				     }
850				 }
851			         PutsCtrlSock "RETR $ftp(RemoteFilename)"
852			         set ftp(State) get_sent
853			       }
854			    default {
855			              if {$ftp(Mode) == "passive"} {
856				          set errmsg "Error setting PASSIVE mode!"
857				      } else {
858				          set errmsg "Error setting port!"
859				      }
860				      set complete_with 0
861				    }
862			   }
863			 }
864
865		get_sent	{
866			  switch $rc {
867			    1 {
868			         set ftp(State) get_close
869			       }
870			    default {
871			              if {$ftp(Mode) == "passive"} {
872			    	         # close already opened DataConnection
873			    	         unset state(data)
874				      }
875				      set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
876				      set complete_with 0
877				    }
878			   }
879		}
880
881		get_close	{
882			  switch $rc {
883			    2       {
884			  	       set complete_with 1
885			            }
886			    default {
887				       set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!"
888				       set complete_with 0
889				     }
890			  }
891		}
892
893
894	}
895
896	# finish waiting
897	if {[info exists complete_with]} {
898		WaitComplete $complete_with
899	}
900
901	# display control channel message
902	if {[info exists buffer]} {
903		if {$VERBOSE} {
904			foreach line [split $buffer \n] {
905				DisplayMsg "C: $line" control
906			}
907		}
908	}
909
910	# display error message
911	if {[info exists errmsg]} {
912		set ftp(Error) $errmsg
913		DisplayMsg $errmsg error
914	}
915
916	# enable fileevent on control socket again
917	#fileevent $ftp(CtrlSock) readable [list ::FTP::StateHandler $ftp(CtrlSock)]
918
919}
920
921#############################################################################
922#
923# Type --
924#
925# REPRESENTATION TYPE - Sets the file transfer type to ascii or binary.
926# (exported)
927#
928# Arguments:
929# type - 		specifies the representation type (ascii|binary)
930#
931# Returns:
932# type	-		returns the current type or {} if an error occurs
933
934proc Type {{type ""}} {
935variable ftp
936
937	if ![info exists ftp(State)] {
938		DisplayMsg "Not connected!" error
939		return {}
940	}
941
942	# return current type
943	if { $type == "" } {
944		return $ftp(Type)
945	}
946
947	# save current type
948	set old_type $ftp(Type)
949
950	set ftp(Type) $type
951	set ftp(State) type
952	StateHandler
953
954	# wait for synchronization
955	set rc [WaitOrTimeout]
956	if {$rc} {
957		return $ftp(Type)
958	} else {
959		# restore old type
960		set ftp(Type) $old_type
961		return {}
962	}
963
964}
965
966#############################################################################
967#
968# NList --
969#
970# NAME LIST - This command causes a directory listing to be sent from
971# server to user site.
972# (exported)
973#
974# Arguments:
975# dir - 		The $dir should specify a directory or other system
976#			specific file group descriptor; a null argument
977#			implies the current directory.
978#
979# Arguments:
980# dir - 		directory to list
981#
982# Returns:
983# sorted list of files or {} if listing fails
984
985proc NList {{dir ""}} {
986variable ftp
987
988	if ![info exists ftp(State)] {
989		DisplayMsg "Not connected!" error
990		return {}
991	}
992
993	set ftp(List) {}
994	if { $dir == "" } {
995		set ftp(Dir) ""
996	} else {
997		set ftp(Dir) " $dir"
998	}
999
1000	# save current type and force ascii mode
1001	set old_type $ftp(Type)
1002	if { $ftp(Type) != "ascii" } {
1003		Type ascii
1004	}
1005
1006	set ftp(State) nlist_$ftp(Mode)
1007	StateHandler
1008
1009	# wait for synchronization
1010	set rc [WaitOrTimeout]
1011
1012	# restore old type
1013	if { [Type] != $old_type } {
1014		Type $old_type
1015	}
1016
1017	unset ftp(Dir)
1018	if {$rc} {
1019		return [lsort $ftp(List)]
1020	} else {
1021		CloseDataConn
1022		return {}
1023	}
1024
1025}
1026
1027#############################################################################
1028#
1029# List --
1030#
1031# LIST - This command causes a list to be sent from the server
1032# to user site.
1033# (exported)
1034#
1035# Arguments:
1036# dir - 		If the $dir specifies a directory or other group of
1037#			files, the server should transfer a list of files in
1038#			the specified directory. If the $dir specifies a file
1039#			then the server should send current information on the
1040# 			file.  A null argument implies the user's current
1041#			working or default directory.
1042#
1043# Returns:
1044# list of files or {} if listing fails
1045
1046proc List {{dir ""}} {
1047variable ftp
1048
1049	if ![info exists ftp(State)] {
1050		DisplayMsg "Not connected!" error
1051		return {}
1052	}
1053
1054	set ftp(List) {}
1055	if { $dir == "" } {
1056		set ftp(Dir) ""
1057	} else {
1058		set ftp(Dir) " $dir"
1059	}
1060
1061	# save current type and force ascii mode
1062	set old_type $ftp(Type)
1063	if { $ftp(Type) != "ascii" } {
1064		Type ascii
1065	}
1066
1067	set ftp(State) list_$ftp(Mode)
1068	StateHandler
1069
1070	# wait for synchronization
1071	set rc [WaitOrTimeout]
1072
1073	# restore old type
1074	if { [Type] != $old_type } {
1075		Type $old_type
1076	}
1077
1078	unset ftp(Dir)
1079	if {$rc} {
1080
1081		# clear "total"-line
1082		set l [split $ftp(List) "\n"]
1083		set index [lsearch -regexp $l "^total"]
1084		if { $index != "-1" } {
1085			set l [lreplace $l $index $index]
1086		}
1087		# clear blank line
1088		set index [lsearch -regexp $l "^$"]
1089		if { $index != "-1" } {
1090			set l [lreplace $l $index $index]
1091		}
1092
1093		return $l
1094	} else {
1095		CloseDataConn
1096		return {}
1097	}
1098}
1099
1100#############################################################################
1101#
1102# FileSize --
1103#
1104# REMOTE FILE SIZE - This command gets the file size of the
1105# file on the remote machine.
1106# ATTANTION! Doesn't work properly in ascci mode!
1107# (exported)
1108#
1109# Arguments:
1110# filename - 		specifies the remote file name
1111#
1112# Returns:
1113# size -		files size in bytes or {} in error cases
1114
1115proc FileSize {{filename ""}} {
1116variable ftp
1117
1118	if ![info exists ftp(State)] {
1119		DisplayMsg "Not connected!" error
1120		return {}
1121	}
1122
1123	if { $filename == "" } {
1124		return {}
1125	}
1126
1127	set ftp(File) $filename
1128	set ftp(FileSize) 0
1129
1130	set ftp(State) size
1131	StateHandler
1132
1133	# wait for synchronization
1134	set rc [WaitOrTimeout]
1135
1136	unset ftp(File)
1137
1138	if {$rc} {
1139		return $ftp(FileSize)
1140	} else {
1141		return {}
1142	}
1143
1144}
1145
1146
1147#############################################################################
1148#
1149# ModTime --
1150#
1151# MODIFICATION TIME - This command gets the last modification time of the
1152# file on the remote machine.
1153# (exported)
1154#
1155# Arguments:
1156# filename - 		specifies the remote file name
1157#
1158# Returns:
1159# clock -		files date and time as a system-depentend integer
1160#			value in seconds (see tcls clock command) or {} in
1161#			error cases
1162
1163proc ModTime {{filename ""}} {
1164variable ftp
1165
1166	if ![info exists ftp(State)] {
1167		DisplayMsg "Not connected!" error
1168		return {}
1169	}
1170
1171	if { $filename == "" } {
1172		return {}
1173	}
1174
1175	set ftp(File) $filename
1176	set ftp(DateTime) ""
1177
1178	set ftp(State) modtime
1179	StateHandler
1180
1181	# wait for synchronization
1182	set rc [WaitOrTimeout]
1183
1184	unset ftp(File)
1185
1186	if {$rc} {
1187		scan $ftp(DateTime) "%4s%2s%2s%2s%2s%2s" year month day hour min sec
1188		set clock [clock scan "$month/$day/$year $hour:$min:$sec" -gmt 1]
1189		unset year month day hour min sec
1190		return $clock
1191	} else {
1192		return {}
1193	}
1194
1195}
1196
1197#############################################################################
1198#
1199# Pwd --
1200#
1201# PRINT WORKING DIRECTORY - Causes the name of the current working directory.
1202# (exported)
1203#
1204# Arguments:
1205# None.
1206#
1207# Returns:
1208# current directory name
1209
1210proc Pwd {} {
1211variable ftp
1212
1213	if ![info exists ftp(State)] {
1214		DisplayMsg "Not connected!" error
1215		return {}
1216	}
1217
1218	set ftp(Dir) {}
1219
1220	set ftp(State) pwd
1221	StateHandler
1222
1223	# wait for synchronization
1224	set rc [WaitOrTimeout]
1225
1226	if {$rc} {
1227		return $ftp(Dir)
1228	} else {
1229		return {}
1230	}
1231}
1232
1233#############################################################################
1234#
1235# Cd --
1236#
1237# CHANGE DIRECTORY - Sets the working directory on the server host.
1238# (exported)
1239#
1240# Arguments:
1241# dir -			pathname specifying a directory
1242#
1243# Returns:
1244# 0 -			ERROR
1245# 1 - 			OK
1246
1247proc Cd {{dir ""}} {
1248variable ftp
1249
1250	if ![info exists ftp(State)] {
1251		DisplayMsg "Not connected!" error
1252		return 0
1253	}
1254
1255	if { $dir == "" } {
1256		set ftp(Dir) ""
1257	} else {
1258		set ftp(Dir) " $dir"
1259	}
1260
1261	set ftp(State) cd
1262	StateHandler
1263
1264	# wait for synchronization
1265	set rc [WaitOrTimeout]
1266
1267	unset ftp(Dir)
1268
1269	if {$rc} {
1270		return 1
1271	} else {
1272		return 0
1273	}
1274}
1275
1276#############################################################################
1277#
1278# MkDir --
1279#
1280# MAKE DIRECTORY - This command causes the directory specified in the $dir
1281# to be created as a directory (if the $dir is absolute) or as a subdirectory
1282# of the current working directory (if the $dir is relative).
1283# (exported)
1284#
1285# Arguments:
1286# dir -			new directory name
1287#
1288# Returns:
1289# 0 -			ERROR
1290# 1 - 			OK
1291
1292proc MkDir {dir} {
1293variable ftp
1294
1295	if ![info exists ftp(State)] {
1296		DisplayMsg "Not connected!" error
1297		return 0
1298	}
1299
1300	set ftp(Dir) $dir
1301
1302	set ftp(State) mkdir
1303	StateHandler
1304
1305	# wait for synchronization
1306	set rc [WaitOrTimeout]
1307
1308	unset ftp(Dir)
1309
1310	if {$rc} {
1311		return 1
1312	} else {
1313		return 0
1314	}
1315}
1316
1317#############################################################################
1318#
1319# RmDir --
1320#
1321# REMOVE DIRECTORY - This command causes the directory specified in $dir to
1322# be removed as a directory (if the $dir is absolute) or as a
1323# subdirectory of the current working directory (if the $dir is relative).
1324# (exported)
1325#
1326# Arguments:
1327# dir -			directory name
1328#
1329# Returns:
1330# 0 -			ERROR
1331# 1 - 			OK
1332
1333proc RmDir {dir} {
1334variable ftp
1335
1336	if ![info exists ftp(State)] {
1337		DisplayMsg "Not connected!" error
1338		return 0
1339	}
1340
1341	set ftp(Dir) $dir
1342
1343	set ftp(State) rmdir
1344	StateHandler
1345
1346	# wait for synchronization
1347	set rc [WaitOrTimeout]
1348
1349	unset ftp(Dir)
1350
1351	if {$rc} {
1352		return 1
1353	} else {
1354		return 0
1355	}
1356}
1357
1358#############################################################################
1359#
1360# Delete --
1361#
1362# DELETE - This command causes the file specified in $file to be deleted at
1363# the server site.
1364# (exported)
1365#
1366# Arguments:
1367# file -			file name
1368#
1369# Returns:
1370# 0 -			ERROR
1371# 1 - 			OK
1372
1373proc Delete {file} {
1374variable ftp
1375
1376	if ![info exists ftp(State)] {
1377		DisplayMsg "Not connected!" error
1378		return 0
1379	}
1380
1381	set ftp(File) $file
1382
1383	set ftp(State) delete
1384	StateHandler
1385
1386	# wait for synchronization
1387	set rc [WaitOrTimeout]
1388
1389	unset ftp(File)
1390
1391	if {$rc} {
1392		return 1
1393	} else {
1394		return 0
1395	}
1396}
1397
1398#############################################################################
1399#
1400# Rename --
1401#
1402# RENAME FROM TO - This command causes the file specified in $from to be
1403# renamed at the server site.
1404# (exported)
1405#
1406# Arguments:
1407# from -			specifies the old file name of the file which
1408#				is to be renamed
1409# to -				specifies the new file name of the file
1410#				specified in the $from agument
1411# Returns:
1412# 0 -			ERROR
1413# 1 - 			OK
1414
1415proc Rename {from to} {
1416variable ftp
1417
1418	if ![info exists ftp(State)] {
1419		DisplayMsg "Not connected!" error
1420		return 0
1421	}
1422
1423	set ftp(RenameFrom) $from
1424	set ftp(RenameTo) $to
1425
1426	set ftp(State) rename
1427
1428	StateHandler
1429
1430	# wait for synchronization
1431	set rc [WaitOrTimeout]
1432
1433	unset ftp(RenameFrom)
1434	unset ftp(RenameTo)
1435
1436	if {$rc} {
1437		return 1
1438	} else {
1439		return 0
1440	}
1441}
1442
1443#############################################################################
1444#
1445# ElapsedTime --
1446#
1447# Gets the elapsed time for file transfer
1448#
1449# Arguments:
1450# stop_time - 		ending time
1451
1452proc ElapsedTime {stop_time} {
1453variable ftp
1454
1455	set elapsed [expr $stop_time - $ftp(Start_Time)]
1456	if { $elapsed == 0 } { set elapsed 1}
1457	set persec [expr $ftp(Total) / $elapsed]
1458	DisplayMsg "$ftp(Total) bytes sent in $elapsed seconds ($persec Bytes/s)"
1459}
1460
1461#############################################################################
1462#
1463# PUT --
1464#
1465# STORE DATA - Causes the server to accept the data transferred via the data
1466# connection and to store the data as a file at the server site.  If the file
1467# exists at the server site, then its contents shall be replaced by the data
1468# being transferred.  A new file is created at the server site if the file
1469# does not already exist.
1470# (exported)
1471#
1472# Arguments:
1473# source -			local file name
1474# dest -			remote file name, if unspecified, ftp assigns
1475#				the local file name.
1476# Returns:
1477# 0 -			file not stored
1478# 1 - 			OK
1479
1480proc Put {source {dest ""}} {
1481variable ftp
1482
1483	if ![info exists ftp(State)] {
1484		DisplayMsg "Not connected!" error
1485		return 0
1486	}
1487
1488	if ![file exists $source] {
1489		DisplayMsg "File \"$source\" not exist" error
1490		return 0
1491     	}
1492
1493	if { $dest == "" } {
1494		set dest $source
1495	}
1496
1497	set ftp(LocalFilename) $source
1498	set ftp(RemoteFilename) $dest
1499
1500	set ftp(SourceCI) [open $ftp(LocalFilename) r]
1501	if { $ftp(Type) == "ascii" } {
1502		fconfigure $ftp(SourceCI) -buffering line -blocking 1
1503	} else {
1504		fconfigure $ftp(SourceCI) -buffering line -translation binary -blocking 1
1505	}
1506
1507	set ftp(State) put_$ftp(Mode)
1508	StateHandler
1509
1510	# wait for synchronization
1511	set rc [WaitOrTimeout]
1512	if {$rc} {
1513		ElapsedTime [clock seconds]
1514		return 1
1515	} else {
1516		CloseDataConn
1517		return 0
1518	}
1519
1520}
1521
1522#############################################################################
1523#
1524# APPEND --
1525#
1526# APPEND DATA - Causes the server to accept the data transferred via the data
1527# connection and to store the data as a file at the server site.  If the file
1528# exists at the server site, then the data shall be appended to that file;
1529# otherwise the file specified in the pathname shall be created at the
1530# server site.
1531# (exported)
1532#
1533# Arguments:
1534# source -			local file name
1535# dest -			remote file name, if unspecified, ftp assigns
1536#				the local file name.
1537# Returns:
1538# 0 -			file not stored
1539# 1 - 			OK
1540
1541proc Append {source {dest ""}} {
1542variable ftp
1543
1544	if ![info exists ftp(State)] {
1545		DisplayMsg "Not connected!" error
1546		return 0
1547	}
1548
1549	if ![file exists $source] {
1550		DisplayMsg "File \"$source\" not exist" error
1551		return 0
1552     	}
1553
1554	if { $dest == "" } {
1555		set dest $source
1556	}
1557
1558	set ftp(LocalFilename) $source
1559	set ftp(RemoteFilename) $dest
1560
1561	set ftp(SourceCI) [open $ftp(LocalFilename) r]
1562	if { $ftp(Type) == "ascii" } {
1563		fconfigure $ftp(SourceCI) -buffering line -blocking 1
1564	} else {
1565		fconfigure $ftp(SourceCI) -buffering line -translation binary -blocking 1
1566	}
1567
1568	set ftp(State) append_$ftp(Mode)
1569	StateHandler
1570
1571	# wait for synchronization
1572	set rc [WaitOrTimeout]
1573	if {$rc} {
1574		ElapsedTime [clock seconds]
1575		return 1
1576	} else {
1577		CloseDataConn
1578		return 0
1579	}
1580
1581}
1582
1583
1584#############################################################################
1585#
1586# Get --
1587#
1588# RETRIEVE DATA - Causes the server to transfer a copy of the specified file
1589# to the local site at the other end of the data connection.
1590# (exported)
1591#
1592# Arguments:
1593# source -			remote file name
1594# dest -			local file name, if unspecified, ftp assigns
1595#				the remote file name.
1596# Returns:
1597# 0 -			file not retrieved
1598# 1 - 			OK
1599
1600proc Get {source {dest ""}} {
1601variable ftp
1602
1603	if ![info exists ftp(State)] {
1604		DisplayMsg "Not connected!" error
1605		return 0
1606	}
1607
1608	if { $dest == "" } {
1609		set dest $source
1610	}
1611
1612	set ftp(RemoteFilename) $source
1613	set ftp(LocalFilename) $dest
1614
1615	set ftp(State) get_$ftp(Mode)
1616	StateHandler
1617
1618	# wait for synchronization
1619	set rc [WaitOrTimeout]
1620	if {$rc} {
1621		ElapsedTime [clock seconds]
1622		return 1
1623	} else {
1624		CloseDataConn
1625		return 0
1626	}
1627
1628}
1629
1630#############################################################################
1631#
1632# Reget --
1633#
1634# RESTART RETRIEVING DATA - Causes the server to transfer a copy of the specified file
1635# to the local site at the other end of the data connection like get but skips over
1636# the file to the specified data checkpoint.
1637# (exported)
1638#
1639# Arguments:
1640# source -			remote file name
1641# dest -			local file name, if unspecified, ftp assigns
1642#				the remote file name.
1643# Returns:
1644# 0 -			file not retrieved
1645# 1 - 			OK
1646
1647proc Reget {source {dest ""}} {
1648variable ftp
1649
1650	if ![info exists ftp(State)] {
1651		DisplayMsg "Not connected!" error
1652		return 0
1653	}
1654
1655	if { $dest == "" } {
1656		set dest $source
1657	}
1658
1659	set ftp(RemoteFilename) $source
1660	set ftp(LocalFilename) $dest
1661
1662	if [file exists $ftp(LocalFilename)] {
1663		set ftp(FileSize) [file size $ftp(LocalFilename)]
1664	} else {
1665		set ftp(FileSize) 0
1666	}
1667
1668	set ftp(State) reget_$ftp(Mode)
1669	StateHandler
1670
1671	# wait for synchronization
1672	set rc [WaitOrTimeout]
1673	if {$rc} {
1674		ElapsedTime [clock seconds]
1675		return 1
1676	} else {
1677		CloseDataConn
1678		return 0
1679	}
1680
1681}
1682
1683#############################################################################
1684#
1685# Newer --
1686#
1687# GET NEWER DATA - Get the file only if the modification time of the remote
1688# file is more recent that the file on the current system. If the file does
1689# not exist on the current system, the remote file is considered newer.
1690# Otherwise, this command is identical to get.
1691# (exported)
1692#
1693# Arguments:
1694# source -			remote file name
1695# dest -			local file name, if unspecified, ftp assigns
1696#				the remote file name.
1697#
1698# Returns:
1699# 0 -			file not retrieved
1700# 1 - 			OK
1701
1702proc Newer {source {dest ""}} {
1703variable ftp
1704
1705	if ![info exists ftp(State)] {
1706		DisplayMsg "Not connected!" error
1707		return 0
1708	}
1709
1710	if { $dest == "" } {
1711		set dest $source
1712	}
1713
1714	set ftp(RemoteFilename) $source
1715	set ftp(LocalFilename) $dest
1716
1717	# get remote modification time
1718	set rmt [ModTime $ftp(RemoteFilename)]
1719	if { $rmt == "-1" } {
1720		return 0
1721	}
1722
1723	# get local modification time
1724	if [file exists $ftp(LocalFilename)] {
1725		set lmt [file mtime $ftp(LocalFilename)]
1726	} else {
1727		set lmt 0
1728	}
1729
1730	# remote file is older than local file
1731	if { $rmt < $lmt } {
1732		return 0
1733	}
1734
1735	# remote file is newer than local file or local file doesn't exist
1736	# get it
1737	set rc [Get $ftp(RemoteFilename) $ftp(LocalFilename)]
1738	return $rc
1739
1740}
1741
1742#############################################################################
1743#
1744# Quote --
1745#
1746# The arguments specified are sent, verbatim, to the remote FTP server.
1747#
1748# Arguments:
1749# 	arg1 arg2 ...
1750#
1751# Returns:
1752#  string sent back by the remote FTP server or null string if any error
1753#
1754
1755proc Quote {args} {
1756variable ftp
1757
1758	if ![info exists ftp(State)] {
1759		DisplayMsg "Not connected!" error
1760		return 0
1761	}
1762
1763	set ftp(Cmd) $args
1764
1765	set ftp(State) quote
1766	StateHandler
1767
1768	# wait for synchronization
1769	set rc [WaitOrTimeout]
1770
1771	unset ftp(Cmd)
1772
1773	if {$rc} {
1774		return $ftp(Quote)
1775	} else {
1776		return {}
1777	}
1778}
1779
1780
1781#############################################################################
1782#
1783# Abort --
1784#
1785# ABORT - Tells the server to abort the previous FTP service command and
1786# any associated transfer of data. The control connection is not to be
1787# closed by the server, but the data connection must be closed.
1788#
1789# NOTE: This procedure doesn't work properly. Thus the FTP::Abort command
1790# is no longer available!
1791#
1792# Arguments:
1793# None.
1794#
1795# Returns:
1796# 0 -			ERROR
1797# 1 - 			OK
1798#
1799# proc Abort {} {
1800# variable ftp
1801#
1802# }
1803
1804#############################################################################
1805#
1806# Close --
1807#
1808# Terminates a ftp session and if file transfer is not in progress, the server
1809# closes the control connection.  If file transfer is in progress, the
1810# connection will remain open for result response and the server will then
1811# close it.
1812# (exported)
1813#
1814# Arguments:
1815# None.
1816#
1817# Returns:
1818# 0 -			ERROR
1819# 1 - 			OK
1820
1821proc Close {} {
1822variable ftp
1823
1824	if ![info exists ftp(State)] {
1825		DisplayMsg "Not connected!" error
1826		return 0
1827	}
1828
1829	set ftp(State) quit
1830	StateHandler
1831
1832	# wait for synchronization
1833	WaitOrTimeout
1834
1835	catch {close $ftp(CtrlSock)}
1836	catch {unset ftp}
1837}
1838
1839#############################################################################
1840#
1841# Open --
1842#
1843# Starts the ftp session and sets up a ftp control connection.
1844# (exported)
1845#
1846# Arguments:
1847# server - 		The ftp server hostname.
1848# user -		A string identifying the user. The user identification
1849#			is that which is required by the server for access to
1850#			its file system.
1851# passwd -		A string specifying the user's password.
1852# options -		-blocksize size		writes "size" bytes at once
1853#						(default 4096)
1854#			-timeout seconds	if non-zero, sets up timeout to
1855#						occur after specified number of
1856#						seconds (default 120)
1857#			-progress proc		procedure name that handles callbacks
1858#						(no default)
1859#			-mode mode		switch active or passive file transfer
1860#						(default active)
1861#			-port number		alternative port (default 21)
1862#
1863# Returns:
1864# 0 -			Not logged in
1865# 1 - 			User logged in
1866
1867proc Open {server user passwd {args ""}} {
1868variable ftp
1869variable DEBUG
1870variable VERBOSE
1871upvar #0 finished state
1872
1873	if [info exists ftp(State)] {
1874       		DisplayMsg "Mmh, another attempt to open a new connection? There is already a hot wire!" error
1875		return 0
1876	}
1877
1878	# default NO DEBUG
1879	if {![info exists DEBUG]} {
1880		set DEBUG 0
1881	}
1882
1883	# default NO VERBOSE
1884	if {![info exists VERBOSE]} {
1885		set VERBOSE 0
1886	}
1887
1888	if {$DEBUG} {
1889		DisplayMsg "Starting new connection with: "
1890	}
1891
1892	set ftp(User) 		$user
1893	set ftp(Passwd) 	$passwd
1894	set ftp(RemoteHost) 	$server
1895	set ftp(LocalHost) 	[info hostname]
1896	set ftp(DataPort) 	0
1897	set ftp(Type) 		{}
1898	set ftp(Error) 		{}
1899	set ftp(Progress) 	{}
1900	set ftp(Blocksize) 	4096
1901	set ftp(Timeout) 	600
1902	set ftp(Mode) 		active
1903	set ftp(Port) 		21
1904
1905	set ftp(State) 		user
1906
1907	# set state var
1908	set state(control) ""
1909
1910	# Get and set possible options
1911	set options {-blocksize -timeout -mode -port -progress}
1912	foreach {option value} $args {
1913		if { [lsearch -exact $options $option] != "-1" } {
1914				if {$DEBUG} {
1915					DisplayMsg "  $option = $value"
1916				}
1917				regexp {^-(.?)(.*)$} $option all first rest
1918				set option "[string toupper $first]$rest"
1919				set ftp($option) $value
1920		}
1921	}
1922	if { $DEBUG && ($args == "") } {
1923		DisplayMsg "  no option"
1924	}
1925
1926	# No call of StateHandler is required at this time.
1927	# StateHandler at first time is called automatically
1928	# by a fileevent for the control channel.
1929
1930	# Try to open a control connection
1931	if ![OpenControlConn] { return 0 }
1932
1933	# waits for synchronization
1934	#   0 ... Not logged in
1935	#   1 ... User logged in
1936	if {[WaitOrTimeout]} {
1937		# default type is binary
1938		Type binary
1939		return 1
1940	} else {
1941		# close connection if not logged in
1942		Close
1943		return 0
1944	}
1945}
1946
1947#############################################################################
1948#
1949# CopyNext --
1950#
1951# recursive background copy procedure for ascii/binary file I/O
1952#
1953# Arguments:
1954# bytes - 		indicates how many bytes were written on $ftp(DestCI)
1955
1956proc CopyNext {bytes {error {}}} {
1957variable ftp
1958variable DEBUG
1959variable VERBOSE
1960upvar #0 finished state
1961
1962	# summary bytes
1963	incr ftp(Total) $bytes
1964
1965	# callback for progress bar procedure
1966	if { ([info exists ftp(Progress)]) && ([info commands [lindex $ftp(Progress) 0]] != "") } {
1967		eval $ftp(Progress) $ftp(Total)
1968	}
1969
1970	# setup new timeout handler
1971	after cancel $ftp(Wait)
1972	set ftp(Wait) [after [expr $ftp(Timeout) * 1000] [namespace current]::Timeout]
1973
1974	if {$DEBUG} {
1975		DisplayMsg "-> $ftp(Total) bytes $ftp(SourceCI) -> $ftp(DestCI)"
1976	}
1977
1978	if {$error != ""} {
1979		catch {close $ftp(DestCI)}
1980		catch {close $ftp(SourceCI)}
1981   		unset state(data)
1982		DisplayMsg $error error
1983
1984	} elseif {[eof $ftp(SourceCI)]} {
1985		close $ftp(DestCI)
1986		close $ftp(SourceCI)
1987   		unset state(data)
1988		if {$VERBOSE} {
1989			DisplayMsg "D: Port closed" data
1990		}
1991
1992	} else {
1993		fcopy $ftp(SourceCI) $ftp(DestCI) -command [namespace current]::CopyNext -size $ftp(Blocksize)
1994
1995	}
1996
1997}
1998
1999#############################################################################
2000#
2001# HandleList --
2002#
2003# Handles ascii/binary data transfer for Put and Get
2004#
2005# Arguments:
2006# sock - 		socket name (data channel)
2007
2008proc HandleData {sock} {
2009variable ftp
2010
2011	# Turn off any fileevent handlers
2012	fileevent $sock writable {}
2013	fileevent $sock readable {}
2014
2015	# create local file for FTP::Get
2016	if [regexp "^get" $ftp(State)] {
2017		set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) w]} msg]
2018		if { $rc != 0 } {
2019			DisplayMsg "$msg" error
2020			return 0
2021		}
2022		if { $ftp(Type) == "ascii" } {
2023			fconfigure $ftp(DestCI) -buffering line -blocking 1
2024		} else {
2025			fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1
2026		}
2027	}
2028
2029	# append local file for FTP::Reget
2030	if [regexp "^reget" $ftp(State)] {
2031		set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) a]} msg]
2032		if { $rc != 0 } {
2033			DisplayMsg "$msg" error
2034			return 0
2035		}
2036		if { $ftp(Type) == "ascii" } {
2037			fconfigure $ftp(DestCI) -buffering line -blocking 1
2038		} else {
2039			fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1
2040		}
2041	}
2042
2043	# perform fcopy
2044	set ftp(Total) 0
2045	set ftp(Start_Time) [clock seconds]
2046	fcopy $ftp(SourceCI) $ftp(DestCI) -command [namespace current]::CopyNext -size $ftp(Blocksize)
2047}
2048
2049#############################################################################
2050#
2051# HandleList --
2052#
2053# Handles ascii data transfer for list commands
2054#
2055# Arguments:
2056# sock - 		socket name (data channel)
2057
2058proc HandleList {sock} {
2059variable ftp
2060variable VERBOSE
2061upvar #0 finished state
2062
2063	if ![eof $sock] {
2064		set buffer [read $sock]
2065		if { $buffer != "" } {
2066			set ftp(List) [append ftp(List) $buffer]
2067		}
2068	} else {
2069		close $sock
2070   		unset state(data)
2071		if {$VERBOSE} {
2072			DisplayMsg "D: Port closed" data
2073		}
2074	}
2075}
2076
2077############################################################################
2078#
2079# CloseDataConn --
2080#
2081# Closes all sockets and files used by the data conection
2082#
2083# Arguments:
2084# None.
2085#
2086# Returns:
2087# None.
2088#
2089proc CloseDataConn {} {
2090variable ftp
2091
2092	catch {after cancel $ftp(Wait)}
2093	catch {fileevent $ftp(DataSock) readable {}}
2094	catch {close $ftp(DataSock); unset ftp(DataSock)}
2095	catch {close $ftp(DestCI); unset ftp(DestCI)}
2096	catch {close $ftp(SourceCI); unset ftp(SourceCI)}
2097	catch {close $ftp(DummySock); unset ftp(DummySock)}
2098}
2099
2100#############################################################################
2101#
2102# InitDataConn --
2103#
2104# Configures new data channel for connection to ftp server
2105# ATTENTION! The new data channel "sock" is not the same as the
2106# server channel, it's a dummy.
2107#
2108# Arguments:
2109# sock -		the name of the new channel
2110# addr -		the address, in network address notation,
2111#			of the client's host,
2112# port -		the client's port number
2113
2114proc InitDataConn {sock addr port} {
2115variable ftp
2116variable VERBOSE
2117upvar #0 finished state
2118
2119	# If the new channel is accepted, the dummy channel will be closed
2120	catch {close $ftp(DummySock); unset ftp(DummySock)}
2121
2122	set state(data) 0
2123
2124	# Configure translation mode
2125	if { $ftp(Type) == "ascii" } {
2126		fconfigure $sock -buffering line -blocking 1
2127	} else {
2128		fconfigure $sock -buffering line -translation binary -blocking 1
2129	}
2130
2131	# assign fileevent handlers, source and destination CI (Channel Identifier)
2132	switch -regexp $ftp(State) {
2133
2134		list {
2135			  fileevent $sock readable [list [namespace current]::HandleList $sock]
2136			  set ftp(SourceCI) $sock
2137			}
2138
2139		get	{
2140			  fileevent $sock readable [list [namespace current]::HandleData $sock]
2141			  set ftp(SourceCI) $sock
2142			}
2143
2144		append  -
2145
2146		put {
2147			  fileevent $sock writable [list [namespace current]::HandleData $sock]
2148			  set ftp(DestCI) $sock
2149			}
2150	}
2151
2152	if {$VERBOSE} {
2153		DisplayMsg "D: Connection from $addr:$port" data
2154	}
2155}
2156
2157#############################################################################
2158#
2159# OpenActiveConn --
2160#
2161# Opens a ftp data connection
2162#
2163# Arguments:
2164# None.
2165#
2166# Returns:
2167# 0 -			no connection
2168# 1 - 			connection established
2169
2170proc OpenActiveConn {} {
2171variable ftp
2172variable VERBOSE
2173
2174	# Port address 0 is a dummy used to give the server the responsibility
2175	# of getting free new port addresses for every data transfer.
2176	set rc [catch {set ftp(DummySock) [socket -server [namespace current]::InitDataConn 0]} msg]
2177	if { $rc != 0 } {
2178       		DisplayMsg "$msg" error
2179       		return 0
2180	}
2181
2182	# get a new local port address for data transfer and convert it to a format
2183	# which is useable by the PORT command
2184	set p [lindex [fconfigure $ftp(DummySock) -sockname] 2]
2185	if {$VERBOSE} {
2186		DisplayMsg "D: Port is $p" data
2187	}
2188	set ftp(DataPort) "[expr "$p / 256"],[expr "$p % 256"]"
2189
2190	return 1
2191}
2192
2193#############################################################################
2194#
2195# OpenPassiveConn --
2196#
2197# Opens a ftp data connection
2198#
2199# Arguments:
2200# buffer - returned line from server control connection
2201#
2202# Returns:
2203# 0 -			no connection
2204# 1 - 			connection established
2205
2206proc OpenPassiveConn {buffer} {
2207variable ftp
2208
2209	if {[regexp {([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $buffer all a1 a2 a3 a4 p1 p2]} {
2210		set ftp(LocalAddr) "$a1.$a2.$a3.$a4"
2211		set ftp(DataPort) "[expr $p1 * 256 + $p2]"
2212
2213		# establish data connection for passive mode
2214		set rc [catch {set ftp(DataSock) [socket $ftp(LocalAddr) $ftp(DataPort)]} msg]
2215		if { $rc != 0 } {
2216			DisplayMsg "$msg" error
2217			return 0
2218		}
2219
2220		InitDataConn $ftp(DataSock) $ftp(LocalAddr) $ftp(DataPort)
2221		return 1
2222	} else {
2223		return 0
2224	}
2225}
2226#############################################################################
2227#
2228# OpenControlConn --
2229#
2230# Opens a ftp control connection
2231#
2232# Arguments:
2233# None.
2234#
2235# Returns:
2236# 0 -			no connection
2237# 1 - 			connection established
2238
2239proc OpenControlConn {} {
2240variable ftp
2241variable DEBUG
2242variable VERBOSE
2243
2244	# open a control channel
2245        set rc [catch {set ftp(CtrlSock) [socket $ftp(RemoteHost) $ftp(Port)]} msg]
2246	if { $rc != 0 } {
2247		if {$VERBOSE} {
2248       			DisplayMsg "C: No connection to server!" error
2249		}
2250		if {$DEBUG} {
2251			DisplayMsg "[list $msg]" error
2252		}
2253       		unset ftp(State)
2254       		return 0
2255	}
2256	# configure control channel
2257	fconfigure $ftp(CtrlSock) -buffering line -blocking 1 -translation {auto crlf}
2258        fileevent $ftp(CtrlSock) readable [list [namespace current]::StateHandler $ftp(CtrlSock)]
2259
2260	# prepare local ip address for PORT command (convert pointed format to comma format)
2261	set ftp(LocalAddr) [lindex [fconfigure $ftp(CtrlSock) -sockname] 0]
2262	regsub -all "\[.\]" $ftp(LocalAddr) "," ftp(LocalAddr)
2263
2264	# report ready message
2265	set peer [fconfigure $ftp(CtrlSock) -peername]
2266	if {$VERBOSE} {
2267		DisplayMsg "C: Connection from [lindex $peer 0]:[lindex $peer 2]" control
2268	}
2269
2270	return 1
2271}
2272
2273# added TkCon support
2274# TkCon is (c) 1995-1999 Jeffrey Hobbs, http://www.purl.org/net/hobbs/tcl/script/tkcon/
2275# started with: tkcon -load FTP
2276if { [uplevel "#0" {info commands tkcon}] == "tkcon" } {
2277
2278	# new FTP::List proc makes the output more readable
2279	proc __ftp_ls {args} {
2280		foreach i [::FTP::List_org $args] {
2281			puts $i
2282		}
2283	}
2284
2285	# rename the original FTP::List procedure
2286	rename ::FTP::List ::FTP::List_org
2287
2288	alias ::FTP::List	::FTP::__ftp_ls
2289	alias bye		catch {::FTP::Close; exit}
2290
2291	set ::FTP::VERBOSE 1
2292	set ::FTP::DEBUG 0
2293}
2294
2295# not forgotten close-brace (end of namespace)
2296}
2297