#
# <20240712.1817.42>
#
# An effort to sanely find UNC ports for windows.
# First we test a couple of register values to see if
# smb is supported and if so SMBv1 or SMBv2
# set wh HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters
# registry get $wh smb1
# If we have SMBv1, there is a good chance there is a browser
# If not, we have to test each visable machine on the local network
#
# We use ipconfig, arp, and ping to try and find these with out
# spending forever doing it.

# then we need to check each machine

namespace eval findUNC {
  variable lanmanParms [join {
    HKEY_LOCAL_MACHINE
    SYSTEM
    CurrentControlSet
    Services
    LanmanServer
    Parameters
  } "\\"]
  # nonServers are devices which respond to ping but not to
  # server enumeration. They are alive but have no resources
  # for us.
  variable wslNativeMap {}
  variable nonServers {}
  
  package require ip
  package require dns

 
  # This routine checks if an IPv4 'ip' is in the sub net defined by the 'mask'
  # findUNC
  proc inSubnet {ip} {
    variable maskParts
    variable myNetParts
    
    foreach ipPart [split $ip .] maskPart $maskParts myNetPart $myNetParts {
      frputs ipPart myNetPart maskPart ip 
      if {(($ipPart ^ $myNetPart) & $maskPart) != 0} {
	frputs ipPart myNetPart maskPart 
	return 0
      }
    }
    frputs "YES " ip
    return 1
  }

 
  # Here we use the registry to find all the local Ip addresses
  # To be usable there must be a gateway for the IP
  
  # findUNC
  proc getLocalIps {} {
    # registery interface keys are here:
    
    set ifKey\
	HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/Interfaces
    lassign {} IpVals
    foreach iFace [registry keys   [file native $ifKey]] {
      set ifData [registry values  [file native $ifKey/$iFace]]
      foreach gate  [lsearch -glob -all -inline $ifData *DefaultGateway] {
	if {[set val [registry get [file native $ifKey/$iFace] $gate]] != {}} {
	  set prefix [regsub {DefaultGateway} $gate {}]
	  if {"${prefix}IPAddress" in $ifData &&\
		  "${prefix}SubnetMask" in $ifData &&\
		  [set Ip [registry get   [file native $ifKey/$iFace]\
		       "${prefix}IPAddress" ]] != {} &&\
		  [set Mask [registry get [file native $ifKey/$iFace]\
		       "${prefix}SubnetMask" ]] != {}} {		  
	    lappend IpVals [list $Ip $Mask $val]
	  }
	}
      }
    }
    return $IpVals
  }

  # findUNC
  proc invert {ip} {set f [join [lreverse [split $ip .]] .].IN-ADDR.ARPA}
  proc ipToName {ip} {
    set name [::dns::name \
		  [set tok \
		       [::dns::resolve [invert $ip]\
			    -type PTR -recurse 0 -class IN]]]
    ::dns::cleanup $tok
    return $name
  }
  proc nameToIp {name} {
    # could this be an ip already??
    if {![catch {::ip::type $name}]} {return $name}
    set ip [::dns::address \
		  [set tok \
		       [::dns::resolve $name\
			    -type A -recurse 0 -class IN]]]
    ::dns::cleanup $tok
    return $ip
  }
    

  # This code sets up an async test of the presence of a given possible
  # cifs server. The result will be one of:
  # 0 This is a server (i.e. port 445 is responding)
  # 1 This is a non-server (our request to open port 445 was rejected)
  # 2 This system is off line (our request timed out)
  #
  # On completion of the test a result code (0 1 or 2) is written into
  # socketTestResults(name) and socketTestResultOut is decremented.
  #
  # There are two parts to the test:
  # 1. We ping to see if the target is alive.  We use "pipeo" to do this
  #    so we don't have to wait. Ping will fail if the server is offline.
  
  # 2. Once we get a positive ping result we attempt to open a socket on
  #    port 445 (a cifs port). If this is successful we have a server.
  #
  # Both these tests take several seconds in the fail scenario. This
  # implies an abort of the test if completion takes too long may be
  # the way to go.
  # 

  # findUNC
  proc socketTest {ip name} {
    variable socketTestResults
    variable socketTestSystems
    variable socketTestResultsOut

    lappend socketTestSystems $name
    # First the ping...
    set cmd [auto_execok ping]
    lassign [pipeoExec "$cmd -n 1 -l 1 -w 100 -4 $ip" r\
		 [list [list ::findUNC::pipeo $name $ip]]] r handle
    if {$r != 0} {
      return -code error "$handle on ip \"$ip\""
    }
    set socketTestResults($name) -1
    incr socketTestResultsOut
    return
  }
  
  # findUNC
  proc pipeo {name ip handle why args} {
    variable socketTestResultsOut
    variable socketTestResults
 
    # frputs ip handle why args
    switch -glob $why {
      data {
	if {[string match "*Reply *" $args]} {
	  # frputs 
	  set socketTestResults($name) -2
	}
      }
      done {
	# frputs status name
	if {$socketTestResults($name) != -2 } {
	  set socketTestResults($name) 2
	  incr socketTestResultsOut -1
	  return
	}
	# It is unsafe to use 'name' here as the system seems to think
	# a name that include a '.' (or possibly '.n' where n is a number
	# must be an ip address and not a name. In any case the ip address
	# should be faster...
	
	set fid [socket -async $ip 445]
	fileevent $fid readable "findUNC::socketTestR $fid $name"
	fileevent $fid writable "findUNC::socketTestR $fid $name"
	set socketTestResults($name) -3
      }
      default {}
    }
 
    # set socketTestResults($name) -1
  }
  
  # findUNC
  proc socketTestR {fid name} {
    variable socketTestResults
    variable socketTestResultsOut
    variable nonServers
    set str [chan configure $fid -error]
    set r 0
    if {$str != ""} {
      set r [expr {[string compare *refused* $str] ? 1 : 2}]
      # Is this list worth keeping???
      if {$name ni $nonServers} {
	lappend nonServers $name
      }
    }
    set  socketTestResults($name) $r
    catch {close $fid}
    incr socketTestResultsOut -1
    # frputs ip r name socketTestResultsOut
  }
   
  # A function to prune a twapi 'record array'
  # A record array contains two lists a & b
  # a) an enumeration of the elements in each sublist of list b
  # b) a list of sublists containing the elements defined in 'a'
  
  # call: pruneRa 'record array' 'list of desired elements in desired order'
  #
  # E.g.  'purneRa foo [list status type]' will return a list containing sublist each 
  #                                        of the entries in the order of 'status type'
  # Mostly this is a short hand and adds two things to the mix
  # a) the list may be abbreviated and the leading "-" is optional
  # findUNC
  proc pruneRa {ra elList} {
    # frputs ra elList
    set possible [lindex $ra 0]
    foreach rq $elList {
      set match [lsearch -all -inline -reg $possible "^-?$rq.*"]
      if {[llength $match] != 1} {
	return -code error "'$rq' not found. Must be one of '$possible'"
      }
      lappend rqList $match
    }
    return [twapi::recordarray getlist $ra -slice $rqList]
  }

 #  # sysToUp sets the system name part of a //system/share to upper case.
 #  # we also change the backslashes to forward.
 #  # findUNC
 # proc sysToUp {sh} {
 #    set sh [regsub -all {\\} $sh /]
 #    set f [string toupper [string range $sh 0 [set lc [string last / $sh]]]]
 #    return $f[string range $sh $lc+1 end]
 #  }

  # examCase return 0 in all caps, 1 if all lower case, 3 if mixed

  # findUNC
  proc examCase {item} {
    return [expr {[string compare $item [string toupper $item]] == 0 ? "0" :\
		      [expr {[string compare $item [string tolower $item]] == 0 ? "1" :\
				 "3"}]}]}

  # resolveDups looks at the 'sysShareL' and resolves duplicate
  # caused by 'case' variability in to one entry. We choose the case to use
  # from among those presented, preferring entry that mix upper and lower cases.
  # We also combine the 'connected', 'drive lettter' and 'comment' to make one entry.
  # User/password info is handled separately.

  # Each sub list in 'sysShareL' is:
  #      (//system/share) (1/0 connected) (drive letter:) (comment)
 
