From d17849461cf82f52e8c2d13d958c7a871d8b3714 Mon Sep 17 00:00:00 2001
From: Denton Gentry <dgentry@tailscale.com>
Date: Sun, 17 Jul 2022 10:05:36 -0700
Subject: [PATCH] ipn/{ipnserver,ipnlocal}: support incoming Taildrop on QNAP

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
---
 ipn/ipnlocal/local.go   |  8 ++++----
 ipn/ipnserver/server.go | 26 +++++++++++++++++++++++++-
 2 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 10cc0cf8e..008f121e4 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -174,11 +174,11 @@ type LocalBackend struct {
 	// same as the Network Extension lifetime and we can thus avoid
 	// double-copying files by writing them to the right location
 	// immediately.
-	// It's also used on Synology & TrueNAS, but in that case DoFinalRename
-	// is also set true, which moves the *.partial file to its final
-	// name on completion.
+	// It's also used on several NAS platforms (Synology, TrueNAS, etc)
+	// but in that case DoFinalRename is also set true, which moves the
+	// *.partial file to its final name on completion.
 	directFileRoot          string
-	directFileDoFinalRename bool // false on macOS, true on Synology & TrueNAS
+	directFileDoFinalRename bool // false on macOS, true on several NAS platforms
 
 	// statusLock must be held before calling statusChanged.Wait() or
 	// statusChanged.Broadcast().
diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go
index cd4094fef..a430f750f 100644
--- a/ipn/ipnserver/server.go
+++ b/ipn/ipnserver/server.go
@@ -772,7 +772,7 @@ func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engi
 
 	dg := distro.Get()
 	switch dg {
-	case distro.Synology, distro.TrueNAS:
+	case distro.Synology, distro.TrueNAS, distro.QNAP:
 		// See if they have a "Taildrop" share.
 		// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
 		path, err := findTaildropDir(dg)
@@ -1123,6 +1123,8 @@ func findTaildropDir(dg distro.Distro) (string, error) {
 		return findSynologyTaildropDir(name)
 	case distro.TrueNAS:
 		return findTrueNASTaildropDir(name)
+	case distro.QNAP:
+		return findQnapTaildropDir(name)
 	}
 	return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg)
 }
@@ -1163,6 +1165,28 @@ func findTrueNASTaildropDir(name string) (dir string, err error) {
 	return "", fmt.Errorf("shared folder %q not found", name)
 }
 
+// findQnapTaildropDir checks if a Shared Folder named "Taildrop" exists.
+func findQnapTaildropDir(name string) (string, error) {
+	dir := fmt.Sprintf("/share/%s", name)
+        fi, err := os.Stat(dir)
+	if err != nil {
+		return "", fmt.Errorf("shared folder %q not found", name)
+	}
+	if fi.IsDir() {
+		return dir, nil
+	}
+
+	// share/Taildrop is usually a symlink to CACHEDEV1_DATA/Taildrop/ or some such.
+	fullpath, err := filepath.EvalSymlinks(dir)
+	if err != nil {
+		return "", fmt.Errorf("symlink to shared folder %q not found", name)
+	}
+        if fi, err = os.Stat(fullpath); err == nil && fi.IsDir() {
+		return dir, nil // return the symlink, how QNAP set it up
+	}
+	return "", fmt.Errorf("shared folder %q not found", name)
+}
+
 func loadExtraEnv() (env []string, err error) {
 	if runtime.GOOS != "windows" {
 		return nil, nil