mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +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+
|
tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+
|
||||||
LW tailscale.com/util/endian from tailscale.com/net/netns+
|
LW tailscale.com/util/endian from tailscale.com/net/netns+
|
||||||
L tailscale.com/util/lineread from tailscale.com/control/controlclient+
|
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/pidowner from tailscale.com/ipn/ipnserver
|
||||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||||
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"golang.org/x/sys/windows/svc/mgr"
|
"golang.org/x/sys/windows/svc/mgr"
|
||||||
"tailscale.com/logtail/backoff"
|
"tailscale.com/logtail/backoff"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
|
"tailscale.com/util/osshare"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -79,6 +80,9 @@ func installSystemDaemonWindows(args []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func uninstallSystemDaemonWindows(args []string) (ret 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()
|
m, err := mgr.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to connect to Windows service manager: %v", err)
|
return fmt.Errorf("failed to connect to Windows service manager: %v", err)
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"tailscale.com/types/flagtype"
|
"tailscale.com/types/flagtype"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
|
"tailscale.com/util/osshare"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
"tailscale.com/wgengine"
|
"tailscale.com/wgengine"
|
||||||
@ -160,7 +161,12 @@ func main() {
|
|||||||
log.Fatalf("--socket is required")
|
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
|
// No need to log; the func already did
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"tailscale.com/types/persist"
|
"tailscale.com/types/persist"
|
||||||
"tailscale.com/types/wgkey"
|
"tailscale.com/types/wgkey"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
|
"tailscale.com/util/osshare"
|
||||||
"tailscale.com/util/systemd"
|
"tailscale.com/util/systemd"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/wgengine"
|
"tailscale.com/wgengine"
|
||||||
@ -105,6 +106,7 @@ type LocalBackend struct {
|
|||||||
inServerMode bool
|
inServerMode bool
|
||||||
machinePrivKey wgkey.Private
|
machinePrivKey wgkey.Private
|
||||||
state ipn.State
|
state ipn.State
|
||||||
|
capFileSharing bool // whether netMap contains the file sharing capability
|
||||||
// hostinfo is mutated in-place while mu is held.
|
// hostinfo is mutated in-place while mu is held.
|
||||||
hostinfo *tailcfg.Hostinfo
|
hostinfo *tailcfg.Hostinfo
|
||||||
// netMap is not mutated in-place once set.
|
// 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")
|
panic("ipn.NewLocalBackend: wgengine must not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osshare.SetFileSharingEnabled(false, logf)
|
||||||
|
|
||||||
// Default filter blocks everything and logs nothing, until Start() is called.
|
// Default filter blocks everything and logs nothing, until Start() is called.
|
||||||
e.SetFilter(filter.NewAllowNone(logf, &netaddr.IPSet{}))
|
e.SetFilter(filter.NewAllowNone(logf, &netaddr.IPSet{}))
|
||||||
|
|
||||||
@ -2256,6 +2260,17 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) {
|
|||||||
cc.SetNetInfo(ni)
|
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) {
|
func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||||
var login string
|
var login string
|
||||||
if nm != nil {
|
if nm != nil {
|
||||||
@ -2270,6 +2285,13 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
|||||||
b.activeLogin = login
|
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 {
|
if nm == nil {
|
||||||
b.nodeByAddr = nil
|
b.nodeByAddr = nil
|
||||||
return
|
return
|
||||||
@ -2378,20 +2400,7 @@ func (b *LocalBackend) OpenFile(name string) (rc io.ReadCloser, size int64, err
|
|||||||
func (b *LocalBackend) hasCapFileSharing() bool {
|
func (b *LocalBackend) hasCapFileSharing() bool {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
return b.hasCapFileSharingLocked()
|
return b.capFileSharing
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileTargets lists nodes that the current node can send files to.
|
// 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()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
if !b.hasCapFileSharingLocked() {
|
if !b.capFileSharing {
|
||||||
return nil, errors.New("file sharing not enabled by Tailscale admin")
|
return nil, errors.New("file sharing not enabled by Tailscale admin")
|
||||||
}
|
}
|
||||||
nm := b.netMap
|
nm := b.netMap
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/netmap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type peerAPITestEnv struct {
|
type peerAPITestEnv struct {
|
||||||
@ -391,18 +390,10 @@ func TestHandlePeerAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var caps []string
|
|
||||||
if tt.capSharing {
|
|
||||||
caps = append(caps, tailcfg.CapabilityFileSharing)
|
|
||||||
}
|
|
||||||
var e peerAPITestEnv
|
var e peerAPITestEnv
|
||||||
lb := &LocalBackend{
|
lb := &LocalBackend{
|
||||||
netMap: &netmap.NetworkMap{
|
|
||||||
SelfNode: &tailcfg.Node{
|
|
||||||
Capabilities: caps,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logf: e.logf,
|
logf: e.logf,
|
||||||
|
capFileSharing: tt.capSharing,
|
||||||
}
|
}
|
||||||
e.ph = &peerAPIHandler{
|
e.ph = &peerAPIHandler{
|
||||||
isSelf: tt.isSelf,
|
isSelf: tt.isSelf,
|
||||||
@ -447,11 +438,7 @@ func TestFileDeleteRace(t *testing.T) {
|
|||||||
ps := &peerAPIServer{
|
ps := &peerAPIServer{
|
||||||
b: &LocalBackend{
|
b: &LocalBackend{
|
||||||
logf: t.Logf,
|
logf: t.Logf,
|
||||||
netMap: &netmap.NetworkMap{
|
capFileSharing: true,
|
||||||
SelfNode: &tailcfg.Node{
|
|
||||||
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
rootDir: dir,
|
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