海运的博客

sftpgo为ftp/webdav添加支持软链接symlink目录和文件patch

发布时间:April 5, 2022 // 分类: // No Comments

之前有写为sftpgo添加匿名访问支持,还有个问题不能访问链接文件,基于sftpgo2.2.2修改制作了个patch以支持软链接,linux下测试通过,windows未测试。

diff -urN -x .git sftpgo/common/connection.go sftpgo3/common/connection.go
--- sftpgo/common/connection.go 2022-04-04 15:06:39.542187282 +0800
+++ sftpgo3/common/connection.go        2022-04-04 14:57:31.027102136 +0800
@@ -242,6 +242,27 @@
                c.Log(logger.LevelDebug, "error listing directory: %+v", err)
                return nil, c.GetFsError(fs, err)
        }
+       if c.protocol == ProtocolWebDAV || c.protocol == ProtocolFTP {
+               for k, file := range files {
+                       if file.Mode()&os.ModeSymlink != 0 {
+                               dst, err := os.Readlink(fsPath + "/" + file.Name())
+                               if err != nil {
+                                       c.Log(logger.LevelError, "error readlink: %#v error: %+v", fsPath+"/"+file.Name(), err)
+                                       continue
+                               }
+                               dstinfo, err := os.Stat(dst)
+                               if err != nil {
+                                       c.Log(logger.LevelError, "error stat: %#v error: %+v", dst, err)
+                                       continue
+                               }
+                               if dstinfo.IsDir() {
+                                       files[k] = vfs.NewFileInfo(file.Name(), true, 0, dstinfo.ModTime(), false)
+                               } else {
+                                       files[k] = vfs.NewFileInfo(file.Name(), false, dstinfo.Size(), dstinfo.ModTime(), false)
+                               }
+                       }
+               }
+       }
        return c.User.AddVirtualDirs(files, virtualPath), nil
 }
 
diff -urN -x .git sftpgo/ftpd/handler.go sftpgo3/ftpd/handler.go
--- sftpgo/ftpd/handler.go      2022-04-04 15:06:39.550187152 +0800
+++ sftpgo3/ftpd/handler.go     2022-04-04 14:55:41.368885080 +0800
@@ -14,7 +14,7 @@
        "github.com/drakkan/sftpgo/v2/common"
        "github.com/drakkan/sftpgo/v2/dataprovider"
        "github.com/drakkan/sftpgo/v2/logger"
-       "github.com/drakkan/sftpgo/v2/util"
+       //"github.com/drakkan/sftpgo/v2/util"
        "github.com/drakkan/sftpgo/v2/vfs"
 )
 
@@ -288,10 +288,12 @@
        if err != nil {
                return files, err
        }
-       if name != "/" {
-               files = util.PrependFileInfo(files, vfs.NewFileInfo("..", true, 0, time.Now(), false))
-       }
-       files = util.PrependFileInfo(files, vfs.NewFileInfo(".", true, 0, time.Now(), false))
+       /*
+               if name != "/" {
+                       files = util.PrependFileInfo(files, vfs.NewFileInfo("..", true, 0, time.Now(), false))
+               }
+               files = util.PrependFileInfo(files, vfs.NewFileInfo(".", true, 0, time.Now(), false))
+       */
        return files, nil
 }
 
diff -urN -x .git sftpgo/vfs/osfs.go sftpgo3/vfs/osfs.go
--- sftpgo/vfs/osfs.go  2022-04-04 15:06:39.574186762 +0800
+++ sftpgo3/vfs/osfs.go 2022-04-04 14:59:06.413551648 +0800
@@ -291,6 +291,7 @@
                virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath)
        }
        r := filepath.Clean(filepath.Join(fs.rootDir, virtualPath))