  # findUNC
  proc resolveDups {} {
    # variable sysL
    variable sysShareL
    while {[llength $sysShareL] > 0 || [incr failsafe] > 10} {
      set lists [lsearch -exact -all -nocase -index 0\
		     $sysShareL [lindex $sysShareL 0 0]]
      frputs lists sysShareL "[lindex $sysShareL 0 0] "
      if {[llength $lists] == 1} {
	lappend result [lindex $sysShareL $lists]
	set sysShareL [lreplace $sysShareL $lists $lists]
	
	continue
      }
      # 2 or more matches, merge results
      set subResult [lindex $sysShareL [lindex $lists 0]]
      set info [lassign [lindex $sysShareL [lindex $lists 0]] sysSh]
      lassign [split $sysSh /] syst share
      set caseSys [examCase $syst]      
      set caseShr [examCase $share]
       
      foreach listEnt [lreplace $lists 0 0] {
	if {$sysSh != [set new [lindex $sysShareL $listEnt 0]]} {
	  lassign [split $new /] newSys newShar
	  frputs new newSys newShar
	  if {$caseSys < [examCase $newSys]} {
	    set syst $newSys
	  }
	  if {$caseShr < [examCase $newShar]} {
	    set share $newShar
	  }
	    #set subResult "//$syst/$share"
	}
	# merge the list elements
	set elem1 [expr {[lindex $info 0] | [lindex $sysShareL $listEnt 1]}]
	set elem2 [expr {[set tm [lindex $info 1]] == {} ? \
			     [lindex $sysShareL $listEnt 2] : $tm}]
	set elem3 [expr {[set tm [lindex $info 2]] == {} ? \
			     [lindex $sysShareL $listEnt 3] : $tm}]
	set info [list $elem1 $elem2 $elem3]
      }
      lappend result [list "$syst/$share" {*}$info]
      # Eliminate the processed elements
      if {$lists == {}} {error}
      foreach listEnt [lreverse $lists] {
	set sysShareL [lreplace $sysShareL $listEnt $listEnt]
      } 
    }
    set sysShareL $result
  }
  
  # findUNC
  proc UNCpoll {long} {
    variable nonServers
    variable maskParts {}
    variable myNetParts {}
    variable socketTestResults
    variable socketTestSystems {}
    variable socketTestResultsOut 0
    variable sys
    variable sysShareL {}
    unset -nocomplain sys sysShare socketTestResults
    

    # First we build a list of all the shares the system knows about
    # connected or otherwise.. This list may  have entries from the
    # "connected shares" "this system info (which may have currently
    # unconnected shares)" "the 'remote system' (as a result of polling)"
    # and "the password locker" list 'sysShareL' is a list of:
    #      (system/share) (1/0 connected) (drive letter:) (comment)
    
    # The 'sys' array is accessed by systen name (it is a server) value
    # is 'Up/Down/??' list elements: (Flags is a two letter code
    # indicating if share is connected (C/N)

    # The case issue is handled by collecting all represented cases in a list
    # Each list entry has a sublist with additional info as we gather it.
    # After all is gathered we will combine the info and select the case to
    # use (one of those found).
    #
    # We assume there are no duplicates in 'wnet_connected_resources' but the
    # system may appear more than once.
    findUNC::findWSLshares
    foreach share [twapi::wnet_connected_resources] {
      lassign $share dl sysSh
      lassign [split [regsub -all {\\} $sysSh /] /] - - syst share
      lappend  sysShareL [list "$syst/$share" 1 $dl]
      set sys([string toupper  $syst]) "C"
    }
    # Here we get a list of shares the system knows about
    set shareList [regsub -all {\\} \
		       [pruneRa [twapi::get_client_shares -level 2]\
			    {localdevice rem}] {/}]
    # This return may contain dups, possibly with differing cases.
    foreach share  $shareList {
      lassign $share dl sysSh
      frputs share
      lassign [split $sysSh /] - - syst share
      frputs 
      set shareName "$syst/$share"
      lappend sysShareL [list $shareName 0 $dl]
      if {[isServerUp $syst]} {
	frputs syst
	doUp $syst
      }
    }
    # So here we compute the broadcast address...
    # and set up the *Parts variables
    #
    frputs EndOfShortPoll
    set serverList {}
    if {!$long} {
      resolveDups
      return $sysShareL
    }
    
    foreach adds [getLocalIps] {
      frputs adds
      lassign $adds myIp mask
      lassign {} bcP myNetParts  maskParts
      frputs adds myIp mask
      #set bcP [set myNetParts [set maskParts {}]]
      set bc [::ip::broadcastAddress $myIp/[::ip::maskToLength $mask]]
      #
      set thisIp [::ip::prefix $myIp/$mask]
      set total [::ip::distance $thisIp $bc]
      set current 0
      incr total -2
      #
      # Here we poll the dns with each subnet address catching
      # addresses that have names. This, we condider, a required
      # condition to be a server.  The dns most likely return
      # names for systems that are now offline (and may never be online)
      
      while {[::ip::distance $thisIp $bc] > 1} {
	set thisIp [::ip::nextIp $thisIp]
	incr current
	if {$thisIp == $myIp} {continue}
	
	set name [ipToName $thisIp]
	
	# Only named entries can host a share...
	if {$name == {}} {continue}
	
	# This logging may be too much
	if {[expr {$current % 10}] == 0} {
	  LogStatusOnly "Checking $thisIp" [expr {($current * 100) / $total}]
	}

	# We need to know if the given system is up and if it has
	# shares.  This should check both...
	# frputs name
	# note 'socketTest' is async and so returns immediately
	socketTest $thisIp [string toupper $name]
      }
      frputs "[array get socketTestResults] " socketTestSystems 
    }
 
    # Now we process the results (possibly waiting for some)
    while {($socketTestResultsOut || $socketTestSystems != {}) &&\
	       [incr failsafe] < 400} {
      set left {}
      frputs socketTestResultsOut socketTestSystems
      foreach name $socketTestSystems {
	switch $socketTestResults($name) {
	  2 {
	    lappend sysL [list $name "?D"]
	  }
	  1 {}
	  0 {doUp $name}
	  default {  
	    lappend left $name
	    continue
	  }
	}
      }
      # All the completed tests are processed.  Remove those systems
      # from the list and give our code a run at completion
      set socketTestSystems $left
      if {$left == {}} {break}
      vwait findUNC::socketTestResultsOut
      frputs left socketTestResultsOut
    }
    resolveDups
    
    # frputs "[array get sys] "
    # frputs "[lh [array get sysShare] 2] "
    
    LogStatusOnly "done" -1
    return $sysShareL
  }


  # 'doUp' given an up server name, processes as much  as can be found
  
