
# VFS ssh code for filerunner
package provide VFSssh 23.03.01

namespace eval VFSssh {
  variable commands
  variable ssh 
  variable debug 1
  variable cannedPrompt {XXXEND}
  variable {}
  
  # In the following array the second variable is either ::MSW (pgm, scp, portF)
  # or winFlag (all the rest) depending on the platform (::MSW) or
  # if connected to a MS windows machine (winFlag)
  #
  array set {} {
    pgm,0 ssh
    pgm,1 ssh     # {changed to {plink -ssh -pw %s} if MSW and no Expect}
    scp,0 scp
    scp,1 scp     # {changed to {pscp -pw %s}  if MSW and no Expect}
    portF,0 -p
    portF,1 -p    # {"case changed  if MSW and no Expect"}
    prompt,0 {PS1="XXXEND"}
    prompt,1 "prompt XXXEND"
    pwd,0    {pwd -P} pwd,1 cd
    cd,0 cd cd,1 {cd /D}
    list,0,1 {QUOTING_STYLE=literal ls -a -Flv}  # { --color=never}
    list,0,0 {QUOTING_STYLE=literal ls -Flv}     # { --color=never}
    list,1,1 {dir /a /q /-c /a}   # {cmd /u /Q /c} 
    list,1,0 {dir /a /q /-c /a-h} # {cmd /u /Q /c} 
    list,0,ind {indicators 1}
    list,1,ind {listF}
    #fmtFile,0 {FixFileName {%s} -l 2 -+ {'()|+&\"}} # {"try to format for a shell"}
    #fmtFile,1 {FixFileName {%s}  -+ {'()|+&\"}} # {"try to format for a shell"}
    fmtFile,0    {FixFileName %s -p UNIX -l 1 -+ '|()+&\"}   # {"format for a shell"}
    fmtFile,1    {FixFileName %s -p MSW  -l 1 -+ '|()+&\"}   # {"format for a shell"}
    scpFmtFile,0 {FixFileName %s -p UNIX -l 1 -+ '()|+&\"} # {"try to format for a shell"}
    scpFmtFile,1 {FixFileName %s -p MSW       -+ '()|+&\"} # {"try to format for a shell"}
    delete,0 {rm -f --} delete,1 {del /F /Q}
    rename,0 mv    rename,1 {move /Y}
    rmdir,0 {rm -fr --} rmdir,1 {rmdir /Q /S}
    # .
  }
#    fmtFile,1 {regsub -all {/} %s {\\} }
		
  if {[set ssh [auto_execok ssh]] == {}} {
    if {$::MSW} {
      PopInfo [_ "The 'ssh' program was not found. This is a service in Windows.
                  \nWith out this service enabled we can not do VFSssh.
                   \nPlease turn on the 'ssh' service."]
      return
    }
    # oooh! that is bad...
    return -code error "ssh program not found. ssh VFS requires the 'ssh' program."
  }
  variable scp [auto_execok scp]
  set ssh {ssh}
  if { [catch "package require Expect" out] != 0} {
    # No Expect at all, We can not continue....
    PopInfo [_ "Expect not found.  VFSssh can not continue"]
    return
  }
  if {[string index $out 0] == 0} {
    # This is the all tcl Expect package.
    if {!$::MSW && [auto_execok sshpass] == {}} {
      PopInfo [_ "Real Expect not found. Continuing with a 'tcl' version.
            \nThis version will likely force a popup window from ssh 
             \nasking for the password.
            \nYou may avoid this problem by doing one of the following:
            \nSetting up your 'ssh' environment to avoid having to do login responses
            \nInstalling the 'sshpass' program from your systems repo.
             \nInstall the 'expect' package from your system repo."]
      
      # It seems expect for MSW is either non-existant or very old.
      # Lets just soldier on, no point in complaining if their is no solution.
    }
    LogSilent "Full Expect not found, using pure Tcl expect"
    set myexpect 1
    if {$::MSW} {
      set (pgm,1) {plink -ssh -pw %s}
      set (pgm,1) ssh
      set (portF,1) -P
      set (scp,1) {pscp -pw %s}
    }
  }
  # if {[set sshpass [auto_execok sshpass]]!= {}} {
  #   set ssh [list $sshpass -p %s {*}$ssh]
  #   set scp [list $sshpass -p %s scp]
  # }
  frputs "found: Expect " out 

 
  array set commands [list\
			  open    ::VFSssh::SSHopen\
			  reopen  ::VFSssh::SSHopenLink\
			  live    ::VFSssh::SSHpwd\
			  cd      ::VFSssh::SSHcd\
			  delete  ::VFSssh::SSHdelete\
			  mkdir   ::VFSssh::SSHmkdir\
			  rmdir   ::VFSssh::SSHrmdir\
			  rmdirEmpty 1\
			  isdir   ::VFSssh::SSHisdir\
			  pwd     ::VFSssh::SSHpwd\
			  chmod   ::VFSssh::SSHchmod\
			  chown   ::VFSssh::SSHchown\
			  list    ::VFSssh::SSHlist\
			  get     ::VFSssh::SSHget\
			  put     ::VFSssh::SSHput\
			  RcopyOk 3\
			  rename  ::VFSssh::SSHrename\
			  link    ::VFSssh::SSHlink\
			  command ::VFSssh::SSHcommand\
			  menu    ::VFSssh::SSHmenu\
			  close   ::VFSssh::SSHclose\
			  search  ::VFSssh::SSH_DoSearch\
			  volumes ::VFSssh::SSHvolumes\
			  debug   ::VFSssh::SSH_debug]

  # Support routines *********************************
  # *******************
  proc eId {n} {return "(ssh $n): "}
  proc dproc {args} {}

  # This allows simple log calls
  proc Log {mess} {
    upvar VFStok VFStok
    VFSglobal log
    set mess [SSHcleanEsc $mess]
    frputs  mess
    eval [list $log "$mess"]
  }

  proc SSHisdir {VFStok dir} {
    set r [catch {SSHcd $VFStok $dir} er]
    frputs dir r er
    # return [expr { ! $r}]
    if {$r == 0} {return [SSHpwd $VFStok]}
    return 0
  }
  # We allow several connections at once. Still we will likely share
  # a few things, for example the abort flag may point to the same var
  # And we will use a common debug flag.
  
  proc SSHopen {URL args} { ; #  host user password port rq_timeout args 
    variable debug
    variable cannedPrompt
    VFSglobal  mySpawn_id user port password host canp\
								winFlag	timeout abortflag\
								log tmpdir pwd cmd ESC es ass
	SSHclose $VFStok
    set cmd {}   ;# this may need to be set to '/bin/sh -i' when accessing
    #             *nixes with the tcl expect - not so good if target is windows
    #             For MSW setting this to 'cmd /Q' turns off the Terminal
    #             control ESC sequences (a VERY good thing).

    set host  [regsub -all {.*@|:.+} $VFSadd {}]
    lassign {0 0 \u001b} debug winFlag ESC
    # The following reg expression is to catch escape sequences of
	# ESC[...<capletter>
    # with the + in a regexp it can catch two or more or, in regsub, all of them
    set es {(\u001b\[[^\u0040-~]*[\u0040-~])}
    set ass {(\u001b\][^\u0007]*\u0007)}
    set canp "(\r|\n|$es|$ass)*${cannedPrompt}($es|$ass)*"
    lassign {} user password timeout
    set abortflag ::[namespace current]::abtflag
    set timefmt "%d%m%y "
    set tmpdir "/tmp"
    set log ::[namespace current]::dproc
    frputs VFStok host user password port timeout args
    # The VFSadd (set by IsVFS) may contain a user name
    # and a port lets seperate them

    # regexp {(.*)@(.*)} $VFSadd ma user VFSadd
    # regexp {(.*):(.*)} $VFSadd ma VFSadd port
    lassign [VFSaddrSplit $VFSadd] user VFSadd port
    if {$port == {}} {
      set port 22
    }
    frputs host user port password
    set poss {user password port timeout abortflag timefmt log debug tmpdir}
    foreach {opt value} [lindex $args 0] {
      set tar [lsearch -glob -all -inline $poss "$opt*"]
      if {$tar == {}} {continue}
      if {[llength $tar] != 1} {
	# puts "Here is tar >$tar<"
	error "[eId 1]SSHopen option ($opt) must be one of: $poss"
      }
      set $tar $value
      frputs tar value
    }
    set password $VFSpro://$user@$VFSadd
    set pwd $VFSpath
    return [SSHopenLink $VFStok]
  }

  proc SSHopenLink {VFStok} {
    variable debug
    variable commands
    variable ssh
    variable cannedPrompt
    variable {}
    
    VFSglobal mySpawn_id user port winFlag canp id_file\
	host password timeout abortflag tmpdir pwd cmd ESC es

    set r [catch {exp_log_user $debug} what]
    frputs #2 r what port cannedPrompt
    # get options from the ssh table
    set run [subst {*}$::stOps {-oPort=$port -p}]
    set rcopyok 3
    set rmdirempty 0
    set strength -1
    set options [VFShelpOptions "ssh" $user $host $port]
    
    # Ok what did we get...
    foreach opt $options {
      set thisOpt [join [lassign [split $opt =] name] = ]
      switch -nocase -exact $name {
	run -
	rcopyok -
	rmdirempty {
	  set [string tolower $name] [subst {*}$::stOps $thisOpt]
	  # frputs opt name "[string tolower $name] " thisOpt run\
	  #    rcopyok rmdirempty
	}
	default {}
      }
    }
	# ISSUE: If ever we want different options for each host, this MUST be
	#        in VFSglobal, not commands which is in our (SHARED) name space.
	
    set commands(rmdirEmpty) $rmdirempty
    set commands(RcopyOk) $rcopyok
    frputs rmdirempty rcopyok run 
    if {$debug} {
      if {[info exists ::myexpect] } {
	# only my expect can handle a Log procedure.
	exp_log_file -a -proc log
      } else {
	catch {exp_log_file}
	exp_log_file  -a $tmpdir/Log
      }
    }
    set newpw [::pwLocker::getPw ssh://$user@$host f 1] 
    # while trying passwords...
    while {1} {
      frputs newpw
      unset -nocomplain spawn_id
      # on the open request we allow a couple of extra timeouts...
      set timeouts 0
      if {$newpw == -1} {set newpw {}}
      if {$newpw == -2} {
		return -code error "[eId 1.5]User canceled login"
      }
      lassign $newpw passwordx idfile passphrase
      if {$idfile != "" } {
		set id_file "-i [file join $::env(HOME)/.ssh $idfile]"
      }	else {
		set id_file ""
      }
      frputs options id_file user host port passwordx run
      # set cmd {-N}
      set stty_init -echo
      # Windows now has ssh, still no password.... option
      frputs "[list spawn -noecho \
              {*}[format "$(pgm,$::MSW)" $passwordx] $user@$host  $(portF,$::MSW) \
              $port $id_file {*}$cmd] "
      set r [catch [list spawn -noecho {*}[format $(pgm,$::MSW) $passwordx]\
			$user@$host $(portF,$::MSW) $port $id_file {*}$cmd] out]
      
      if {$r != 0} {return -code error [_ "[eId 2]Really bad error: %s" $out]}
      set re 100
      set mySpawn_id $spawn_id
      set failsafe 0
      # Clean leading \r & \n and escape sequences
      expect_before -i $mySpawn_id \
		  -re "^(\r|\n|($es))+" {
			frputs  #3  TRAP-ESC expect_out(1,string)
			exp_continue
		  }\
		  -re "^\[\r\n]+" {
			frputs #3 TRAP-RTNLF
			exp_continue
		  }
      
      #  puts "doine expect_after"
      expect_after -i $mySpawn_id \
	  eof {set expect_out(1,string) "Eof, Connection closed $VFStok" ;set re 1}

      frputs "doing expect 1 "
      exp_log_user $debug
      expect -i $mySpawn_id\
	 timeout {
	   if {[info exists ignorto] || [incr timeouts] < 2} {
	     Log "[eId 1]Time out ignored" ;exp_continue
	   } else {
	     catchDebris $VFStok
	     set expect_out(1,string) "Connection timed out $VFStok" ;set re 1
	   }
	 }\
	 -re "^(($es)|(\[\r\n]))+" {frputs;exp_continue}\
	 -re "(.*ermission denied.*try again\.)\r?\n" {
	   Log "[eId 1.5]$expect_out(1,string)"
	   exp_continue
	 }\
	 -re "(.*assword\[^:]*: *)" {
	   Log "[eId 2.]$expect_out(1,string)"
	   Log "[eId 3.]sending password1: xxxxx"
	   exp_send -i $mySpawn_id $passwordx\r
	   sleep 1
	   exp_continue
	 }\
	 -re "(.*sername .*)\r?\n" {
	   Log "[eId 4.]$expect_out(1,string)"
	   exp_send -i $mySpawn_id "\r"
	   exp_continue
	 }\
	 -re "(.*assphrase for key \[^:]*: )" {
	   Log "[eId 5.]$expect_out(1,string)"
	   Log "[eId 6.]sending passphrase"
	   frputs passphrase
	   exp_send -i $mySpawn_id "$passphrase\r"
	   exp_continue
	 }\
	 -re "(.* host key is not .*y/n. |.* authenticity of host .*\(yes/no\)\? )" {
	   set st [regsub -all {\r} $expect_out(1,string) {}] 
	   Log "[eId 7.]$st" 
	   incr ignorto
	   if { [smart_dialog .apop . [_ "Accept new host?"] \
		     [list {} "$st" [_ "\nClick your answer."]] \
		     1 2 [list [_ "No"] [_ "Yes"]]] == 1} {
	     set an [expr {[string match "*(yes/*" $expect_out(1,string)] ? \
			       "yes" : "y" } ]
	     unset ignorto
	     frputs "back from y/n " an
	     Log "[eId 8.]sending $an" 
	     exp_send -i $mySpawn_id "$an\r"
	     exp_continue 
	   } else {
	     Log "[eId 9.]sending 'no'" 
	     exp_send -i $mySpawn_id "no\r"
	     Log "[eId 10.]Aborting Login"
	     set re 10
	   }
	 } \
	 -re "\r?(.*yes\r\n)" {
	   Log "[eId 10.]$expect_out(1,string)"
	   exp_continue
	 } \
	 -re "\r?(.*Connecting .*)" {
	   Log [eId 11.]$expect_out(1,string)
	   Log "[eId 11.1]sending password2"
	   exp_send -i $mySpawn_id $passwordx\r
	   exp_continue
	 } \
	 -re "(.*Connected to.*)\r?\n" {
	   Log "[eId 12.]$expect_out(1,string)"
	   exp_continue
	 } \
	 -re ".*(Remote working directory is .*)\r?\n" {
	   Log "[eId 13.]$expect_out(1,string)"
	   exp_continue
	 } \
	 -re "(.*Permanently added .*)\r*\n" {
	   Log "[eId 14.]$expect_out(1,string)"
	   exp_continue
	 }\
	 -re "(.*ermission denied.*)" {
	   set re 5
	 }\
	-re "\r?(.*assword.*|.*Connection refused.*|.*No such file.*|.*unable.*|.*assphrase.*|.*onnection closed.*)" {
	  Log "[eId 15.]exit open: Failed with >$expect_out(1,string)<"
	   frputs #3 expect_out(buffer)
	   set re 5
	 }\
	 -re "\r?(.*Warning: .*)\r?\n" {
	   Log "[eId 16.][regsub -all {\r} $expect_out(1,string) {}]"
	   exp_continue
	 }\
	 -re "(.*PS1=$cannedPrompt)" {
	   Log "[eId 16.1]$expect_out(1,string)"
	   exp_send -i $mySpawn_id \r
	   exp_continue
	 }\
	 -re "((%|#|\\$) $|.*shell.*\r?\n)" {
	   Log "[eId 17.]$expect_out(1,string):: sending new prompt $(prompt,$winFlag)<"	   
	   exp_send -i $mySpawn_id "$(prompt,$winFlag)\r"
	   exp_continue
	 }\
	 -re ".*Microsoft Corporation. \[^\r\n]+\r?\r?\n" {
	   # Its windows! set the windows flag and send the prompt
	   set winFlag 1
	   Log "[eId 18.7]$expect_out(buffer)  sending prompt"
	   exp_send -i $mySpawn_id "\r\r$(prompt,$winFlag)\r"
	   exp_continue
	 }\
	 -re ".*load pubkey .* invalid format.*\r*\n\r?" {
	   Log "[eId 18.9]Ignore pubkey error $expect_out(buffer)"
	   exp_continue
	 }\
	 -re "(.*)$canp" {
	   Log "[eId 18.]$expect_out(1,string)"
	   Log "[eId 18.5]$expect_out(2,string)"
	   Log "[eId 18.6]$expect_out(buffer)"
	   set re 2
	 }\
	 -re {(.*)>$} {
	   Log "[eId 18.8]$expect_out(buffer)"
	   # Assume the prompt was not yet processed?
	   exp_send -i $mySpawn_id "$(prompt,$winFlag)\r"
	   exp_continue
	 }\
	 -re "(\[^\r\n]*)\r*\n\r?" {
	   Log "[eId 19.]$expect_out(1,string)"
	   frputs expect_out(buffer)
	   if {[incr failsafe] > 10} {
	     return -code error "Failsafe triped on login"
	   }
	   # exp_send -i $mySpawn_id \r
	   exp_continue
	 }
      if {$re == 6} {continue}
      frputs  re mySpawn_id expect_out(1,string) expect_out(buffer)
      if {$re ni {0 2}} {
	catch {exp_close -i $mySpawn_id}
	set mySpawn_id -1
      }
      if {$re == 5} {
	# Lets try again...
	set newpw [::pwLocker::getPw ssh://$user@$host full 1\
		       startwith [list $passwordx $idfile $passphrase]\
		       prompt  [regsub -all \r [list "[eId 3]Password not accepted:\
	    	           $expect_out(1,string)"] {}]]
	if {$newpw == -1} {set newpw {}}
	continue
      }
      if {$re == 10} {
	#We will not ok a host.  It is up to the user to do that,
	PopError "Host not seen before and rejected, login aborted."
	catch {exp_close -i $mySpawn_id}
	set mySpawn_id -1
	return -code error "[eId 4]$expect_out(1,string)"
      }
      if {$mySpawn_id == -1} {
	return -code error "[eId 5]$expect_out(1,string)"     
      }
      if {$re == 2 && [set ms [regsub -all $es $expect_out(2,string) {}]] != {}} {
	LogSilent "Possible error:\n>$ms<\ncontinuing..."
      }
      # looks good, save the password
      set password $newpw
      if {$pwd != "/"} {
	SSHcd $VFStok $pwd
      }
      break
    } ; # End of trying passwords...
    return {}
  }


  proc SSHcd {VFStok req_wd} {
    variable debug
    variable cannedPrompt
    variable {}
    
    if {$req_wd == {}} {return}
    
    VFSglobal mySpawn_id timeout abortflag pwd new_wd winFlag canp

    set new_wd $req_wd
    exp_log_user $debug
    catchDebris $VFStok 
    frputs new_wd req_wd "cd [list {*}$(cd,$winFlag)] " new_wd
    Log "[eId 20.]cd to $new_wd"
    set sent "[list {*}$(cd,$winFlag)] $new_wd"
    frputs #1 #2 #3 #4 #5 sent
    set re 0
    set cdcmd [frECF "$(cd,$winFlag) %m"\
				   [list $new_wd] -m $(fmtFile,$winFlag) -f 1]
    # frECF "exp_send -i $mySpawn_id \"$(cd,$winFlag) %m\r\""\
	# [list $new_wd] -m $(fmtFile,$winFlag)
    #-m  $(fmtFile,$winFlag)
	# set cdcmd [regsub -all {\\} $cdcmd {\\\\}]
	frputs cdcmd
    exp_send -i $mySpawn_id "$cdcmd\r"
    expect -i $mySpawn_id \
	-re "cd \[\r\n]*\[\r\n]*" {
	  frputs expect_out(buffer)
	  exp_continue
	}\
	-re ".?(Directory .*)$canp" {set re 2} \
	-re "(.*No such file or directory.?\[^\n]*)$canp" {set re 3}\
	-re "(.*(cannot|can't)\[^\n]*)$canp" {set re 4}\
	-re ".?Remote .* now \[^\r\n]*$canp" {set re 11} \
	-re "(Can't change directory\[^\n]*)$canp" {set re 5} \
	-re "(Couldn't canonicalise\[^\n]*)$canp" {set re 6}\
	-re "(The .*\[^\n]*)$canp" {set re 6}\
	-re "$canp" {set re 12}\
	-re "(.*\[^\r\n]+)$canp" {set re 7}
    
	#-re "([regsub -all {\\} $sent {\\}])*\[\r\n]*$cannedPrompt"       {set re 12}
    
    #  puts "cd... $x"
    frputs CDTO req_wd re expect_out(buffer) expect_out(1,string)
    if {$re < 10 } {
      Log "[eId 21.][regsub -all {\r} $expect_out(1,string) {}]"
      return -code error "[eId 6]\
                        [regsub -all {\r} [SSHcleanEsc $expect_out(1,string)] {}]"
    }
    #  puts "cd returns $x"
    set pwd $new_wd
    return 1
  }

  proc SSHrename {VFStok  old new} {
    variable cannedPrompt
    VFSglobal mySpawn_id timeout winFlag canp
    variable {}
    frputs old new
    # current name old change to new
    # ren for windows
    catchDebris $VFStok
    set recmd [frECF  "$(rename,$winFlag) %m %m"\
				   [list  [file tail $old] $new ] -m $(fmtFile,$winFlag) -f 1]
    # frECF "exp_send -i $mySpawn_id  { %r %m %m \r} "\
	# [list $(rename,$winFlag) $old $new ] -m $(fmtFile,0)
	frputs recmd
    exp_send  -i $mySpawn_id [string trim $recmd]\r
    expect -i $mySpawn_id\
	-re "(Couldn't .*)$canp" {incr re} \
	-re "(.*: no such file .*)$canp" {incr re} \
	-re "(.*: \[^/\r\n]*)$canp" {incr re} \
	-re ".*$canp" {incr re 0}
	frputs AFTERRENAME expect_out(1,string) expect_out(buffer)
    if {$re} {return -code error "[eId 6]$expect_out(1,string)"}
    return $re 
  }

  proc SSHdelete {VFStok filename} {
    variable cannedPrompt
    variable {}
    VFSglobal mySpawn_id timeout winFlag canp
    catchDebris $VFStok
    Log "40.DELETE: Deleting $filename with $(delete,$winFlag)"
    # The "del" command requires windows path names which we can not
    # seem to get right. Dodge the bullet by cd'ing to the path and
    # using just the tail.
    
    SSHcd $VFStok [file dir $filename]
    set dcmd [frECF "$(delete,$winFlag) %m"\
				  [list  [file tail $filename]] -m $(fmtFile,$winFlag) -f 1]
    # frECF  "exp_send -i $mySpawn_id \"$(delete,$winFlag) %m\r\""\
	# [list   $filename] -m $(fmtFile,0)
	frputs dcmd
	exp_send -i $mySpawn_id $dcmd\r
    # exp_send -i $mySpawn_id "$(delete,$winFlag) [sshfixfilename $filename]\r"
    expect -i $mySpawn_id\
	-re "(Couldn't delete file: .*)$canp" {incr re} \
	-re "(Removing .*)$canp" {incr re 0}\
	-re ".*rm .*: OK$canp" {incr re 0} \
	-re "(.*: \[^/\r\n]*)$canp" {incr re}\
	-re ".?$canp" {incr re 0}\
	-re ".*rm:\[^\r\n]+"   {exp_send "no\r" ;incr re}
    frputs DELETE re expect_out(buffer)
    if {$re} {return -code error "[eId 7][SSHcleanEsc $expect_out(1,string)]"}
    return $re 
  }

  proc SSHmkdir { VFStok dir } {
    variable debug
    variable {}
    VFSglobal cannedPrompt mySpawn_id timeout winFlag canp
    exp_log_user $debug

    frputs MKDIR dir
    # create dir ssh($VFStok,mkdir,dir)
    #  exp_send "mkdir [regsub -all {\ } $dir {\\ }]\r"
    catchDebris $VFStok
    SSHcd $VFStok [file dir $dir]
    set mkcmd [frECF "mkdir %m" [list $dir] -m $(fmtFile,$winFlag) -f 1]
    # frECF "exp_send -i $mySpawn_id {mkdir %m \r}"\
	# [list $dir] -m $(fmtFile,$winFlag)
	
    exp_send -i $mySpawn_id "$mkcmd\r"
    
    expect -i $mySpawn_id\
	-re "\r?(Couldn't .*)$canp" {incr re} \
	-re "(.*mkdir .*: OK)$canp" {incr re 0} \
	-re "(.*: \[^/\r\n]*)$canp" {incr re} \
	-re "(.*)$canp" {incr re 0}
    Log "[eId 7.5] MKDIR:$expect_out(1,string)" 
    if {$re} {return -code error "[eId 8][SSHcleanEsc $expect_out(1,string)]"}
    return $re 
  }

  proc SSHrmdir { VFStok dir } {
    variable cannedPrompt
    variable {}
    VFSglobal mySpawn_id timeout winFlag canp
    catchDebris $VFStok

    # dir to remove is ssh($VFStok,rmdir,dir)
    #  exp_send "rmdir [regsub -all {\ } $dir {\\ }]\r"
    set rmdcmd [frECF  "$(rmdir,$winFlag) %m" [list $dir]\
					-m $(fmtFile,$winFlag) -f 1]
    # frECF  "exp_send -i $mySpawn_id \"$(rmdir,$winFlag) %m\r\""\
    #          [list $dir] -m $(fmtFile,$winFlag)
    
    exp_send -i $mySpawn_id $rmdcmd\r
    expect -i $mySpawn_id\
	-re "(Couldn't remove directory: .*)$canp" {incr re} \
	-re "(.*cannot access.*)$canp" {incr re} \
	-re "(.*rmdir .*: OK)$canp" {incr re 0} \
	-re "(.*: \[^/\r\n]*)$canp" {incr re} \
	-re "(.*)$canp" {incr re 0}
    # It seems that ssh thinks links to dirs are simple files...
    # if {$re} {return -code error "[eId 9]$expect_out(1,string)"}
    set RMDIR $expect_out(1,string)
    frputs RMDIR
    if {$re} {
      return -code error "[eId 8.5][SSHcleanEsc $expect_out(1,string)]"
    }
    return 0
  }

  proc SSHpwd { VFStok } {
    variable debug
    variable {}
    variable cannedPrompt
    VFSglobal mySpawn_id timeout pwd winFlag canp
    catchDebris $VFStok

    frputs #2 #3 mySpawn_id
    exp_log_user $debug
    #  Log "sendingfrom pwd: pwd "
    exp_send -i $mySpawn_id $(pwd,$winFlag)\r
    set re 0
    expect -i $mySpawn_id \
	-re "((${canp}cd|cd|pwd) ?\[^\n\r]*\r?\n)" {
	  frputs expect_out(1,string) expect_out(buffer)
	  exp_continue}\
	-re ".*Remote working directory: (\[^\r]*)$canp" {incr re 0} \
	-re ".*Remote working directory is (\[^\r]*)$canp" {incr re 0} \
	-re ".*Remote directory is (\[^\r]*)$canp" {incr re 0} \
	-re "^((\[^\r\n]*))$canp" {incr re 0}\
	-re "^( *$canp)" {
	  incr re 2
	  frputs expect_out(1,string) expect_out(buffer)
	}\
	-re "^(\r?\n)" {
	  frputs expect_out(1,string) expect_out(buffer)
	  exp_continue
	} 
    #  Log "pwd returns $expect_out(1,string) & $re"
    frputs expect_out(buffer) "[array get expect_out] "
    frputs PWDWAS pwd  re expect_out(1,string) expect_out(buffer)
    switch $re {
      0 {
	set PWD [SSHcleanEsc $expect_out(1,string)]
	set rtn [normalizeVFSPath [regsub -all {\\} [string trim $PWD] {/}]]
	frputs rtn
	return $rtn
      }
      1 {return -code error "[eId 10]$expect_out(1,string)"}
      2 -
      default {return -code error "[eId 11]Unexpected return from pwd:\
                                $expect_out(1,string)"}
    } 
  }

  proc SSHlist {URL all} {
    variable {}
    variable cannedPrompt
    VFSglobal mySpawn_id timeout abortflag minusFflag winFlag ESC pwd canp es
    frputs LIST URL VFStok
    catchDebris $VFStok
   
    # use -l (long list) -f (no sort) -a depends on all
    # older versions of ssh do not recognize the -f...
    # about 12.5k entries at 80 chars each (hay, just poke around on sourceforge)
    # exp_match_max -i $mySpawn_id 1000000

    # So here is the issue...
    # The data is comming so fast the Expect can not hold the whole buffer.
    # We can not move it fast enough if we do any real processing on it.
    # For MSW, we need to both handle UTF as well as a bunch of escape
    # sequences embedded in the data.
    global buff ;# global for debugging only
    set buff {}
    append buff DISCOUNT=$(list,$winFlag,$all)\n
    append buff "$(list,$winFlag,ind)\n"
    set lnBuff [string length $buff]
    match_max  -i $mySpawn_id 50000
    
    # set flags "-Fl --color=never"

    # We tear this apart a line at a time, mostly because the MSW return
    # is, well, messy.
    frputs (list,$winFlag,$all) (list,$winFlag,ind) canp
    exp_send -i $mySpawn_id "$(list,$winFlag,$all)\r"
    expect -i $mySpawn_id \
	timeout {
	  frputs abortflag $abortflag expect_out(buffer)
	  if {[info exists expect_out(buffer)]} {
	    append ::buff $expect_out(buffer)
	  }
	  if {[set $abortflag] == 1} {
	    Log "[eId 29.9]Aborting"
	    SSHclose $VFStok
	    set expect_out(1,string) "User Abort"
	    set re 1
	  } else {
	    Log "[eId 30]Large directory, have patience.\
                       You may abort with Stop button. [incr co]"
	    frputs expect_out(buffer)
	    if {[info exists expect_out(buffer)]} {
	      append ::buff $expect_out(buffer)
	    }
	    exp_continue
	  }
	} \
	-re ".*: (Permission denied).*$canp" {set re 1} \
	-re "Listing directory \[^\n]*\n" {exp_continue} \
	full_buffer {
	  append buff $expect_out(buffer)
	  # frputs FULL expect_out(buffer)
	  exp_continue
	}\
	-re "\n*${cannedPrompt}$(list,$winFlag,$all)\n?" {exp_continue}\
	-re "(^\n+)" {
	  append buff \n
	  exp_continue
	}\
	-re "(\[^\n\]*\n)" {
	  append buff $expect_out(buffer)
	  exp_continue
	}\
	-re "(.*)$canp" {
	  append buff $expect_out(buffer)
	  # This should trap most everything...
	  Log "[eId 31]$expect_out(1,string)"
	  # frputs #2 31 expect_out(buffer)
	  if {$winFlag && ![string match *Volume* $buff] } {
	    exp_continue
	  } else {
	    incr re 0
	  }
	}\
	-re "^(.*)$canp" {
	  incr re -1
	  frputs #2  31.1 expect_out(buffer)
	  append buff $expect_out(1,string)
	  # frputs DONE "[string length $buff] "
	} \
	-re ".*$canp.*" {
	  incr re -2
	  frputs #2  31.2 expect_out(buffer)
	  append buff $expect_out(buffer)
	  set expect_out(1,string) {}
	}
    # -re "(.+)" {
    #   frputs 31.3 expect_out(1,string) expect_out(buffer)
    #   append buff $expect_out(buffer)
    #   Log "[eId 31.2]$expect_out(1,string)"
    #   exp_continue
    # }
    
    if {$re == 1}  {return -code error "[eId 31.3]$expect_out(1,string)"}

    # frputs POSTLIST re buff
    set buff [regsub -all {\\} $buff {/}]
    # Need a bit of clean up here...
    #set buff [regsub -all {$es|(\u001b.*\u0007)|(\u0008\r\n)} $buff {}]
    # It appears the MSW sometimes uses cursor positioning in stead of return/linefeed
    # here we undo that.
    set buff [regsub -all {\u001b\[(\[0-9\]+\[EB\])|(\[0-1\]*;\[0-9\]*\[Hf\])} \
		  $buff {\r\l}]
    set buff [regsub -all {\u001b{1,1}?\][^\u0007]*\u0007} $buff {}]
    # frputs B buff
    set buff [regsub -all {\u0008\r\n} $buff {}]
    # frputs A buff
    set buff [regsub -all $es $buff {}]
    set buff [lrange [split [regsub -all {\r} $buff {}] \n] 0 end-1]
    foreach ent $buff {
      lappend buff2 [string trimright $ent]
    }
    if {!$winFlag} {
      set buff [lrange [split [regsub -all {\r} $buff {}] \n] 0 end-1]
      frputs buff2'
      return $buff2
    } else {
      match_max  -i $mySpawn_id 2000
      package require processwindir
      frputs pwd
      frputs buff2'
      if {[catch {processWinDir::processWinDirList $URL $buff2 $pwd} rtn] != 0} {
	frputs rtn ::errorInfo
	return -code error $rtn
      }
      frputs rtn'
      return $rtn
      
    }
  }

  proc SSH_DoSearch { VFStok filename } {
    VFSglobal canp
    # We assume filename is a list of 'find' operatives i.e.:
    # {{find options} {pcall option} {find expressions} where:
    # pcall is one of {{} -iname -name -iregex -regex}}
    lassign $filename options pcall expressions
    set cmd "find {*}$options 'pwd'/ $pcall {*}$expressions -print 2>"
    exp_send -i $mySpawn_id $cmd\r
    expect -i $mySpawn_id \
	-ex "$cmd\r\n" {exp_continue}\
	timeout {
	  frputs abortflag $abortflag
	  if {[set $abortflag] == 1} {
	    SSHclose $VFStok
	    set expect_out(1,string) "User Abort"
	    set re 1
	  } else {
	    Log "[eId 40]Have patience. You may abort with Stop button. [incr co]"
	    exp_continue
	  }
	}\
	-re "(.*)$canp" {incr re 0} \
	-re "$canp" {incr re 0; set expect_out(1,string) {}}
    
    if {$re}  {return -code error "[eId 13]$expect_out(1,string)"}
    # remove the \r chars
    #  puts "$expect_out(1,string)"
    set re [regsub -all {\r} $expect_out(1,string) "" ]
    return [split $re \n]
  }


  # It seems we need an extra level of 'space' protection on the remote
  # file name, so we pass along the full conversion command.
  proc SSHget {VFStok remoteFileName localFileName args} {
    IsVFS $VFStok
    VFSglobal winFlag id_file
    frputs args remoteFileName localFileName winFlag VFStok
    return [SSHgetPut $VFStok\
				"spawn -noecho  %r -P %r -p -q -r $id_file %\"$VFSadd:%!%m%!%\" %m \r"\
		[sshfixfilename  $remoteFileName] \
		[sshfixfilename $localFileName 1]]
  }

  proc SSHput {VFStok localFileName remoteFileName args} {
    IsVFS $VFStok
    VFSglobal winFlag id_file
    frputs args remoteFileName localFileName winFlag 
    return [SSHgetPut $VFStok\
		"spawn -noecho  %r -P %r -p -q -r $id_file %m %\"$VFSadd:%!%m%!%\" \r"\
		[sshfixfilename $localFileName 1] \
		[sshfixfilename $remoteFileName]]
  }

  
  # For getPut we assume that the remote file has the proper
  # location prepended. Should look like "VFStok:"
  # source first, then destination

  # NOTE: The SSHgetPut routine opens an independent connection to the
  #       target system. It keeps a local spawn_id to allow async
  #       calls while expect may be used else where.

  # It seems scp requires an extra level of backslash protection as well
  # as additional quotes for the remote file name. Thus we have scpFmtFile
  # for %m in the frECF call.

  proc SSHgetPut { VFStok cmd sourceFileName destinationFileName args} {
    # args here are extra stuff like expected size and restart flags
    # which we don't use.
    VFSglobal user port	host password timeout\
	abortflag tmpdir pwd canp es winFlag
    variable debug
    variable commands
    variable {}
    
    if {$debug} {
      if {[info exists ::myexpect] } {
	# only my expect can handle a Log procedure.
	exp_log_file -a -proc log
      } else {
	catch {exp_log_file}
	exp_log_file  -a $tmpdir/Log
      }
    }
    lassign $password passwordx idfile passphrase
    exp_log_user $debug
    # get  remoteFileName and put it localFileName
    # use -p to get full times etc.
    set recur [expr {$commands(RcopyOk) & 1 == 1 ? "-r" : ""}]
    set opt [expr {!$::MSW ? "-P $recur" : "$recur"} ]
    Log "[eId 50]scp $opt $sourceFileName to $destinationFileName"
    frputs passwordx password
      # frputs "spawn  -noecho [format $(scp,$::MSW) $passwordx]  -P $port -p\
      #         -q -r $id_file  $sourceFileName $destinationFileName "
    frECF $cmd \
	[list [format $(scp,$::MSW) $passwordx] $port\
	     $sourceFileName $destinationFileName] -m $(scpFmtFile,0)
    # set r [catch {eval $cmd} out]
    # set r [catch {spawn  -noecho {*}[format $(scp,$::MSW) $passwordx]\
    # 		      -P $port -p -r\
    # 			 $sourceFileName $destinationFileName} out]
    frputs spawn_id r
    # if {$r != 0} {return -code \
    # 		      error [_ "[eId 50.1]Really bad error: %s" $out]}

    set localSpawn_id $spawn_id
    #  puts "doine expect_after"
    expect_after -i $localSpawn_id \
	timeout	{
	  frputs TIMEOUT
	  set re 1
	  if {[set $abortflag] == 1} {
	    set expect_out(1,string) "User abort"
	  } else {
	    set expect_out(1,string) "Connection timed out $VFStok"
	    set re 1
	  }
	}\
	eof {
	  set expect_out(1,string) " Success"
	  frputs EOF expect_out(buffer)
	  set re 0
	}
    set re 0
    expect -i $localSpawn_id\
	-re {Fetching [^\r]*} {
	  Log "[eId 50.1] scp -> $expect_out(buffer)"
	  exp_continue\
	    } \
	-re ".* *(\[^ ].*:\[0-9]\[0-9].*)\[\r\n]+" {
	  LogStatusOnly \
	      "Transfer [file tail $destinationFileName] $expect_out(1,string) ETA"
	  if {[set $abortflag] == 1} {
	    set expect_out(buffer) "User abort"
	    SSHclose  -i $localSpawn_id $VFStok
	    set re 1
	  } else {
	    exp_continue
	  }
	}\
	-re "(.*assword:.*)" {
	  Log "[eId 60.2]$expect_out(1,string)"
	  Log "[eId 68.3]sending password1"
	  exp_send $passwordx\r
	  exp_continue}\
	-re "(.*.?assphrase for key .*: )" {
	  Log "[eId 68.4]$expect_out(1,string)"
	  Log "[eId 68.6]sending passphrase"
	  frputs passphrase
	  exp_send "$passphrase\r"
	  exp_continue}\
	-re "(.*)\[\r\n]+" {
	  Log "[eId 50.2] scp -> $expect_out(buffer)"
	  if {![string is space $expect_out(1,string)]} {
	    frputs expect_out(1,string)
	    append er "\nerr: $expect_out(1,string)"
	  }
	  exp_continue
	}
	 
	    
    if {[info exists er]} {
      return -code error "[eId 13.2]$er"
    }
    if {$re} {return -code error "[eId 13.3]$expect_out(buffer)"}
    frputs expect_out(1,string) expect_out(buffer) re
    catch {exp_close -i $localSpawn_id}
    return $re
  }

  proc SSHlink { VFStok exist new ar} {
    variable cannedPrompt
    VFSglobal mySpawn_id timeout winFlag canp

    variable debug
    catchDebris $mySpawn_id

    exp_log_user $debug
    frputs exist new
    # point to this: ssh($VFStok,exist_fname) with this name ssh($VFStok,ln_fname)
    set opt [expr {[string match *r* $ar] ? "-sr" : "-s"}]
    exp_send -i $mySpawn_id\
	"ln $opt [sshfixfilename [list $exist]] [sshfixfilename [list $new]]\r"

    expect -i $mySpawn_id \
	-re "Permission denied.*$canp" {incr re} \
	-re "(.*pssh: unknown .*)$canp" {incr re} \
	-re "Couldn't .$canp" {incr re} \
	-re "(.*)$canp" {incr re 0}
    if {$re} {return -code error "[eId 16][regsub -all $es $expect_out(buffer) {}]"}
    return $re
  }

  proc SSHchmod {VFStok mode file} {
    variable cannedPrompt
    VFSglobal mySpawn_id timeout winFlag canp
    variable debug

    exp_log_user $debug
    frputs VFStok mode file
    exp_send -i $mySpawn_id "chmod $mode [sshfixfilename $file]\r"
    expect -i $mySpawn_id \
	-re "(Changing mode \[^\n]*)$canp" {incr re 0} \
	-re "((.ouldn't .*)|(.*denied.*))$canp" {incr re} \
	-re "(.*chmod: .*)$canp" {incr re} \
	-re "(.*: no such file .*)$canp" {incr re} \
	-re "(.*)$canp" {incr re 0 }

    frputs re expect_out(1,string)
    if {! $re} return ""
    return [regsub -all {\r} $expect_out(1,string) {}]
  }
  proc SSHchown {VFStok mode file} {
    variable cannedPrompt
    VFSglobal mySpawn_id timeout winFlag canp
    variable debug

    exp_log_user $debug
    catchDebris $VFStok
    frputs VFStok mode file
    exp_send -i $mySpawn_id "chown $mode [sshfixfilename $file]\r"
    expect -i $mySpawn_id \
	-re "(Changing .*\[^\n]*)$canp" {incr re 0} \
	-re "((.ouldn't .*)|(.*denied.*))$canp" {incr re} \
	-re "(.*chown: .*)$canp" {incr re} \
	-re "(.*: no such file .*)$canp" {incr re} \
	-re "(.*)$canp" {incr re 0 }
    
    frputs re expect_out(1,string)
    if {! $re} return ""
    return [regsub -all {\r} $expect_out(1,string) {}]
  }

  proc SSHclose { VFStok} {
    variable cannedPrompt
    VFSglobal mySpawn_id

    if {![info exists mySpawn_id] || $mySpawn_id == 0} {return}
	catch {
	  exp_send -i $mySpawn_id "\x03"
	  exp_send -i $mySpawn_id [expr {!$::MSW ? "quit\r" : "exit\r"} ]
	  exp_close -i $mySpawn_id
	}
	exp_log_file
    set mySpawn_id 0
  }

  # It seems that ssh and scp handle the standard unix file format so
  # lets not use back slash files at all...
  proc sshfixfilename {n {local 0}} {
    return  $n
  }
  # For local files, set the local flag then the formating will
  # be done based on ::MSW, otherwise we use the remote "winFlag"
  # proc sshfixfilename { n {local 0}} {
  #   variable {}
  #   upvar winFlag winFlag
  #   set msw [expr {$local ? $::MSW : $winFlag}]
  #   # if there are already escaped spaces, do nothing.
  #   if {![string match {*\\ *} $n]} {
  #     set n [FixFileName $n -p $(fixfilename,$msw)]
  #     if {[string first "'" $n] > 0} {
  # 	set n \"$n\"
  #     }
  #     if {$msw} {
  # 	set n [regsub -all {/} $n {\\\\} ]
  #     }
  #   }
  #   set n [regsub -all " " $n {\\ }]
  #   # since the result is to go into a list (agrs for call) we
  #   # nock off any quotes of the whole string
  #   set n [regsub -all {^"|"$} $n {}]
  #   return $n
  # }
  proc SSHcommand {VFStok command} {
    variable cannedPrompt
    VFSglobal mySpawn_id timeout abortflag canp es
    variable debug
    frputs mySpawn_id abortflag command VFStok st
    # set st [stty < $mySpawn_id]
    # frputs st
    set mySpawn_idOrg $mySpawn_id
    catchDebris $VFStok
    set $abortflag 0
    set cmds [split [subst $command] \r]
    set buff {}
    foreach cmd  $cmds {
      frputs cmd cmds
      if {[incr wu] != 1} {
	#sleep 4
	#append buff [catchDebris $VFStok] LOOP
      }
      exp_send -i $mySpawn_id "$cmd\r"
    }
    set count [llength $cmds]
    set re 0
    expect -i $mySpawn_id \
	eof {frputs "EOF " ; return "eof"}\
	timeout {
	  frputs mySpawn_id $abortflag
	  if {$mySpawn_id != $mySpawn_idOrg} {
	    frputs mySpawn_id mySpawn_idOrg
	    return "User Abort\n"}
	  if {[set $abortflag] != 0 || [CheckAbort "$VFStok command"]} {
	    append buff "User Abort\n"
	    SSHclose  $VFStok
	    set re 1
	  } else {
	    Log "[eId 90]Have patience. You may abort with Stop button. [incr co]"
	    realWaitForIdle
	    exp_continue
	  }
	}\
	full_buff {
	  append buff $expect_out(buffer)
	  frputs MORE expect_out(buffer)
	  exp_continue
	}\
	-re "(\[\r\n] | $es )*$command$es\r?" {
	  append buff $expect_out(buffer)
	  # Log "GONE $buff"
	  frputs GONE expect_out(1,string)
	  exp_continue
	}\
	-re "(^.*)$canp" {
	  append buff $expect_out(1,string)
	  Log "HERE $expect_out(buffer)"
	  frputs #2 HERE count expect_out(buffer) buff
	  if {[incr count -1] > 0} {
	    exp_continue
	  }
	  incr re 2
	}\
	-re "$canp"     {
	  append buff $expect_out(buffer)
	  frputs THERE expect_out(buffer)
	  # Log "THERE $buff"
	  if {[incr count -1] > 0} {
	    exp_continue
	  }
	}
    # clean up all the esc and line break chars
    set buff [SSHcleanEsc $buff]
    # Lets do a bit more cleanup
    set buff [regsub -all {\r} $buff {}]
    foreach cmd $cmds {
      set buff [regsub "(^|\n)$cmd\n" $buff {}]
    }
    frputs buff
    set buff [regsub -all {^\n*} $buff {}]
    set buff [regsub -all {(\n)\n*} $buff {\1}]
    frputs re command expect_out(1,string) expect_out(buffer) buff
    return $buff
  }
  
  # This code may be used with multi line returns as well as single.
  # The only line feed or return is messes with is the line break and the prompt
  proc SSHcleanEsc {buf} {
    variable cannedPrompt
    upvar VFStok VFStok
    VFSglobal es canp
    frputs #2 buf
    # order is inportant here as the first sequence may be in the middle of the second
    set buf [regsub -all {(\u001b{1,1}?\][^\u0007]*\u0007)} $buf {}]
    #  frputs 1 buf
    set buf [regsub -all {\u0008\r\n} $buf  {}]
    #  frputs 2 buf
    set buf [regsub -all "$es" $buf {}]
    #  frputs 3 buf
    set rtn $buf
    set r [regexp "(.*\[^\r\n])$canp" $buf -> rtn]
      frputs 4 r rtn ->
    return $rtn
  }
  
  proc SSHdebug {VFStok bool} {
    variable debug
    if {[set debug $bool]} {
      if {[info procs [namespace current]::frputs] != {}} {
	rename [namespace current]::frputs [namespace current]::dum
      }
    } elseif {[info procs [namespace current]::frputs] == {}} {
      rename [namespace current]::dum  [namespace current]::frputs
    }
    
  }
  proc SSHvolumes {VFStok} {
    variable {}
    VFSglobal winFlag
    if {$winFlag} {
      set ret [SSHcommand $VFStok "fsutil fsinfo drives"]
      frputs ret
      set ret [split $ret \n]
      foreach line $ret {
	if {[string match -nocase "Drives: [A-Z]*" $line]} {
	  return [lrange [regsub -all {\\} $line {/}] 1 end]
	}
      }
    }
    return -code error "Volumes command not supported."
    
  }
  # nothing to add now.
  proc SSHmenu {URL inst} {{}}

  # A debug routine to check on debris left in the expect buffer.
  # Pass the spawn_id to use
  
  proc catchDebris {VFStok} {
    # return
    VFSglobal SSHdebris mySpawn_id
    set DEBRISFOUND {}
    if {![info exists SSHdebris]} {set SSHdebris {1 1}}
    set lastCall $SSHdebris
    set thisCall [set SSHdebris [info frame -1]]
    # frputs #2 DEBRICHECK
    expect -i $mySpawn_id\
	-re "(.*)" {set DEBRISFOUND $expect_out(buffer)}\
	eof        {set DEBRISFOUND  "eofFlag"}
    
    if {$DEBRISFOUND != {}} {
      array set last $lastCall
      array set this $thisCall
      frputs #2  DEBRISFOUND last(proc) last(line) this(proc) this(line)
    } else {
      frputs #2  "NODEBRISFOUND "
    }
    return $DEBRISFOUND
  }
}