+       return r, nil
        p, err := filepath.EvalSymlinks(r)
        if err != nil && !os.IsNotExist(err) {
                return "", err

1.修改vfs/osfs.go文件取消对链接目标的校验是否在家目录内,这时部分客户端已正常,因为sftpgo返回链接目录属性为l而非d,导致有的客户端将链接目录识别为文件。
2.修改common/connection.go文件是修改返回的链接目录/文件的属性为标准目录和文件。
参考:
https://github.com/drakkan/sftpgo/issues/336

sftpgo为ftp/webdav/sftp添加匿名anonymous访问

发布时间:April 5, 2022 // 分类: // No Comments

仅通过check_password_hook验证用户,用户信息使用web配置,将以下脚本保存为文件添加到sftpgo.json配置文件内check_password_hook参数。

#!/bin/bash -eu
#if [[ "${SFTPGO_AUTHD_USERNAME}" = "guest" ]] && [[ "${SFTPGO_AUTHD_PASSWORD}" = "pass" ]]; then
if [[ "${SFTPGO_AUTHD_USERNAME}" = "anonymous" ]] || [[ "${SFTPGO_AUTHD_USERNAME}" = "guest" ]]; then
cat <<EOF
{
  "status": 1
}
EOF
exit 0
fi

使用external_auth_hook外部验证用户并直接返回虚拟目录权限等信息:

#!/bin/bash -eu
if [[ "${SFTPGO_AUTHD_USERNAME}" = "anonymous" ]] || [[ "${SFTPGO_AUTHD_USERNAME}" = "guest" ]]; then
cat <<EOF
{
  "status": 1,
  "username": "${SFTPGO_AUTHD_USERNAME}",
  "home_dir": "/data/ftp",
  "permissions": {
    "/": ["list", "download"]
  },
  "virtual_folders": [
    {
      "name": "dir1",
      "mapped_path": "/data/dir1",
      "virtual_path": "/dir1"
    }, 
    {
      "name": "dir2",
      "mapped_path": "/data/dir2",
      "virtual_path": "/dir2"
    } 
  ]
}
EOF
fi

ftp/webdav匿名访问允许空密码patch:

diff -urN -x .git sftpgo2/ftpd/server.go sftpgo/ftpd/server.go
--- sftpgo2/ftpd/server.go      2022-04-05 18:55:25.959456839 +0800
+++ sftpgo/ftpd/server.go       2022-04-05 19:01:32.841334341 +0800
@@ -9,6 +9,7 @@
        "os"
        "path/filepath"
        "sync"
+        "strings"
 
        ftpserver "github.com/fclairamb/ftpserverlib"
 
@@ -182,6 +183,9 @@
 
 // AuthUser authenticates the user and selects an handling driver
 func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string) (ftpserver.ClientDriver, error) {
+       if len(strings.TrimSpace(password)) == 0 {
+               password = "guest"
+       }
        loginMethod := dataprovider.LoginMethodPassword
        if s.isTLSConnVerified(cc.ID()) {
                loginMethod = dataprovider.LoginMethodTLSCertificateAndPwd

diff -urN -x .git sftpgo2/webdavd/server.go sftpgo/webdavd/server.go
--- sftpgo2/webdavd/server.go   2022-04-05 18:55:25.995456233 +0800
+++ sftpgo/webdavd/server.go    2022-04-05 19:01:44.537140407 +0800
@@ -13,6 +13,7 @@
        "path/filepath"
        "runtime/debug"
        "time"
+        "strings"
 
        "github.com/go-chi/chi/v5/middleware"
        "github.com/rs/cors"
@@ -232,6 +233,9 @@
        var tlsCert *x509.Certificate
        loginMethod := dataprovider.LoginMethodPassword
        username, password, ok := r.BasicAuth()
+       if len(strings.TrimSpace(password)) == 0 {
+               password = "guest"
+       }
        if s.binding.isMutualTLSEnabled() && r.TLS != nil {
                if len(r.TLS.PeerCertificates) > 0 {
                        tlsCert = r.TLS.PeerCertificates[0]

参考:
https://github.com/drakkan/sftpgo/issues/373
https://github.com/drakkan/sftpgo/blob/main/docs/check-password-hook.md
https://github.com/drakkan/sftpgo/blob/main/docs/external-auth.md

使用avahi配置mdns/dns-sd网络发现samba/ftp

发布时间:April 4, 2022 // 分类: // No Comments

配置avahi使用mdns/dns-sd零配置发现samba/smb共享存储:

apt install avahi-daemon
cat <<EOF > /etc/avahi/services/smb.service
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
  <name replace-wildcards="yes">Samba</name>
  <service>
    <type>_smb._tcp</type>
    <host-name>smb.haiyun.me.local</host-name>
    <port>445</port>  
    <txt-record>path=/share</txt-record>
    <txt-record>u=guest</txt-record>
    <txt-record>p=pass</txt-record>
  </service>
</service-group>
EOF
echo '192.168.168.1 smb.haiyun.me.local' >> /etc/avahi/hosts 

ftp服务器:

cat <<EOF > /etc/avahi/services/ftp.service
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
  <name replace-wildcards="yes">FTP</name>
  <service>
    <type>_ftp._tcp</type>
    <host-name>ftp.haiyun.me.local</host-name>
    <port>21</port>  
    <txt-record>path=/share</txt-record>
    <txt-record>u=guest</txt-record>
    <txt-record>p=pass</txt-record>
  </service>
</service-group>
EOF
echo '192.168.168.2 ftp.haiyun.me.local' >> /etc/avahi/hosts 

注意:
1.host-name可使用FQDN标准域名,但是smb有的客户端不识别。
2.avahi默认会为hosts内目标ip添加反查域名,所以当在/etc/avahi/hosts内添加多个域名指向同一ip时会出错:

Static host name smb.haiyun.me.local: avahi_server_add_address failure: Local name collision

可以使用avahi-publish手工发布域名且不添加ip反查域名:

avahi-publish -a -R smb.haiyun.me.local 168.168.168.1

也可以修改systemd service让avahi启动时自动启动avahi-publish:

apt install avahi-utils
cat <<EOF > /usr/local/bin/avahi-host.sh
#!/bin/bash
/usr/bin/avahi-publish -a -R smb.haiyun.me.local 168.168.168.1 > /dev/null 2>&1 &
EOF
chmod +x /usr/local/bin/avahi-host.sh
sed -i '/ExecStart/aExecStartPost=\/usr\/local\/bin\/avahi-host\.sh' /lib/systemd/system/avahi-daemon.service 
systemctl daemon-reload 
systemctl restart avahi-daemon.service 
#手工修改添加ExecStartPost=/usr/local/bin/avahi-host.sh
#systemctl edit --full avahi-daemon.service 

使用dig测试mdns/dns-sd:

dig -p 5353 @224.0.0.251 ftp.haiyun.me.local
dig -p 5353 @224.0.0.251 _smb._tcp.local ptr
dns-sd -B _services._dns-sd._udp
dns-sd -B _smb._tcp
dns-sd -Z _smb._tcp
dns-sd -Q smb.haiyun.me.local
dns-sd -Q smb.haiyun.me.local aaaa

当同一ip有多个服务时也可使用基于golang dnssd的mdns/dns-sd服务:

package main

import (
        "context"
        "encoding/json"
        "fmt"
        "github.com/brutella/dnssd"
        "net"
        "os"
        "os/signal"
        "time"
)

type service struct {
        Name   string
        Type   string
        Domain string
        Host   string
        Ips    []string
        Port   int
        Text   map[string]interface{}
}

type config struct {
        Service []service
}

func main() {
        cfg_str, err := os.ReadFile("config.json")
        if err != nil {
                fmt.Println("load config file error:", err)
                os.Exit(1)
        }
        var cfg config
        err = json.Unmarshal(cfg_str, &cfg)
        if err != nil {
                fmt.Println("decode json err:", err)
                os.Exit(1)
        }

        if resp, err := dnssd.NewResponder(); err != nil {
                fmt.Println(err)
        } else {
                for _, v := range cfg.Service {
                        var ips []net.IP
                        for _, ip := range v.Ips {
                                ips = append(ips, net.ParseIP(ip))
                        }
                        text := make(map[string]string)
                        for key, value := range v.Text {
                                text[key] = fmt.Sprintf("%v", value)
                        }
                        sd_cfg := dnssd.Config{
                                Name:   v.Name,
                                Type:   v.Type,
                                Domain: v.Domain,
                                Host:   v.Host,
                                IPs:    ips,
                                Port:   v.Port,
                                Text:   text,
                        }

                        srv, err := dnssd.NewService(sd_cfg)
                        if err != nil {
                                fmt.Println(err)
                        }
                        go func() {
                                time.Sleep(1 * time.Second)
                                handle, err := resp.Add(srv)
                                if err != nil {
                                        fmt.Println(err)
                                } else {
                                        fmt.Printf("%s  Got a reply for service %s: Name now registered and active\n", time.Now().Format("2006-01-02 15:04:05"), handle.Service().ServiceInstanceName())
                                }
                        }()
                }

                ctx, cancel := context.WithCancel(context.Background())
                defer cancel()
                go func() {
                        stop := make(chan os.Signal, 1)
                        signal.Notify(stop, os.Interrupt)

                        select {
                        case <-stop:
                                cancel()
                        }
                }()

                err = resp.Respond(ctx)
                if err != nil {
                        fmt.Println(err)
                }
        }
}

配置文件保存到同目录config.json:

{
  "service":[
    {
      "name":"Samba",
      "type":"_smb._tcp",
      "domain":"local",
      "host":"smb.haiyun.me",
      "ips":[
        "10.0.0.1",
        "fdb2:808c:d0ef:0:20c:29ff:fe7c:264d"
      ],
      "port":445,
      "text":{
        "u":"user",
        "p":"pass",
        "path":"/share"
      }
    },
    {
      "name":"Ftp",
      "type":"_ftp._tcp",
      "domain":"local",
      "host":"ftp.haiyun.me",
      "ips":[
        "10.0.0.1",
        "fdb2:808c:d0ef:0:20c:29ff:fe7c:264d"
      ],
      "port":21,
      "text":{
        "u":"user",
        "p":"pass",
        "path":"/share"
      }
    }
  ]
}

更多服务类型及参数见:
http://www.dns-sd.org/ServiceTypes.html
本地多个不同网段mdns中继:
https://www.haiyun.me/archives/1439.html
参考:
https://github.com/brutella/dnssd
https://kodi.wiki/view/Avahi_Zeroconf
https://linux.die.net/man/5/avahi.service
https://github.com/lathiat/avahi/issues/40
https://pi3g.com/2019/04/10/avahi-how-to-assign-several-local-names-to-same-ip/

OpenWrt/PandoraBox多网段转发udp广播包

发布时间:April 1, 2022 // 分类: // No Comments

之前有写不同网段间转发mdns消息,对于依赖udp广播的程序可通过iptables tee镜像流量转发广播到不同网段。
安装tee模块:

opkg install iptables-mod-tee
#将10.0.0.0网段udp广播目标端口9687转发到10.0.1.0网段
iptables -t mangle -I INPUT -i br-lan -d 255.255.255.255 -p udp --dport 9687 -m ttl --ttl-gt 0 -j TTL --ttl-set 1
iptables -t mangle -I INPUT -i br-lan -d 255.255.255.255 -p udp --dport 9687 -m ttl --ttl-gt 0 -j TEE --gateway 10.0.1.255

#反向
iptables -t mangle -I INPUT -i br-robot -d 255.255.255.255 -p udp --dport 9687 -m ttl --ttl-gt 0 -j TTL --ttl-set 1
iptables -t mangle -I INPUT -i br-robot -d 255.255.255.255 -p udp --dport 9687 -m ttl --ttl-gt 0 -j TEE --gateway 10.0.0.255

iptables放行9687数据包:

iptables -A FORWARD -i br-robot -o br-lan -p udp --dport 9687 -j ACCEPT

不支持iptables tee模块可使用独立的udp广播转发程序:
https://github.com/nomeata/udp-broadcast-relay
参考:
https://odi.ch/weblog/posting.php?posting=731

分类
最新文章
最近回复
  • 海运: ups不知有没选项可设置此参数,不过你可以在另外一台电脑上安装nut客户端自动关机。
  • kgami: 想请教一下,设置了的电脑自动关机之后,几秒后UPS怎么也跟着关机了,导致另外一台电脑没关机就断...
  • 海运: 写的很详细了啊,/etc/nut/hosts.conf用以nut-cgi连接nut服务器参数,...
  • ryan: 请问下nginx配置好了,怎么和这个nut链接呢?最后可视化管理这块能给个详细一点的教程么?谢谢。
  • 1: /etc/config/fstab配置文件 https://openwrt.org/zh/do...
  • 听雨看雪: 找了好久,终于找到UP主,给出的正确解决方案,太感谢了,困扰大半年的问题,重装了N道PVE系统...
  • zr: 大佬,这个bash-completion是从哪个源搞到的
  • 姚生: 要要下载
  • 阿东: 我在编译树莓派的时候也遇到同样的问题,后来发现是make menuconfig 的时候忘了带环...
  • crowjin: 你确定这能过滤??不是所有请求都返回空地址::?