  # findUNC
  proc doUp {name} {
    variable sys
    variable sysShareL
    
    # see if we can get a list from the system
    # if we don't supply a user/pw the system uses the current users name/pw
    # This may not be the desired thing. While we know the users name we don't
    # know their password.
    # If this system has a connected share we are good to go, so test that first.
    
    if {![info exists sys([set tm [string toupper $name]])]} {
      # Get possible name/pws from the password locker
      set pwList [pwLocker::patternGet cifs:/(/|/.*@)$name/ regexp 1]
      set done {}
      foreach pwds $pwList {
	set parts [URLsplit $pwds]
	set user [lindex $parts 1]
	set pw   [lindex $pwds 1]
	frputs name pw
 	if {($pw != {} || $user != {}) && "$user/$pw" ni $done} {
	  lappend done "$user/$pw"
	  if {[catch {twapi::connect_share [file native //$name/ipc$]\
			   {*}[expr {$user == {} ? {} : "-user $user"}]\
			   {*}[expr {$pw   == {} ? {} : "-password [twapi::conceal $pw]"}]\
			 } rr] == 0} {
	    # This password/user works, get the info
	    break
	  }
	}
      }
    }
    frputs name
    # connected or not, here we go...
    # What we want...
    # 'Flags (C or N)' 'drive letter' 'systen' 'share' 'comment'
    # This will get the name and comment for each share on 'name'
    if {[catch {twapi::get_shares\
		    -system $name\
		    -level 1\
		    -type file\
		    -excludespecial} r] != 0} {
      # access issues
      lappend sysShareL  [list "$name/ipc$" 0 {} "Need 'name/password' to access"]
      frputs name  r
    } else {
      foreach shar [pruneRa $r {name comment}] {
	lassign $shar share com
	if {$share == "ipc$"} {continue}
	lappend sysShareL [list "$name/$share" 0 {} $com]
	frputs name share com
      }
    }
    # disconnect the ipc share
    set r [catch {twapi::disconnect_share [file native //$name/ipc$]} er]
    return $sysShareL
  }

  # Setup a Display Name for a share

  # findUNC
  proc setDN {mp share server target} {
    set drive $mp
    global config
    set cv {format "%s(%s)(%s)" $share $server $drive}
    if {$config(DisplayNameShare) != {}} {
      set cv $config(DisplayNameShare)
    }
    set displayName  [eval $cv]
    # This deletes a possibly old display name with nil drive
    set drive "*"
    set displayNamePat [eval $cv]
    set co [delDNpat $displayNamePat]
    frputs co displayNamePat
    set r [catch {addDN  [list $target $displayName]} er]
    frputs er mp share server target drive
    if {$r != 0 && [string index $er 0] != "-"} {
      PopWarn $er
    }
  }
  #
  # We generate a mapping to follow wsl side
  # links that point back to the MSW side of the house.
  #
  # I.e. files that look like: //wsl$/<name>/mnt/c/... -> c:/...
  # except that '/mnt/c' is just the default and does not cover mapping to
  # MSW side remote shares. So we do it all here.
  # This only works on wsl 1 shares so we don't recommend using it.
  # We also note that there may be more than one wsl system.

  # And now there are two versions 1 & 2
  #
  # findUNC
  proc findWSLshares {} {
    variable wslNativeMap {} ; # list of wsl istances {name rootfs version}
    # variable connected\
    # 	[string tolower \
    # 	     [regsub -all {\\} [::twapi::wnet_connected_resources -type file] {/}]]
    # Get WSL info from the registery
    set wslKey\
	HKEY_CURRENT_USER/SOFTWARE/Microsoft/Windows/CurrentVersion/Lxss
    if {[catch {registry keys [file native $wslKey]} wslEnts] != 0} {return 0}
    foreach wslEnt $wslEnts {
      # not all the 'Lxss' keys are wsl entries. This ignores the others
      if {[catch {registry get [file native $wslKey/$wslEnt] DistributionName}\
	       wslName] != 0} {
	frputs wslKey wslEnt wslName
	continue
      }
      #set wslVers  [registry get  [file native $wslKey/$wslEnt] Version] 
      set wslBase  [registry get  [file native $wslKey/$wslEnt] BasePath]
      set wslBase  [regsub {^//\?/} [regsub -all {\\} $wslBase {/}] {}]
      set wslVers  [registry get  [file native $wslKey/$wslEnt] Flags]
      set wslVers  [expr {($wslVers >> 3) + 1}]

      if {[lsearch -exact -index 0 $wslNativeMap "//wsl$/$wslName"] == -1} {
	lappend wslNativeMap [list "//wsl$/$wslName" $wslBase $wslVers]
      }
    }
      
    # Still here?? Ok, get the info
    # now that we know the wsl shares, lets put together a mapping for
    # wsl files that reference back to the non-wsl files.
    # This will be used by the link de-code code in 'getWsLink'
    # in 'winLnkFile.tcl'
    # I suppose it is possible that the /etc/wsl.conf is itself a link which we
    # will try to figure (it is not clear if the wsl set up allows this).
    # Here we compile the translations from //wsl$/../ files that link back
    #
    # We assume that /etc/wsl.config (if it exists) is not a link to MSW land.
    #
     
    foreach {wslId dontcare vers} $wslNativeMap {
      # Set the default...
      set root {}
      set mountFsTab 0
      set enabled 0
      if {[catch {read [set fid [open $wslId/etc/wsl.conf r]]} wslInfo] == 0} {
	foreach line [split $wslInfo \n] {
	  set line [string trim $line]
	  if {[string index $line 0] == "\;"} {continue}
	  if {[string match -nocase "enabled*" $line]} {
	    set sl [split $line =]
	    set enabled [lindex $sl 1]
	  }
	  if {[string match -nocase "root*" $line]} {
	    set sl [split $line =]
	    set root [lindex $sl 1]
	  }
	  if {[string match -nocase "mountFsTab*" $line]} {
	    set sl [split $line =]
	    set mountFsTab [lindex $sl 1]
	  }
	}
	frputs fid wslId
	catch {close $fid}
	if {$root == {} || !$enabled } {
	  set mountFsTab 0
	  set root "/mnt"
	}
	foreach vol [file volum] {
	  if {[::twapi::get_drive_type $vol] != "fixed"} {continue}
	  frputs vol root 
	  lappend wslMswMap $wslId$root/[string tolower [string index $vol 0]]\
	      [string range $vol 0 1]
	}
	# That should take care of the wsl.config file. Did it turn on fstab?
	if {!$mountFsTab} {continue}
	set fstab $wslId/etc/fstab
	if {[file type fstab] == "link"} {
	  lassign [getWsLink $wslId/etc "fstab"] fstab cd
	}
	if {[catch {read [set fid [open $fstab r]]} wslInfo] == 0} {
	  foreach line [split $wslInfo \n] {
	    set line [string trim $line]
	    if {[string index $line 0] == "#"} {continue}
	    # The lines we are looking for lead with the following:
	    # <vol>.. DrvFs <wsl mount point> etc....
	    set pos [file volum]
	    if {[string toupper\
		     [regsub {\\} [string range $line 0 2] {/}]] in $pos} {
	      lappend wslMswMap $wslId[lindex $line 2]\
		  [file native [lindex $line 0]]
	    }
	  }
	}
	catch {close $fid}
      }
    }
    return $wslNativeMap
  }
}

# This routine builds a list of all connected file systems. It expands
# on twapi::wnet_connected_resources in that it includes wsl shares that
# are not mapped to a drive. It depends on the 'wslNativeMap' built by
# 'findWSLshares'. Format is the same as returned by 'wslNativeMap'
# i.e. the drive {which may be {}} and the share name.

proc findUNC::getConnectedResources {} {
  set return [regsub -all {\\} [twapi::wnet_connected_resources -type file] /]
  foreach ent $findUNC::wslNativeMap {
    if {[lsearch -exact -nocase -index 1 $return [lindex $ent 0]] == -1} {
      lappend return [list {} [lindex $ent 0]]
    }
  }
  return $return
}

# This routine is used to augment the 'file vol' code which only
# returns mapped volumes, i.e. those with drive letters.  Here
# we only return those that don't have drive letters

proc volEntShares {} {
  set result {}
  foreach share [findUNC::getConnectedResources] {
    lassign $share dr sh
    if {$dr == {}} {
      lappend result $sh
    }
  }
  return $result
}

# This code gets called on reading of the config file so it
# must not redo things it has already done

# It is called with no parms. It looks at all the config(cifs,shares) entries.


# A word on case. This is MSW, there is NO case. That said, one may want
# a particular case. The UNC find code seems to return uppercase for the
# server and lower for the path (the part after the server: //server/path
# (Likely because 'nbtstat' returns upper case for the server.)
# So here is how we allow the user to do what they want.
# When we check if this is a new share (in the passwordLocker) we use '-nocase'



proc isServerUp {server} {
  # frputs #2 server
  return [expr {[set ip [findUNC::nameToIp $server]] != {} && \
	      [set r [catch {
		exec {*}"[auto_execok ping] -n 1 -l 1 -w 100 -4 $ip"
	      } rt]] == 0}]
}

proc removeConfigCifsShare {key} {
  global config
  set indx [lsearch -exact -nocase -index 0 $config(cifs,shares) $key]
  set config(cifs,shares) [lreplace $config(cifs,shares) $indx $indx]
  # frputs indx key
  return $indx
}


# The setUpShare routine attempts to set up all shares in config(cifs,shares)
# save those marked with 'Ignore'.
# config(cifs,shares) format is:
# server/share -user user -drive drive -display flags -comment "what ever s/he wants'
# 'flags' should be one of {{} None Hot&Vol VolOnly Ignor}
#
# Can also be called with parameter (args) a list as in config(cifs,shares)


proc setUpShare {args} {
  global config 
  set mess {}
  # lappend mess "start setupshare"
  # startProc "Start setUpShare"
  findUNC::findWSLshares
  set connected [findUNC::getConnectedResources]
  if {$args == {}} {
    set args $config(cifs,shares)
  }
		   
  foreach ent $args {
    set ops [lassign $ent server-share]
    array set tmp [list -drive {} -user {} -display {}]
    array set tmp $ops
    if {[string index $tmp(-display) 0] in {i I}} {continue}
    # So we have one to set up.
    lassign [split ${server-share} /] server share
    if {$share == {}} {
      lappend mess "config(cifs,shares) entry \"${server-share}\"\
                            is not a legal share name.\
       	                   \nEntry removed and ignored."
      removeConfigCifsShare ${server-share}
      continue
    }
    set conAs [lsearch -all -exact -inline -nocase -index 1\
		   $connected "//${server-share}" ]
    set done 0
    foreach con $conAs {
      set dd [lindex $con 0]
      # if connected with the requested drive we are done
      if {[string equal -nocase $tmp(-drive)  $dd] ||\
	      ($dd != {} && $tmp(-drive) == "*")} {
	incr done
	continue
      }
      if {$dd != {}} {
	lappend dr $dd
      }
    }
    frputs dr  tmp(-drive) conAs done
    set conLet {}
    set dr {}
    if {!$done} {
      if {$tmp(-drive) != {} && $dr != {}} {
	# if share is connected but requested drive is not the one and there are
	# connected drives for this share, use one.
	set $tmp(-drive) [lindex $dr 0]
      }
      # if dr is nil and conAS is not, we have a connected share
      # with no drive letter
      # if conAs is nil, the share is not connected.
      # in either case we attempt to connect with the requsted share.
      frputs  user reqDrive  conAs dr
      if {$conAs == {} || $dr == {}} {
	# not connected. Check to see if the requested system is up.
	if {$server ni {wsl$ WSL$}} {
	  if {![isServerUp $server]} {
	    lappend mess "System '$server' not found or offline,\
                  \nskipping set up of ${server-share}."
	    continue
	  }
	} else {
	  # wsl and not connected. check if we have such
	  if {[lsearch -exact -index 0 \
		   $findUNC::wslNativeMap "//wsl$/$share"] == -1} {
	    # Must be a stale entry in config(cifs,shares)
	    set newEn ${server-share}
	    set tmp(-display) "Ignore"
	    set ms "WSL share \"$share\" is not available."
	    if {[removeConfigCifsShare $newEn] != -1} {
	      # only save if this existed in the config(cifs,shares)
	      lappend config(cifs,shares) [list $newEn {*}[array get tmp]]
	      set ms "$ms \nChanged config(cifs,shares) entry to ignore."
	    }
	    lappend mess $ms
	    continue
	  }
	  
	}
	# All we need to connect is the password...
	# Use a while loop incase we need a better password
	set startPw {}
	set pwA(-user) $tmp(-user)
	set pwA(-localdevice) $tmp(-drive)
	unset -nocomplain pwA(-password)
	
	while {[incr failsafe] < 10} {
	  # First try to connect with no password
	  set r [catch {::twapi::connect_share [file native //${server-share}] \
			    {*}[array get pwA] } out]
	  frputs r out ops "[array get pwA] "
	  if {!$r} {break}
	  if {[info exists pwA(-password)]} {
	    lappend mess "Connection share '//${server-share}' failed with error:\
                          \n$out"
	    break
	  }
	    
	  # password failed, see if we have a pw in the locker
	  set lockerRtn [::pwLocker::getPw cifs://$server user $tmp(-user)]
	  # If s/he canceled, break out of here
	  if {$lockerRtn in {0 -1 -2}} {
	    set r 20
	    break
	  }
	  set pwA(-password) [::twapi::conceal $lockerRtn]
	  frputs lockerRtn
	}
	if {$failsafe >= 10} {error "failsafe failed"}
	# If 'r' shows failure, we must be here by way of the user not giving
	# a password..
	if {$r == 20} {
	  lappend mess "User aborted password entry for $server."
	  continue
	} elseif {$r != 0} {continue}
	# At this point we should be connected 'out' should be the
	# access method (driver letter of //server/share
	frputs out
	set tmp(-drive) [expr {[string index $out 0] == "/" ? {} : $out}]
      }
    }
    # Time to set up the display name
    set target [expr {$tmp(-drive) == {} ? "//${server-share}" : $tmp(-drive)}]
    frputs target server-share
    # first make sure it is not in the hotlist exclusion
    if {[info exists ::hotlistExclusion]} {
      set indx [lsearch -exact -nocase $::hotlistExclusion $target]
      set ::hotlistExclusion [lreplace $::hotlistExclusion $indx $indx]
    }
    # If none requested, just move on
    set dn [string index $tmp(-display) 0]
    if {$dn in {{} "n" "N"}} {continue}
    
    ::findUNC::setDN $tmp(-drive) $share $server $target
    if {$dn ni {h H} } {
      lappend ::hotlistExclusion $target
    }
  }
  return $mess
}

# Here we build an interactive window which displays each 'Share' we find on
# the network and all we know about it. We then allow interactions to change
# those things.

# Here we have a couple of entries for the Utilities menu
#
catch {namespace delete ::UtilsMenu}

namespace eval ::UtilsMenu {
  # These entries are added to the utilities menu for Windows
  variable last {}
  # We want these in the order they appear in the table
  variable readOnly {server-share share drive passwordStatus}
  variable colors
  variable disAry
  variable disAryBackUp
  variable wayOut
  variable debug 0
  variable displayValues {"" None Hot&Vol VolOnly Ignore}

  variable ents {
    {-label {Enumerate remote shares} -command {getRemoteShares 0}}
    {-label {Find remote shares} -command {getRemoteShares 1}}
    {-label {Refresh PATH \n(after add/delete programs)}\
	 -command {::auto_execNS::buildPath}}
  }
  #  {-label {Assign drive letter} -command {setWinVolLetter}}
}
  proc getRemoteShares {args} {
    after 0 "::UtilsMenu::enumShares {*}$args"
}
proc ::UtilsMenu::infoLineHead {add args} {

  foreach arg $args {lappend result [list {} [list -text $arg {*}$add]]}
  return $result
}
proc ::UtilsMenu::infoLine {add text} {
  return [list [list fullspan [list -text $text {*}$add]]]
}

# The dataLine routine takes a 'content' list (ordered by infoList) and builds:
# 1) the info line for the smart_dialog call
# 2) the 'disAry' elements for the given line
# 3) we passback the line for 'smart_dialog' alone with 'status' and 'passwordStatus'

proc ::UtilsMenu::dataLine {entryNo content} {
  variable disAry
  variable infoList1
  variable infoList2
  variable shareList1
  variable shareList2
  variable readOnly
  variable colors
  variable availDr
  variable displayValues
  # frputs content
  # global dispList result
  # get the content values
  set drive {}
  set disAry($entryNo,status) [lindex $content end]
  set oneTwo [expr {[lindex $content end] >= 10 ? 2 : 1}]
  lassign $content {*}[set shareList$oneTwo]
  # server-share format is server/share. break that up
  lassign [split ${server-share} /]  server-share share
  set availDr [availDr $drive]
  # frputs server-share share
  set dispList {}
  foreach {ent t1 op} [set infoList$oneTwo]  {
    if {$t1 == {}} {continue}
    lappend dispList [expr {$op != {} ? [subst $op] : $ent}]
  }
  frputs #2
  set result {}

  foreach arg $dispList {
    lassign $arg ar option
    set disAry($entryNo,$ar) [set $ar]
    # frputs ar $ar disAry($entryNo,$ar)
    lappend  result\
	[list {}\
	     [list {*}[expr {$option != {} ? $option : {}}]\
		  -textvariable [namespace current]::disAry($entryNo,$ar)\
		  -state disabled -disabledbackground $colors(bg) \
		  -bg $colors(sbg)\
		  {*}[expr {$ar in $readOnly ?\
				[list -foreground  $colors(hlight)\
				     -disabledforeground $colors(hlight)] : {}}]\
		  {*}[expr {$ar == "password" ? [list -show "*"] : {}}]]
	    ]
  }
  # frputs result
  return [list $result $status $share]
}

# available drives list
proc ::UtilsMenu::availDr {availDr} {
  lappend availDr {} "*"
  set taken [list {*}[file volume] {:/}]
  foreach dr {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
    if {"$dr:/" ni $taken} {
      lappend availDr $dr:
    }
  }
  return [lsort -unique $availDr]
}


proc ::UtilsMenu::enumShares {{long 0}} {
  # for debug
  # global data
  variable windowPre {}
  variable disAry
  variable disAryBackUp
  # variable pwInfoAry
  # variable pwsExp
  # variable PwList
  variable perLine1
  variable perLine2
  variable debug
  variable displayValues 
  variable wayOut
  variable longPoll 0
  variable last
  variable feedback {}  ; # text messages go here
  #                      status values nil   -> connected good password
  #                                    BadPw -> connected bad  password
  #                                    NoPw  -> connected no   password
  #                                    Found -> not connected ?? password
  #                                    Pw    -> not connected ?? password

  # Here we lay out the display columns. A change here will reorganize
  # the columns, heading and data.
  
  # The following bits are in each sublist for each share:
  # colunm 1 is the name we use when expanding (lassign) the share structure
  # colunm 2 is the header text for the given entry
  # colunm 3 is the script to use in the smart_dialog call (if nill use col 1)
  #          This script request a combo box that will only accept selections
  #          from the dropdown menu.
  # Notes: 'share' is expanded from 'server-share' only  on display
  #        'connected' does not appear, per-say in the display but is in the list
  
  variable infoList1 {
    server-share   "Server\n"           {} 
    share          "Share\n"            {}
    user           "User\n"             {}
    drive          "Drive\nActual"      {}
    reqDrive       "Drive\nRequest"     {[list reqDrive [list -values $availDr -edit 0]]}
    DN             "Display\nName Flag" {[list DN [list -values $displayValues -edit 0]]}
    comment        "Comments\n"         {}
    status         ""
  }
  variable infoList2 {
    server-share   "Server\n"           {} 
    user           "User\n"             {}
    password       "Password\n"         {}
    passwordStatus "Password\nOK"       {}
    comment        "Comments\n"         {}
    status         ""
  }
  foreach oneTwo {1 2} {
    variable shareList$oneTwo {}
    # build the 'shareList' structure
    foreach {ent t1 t2} [set infoList$oneTwo] {
      lappend shareList$oneTwo $ent
      lappend newEntry$oneTwo {}
    }

    set perLine$oneTwo [llength [set shareList$oneTwo]]; # Entries per line in our tables
    incr perLine$oneTwo -1
  }
  set newEntry1 [lreplace $newEntry1 end end 2]
  set newEntry2 [lreplace $newEntry2 end end "11"]
  
  variable colors
  variable messLoc
  variable availDr
  variable connectedShares
  variable entryNo
  variable wt1
  variable wt2
  variable wt3
  variable w
  variable readOnlyExempt
  global foundShares

  global config
  array set colors  [list \
                         sbg      $config(gui,color_select_bg)\
                         bg       $config(gui,color_bg)\
                         hlight   $config(gui,color_highlight_fg)\
                         hlightbg $config(gui,color_highlight_bg)\
                         info     $config(gui,color_cmd)]
  
  set mes "This may take a while. There are two parts:

a) Find shares on the local network and
b) Analyze and compare with the connected shares and the password locker.

Each part will generate a progress bar at the top of filerunner's window."
  
  set longPoll [expr {$long != 0}]
  set w .shares
  destroy $w
  if {![winfo exists $w]} {
    toplevel $w
    wm title $w "Shares $::glob(orgTitle)"
    set wt1 [canvas  $w.t1 -height 70 -width 400]
    grid $wt1 -in $w -column 0 -row 0 -rowspan 2 -sticky news
    grid rowconfigure $w 0 -weight 1
    grid columnconfigure $w all  -weight 1
    grid columnconfigure $w 1    -weight 0
    set wt2 $wt1.t2
    set wt3 $wt1.t3
    set label [label $wt1.lab]
    bind $w <1> [list ::UtilsMenu::highlight %W %x]
    #bind $wt1 <Configure> {::UtilsMenu::sizeAdjust %W}
    bind $w   <Configure> {::UtilsMenu::sizeAdjust %W}
    # The embedding seems to mess up the %W such that the
    # scroll commands get delivered to the embedded window
    # rather than the one that needs/uses them.
    bind $w <MouseWheel> [list ::UtilsMenu::scroll %D]
    bind $w <Shift-MouseWheel> [list ::UtilsMenu::scroll %D x]
    update
  }
  while {1} {
    if {$longPoll} {
       $label config -text $mes 
     
      #$wt1 insert 0.0 $mes
    } else {
      $label config -text "Just a moment..."
      #$wt1 insert 0 {.1 .1} "Just a moment..."
    }
    grid $label -in $wt1
    wm deicon $w
    if {[info exist notFirstTime]} {
      wm geo $w $messLoc
      catch {grid forget $wt2 $wt3}
    } else {
      centerWin $w
      set messLoc [wm geo $w]
    }

    # We build a list of shares and add sublist info as we find it.

    set AllShares {}
    # The following bits are in each sublist for each share:
    # set shareList {server-share user password passwordStatus\
    # 		       drive reqDrive DN comment connected}
    # this is converted to individual entries using 'lassign'
    # And the password list...

    # set pwList {server usr pw passwordStat}
    
    if {![info exists ::foundShares] || $::frputs::on == 0 || 1} {
      set foundSharesW [::findUNC::UNCpoll $longPoll]
      set connectedShares {}
      foreach fshare $foundSharesW {
	# UNCpoll returns a list of lists. Each entry is:
	# (sys/share) (connected or not flag (1/0)) (drive-letter/()) (comment)
	# Returns of (sys/??) indicate the the system is a server, is up,
	# is not connected, and we have no valid name/password for it. In this
	# case the 'comment'  will be "Need 'name/password' to access" 
	# lappend foundSharesW [list $sh {*}$shInfo]
	frputs fshare
	lassign {} {*}$shareList1
	set com [lassign $fshare server-share status drive comment]
	if {$comment == {}} {
	  set comment "no-comment"
	}
	# combine and save
	set new {}
	foreach ent $shareList1 {
	  lappend new [set $ent]
	}
	lappend AllShares $new
	if {$status == 1} {
	  lappend connectedShares $new
	}
      }
      set ::foundShares [lsort $foundSharesW]
    }
    # array unset pwInfoAry
    
    frputs foundShares
      
    set foundSharesW $foundShares
    set last {}
    # WSL shares are always connected but, for unknown reasons do not appear here
    # unless they have a drive letter.
    
    foreach ent [findUNC::findWSLshares] {
      lassign {} {*}$shareList1
      set server-share [string trim [lindex $ent 0] /]
      frputs server-share share
      set indx [lsearch -exact -nocase -index 0 $AllShares ${server-share}]
      if {$indx != -1} {
	lassign [lindex $AllShares $indx] {*}$shareList1
      }
      if {$drive == {}} {
	set drive [lindex $ent 3]
      }
      set status 1
      set comment "No usr/pw for WSL$ shares"
      set new {}
      foreach ent $shareList1 {
	lappend new [set $ent]
      }
      set AllShares [lreplace $AllShares $indx $indx $new]
      
      # foreach share $::findUNC::wslNativeMap {
      # if {![string match -nocase *[lindex $share 0]* $connectedShares]} {
      # 	lappend connectedShares [list [lindex $share 0] {} {}]
      # }}
    }
    
    set connectedShares [lsort $connectedShares]
    # Make a stab at the total count
    set shareNum [llength [lsort -unique [concat $foundShares $connectedShares]]] 
    frputs connectedShares
    # We have all shares that are on line at the moment.  There may be others
    # that we can not find due to the server being off line.  Some of those may
    # be in the password locker so lets get all of that info. While there is a
    # comment section of the password we ignore that. The comments we do use
    # come frome config(cifs,shares) which we use and manage here.

    # here we get all the config(cifs,shares) info
    # From here on we will have user name to help

    foreach ent $config(cifs,shares) {
      array set tmp {-display "Ignore" -drive {} -comment {} -user {}}
      array set tmp [lrange $ent 1 end]
      # separate the user out of the first
      lassign [URLsplit x://[lindex $ent 0]] -> -> ser -> sh
      # must have a share here to be useful
      if {$sh == {}} {continue}
      set indx [lsearch -nocase -index 0 -exact  $AllShares "$ser/$sh"]
      frputs indx AllShares ser sh
		   
      lassign [expr {$indx != -1 ? \
			 [lindex $AllShares $indx] : {}}] {*}$shareList1
      set server-share $ser/$sh
       
      foreach {key rkey} {drive reqDrive display DN comment comment user user} {
	set $rkey $tmp(-$key)
      }
      set new {}
      foreach ent $shareList1 {
	lappend new [set $ent]
      }
      frputs new server-share
      set AllShares [lreplace $AllShares $indx $indx $new]
    }
    frputs AllShares'
    # Here we collect bits from the password locker
    # For now we will build entries with out trying to merge them
    #
    set pws [pwLocker::patternGet cifs://*]
    # build an array of the bits we find
    frputs pws
    # set PwList {}
    # set pwsExp {}
    foreach pwEnt $pws {
      lassign {} {*}$shareList2
      lassign $pwEnt id pw comment
      set splits [URLsplit $id]
      frputs splits
      if {$splits == {}} {
	# Oops an entry that does not follow the rules. Save and report later
	lappend ruleBender $id
	continue
      }
      lassign $splits -> user server-share
      if {[string match -nocase wsl$* ${server-share}]} {continue}
      set password [lindex $pw 0]
      # The password locker should not return share names
      # Try to verify this password

      # So we don't hang here too long, first verify that the server exists and
      # responds to a ping.
      set error "network"
      set r 1
      if {[isServerUp ${server-share}]} {
	set r [catch {::twapi::connect_share [file native //${server-share}/ipc$]\
			  -user $user\
			  -password [twapi::conceal $password] \
			} error]
	frputs r server-share user password error 
	catch {twapi::disconnect_share $error -force -updateprofile}
      }
      # 4 possibilities:
      # no error -> good pw
      # dual connect     "Multiple*"
      # bad pw           "*invalid address*"
      # server not found "*network*"
      if {$r == 0} {
	set passwordStatus "Good"
      } elseif {[string match -nocase "Multiple*" $error]} {
	set passwordStatus "??"
	frputs 
      } elseif {[string match -nocase "*invalid address*" $error]} {
	set passwordStatus "Bad"
      } elseif {[string match -nocase "*network*" $error]} {
	set passwordStatus "Off Line"
      } else {
	set passwordStatus $error
      }
      set status 10
      set new {}
      foreach ent $shareList2 {
	lappend new [set $ent]
      }
      lappend AllShares $new
      # lappend PwList [list ${server-share} $user  $password $passwordStatus]
      # lappend pwsExp [list //${server-share} $user $password]
    }
    
     # Lets display what we have and open up changes to it.
    # Get the column heads
    foreach oneTwo {1 2} {
      set headData$oneTwo {}
      foreach {t1 he t2} [set infoList$oneTwo] {
	if {$he == {}} {continue}
	lappend headData$oneTwo $he
      }
      set columnHead$oneTwo [infoLineHead [list\
				      -bg $colors(info)\
				      -borderwidth 2\
				      -relief sunken]\
				 {*}[set headData$oneTwo]]
    }
    set head         [infoLine {} "Connected Shares"]
    set headFound    [infoLine {} "Unconnected Shares Found"]
    set headNewShare [infoLine {} "Add new share here"]
    set headPw       [infoLine {} "Share password entries"]
    set headNewPw    [infoLine {} "Add new Password entry here"]
    set entryNo -1
    set rejects {}
    array unset disAry
    set connectedShares [lsort -index 0 $connectedShares] 
    # frputs connectedShares
    lassign {} dataBadPw data dataNoPw dataFound dataPw
    LogStatusOnly {} 1
    
  
    # Build the display lines
    # First the connected shares
    array unset disAry
    array unset disAryBackUp
    lassign {} data dataFound dataPw
    set entryNo -1
    set lnShareList [llength $shareList1]
    set dataPwCount 0
    lassign {} newShare newPw readOnlyExempt
    set AllShares [lsort -nocase $AllShares]
    lappend AllShares $newEntry1 $newEntry2
    foreach ent $AllShares {
      incr entryNo
 
      lassign [dataLine $entryNo $ent] dLine stat shar
      frputs stat
      switch $stat {
	1 {set data [concat $data $dLine]}
	2 {
	  set newShare $dLine
	  lappend readOnlyExempt $entryNo
	}
	11 {
	  set newPw    $dLine
	  lappend readOnlyExempt $entryNo
	}
	default {;frputs newPw stat shar
	  if {$shar != {}} {
	    # Found share but not connected
	    set dataFound [concat $dataFound $dLine]
	    continue
	  } else {
	    # Pass word entries
	    set dataPw [concat $dataPw $dLine]
	    incr dataPwCount
	  }
	}
      }
    }
 
    # The group by name:
    #          {}  Connected with good password
    #        Found Unconnected but found
    #        Pw    password entries
    
    set groups {{}  Found }
    # frputs data dataBadPw dataNoPw dataFound dataPw
    foreach post $groups {
      if {[set len$post [llength [set data$post]]] == 0} {
        set head$post {}
      }
    }
    frputs len  lenBadPw  lenNoPw  lenFound  lenPw
    # frputs head headBadPw headNoPw headFound headPw
    
     
    set feedBack [infoLine [list -textvariable ::UtilsMenu::feedback\
				-bg white -fg red -bd 3 -relief sunken] {}]
    set actButs [list \
		     [list "Explain\nthis table" [list -command ::UtilsMenu::explain]]\
		     [list "Edit"               [list -command ::UtilsMenu::edit]]\
		     [list "Connect\nshare"     [list -command ::UtilsMenu::connect]]\
		     [list "Disconect\nshare"   [list -command ::UtilsMenu::disconnect]]\
		     [list "Remove\nentry" [list -command ::UtilsMenu::removePw]]\
		     [list "Show\npassword"     [list -command ::UtilsMenu::showPw]]\
		     [list "Disconnect all shares\nand do turn on setup"\
			  [list -command ::UtilsMenu::disResart]]\
		     [list {*}[list [expr {$longPoll ?\
					    "Enumerate\nShares" : "Find\nShares"}]]\
			  [list -command ::UtilsMenu::longPoll]]\
                     [list "Refresh\ntable" [list -command {::UtilsMenu::exit 10}]]\
		     [list "Ok" [list -command {::UtilsMenu::exit -1}]]]

 
     # Set up the readonly trace code...
    array set disAryBackUp [array get disAry]
    trace add variable ::UtilsMenu::disAry write ::UtilsMenu::readOnly 
 
    LogStatusOnly "done" -1
    catch {$wt1 delete 0.0 end}
    
    set wayOut1 [smart_dialog $wt2 . "Shares $::glob(orgTitle)"  {} -1 -1\
	       [list \
		    {*}$columnHead1\
		    {*}$head\
		    {*}$data\
		    {*}$headFound\
		    {*}$dataFound\
		    {*}$headNewShare\
		    {*}$newShare]\
		    arrange [list 0 2$perLine1 3]  \
		    debug $debug ]
  
    #$wt1 window create 0.0 -window $wt2
    # Here we start dataPwCount at 1 so the 1 below is for the column heads
   
    set wayOut [smart_dialog $wt3 . "Shares $::glob(orgTitle)"  {} -1 -1\
	       [list \
		    {*}$columnHead2\
		    {*}$headPw\
 		    {*}$dataPw\
		    {*}$headNewPw\
		    {*}$newPw\
		    {*}$feedBack\
		    {*}$actButs]\
		    arrange [list 0 2$perLine2 3]  \
		     debug $debug ]
    
    
    #$wt1 window create end -window $wt3
    grid forget $label
    grid $wt2 -in $wt1 -column 0 -sticky ew
    grid $wt3 -in $wt1 -column 0 -sticky ew
    # realWaitForIdle
    update
    # wm maxsize $w\
    # 	[set reqw [expr {max ([winfo reqwidth $wt2] , [winfo reqwidth $wt3])}]]\
    # 	[set reqh [expr {[winfo reqheight $wt2] + [winfo reqheight $wt3]}]]
    
    # wm geo $w ${reqw}x$reqh
    frputs "[winfo reqheight $wt2] [winfo reqheight $wt3] "
    cursor::propagate $wt2 arrow
    cursor::propagate $wt3 arrow
    bind $wt2 <Configure> {::UtilsMenu::sizeAdjust %W}
    if {![info exist notFirstTime]} {
      incr notFirstTime
      lassign [split [wm geo .] x+] width height xloc yloc
      
      #wm geo $w ${reqw}x[expr {$height - 0}]+${xloc}+[expr {$yloc + 0}]
      centerWin $w
    } else {
      wm geo $w $notFirstTime
      update
      $wt1 yview moveto .9
    }
    wm deicon $w
    update idletasks
    sizeAdjust do
    centerWin $w .
    frputs wayOut
    vwait $wayOut
    frputs $wayOut
    if {[set $wayOut] ==  -1} {
      frputs $wayOut
      break
    }
    catch {$wt1 delete 0.0 end}
    set notFirstTime [wm geo $w]
    #destroy $wt2
    frputs  $wayOut
  }
  destroy $w
}
proc ::UtilsMenu::exit {x} {
  variable wayOut
  set $wayOut $x
}

proc ::UtilsMenu::scroll {D {xy y}} {
  variable wt1
  
  if {$D >= 0} {
    $wt1 ${xy}view scroll [expr {-$D/3}] units
  } else {
    $wt1 ${xy}view scroll [expr {(2-$D)/3}] units
  }
}

proc ::UtilsMenu::sizeAdjust {args} {
  variable wt1 ; # The text window which contains
  variable wt2 ; # this window.
  variable wt3 ; # this window.
  variable w
  variable messLoc

  # size changes come from two user actions
  # 1st from wt2 as a result of overt window size changes and
  # 2ed from entries (primarily in the Flags column)
  # we also get an initial call after wt2 is built
  if {[winfo exists $wt2] && [winfo exists $wt3]} {
     wm maxsize $w\
	[set reqw [expr {max ([winfo reqwidth $wt2] , [winfo reqwidth $wt3])}]]\
	 [set reqh [expr {[winfo reqheight $wt2] + [winfo reqheight $wt3]}]]
    if {$args == "do"} {
      wm geo $w ${reqw}x$reqh
    }
  }
  # frputs args
  set messLoc [wm geo $w]

}

proc ::UtilsMenu::readOnly {Ary elem dc} {
  variable disAry
  variable disAryBackUp
  variable readOnlyExempt
  variable readOnly
  # While we exempt the input lines we also exempt the password  quality
  # and the actual drive, but we will drop any entry there during the edit phase
  set id [string range $elem [set noLoc [string first "," $elem]]+1 end]
  set entNo [string range $elem 0 $noLoc-1]
  frputs readOnlyExempt id entNo
  if {$id in $readOnly && $entNo ni $readOnlyExempt} {
    set disAry($elem) $disAryBackUp($elem)
  }
  
}

# bind and action routines

# We highlight by using 'disable' and 'normal'. This work for the combobox
# as well... except we would really like to have the dropdown be the disabled
# color. This does not seem possible with the current commbobox code.
proc ::UtilsMenu::highlightDoit {state} {
  variable startBut
  variable windowPre
  variable curEnt
  variable startEntNo
  frputs #3 #2 windowPre startBut curEnt startEntNo
  if {![info exists windowPre] || $windowPre == {}} {return}
  set localBut $startBut
  while {1} {
    $windowPre.$localBut config -state $state
    incr localBut
    update idletasks
    if {[catch {$windowPre.$localBut cget -textvariable} pre] ||\
	    [string range $pre $startEntNo+1 [string first "," $pre]-1] != $curEnt} {
      break
    }
    # This prevents window shimmy
  }
}

proc ::UtilsMenu::highlight {what x} {
  variable last
  variable curEnt
  variable startBut
  variable startEntNo
  variable windowPre
  variable colors
  variable feedback {}
  variable time
  # We come here for most all <1> pushes
   frputs what time
 
  # Our combo button return suffix stuff which we remove here
  while {![string is integer [string range [file ext $what] 1 end]] && $what != ""} {
    set what [winfo parent $what]
  }
  # Ignore those that come from a command button
  if {![catch {$what cget -command} cmd] && $cmd !={}} {
    frputs cmd
    return
  }
  # First remove any existing selection...
  showPw 1
  highlightDoit disabled
  set windowPre {}
  frputs what
  if {[catch {$what cget -textvariable} textV] ||\
	  $textV == {} ||\
	  [set startEntNo [string first "(" $textV]] == -1} {
    frputs  "off the map "
    return
  }
  # the text variable looks like "::UtilsMenu::disAry(entryNo,item)"
  # where 'item' is one of infoList1 or 2 entries.
  # To find what we need (the window name of the first entry)
  # we just backup until we find a different 'entryNo' or no 'textvariable'
  # Like wise we can go forward in the same way.
  #
  # the start will not move...
 
  set curEnt [string range $textV $startEntNo+1 [string first "," $textV]-1]
  set windowPre [string range $what 0 [string last "." $what]-1]
  set startBut [string range $what [string last "." $what]+1 end]
  while {1} {
    set pre [$windowPre.$startBut cget -textvariable]
    if {[string range $pre $startEntNo+1 [string first "," $pre]-1] != $curEnt} {
      incr startBut
      break
    }
    incr startBut -1
  }
  highlightDoit normal
  if {[winfo exist $what.entry]} {
    while {1} {
      frputs what
      update idletasks
      ::combobox::HandleEvent $what <1>
      break
    }
  } else {
    frputs what
    tk::EntryButton1 $what $x
    $what selection clear
  }
  incr time
  frputs #3 #2 what time
  return
}

# This routine returns the currently selected server entry's number.
# If it is a password entry, it returns an empty string

  
proc ::UtilsMenu::showPw {{hide {}}} {
  variable feedback
  variable startBut
  variable windowPre
  variable curEnt
  variable startEntNo
  variable shareList2
  if {[info exist startBut] &&\
	  [info exists windowPre] && $windowPre != {} &&\
	  ![catch {$windowPre.[set passwordLoc \
				  [expr {[lsearch $shareList2 password] +\
					     $startBut}]] \
				  cget -textvariable} tvar] &&\
	  [string range $tvar [string first "," $tvar]+1 end-1] == "password"} {
    frputs windowPre passwordLoc
    if {[$windowPre.$passwordLoc cget -show] == {} || $hide != {}} {
      $windowPre.$passwordLoc config -show {*}
    } else {
      $windowPre.$passwordLoc config -show {}
    }
    return
  }
  frputs  windowPre passwordLoc tvar hide
  if {$hide == {}} {
    set feedback "ShowPw: Password line not selected."
  }
}


# change the old entry to the new one as marked
# entries that are not read only are {user reqDrive DN comment password}
# of these {user reqDrive DN comment} come from the config(cisf,shars)
# and {user password} come from the password locker. Or, in the 'Add new ...'
# every thing but {passwordStatus drive-actual}

# We scan the whole table and do each requested edit...

proc ::UtilsMenu::edit {} {
  variable last
  variable curEnt
  variable butToEnt
  variable perLine
  variable disAry
  variable disAryBackUp
  variable readOnly 
  variable tablEntries
  variable wayOut
  variable w
  variable feedback {}
  variable entryNo
  variable readOnlyExempt 
  global config

  frputs EDIT
  set no 0
  for {set curEnt 0} {$curEnt <= $entryNo} {incr curEnt} {
    set change 0
    frputs curEnt
    foreach {ent val} [array get disAryBackUp "$curEnt,*"] {
      frputs val disAry($ent)
      if {$val == $disAry($ent)} {continue}
      incr change
      break
    }
    if {!$change} {continue}
    # Something changed. Is it cifs share or password
    if {[info exists disAry($curEnt,password)]} {
      # It is a password entry  format and pass to the password locker
      set curId "cifs://"
      set usrProm {}
      append curId  [expr {$disAry($curEnt,user) == {} ? {} : \
			       "[string trim $disAry($curEnt,user)]@"}]\
	  [string trim $disAry($curEnt,server-share)]
      if {$disAry($curEnt,password) != $disAryBackUp($curEnt,password) ||\
	    $disAry($curEnt,user) != $disAryBackUp($curEnt,user)} {
      	set usrProm [list user [list {*}[string trim $disAry($curEnt,user)]]]
      }
      incr no
      set lockerRtn [::pwLocker::getPw $curId \
			 startwith $disAry($curEnt,password)\
			 returnlist 1\
			 prompt [list "If this is right press \"Ok\" else fix it first."]\
			 {*}$usrProm \
			 comment $disAry($curEnt,comment)\
			 newuser 1]
      
      if {$lockerRtn in {0 -1 -2}} {
	set feedback "Password change rejected. Terminating edit."
	toTop $w
	return
      }
      lassign $lockerRtn newId pw
      if {![string equal -nocase $curId  $newId]} {
	incr change
	set r [smart_dialog .apop[incr ::uni] . "!" \
		   [list {} "Id \"$curId\" is not \"$newId\"\nDelete $curId?"]\
		   0 2 \
		   [list [_ "Yes"] [_ "No"]]]
	if {$r == 0} {
	  if {[set entries [::pwLocker::delete $curId case 1]] != 1} {
	    # Something bad going on. We should always get 1
	    PopError "Delete of ID \"$id\" returned 'deleted $entries', should be exactly 1."
	  }
	}
      }
    } else {
      # Now take care of config(cifs,shares)
      set new [list $disAry($curEnt,server-share)/$disAry($curEnt,share)\
		   -user $disAry($curEnt,user)\
		   -drive $disAry($curEnt,reqDrive)\
		   -display $disAry($curEnt,DN) \
		   -comment $disAry($curEnt,comment)]
      # Is this new or an edit
      set indx [lsearch -nocase -exact -index 0 $config(cifs,shares)\
		    $disAry($curEnt,server-share)/$disAry($curEnt,share)]
      set config(cifs,shares) [lreplace $config(cifs,shares) $indx $indx $new]
      incr no	
    }
  }
  set $wayOut 100
  set feedback "$no change(s) made."
  return
}

proc ::UtilsMenu::longPoll {} {
  variable longPoll
  variable wayOut
  set longPoll [expr {$longPoll ^ 1}]
  set $wayOut 100
  return
}

proc ::UtilsMenu::connectThis {entry} {
  variable disAry
  # build a config(cifs,shars) entry and call the setUpShare code
  set mess [setUpShare [list $disAry($entry,server-share)/$disAry($entry,share)\
			    -drive $disAry($entry,reqDrive)\
			    -user $disAry($entry,user)\
			    -display $disAry($entry,DN)]]
  if {$mess != {}} {
    PopWarn $mess
  }
}

# This function attempts to connect the highlighted share.
# If already connected it does nothing. It ignores any edits to the
# share. If the highlighted is a Password entry, it will attempt to
# connect all shares on the given server

proc ::UtilsMenu::connect {} {
  variable entryNo
  variable curEnt
  variable disAry
  variable windowPre
  variable feedback {}
  variable wayOut
  
  if {[info exists windowPre] && $windowPre != {}} {
    # 'curEnt' is the target entry
    set no 0
    if {[info exists disAry($curEnt,password)]} {
      # doing all shares on this server
      set server $disAry($curEnt,server-share)
      set ent -1
      while {[incr ent] < $entryNo} {
	frputs server disAry($ent,server-share)
	if {![info exists disAry($ent,password)] &&\
		[string equal -nocase $server $disAry($ent,server-share)] } {
	  connectThis $ent
	  incr no
	}
      }
    } else {
      # only one
      connectThis $curEnt
      incr no
    }   
    set feedback "Attempted to connect $no shares."
    set $wayOut 100
    return
  }
  set feedback "Connect: Nothing selected."
}

proc ::UtilsMenu::disConnectThis {ent args} {
  variable disAry
  variable feedback

  if {$disAry($ent,status) != 1} {return 0}
  set wsl [string equal -nocase $disAry($ent,server-share) "wsl$"]
  if {$args != 1 && $wsl && $disAry($ent,drive) == {} } {
    append feedback \
	"Disconnect: 'WSL$' shares without drive letter can not be disconnected."
    return 0
  }
 
  set id [expr {$disAry($ent,drive) == {} ? \
                   "//$disAry($ent,server-share)/$disAry($ent,share)" :\
		    $disAry($ent,drive)}]
  if {[catch {twapi::disconnect_share [file native $id]\
		  -force -updateprofile} error] != 0} {
    append feedback \
	"Disconnect: Disconnect of '$id' failed with error '$error'"
  } else {
    delDNpat "*$id*" 
    if {$wsl} {
      # We just disconnected a wsl share with a drive letter, however, the
      # share with no drive letter is still connected. This fixes the DN for
      # that.
      set disAry($ent,reqDrive) ""
      connectThis $ent
    }
    return 1
  }
  return 0
}

proc ::UtilsMenu::disconnect {} {
  variable entryNo
  variable curEnt
  variable disAry
  variable windowPre
  variable feedback {}
  variable wayOut
  
  if {[info exists windowPre] && $windowPre != {}} {
    
    # 'curEnt' is the target entry
    set no 0
    if {[info exists disAry($curEnt,password)]} {
      # doing all shares on this server
      set server $disAry($curEnt,server-share)
      set ent -1
      while {[incr ent] < $entryNo} {
	if {![info exists disAry($ent,password)] &&\
		[string equal -nocase $server $disAry($ent,server-share)]} {
	  set no [disConnectThis $ent]
	}
      }
      append feedback "Disconnect $no shares."
    } else {
      # only one
      if {$disAry($curEnt,status) == 1} {
	incr no [disConnectThis $curEnt]
      } else {
	set feedback "Disconnect: Not connected! Abandoning disconnect call!"
      }
    }
  } else {
    set feedback "Disconnect: Nothing selected."
  }
  set $wayOut 100
  return
}
# Here we disconnect all and do the startup thing
proc ::UtilsMenu::disResart {} {
  variable connectedShares
  variable entryNo
  variable wayOut
  
  set no 0
  set ent -1
  while {[incr ent] < $entryNo} {
    incr no [disConnectThis $ent 1]
  }
  # foreach share $connectedShares {
  #   lassign $share server-share -> -> drv
  #   set id [expr {$drv != {} ? $drv : [file native ${server-share}]}]
  #   catch  {twapi::disconnect_share $id -force -updateprofile}
  # }
  setUpShare
  set $wayOut 100
}

proc ::UtilsMenu::removePw {} {
  global config
  variable curEnt
  variable disAry
  variable windowPre
  variable feedback {}
  variable wayOut
  if {[info exists windowPre] && $windowPre != {} } {
    if {[info exists disAry($curEnt,password)]} {
    
    # 'curEnt' is the target entry
    
    set target "cifs://"
    if {$disAry($curEnt,user) != ""} {
      append target "$disAry($curEnt,user)@"
    }
    append target $disAry($curEnt,server-share)
    if {[::pwLocker::delete $target case 1] != 1} {
      set feedback "Failed to remove $target"
      return
    } 
    set feedback "RemovePw: Sucess! '$target' removed."
  } else {
    # It must be a share entry which is to be removed from config(cifs,shares)
    set idx [lsearch -exact -nocase -index 0 $config(cifs,shares) \
		 $disAry($curEnt,server-share)/$disAry($curEnt,share)]
    set config(cifs,shares) [lreplace $config(cifs,shares) $idx $idx]
  }
    set $wayOut 100
    return    
  }
  set feedback "RemovePw: Password entry not selected."
}

proc ::UtilsMenu::textfun {w} {
  variable colors
  frputs w
  $w insert insert "The grid shows the status of the available shares at this time.\
 However, if a server is offline, its share(s) may not be here or its password entry\
 and config(cifs,shares) may be the only part available.\n
You may use the action buttons to edit the displayed elements, i.e the\
 'Request Drive' 'User' 'Password' and 'Display Name Flags'. Only drive letters in the\
 'Request Drive' drop down are allowed. This may mean you will need to free a drive\
 letter from another share before using it.

After each action is taken either the window is withdrawn and rebuilt or and/or an error\
 message will appear just above the action buttons.\n"
  catch {font delete tm}
  $w tag configure bold -font \
      [font create tm {*}[font actual [$w cget -font]] -weight bold]

  $w insert insert\
      "\nThe 'Disconnect share' action disconnects the share even if it is use!!\n" bold
  
  $w tag configure select -background $colors(sbg)
  $w tag configure bg -background $colors(bg)
  $w tag configure readonly -foreground $colors(hlight)
  $w tag configure head -background $colors(info)
  $w tag configure feedback -background $colors(hlightbg) -foreground $colors(hlight)
 
  $w insert insert "\nColor meanings in the below grid:\n"
  $w insert insert "This is the normal background color (Entry BG).\n" bg
  $w insert insert "This is the column head background color (Shell Cmd).\n" head
  $w insert insert "This is the read-only foreground color (Highlight FG).\n" readonly
  $w insert insert "This is the selected background color (Selection BG).\n" select
  $w insert insert "This is the feedback line coloring (Selection BG Highlight FG).\n"\
      feedback
  $w insert insert "\nThe 'Drive Request' and 'Display Name Flag' fields contain\
  info from 'config(cifs,shares)'. (This should only be changed here.) Each of these\
  contains\
  a drop down menu of possible entries. The 'Drive Request' indicates the drive letter\
  you would like to use for this share. The letters presented are those that were\
  available when the table was generated. No entry here says to connect the share\
  with out a drive letter (the share is accessed as '//system/share'). A '*' here\
  indicates that you want the system to assign a drive letter of its choosing.\

The 'Display Name Flag' values are 'None', 'Hot&Vol', 'VolOnly' 'Ignore' and ''.\
  'None' and '' indicate no 'Display Name' is desired.
  If filerunner should not generate a\
 'Display Name' for this share (the default is to generate a 'Display Name'). A share\
 is a 'volume' so it will appear in the 'volume' directory either as its 'Drive' letter\
 or its 'Display Name' (if generated) or as //system/share. The flag field allows you to include it or not in the 'Hotlist'.

Display Name Flag meanings are:
 None or \"\"  do not generate a 'Display Name'.\n\
 VolOnly       generate a 'Display Name'. \n\
 Hot&Vol       generate a 'Display Name' and also put in the 'HotList'.\n\
 Ignore        Do not set up this share.\n


Name/password convention. All shares on a given server use the same name/password.\
It is legal to have more than one name/password for a given server, however, only\
one name/password may be active (this is a 'windows' convention). The 'name' from\
the 'config(cifs,shares)' for the share is used. When a\
password is required the pssswordLocker entry for the given user/server is\
examined to get the password to use. The user name comes from config(cifs,shares).\
Entries in the password locker are for the user/server and do not include share).\

To switch to another name/password for a given\
server all\
shares on that server must be disconnected before the system will accept the new\
name/password.

The 'Password OK' column summarizes the above as follows:\n
Good     a good password was found in the shares pw-locker entry 
Bad      a bad password was found in the shares pw-locker entry
Off Line the server did not respond
??       the server is connected with a different password 

A null password IS a password and MAY work or not.

Case conventions: //SERVER/Share names are case insensitive.\
The share name case we show is determined by the case found in the various locations\
searched to find the servers and shares. All lookup and compares are case insensitive.\

(\"Shift-Mmousewheel\" scrolls horizontally  \"Mouse-Wheel\" scrolls vertically)

The buttons \"Connect share\" \"Disconnect share\" \"Remove entry\" at the bottom\
    apply to both the \"shares\" and the \"password\" sections. In the case of\
    \"Connect share\" and \"Disconnect share\" if a \"password\" section is selected,\
    all shares on the given server are connected or disconnected. \"Remove entry\"\
    removes the selected entry.\

The \"Find Shares\" button causes a polling of every address on the same subnet as this\
    computer."
}
proc ::UtilsMenu::explain {} {
  variable w  
  set r [smart_dialog .shHelp . Help\
             [list y x] -1 1 \
             [list \
                  [list Ok {-command [list destroy .shHelp]}]]\
             textfun ::UtilsMenu::textfun ]
  realWaitForIdle
  toTop $w
  frputs r
}
