mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
ipn/ipnlocal: add file sharing to windows shell
Updates: tailscale/winmin#33 Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
This commit is contained in:
parent
e41075dd4a
commit
7c985e4944
@ -134,6 +134,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+
|
||||
LW tailscale.com/util/endian from tailscale.com/net/netns+
|
||||
L tailscale.com/util/lineread from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
|
||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
||||
|
@ -16,6 +16,7 @@
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/osshare"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -79,6 +80,9 @@ func installSystemDaemonWindows(args []string) (err error) {
|
||||
}
|
||||
|
||||
func uninstallSystemDaemonWindows(args []string) (ret error) {
|
||||
// Remove file sharing from Windows shell (noop in non-windows)
|
||||
osshare.SetFileSharingEnabled(false, logger.Discard)
|
||||
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to Windows service manager: %v", err)
|
||||
|
@ -40,6 +40,7 @@
|
||||
"tailscale.com/types/flagtype"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/util/osshare"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
@ -160,7 +161,12 @@ func main() {
|
||||
log.Fatalf("--socket is required")
|
||||
}
|
||||
|
||||
if err := run(); err != nil {
|
||||
err := run()
|
||||
|
||||
// Remove file sharing from Windows shell (noop in non-windows)
|
||||
osshare.SetFileSharingEnabled(false, logger.Discard)
|
||||
|
||||
if err != nil {
|
||||
// No need to log; the func already did
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -46,6 +46,7 @@
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/wgkey"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/osshare"
|
||||
"tailscale.com/util/systemd"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine"
|
||||
@ -105,6 +106,7 @@ type LocalBackend struct {
|
||||
inServerMode bool
|
||||
machinePrivKey wgkey.Private
|
||||
state ipn.State
|
||||
capFileSharing bool // whether netMap contains the file sharing capability
|
||||
// hostinfo is mutated in-place while mu is held.
|
||||
hostinfo *tailcfg.Hostinfo
|
||||
// netMap is not mutated in-place once set.
|
||||
@ -145,6 +147,8 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
|
||||
panic("ipn.NewLocalBackend: wgengine must not be nil")
|
||||
}
|
||||
|
||||
osshare.SetFileSharingEnabled(false, logf)
|
||||
|
||||
// Default filter blocks everything and logs nothing, until Start() is called.
|
||||
e.SetFilter(filter.NewAllowNone(logf, &netaddr.IPSet{}))
|
||||
|
||||
@ -2256,6 +2260,17 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) {
|
||||
cc.SetNetInfo(ni)
|
||||
}
|
||||
|
||||
func hasCapability(nm *netmap.NetworkMap, cap string) bool {
|
||||
if nm != nil && nm.SelfNode != nil {
|
||||
for _, c := range nm.SelfNode.Capabilities {
|
||||
if c == cap {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
var login string
|
||||
if nm != nil {
|
||||
@ -2270,6 +2285,13 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
b.activeLogin = login
|
||||
}
|
||||
|
||||
// Determine if file sharing is enabled
|
||||
fs := hasCapability(nm, tailcfg.CapabilityFileSharing)
|
||||
if fs != b.capFileSharing {
|
||||
osshare.SetFileSharingEnabled(fs, b.logf)
|
||||
}
|
||||
b.capFileSharing = fs
|
||||
|
||||
if nm == nil {
|
||||
b.nodeByAddr = nil
|
||||
return
|
||||
@ -2378,20 +2400,7 @@ func (b *LocalBackend) OpenFile(name string) (rc io.ReadCloser, size int64, err
|
||||
func (b *LocalBackend) hasCapFileSharing() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.hasCapFileSharingLocked()
|
||||
}
|
||||
|
||||
func (b *LocalBackend) hasCapFileSharingLocked() bool {
|
||||
nm := b.netMap
|
||||
if nm == nil || nm.SelfNode == nil {
|
||||
return false
|
||||
}
|
||||
for _, c := range nm.SelfNode.Capabilities {
|
||||
if c == tailcfg.CapabilityFileSharing {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return b.capFileSharing
|
||||
}
|
||||
|
||||
// FileTargets lists nodes that the current node can send files to.
|
||||
@ -2400,7 +2409,7 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if !b.hasCapFileSharingLocked() {
|
||||
if !b.capFileSharing {
|
||||
return nil, errors.New("file sharing not enabled by Tailscale admin")
|
||||
}
|
||||
nm := b.netMap
|
||||
|
@ -19,7 +19,6 @@
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/netmap"
|
||||
)
|
||||
|
||||
type peerAPITestEnv struct {
|
||||
@ -391,18 +390,10 @@ func TestHandlePeerAPI(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var caps []string
|
||||
if tt.capSharing {
|
||||
caps = append(caps, tailcfg.CapabilityFileSharing)
|
||||
}
|
||||
var e peerAPITestEnv
|
||||
lb := &LocalBackend{
|
||||
netMap: &netmap.NetworkMap{
|
||||
SelfNode: &tailcfg.Node{
|
||||
Capabilities: caps,
|
||||
},
|
||||
},
|
||||
logf: e.logf,
|
||||
capFileSharing: tt.capSharing,
|
||||
}
|
||||
e.ph = &peerAPIHandler{
|
||||
isSelf: tt.isSelf,
|
||||
@ -447,11 +438,7 @@ func TestFileDeleteRace(t *testing.T) {
|
||||
ps := &peerAPIServer{
|
||||
b: &LocalBackend{
|
||||
logf: t.Logf,
|
||||
netMap: &netmap.NetworkMap{
|
||||
SelfNode: &tailcfg.Node{
|
||||
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
||||
},
|
||||
},
|
||||
capFileSharing: true,
|
||||
},
|
||||
rootDir: dir,
|
||||
}
|
||||
|
13
util/osshare/filesharingstatus_noop.go
Normal file
13
util/osshare/filesharingstatus_noop.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package osshare
|
||||
|
||||
import (
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func SetFileSharingEnabled(enabled bool, logf logger.Logf) {}
|
107
util/osshare/filesharingstatus_windows.go
Normal file
107
util/osshare/filesharingstatus_windows.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package osshare
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
sendFileShellKey = `*\shell\tailscale`
|
||||
)
|
||||
|
||||
var ipnExePath struct {
|
||||
sync.Mutex
|
||||
cache string // absolute path of tailscale-ipn.exe, populated lazily on first use
|
||||
}
|
||||
|
||||
func getIpnExePath(logf logger.Logf) string {
|
||||
ipnExePath.Lock()
|
||||
defer ipnExePath.Unlock()
|
||||
|
||||
if ipnExePath.cache != "" {
|
||||
return ipnExePath.cache
|
||||
}
|
||||
|
||||
// Find the absolute path of tailscale-ipn.exe assuming that it's in the same
|
||||
// directory as this executable (tailscaled.exe).
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
logf("os.Executable error: %v", err)
|
||||
return ""
|
||||
}
|
||||
if p, err = filepath.EvalSymlinks(p); err != nil {
|
||||
logf("filepath.EvalSymlinks error: %v", err)
|
||||
return ""
|
||||
}
|
||||
p = filepath.Join(filepath.Dir(p), "tailscale-ipn.exe")
|
||||
if p, err = filepath.Abs(p); err != nil {
|
||||
logf("filepath.Abs error: %v", err)
|
||||
return ""
|
||||
}
|
||||
ipnExePath.cache = p
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// SetFileSharingEnabled adds/removes "Send with Tailscale" from the Windows shell menu.
|
||||
func SetFileSharingEnabled(enabled bool, logf logger.Logf) {
|
||||
logf = logger.WithPrefix(logf, fmt.Sprintf("SetFileSharingEnabled(%v) error: ", enabled))
|
||||
if enabled {
|
||||
enableFileSharing(logf)
|
||||
} else {
|
||||
disableFileSharing(logf)
|
||||
}
|
||||
}
|
||||
|
||||
func enableFileSharing(logf logger.Logf) {
|
||||
path := getIpnExePath(logf)
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
|
||||
k, _, err := registry.CreateKey(registry.CLASSES_ROOT, sendFileShellKey, registry.WRITE)
|
||||
if err != nil {
|
||||
logf("failed to create HKEY_CLASSES_ROOT\\%s reg key: %v", sendFileShellKey, err)
|
||||
return
|
||||
}
|
||||
defer k.Close()
|
||||
if err := k.SetStringValue("", "Send with Tailscale..."); err != nil {
|
||||
logf("k.SetStringValue error: %v", err)
|
||||
return
|
||||
}
|
||||
if err := k.SetStringValue("Icon", path+",0"); err != nil {
|
||||
logf("k.SetStringValue error: %v", err)
|
||||
return
|
||||
}
|
||||
c, _, err := registry.CreateKey(k, "command", registry.WRITE)
|
||||
if err != nil {
|
||||
logf("failed to create HKEY_CLASSES_ROOT\\%s\\command reg key: %v", sendFileShellKey, err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
if err := c.SetStringValue("", "\""+path+"\" /push \"%1\""); err != nil {
|
||||
logf("c.SetStringValue error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func disableFileSharing(logf logger.Logf) {
|
||||
if err := registry.DeleteKey(registry.CLASSES_ROOT, sendFileShellKey+"\\command"); err != nil &&
|
||||
err != registry.ErrNotExist {
|
||||
logf("registry.DeleteKey error: %v\n", err)
|
||||
return
|
||||
}
|
||||
if err := registry.DeleteKey(registry.CLASSES_ROOT, sendFileShellKey); err != nil && err != registry.ErrNotExist {
|
||||
logf("registry.DeleteKey error: %v\n", err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user