Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package goshs for openSUSE:Factory checked in at 2026-05-28 17:28:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/goshs (Old) and /work/SRC/openSUSE:Factory/.goshs.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "goshs" Thu May 28 17:28:35 2026 rev:9 rq:1355542 version:2.0.9 Changes: -------- --- /work/SRC/openSUSE:Factory/goshs/goshs.changes 2026-05-13 20:59:32.329637344 +0200 +++ /work/SRC/openSUSE:Factory/.goshs.new.1937/goshs.changes 2026-05-28 17:29:26.201921844 +0200 @@ -1,0 +2,9 @@ +Wed May 27 18:56:18 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 2.0.9 + * Add FTP/SFTP. + * Fix insecure auth options in FTP Server. + * Fix timing attack surface on ftp password comparison. + * Fixed entropy on cert generation in Cert Serial Number. + +------------------------------------------------------------------- Old: ---- goshs-2.0.8.tar.gz New: ---- goshs-2.0.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ goshs.spec ++++++ --- /var/tmp/diff_new_pack.tQdOhv/_old 2026-05-28 17:29:27.077958107 +0200 +++ /var/tmp/diff_new_pack.tQdOhv/_new 2026-05-28 17:29:27.081958273 +0200 @@ -16,7 +16,7 @@ # Name: goshs -Version: 2.0.8 +Version: 2.0.9 Release: 0 Summary: A simple HTTP server License: MIT ++++++ goshs-2.0.8.tar.gz -> goshs-2.0.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/.github/workflows/codecov.yml new/goshs-2.0.9/.github/workflows/codecov.yml --- old/goshs-2.0.8/.github/workflows/codecov.yml 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/.github/workflows/codecov.yml 2026-05-27 15:49:51.000000000 +0200 @@ -10,7 +10,7 @@ steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/[email protected] with: fetch-depth: 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/.github/workflows/codeql-analysis.yml new/goshs-2.0.9/.github/workflows/codeql-analysis.yml --- old/goshs-2.0.8/.github/workflows/codeql-analysis.yml 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/.github/workflows/codeql-analysis.yml 2026-05-27 15:49:51.000000000 +0200 @@ -44,7 +44,7 @@ # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v3 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -55,7 +55,7 @@ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v3 + uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 # âšī¸ Command-line programs to run using the OS shell. # đ https://git.io/JvXDl @@ -69,4 +69,4 @@ # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v3 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/.github/workflows/release.yml new/goshs-2.0.9/.github/workflows/release.yml --- old/goshs-2.0.8/.github/workflows/release.yml 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/.github/workflows/release.yml 2026-05-27 15:49:51.000000000 +0200 @@ -29,7 +29,7 @@ choco --version - name: Run GoReleaser - uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1 + uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2 with: distribution: goreleaser version: latest @@ -43,13 +43,15 @@ needs: goreleaser runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') + continue-on-error: true steps: - name: Trigger COPR build env: COPR_LOGIN: ${{ secrets.COPR_LOGIN }} COPR_TOKEN: ${{ secrets.COPR_TOKEN }} run: | - curl -sf -X POST \ + curl -sf --retry 5 --retry-delay 10 --retry-all-errors \ + -X POST \ --user "${COPR_LOGIN}:${COPR_TOKEN}" \ -F "ownername=patrickhener" \ -F "projectname=goshs" \ @@ -58,4 +60,4 @@ -F "committish=${{ github.ref_name }}" \ -F "spec=packaging/rpm/goshs.spec" \ -F "srpm_build_method=rpkg" \ - "https://copr.fedorainfrastructure.org/api_3/build/create/scm" + "https://copr.fedorainfracloud.org/api_3/build/create/scm" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/.github/workflows/snap.yml new/goshs-2.0.9/.github/workflows/snap.yml --- old/goshs-2.0.8/.github/workflows/snap.yml 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/.github/workflows/snap.yml 2026-05-27 15:49:51.000000000 +0200 @@ -20,7 +20,7 @@ steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 @@ -29,7 +29,7 @@ id: build - name: Upload snap artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: snap-${{ matrix.arch }} path: ${{ steps.build.outputs.snap }} @@ -40,7 +40,7 @@ steps: - name: Download snap artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: snap-* merge-multiple: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/README.md new/goshs-2.0.9/README.md --- old/goshs-2.0.8/README.md 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/README.md 2026-05-27 15:49:51.000000000 +0200 @@ -1,4 +1,4 @@ - + [](https://github.com/patrickhener/goshs/blob/master/LICENSE)  [](https://github.com/patrickhener/goshs/issues) @@ -11,7 +11,7 @@ You're mid-engagement. You need to transfer a file, catch an SMB hash, or stand up a quick HTTPS server â and `python3 -m http.server` won't cut it. -**goshs** is a single-binary file server built for the moments when you need more than Python's SimpleHTTPServer but don't want to configure Apache. HTTP/S, WebDAV, SFTP, SMB, LDAP/S, basic auth, share links, DNS/SMTP callbacks, NTLM hash capture + cracking â all from one command. +**goshs** is a single-binary file server built for the moments when you need more than Python's SimpleHTTPServer but don't want to configure Apache. HTTP/S, WebDAV, FTP/SFTP, SMB, LDAP/S, basic auth, share links, DNS/SMTP callbacks, NTLM hash capture + cracking â all from one command.  @@ -53,7 +53,7 @@ | | | |---|---| | đ **File Operations** | Download, upload (drag & drop, POST/PUT), delete, bulk ZIP, QR codes | -| đ **Protocols** | HTTP/S, WebDAV, SFTP, SMB, LDAP/S | +| đ **Protocols** | HTTP/S, WebDAV, FTP/SFTP, SMB, LDAP/S | | đ **Auth & Security** | Basic auth, certificate auth, TLS (self-signed, Let's Encrypt, custom cert), IP whitelist, file-based ACLs | | âī¸ **Server Modes** | Read-only, upload-only, no-delete, silent, invisible, CLI command execution | | đ **Share Links** | Token-based sharing, download limit, time limit | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/ca/ca.go new/goshs-2.0.9/ca/ca.go --- old/goshs-2.0.8/ca/ca.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/ca/ca.go 2026-05-27 15:49:51.000000000 +0200 @@ -27,7 +27,7 @@ ) func randomSerial() (big.Int, error) { - n, err := rand.Int(rand.Reader, big.NewInt(1000)) + n, err := rand.Int(rand.Reader, big.NewInt(1).Lsh(big.NewInt(1), 128)) if err != nil { return *big.NewInt(0), err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/config/config.go new/goshs-2.0.9/config/config.go --- old/goshs-2.0.8/config/config.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/config/config.go 2026-05-27 15:49:51.000000000 +0200 @@ -59,10 +59,6 @@ WebhookURL string `json:"webhook_url"` WebhookProvider string `json:"webhook_provider"` WebhookEvents []string `json:"webhook_events"` - SFTP bool `json:"sftp"` - SFTPPort int `json:"sftp_port"` - SFTPKeyFile string `json:"sftp_keyfile"` - SFTPHostKeyFile string `json:"sftp_host_keyfile"` Whitelist string `json:"whitelist"` TrustedProxies string `json:"trusted_proxies"` Tunnel bool `json:"tunnel"` @@ -84,6 +80,11 @@ LDAPJNDIEnabled bool `json:"ldap_jndi"` LDAPJNDIBase string `json:"ldap_jndi_base"` LDAPWordlist string `json:"ldap_wordlist"` + FTP bool `json:"ftp"` + FTPPort int `json:"ftp_port"` + FTPSFTPMode bool `json:"ftp_sftp_mode"` + FTPKeyFile string `json:"ftp_keyfile"` + FTPHostKeyFile string `json:"ftp_host_keyfile"` } func LoadConfig(opts *options.Options) (*options.Options, error) { @@ -140,10 +141,6 @@ opts.WebhookURL = cfg.WebhookURL opts.WebhookProvider = cfg.WebhookProvider opts.WebhookEventsParsed = cfg.WebhookEvents - opts.SFTP = cfg.SFTP - opts.SFTPPort = cfg.SFTPPort - opts.SFTPKeyFile = cfg.SFTPKeyFile - opts.SFTPHostKeyFile = cfg.SFTPHostKeyFile opts.Whitelist = cfg.Whitelist opts.TrustedProxies = cfg.TrustedProxies opts.Invisible = cfg.Invisible @@ -165,6 +162,11 @@ opts.LDAPPort = cfg.LDAPPort opts.LDAPJNDIEnabled = cfg.LDAPJNDIEnabled opts.LDAPJNDIBase = cfg.LDAPJNDIBase + opts.FTP = cfg.FTP + opts.FTPPort = cfg.FTPPort + opts.FTPSFTPMode = cfg.FTPSFTPMode + opts.FTPKeyFile = cfg.FTPKeyFile + opts.FTPHostKeyFile = cfg.FTPHostKeyFile // Default upload folder to webroot if not set in config if opts.UploadFolder == "" { @@ -211,10 +213,6 @@ WebhookURL: "", WebhookProvider: "discord", WebhookEvents: []string{"all"}, - SFTP: false, - SFTPPort: 2022, - SFTPKeyFile: "", - SFTPHostKeyFile: "", Whitelist: "", TrustedProxies: "", Tunnel: false, @@ -235,6 +233,11 @@ LDAPPort: 389, LDAPJNDIEnabled: false, LDAPJNDIBase: "", + FTP: false, + FTPPort: 2121, + FTPSFTPMode: false, + FTPKeyFile: "", + FTPHostKeyFile: "", } b, err := json.MarshalIndent(defaultConfig, "", " ") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/config/config_test.go new/goshs-2.0.9/config/config_test.go --- old/goshs-2.0.8/config/config_test.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/config/config_test.go 2026-05-27 15:49:51.000000000 +0200 @@ -194,21 +194,23 @@ require.Equal(t, "10.0.0.1", result.DNSIP) } -func TestLoadConfig_SFTPFields(t *testing.T) { +func TestLoadConfig_FTPSFTPFields(t *testing.T) { cfg := Config{ - SFTP: true, - SFTPPort: 2022, - SFTPKeyFile: "/etc/goshs/authorized_keys", - SFTPHostKeyFile: "/etc/goshs/host_key", + FTP: true, + FTPPort: 2022, + FTPSFTPMode: true, + FTPKeyFile: "/etc/goshs/authorized_keys", + FTPHostKeyFile: "/etc/goshs/host_key", } path := writeTempConfig(t, cfg) opts := &options.Options{ConfigFile: path} result, err := LoadConfig(opts) require.NoError(t, err) - require.True(t, result.SFTP) - require.Equal(t, 2022, result.SFTPPort) - require.Equal(t, "/etc/goshs/authorized_keys", result.SFTPKeyFile) - require.Equal(t, "/etc/goshs/host_key", result.SFTPHostKeyFile) + require.True(t, result.FTP) + require.Equal(t, 2022, result.FTPPort) + require.True(t, result.FTPSFTPMode) + require.Equal(t, "/etc/goshs/authorized_keys", result.FTPKeyFile) + require.Equal(t, "/etc/goshs/host_key", result.FTPHostKeyFile) } func TestLoadConfig_TLSFields(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/example/goshs.json.example new/goshs-2.0.9/example/goshs.json.example --- old/goshs-2.0.8/example/goshs.json.example 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/example/goshs.json.example 2026-05-27 15:49:51.000000000 +0200 @@ -36,15 +36,16 @@ "webhook_events": [ "all" ], - "sftp": false, - "sftp_port": 2022, - "sftp_keyfile": "", - "sftp_host_keyfile": "", + "ftp": false, + "ftp_port": 2121, + "ftp_sftp_mode": false, + "ftp_keyfile": "", + "ftp_host_keyfile": "", "whitelist": "", "trusted_proxies": "", "tunnel": false, "dns_server": false, - "dns_port": 8053 + "dns_port": 8053, "dns_ip": "127.0.0.1", "smtp_server": false, "smtp_port": 2525, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/ftpserver/ftpserver.go new/goshs-2.0.9/ftpserver/ftpserver.go --- old/goshs-2.0.8/ftpserver/ftpserver.go 1970-01-01 01:00:00.000000000 +0100 +++ new/goshs-2.0.9/ftpserver/ftpserver.go 2026-05-27 15:49:51.000000000 +0200 @@ -0,0 +1,146 @@ +package ftpserver + +import ( + "crypto/subtle" + "crypto/tls" + "fmt" + "net" + "strconv" + + ftplib "github.com/fclairamb/ftpserverlib" + "github.com/spf13/afero" + "goshs.de/goshs/v2/httpserver" + "goshs.de/goshs/v2/logger" + "goshs.de/goshs/v2/options" + "goshs.de/goshs/v2/webhook" +) + +type FTPServer struct { + IP string + Port int + Root string + Username string + Password string + ReadOnly bool + NoDelete bool + Webhook webhook.Webhook + Whitelist *httpserver.Whitelist +} + +func NewFTPServer(opts *options.Options, wl *httpserver.Whitelist, wh webhook.Webhook) *FTPServer { + return &FTPServer{ + IP: opts.IP, + Port: opts.FTPPort, + Root: opts.Webroot, + Username: opts.Username, + Password: opts.Password, + ReadOnly: opts.ReadOnly, + NoDelete: opts.NoDelete, + Webhook: wh, + Whitelist: wl, + } +} + +func (s *FTPServer) Start() error { + driver := &mainDriver{srv: s} + srv := ftplib.NewFtpServer(driver) + logger.Infof("Starting FTP server on %s:%d", s.IP, s.Port) + return srv.ListenAndServe() +} + +func (s *FTPServer) HandleWebhookSend(action, path, ip string, blocked bool) { + var message string + if blocked { + message = fmt.Sprintf("[FTP] BLOCKED %s - [%s] - \"%s\"", ip, action, path) + } else { + message = fmt.Sprintf("[FTP] %s - [%s] - \"%s\"", ip, action, path) + } + logger.HandleWebhookSend(message, "ftp", s.Webhook) +} + +// mainDriver implements ftplib.MainDriver +type mainDriver struct { + srv *FTPServer +} + +func (d *mainDriver) GetSettings() (*ftplib.Settings, error) { + return &ftplib.Settings{ + ListenAddr: net.JoinHostPort(d.srv.IP, strconv.Itoa(d.srv.Port)), + Banner: "goshs FTP server ready", + ActiveTransferPortNon20: true, + }, nil +} + +func (d *mainDriver) ClientConnected(cc ftplib.ClientContext) (string, error) { + clientIP := cc.RemoteAddr().String() + if !isAllowedIP(cc.RemoteAddr(), d.srv.Whitelist) { + logger.Warnf("[FTP] [WHITELIST] Access denied for %s", clientIP) + return "", fmt.Errorf("access denied") + } + logger.Infof("[FTP] Client connected from %s", clientIP) + return "goshs FTP server", nil +} + +func (d *mainDriver) ClientDisconnected(cc ftplib.ClientContext) { + logger.Infof("[FTP] Client disconnected: %s", cc.RemoteAddr()) +} + +func (d *mainDriver) AuthUser(cc ftplib.ClientContext, user, pass string) (ftplib.ClientDriver, error) { + if d.srv.Username != "" || d.srv.Password != "" { + userOK := subtle.ConstantTimeCompare([]byte(user), []byte(d.srv.Username)) == 1 + passOK := subtle.ConstantTimeCompare([]byte(pass), []byte(d.srv.Password)) == 1 + if !userOK || !passOK { + logger.Warnf("[FTP] Auth failed for user '%s' from %s", user, cc.RemoteAddr()) + d.srv.HandleWebhookSend("AUTH", user, cc.RemoteAddr().String(), true) + return nil, fmt.Errorf("invalid credentials") + } + } + logger.Infof("[FTP] User '%s' authenticated from %s", user, cc.RemoteAddr()) + d.srv.HandleWebhookSend("AUTH", user, cc.RemoteAddr().String(), false) + + base := afero.NewBasePathFs(afero.NewOsFs(), d.srv.Root) + if d.srv.ReadOnly { + return afero.NewReadOnlyFs(base), nil + } + if d.srv.NoDelete { + return &noDeleteFs{Fs: base}, nil + } + return base, nil +} + +func (d *mainDriver) GetTLSConfig() (*tls.Config, error) { + return nil, nil +} + +// noDeleteFs wraps afero.Fs and blocks remove operations. +type noDeleteFs struct { + afero.Fs +} + +func (fs *noDeleteFs) Remove(name string) error { + return fmt.Errorf("delete not allowed") +} + +func (fs *noDeleteFs) RemoveAll(path string) error { + return fmt.Errorf("delete not allowed") +} + +func isAllowedIP(addr net.Addr, wl *httpserver.Whitelist) bool { + if !wl.Enabled { + return true + } + host, _, err := net.SplitHostPort(addr.String()) + if err != nil { + host = addr.String() + } + ip := net.ParseIP(host) + if ip == nil { + return false + } + for _, n := range wl.Networks { + if n.Contains(ip) { + return true + } + } + return false +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/go.mod new/goshs-2.0.9/go.mod --- old/goshs-2.0.8/go.mod 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/go.mod 2026-05-27 15:49:51.000000000 +0200 @@ -56,6 +56,7 @@ github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect + github.com/fclairamb/ftpserverlib v0.31.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect @@ -89,6 +90,7 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/shirou/gopsutil/v4 v4.25.1 // indirect + github.com/spf13/afero v1.15.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -104,7 +106,7 @@ go.opentelemetry.io/proto/otlp v1.6.0 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect + golang.org/x/sys v0.44.0 // indirect golang.org/x/term v0.42.0 // indirect golang.org/x/tools v0.44.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/go.sum new/goshs-2.0.9/go.sum --- old/goshs-2.0.8/go.sum 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/go.sum 2026-05-27 15:49:51.000000000 +0200 @@ -74,6 +74,8 @@ github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk= github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ= +github.com/fclairamb/ftpserverlib v0.31.0 h1:ws4fLJvuU/mgg1uIgRjCJfc6CrFSgIBAOxVsnawXegE= +github.com/fclairamb/ftpserverlib v0.31.0/go.mod h1:C5hUT0sd9P5XsHMxsmpcrT6wdZexu3qtUu1z0Ih6aG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= @@ -186,6 +188,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= @@ -304,6 +308,8 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/goshsversion/version.go new/goshs-2.0.9/goshsversion/version.go --- old/goshs-2.0.8/goshsversion/version.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/goshsversion/version.go 2026-05-27 15:49:51.000000000 +0200 @@ -1,3 +1,3 @@ package goshsversion -var GoshsVersion = "v2.0.8" +var GoshsVersion = "v2.0.9" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/options/options.go new/goshs-2.0.9/options/options.go --- old/goshs-2.0.8/options/options.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/options/options.go 2026-05-27 15:49:51.000000000 +0200 @@ -34,10 +34,6 @@ CertAuth string // "" WebDav bool // false WebDavPort int // 8001 - SFTP bool // false - SFTPPort int // 2022 - SFTPKeyFile string // "" - SFTPHostKeyFile string // "" UploadOnly bool // false ReadOnly bool // false NoClipboard bool // false @@ -81,6 +77,11 @@ LDAPJNDIEnabled bool // false â when true, use search baseDN as class name LDAPJNDIBase string // "" auto-constructs from IP/port LDAPWordlist string // "" optional wordlist path for NTLM hash cracking + FTP bool // false + FTPPort int // 2121 + FTPSFTPMode bool // false + FTPKeyFile string // "" + FTPHostKeyFile string // "" } func Parse() (*Options, bool) { @@ -152,13 +153,6 @@ flag.StringVar(&opts.WebhookEvents, "webhook-events", "all", "") flag.StringVar(&opts.WebhookProvider, "Wp", "Discord", "") flag.StringVar(&opts.WebhookProvider, "webhook-provider", "Discord", "") - flag.BoolVar(&opts.SFTP, "sftp", false, "sftp") - flag.IntVar(&opts.SFTPPort, "sp", 2022, "sftp port") - flag.IntVar(&opts.SFTPPort, "sftp-port", 2022, "sftp port") - flag.StringVar(&opts.SFTPKeyFile, "skf", "", "") - flag.StringVar(&opts.SFTPKeyFile, "sftp-keyfile", "", "") - flag.StringVar(&opts.SFTPHostKeyFile, "shk", "", "") - flag.StringVar(&opts.SFTPHostKeyFile, "sftp-host-keyfile", "", "") flag.StringVar(&opts.Whitelist, "ipw", "", "") flag.StringVar(&opts.Whitelist, "ip-whitelist", "", "") flag.StringVar(&opts.TrustedProxies, "tpw", "", "") @@ -195,6 +189,13 @@ flag.BoolVar(&opts.LDAPJNDIEnabled, "ldap-jndi", false, "Enable dynamic JNDI mode (baseDN becomes the class name)") flag.StringVar(&opts.LDAPJNDIBase, "ldap-jndi-base", "", "JNDI codeBase URL override (default: auto from HTTP server)") flag.StringVar(&opts.LDAPWordlist, "ldap-wordlist", "", "Wordlist file for LDAP NTLM hash cracking") + flag.BoolVar(&opts.FTP, "ftp", false, "Enable FTP server") + flag.IntVar(&opts.FTPPort, "ftp-port", 2121, "FTP server port") + flag.BoolVar(&opts.FTPSFTPMode, "ftp-sftp", false, "Use SFTP instead of plain FTP") + flag.StringVar(&opts.FTPKeyFile, "fkf", "", "") + flag.StringVar(&opts.FTPKeyFile, "ftp-keyfile", "", "") + flag.StringVar(&opts.FTPHostKeyFile, "fhk", "", "") + flag.StringVar(&opts.FTPHostKeyFile, "ftp-host-keyfile", "", "") // One-shot flags upd := flag.Bool("update", false, "update") @@ -270,11 +271,12 @@ -slh, --le-http Port to use for Let's Encrypt HTTP Challenge (default: 80) -slt, --le-tls Port to use for Let's Encrypt TLS ALPN Challenge (default: 443) -SFTP server options: - -sftp Activate SFTP server capabilities (default: false) - -sp, --sftp-port The port SFTP listens on (default: 2022) - -skf, --sftp-keyfile Authorized_keys file for pubkey auth - -shk, --sftp-host-keyfile SSH Host key file for identification +FTP/SFTP server options: + -ftp Activate FTP server capabilities (default: false) + -ftp-port The port FTP/SFTP listens on (default: 2121) + -ftp-sftp Switch to SFTP instead of plain FTP (default: false) + -fkf, --ftp-keyfile Authorized_keys file for SFTP pubkey auth + -fhk, --ftp-host-keyfile SSH host key file for SFTP identification SMB server options: -smb Activate SMB server capabilities (default: false) @@ -314,7 +316,7 @@ -Wu, --webhook-url URL to send webhook requests to -We, --webhook-events Comma separated list of events to notify [all, upload, delete, download, view, webdav, - sftp, smb, dns, smtp, redirect, verbose] (default: all) + ftp, smb, dns, smtp, redirect, verbose] (default: all) -Wp, --webhook-provider Webhook provider [Discord, Mattermost, Slack] (default: Discord) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/packaging/rpm/goshs.spec new/goshs-2.0.9/packaging/rpm/goshs.spec --- old/goshs-2.0.8/packaging/rpm/goshs.spec 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/packaging/rpm/goshs.spec 2026-05-27 15:49:51.000000000 +0200 @@ -1,5 +1,5 @@ Name: goshs -Version: 2.0.8 +Version: 2.0.9 Release: 1%{?dist} Summary: Beyond Python's http.server â single-binary file server for pentesters @@ -7,7 +7,7 @@ URL: https://github.com/patrickhener/goshs Source0: %{url}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz -BuildRequires: golang >= 1.26 +BuildRequires: golang %description A single-binary file server for pentesters, CTF players, and sysadmins. @@ -40,7 +40,9 @@ %{_bindir}/%{name} %changelog -* Tue May 13 2026 Patrick Hener <[email protected]> - 2.0.8-1 +* Wed May 27 2026 Patrick Hener <[email protected]> - 2.0.9-1 +- Add new version v2.0.9 +* Wed May 13 2026 Patrick Hener <[email protected]> - 2.0.8-1 - Add more packaging -* Tue May 13 2026 Patrick Hener <[email protected]> - 2.0.7-1 +* Wed May 13 2026 Patrick Hener <[email protected]> - 2.0.7-1 - Initial COPR package diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/sanity/checks.go new/goshs-2.0.9/sanity/checks.go --- old/goshs-2.0.8/sanity/checks.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/sanity/checks.go 2026-05-27 15:49:51.000000000 +0200 @@ -51,14 +51,14 @@ } // Sanity check for invisible mode if opts.Invisible { - opts.SFTP = false opts.WebDav = false opts.MDNS = false opts.Silent = false opts.DNS = false opts.SMTP = false opts.LDAP = false - logger.Warn("Invisible mode activated, disabling SFTP, WebDAV, silent mode, DNS, SMTP, LDAP and mDNS support") + opts.FTP = false + logger.Warn("Invisible mode activated, disabling FTP/SFTP, WebDAV, silent mode, DNS, SMTP, LDAP and mDNS support") } // Sanity check for upload only vs read only @@ -85,13 +85,13 @@ logger.Fatal("To use certificate based authentication with a CA cert you will need tls in any mode (-ss, -sk/-sc, -p12, -sl)") } - // Sanity check either user:pass or keyfile when using sftp - if opts.SFTP && (opts.BasicAuth == "" && opts.SFTPKeyFile == "") { - logger.Fatal("When using SFTP you need to either specify an authorized keyfile using -sfk or username and password using -b") + // Sanity check either user:pass or keyfile when using sftp mode + if opts.FTP && opts.FTPSFTPMode && (opts.BasicAuth == "" && opts.FTPKeyFile == "") { + logger.Fatal("When using SFTP you need to either specify an authorized keyfile using -fkf or username and password using -b") } // Sanity check: empty username is not valid for SFTP password auth - if opts.SFTP && strings.HasPrefix(opts.BasicAuth, ":") { + if opts.FTP && opts.FTPSFTPMode && strings.HasPrefix(opts.BasicAuth, ":") { logger.Fatal("When using SFTP with password authentication, the username cannot be empty. Please provide a non-empty username with -b 'user:pass'.") } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/sanity/checks_test.go new/goshs-2.0.9/sanity/checks_test.go --- old/goshs-2.0.8/sanity/checks_test.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/sanity/checks_test.go 2026-05-27 15:49:51.000000000 +0200 @@ -19,17 +19,18 @@ func TestCheck_InvisibleDisablesFeatures(t *testing.T) { opts := &options.Options{ - Invisible: true, - SFTP: true, - WebDav: true, - MDNS: true, - Silent: true, - DNS: true, - SMTP: true, + Invisible: true, + FTP: true, + FTPSFTPMode: true, + WebDav: true, + MDNS: true, + Silent: true, + DNS: true, + SMTP: true, } result, err := Check(opts) require.NoError(t, err) - require.False(t, result.SFTP) + require.False(t, result.FTP) require.False(t, result.WebDav) require.False(t, result.MDNS) require.False(t, result.Silent) @@ -206,7 +207,7 @@ func TestCheck_InvisibleWithSMBOnly(t *testing.T) { opts := &options.Options{ Invisible: true, - SFTP: false, + FTP: false, WebDav: false, } result, err := Check(opts) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/server/server.go new/goshs-2.0.9/server/server.go --- old/goshs-2.0.8/server/server.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/server/server.go 2026-05-27 15:49:51.000000000 +0200 @@ -5,6 +5,7 @@ "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/dnsserver" + "goshs.de/goshs/v2/ftpserver" "goshs.de/goshs/v2/httpserver" "goshs.de/goshs/v2/ldapserver" "goshs.de/goshs/v2/logger" @@ -38,11 +39,6 @@ go webdavSrv.Start("webdav") } - if opts.SFTP { - sftpSrv := sftpserver.NewSFTPServer(opts, wl, *wh) - go sftpSrv.Start() - } - if opts.DNS { dnsSrv := dnsserver.NewDNSServer(opts, hub, wh) go dnsSrv.Start() @@ -63,9 +59,19 @@ go ldapSrv.Start() } + if opts.FTP { + if opts.FTPSFTPMode { + sftpSrv := sftpserver.NewSFTPServer(opts, wl, *wh) + go sftpSrv.Start() + } else { + ftpSrv := ftpserver.NewFTPServer(opts, wl, *wh) + go ftpSrv.Start() + } + } + // Zeroconf mDNS if opts.MDNS { - err := utils.RegisterZeroconfMDNS(opts.SSL, opts.Port, opts.WebDav, opts.WebDavPort, opts.SFTP, opts.SFTPPort, opts.SMTP, opts.SMTPPort, opts.DNS, opts.DNSPort, opts.SMB, opts.SMBPort, opts.LDAP, opts.LDAPPort) + err := utils.RegisterZeroconfMDNS(opts.SSL, opts.Port, opts.WebDav, opts.WebDavPort, opts.SMTP, opts.SMTPPort, opts.DNS, opts.DNSPort, opts.SMB, opts.SMBPort, opts.LDAP, opts.LDAPPort, opts.FTP, opts.FTPSFTPMode, opts.FTPPort) if err != nil { logger.Warnf("error registering zeroconf mDNS: %+v", err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/sftpserver/handler_test.go new/goshs-2.0.9/sftpserver/handler_test.go --- old/goshs-2.0.8/sftpserver/handler_test.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/sftpserver/handler_test.go 2026-05-27 15:49:51.000000000 +0200 @@ -306,15 +306,15 @@ func TestNewSFTPServer(t *testing.T) { opts := &options.Options{ - IP: "127.0.0.1", - SFTPPort: 2222, - SFTPKeyFile: "keys", - Username: "user", - Password: "pass", - Webroot: "/tmp", - ReadOnly: true, - UploadOnly: false, - SFTPHostKeyFile: "hostkey", + IP: "127.0.0.1", + FTPPort: 2222, + FTPKeyFile: "keys", + Username: "user", + Password: "pass", + Webroot: "/tmp", + ReadOnly: true, + UploadOnly: false, + FTPHostKeyFile: "hostkey", } wh := webhook.Register(false, "", "discord", []string{}) wl := &httpserver.Whitelist{} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/sftpserver/helper.go new/goshs-2.0.9/sftpserver/helper.go --- old/goshs-2.0.8/sftpserver/helper.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/sftpserver/helper.go 2026-05-27 15:49:51.000000000 +0200 @@ -13,10 +13,10 @@ "runtime" "strings" - "goshs.de/goshs/v2/httpserver" - "goshs.de/goshs/v2/logger" "github.com/pkg/sftp" gossh "golang.org/x/crypto/ssh" + "goshs.de/goshs/v2/httpserver" + "goshs.de/goshs/v2/logger" ) var authorizedKeysMap map[string]bool @@ -50,6 +50,7 @@ sftpRoot = rewritePathWindows(sftpRoot) } clean := filepath.Clean("/" + strings.TrimLeft(clientPath, "/")) + clean = strings.TrimPrefix(clean, sftpRoot) abs := filepath.Join(sftpRoot, clean) rootClean := filepath.Clean(sftpRoot) if abs != rootClean && !strings.HasPrefix(abs, rootClean+string(filepath.Separator)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/sftpserver/sftpserver.go new/goshs-2.0.9/sftpserver/sftpserver.go --- old/goshs-2.0.8/sftpserver/sftpserver.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/sftpserver/sftpserver.go 2026-05-27 15:49:51.000000000 +0200 @@ -35,14 +35,14 @@ func NewSFTPServer(opts *options.Options, wl *httpserver.Whitelist, webhook webhook.Webhook) *SFTPServer { return &SFTPServer{ IP: opts.IP, - Port: opts.SFTPPort, - KeyFile: opts.SFTPKeyFile, + Port: opts.FTPPort, + KeyFile: opts.FTPKeyFile, Username: opts.Username, Password: opts.Password, Root: opts.Webroot, ReadOnly: opts.ReadOnly, UploadOnly: opts.UploadOnly, - HostKeyFile: opts.SFTPHostKeyFile, + HostKeyFile: opts.FTPHostKeyFile, Webhook: webhook, Whitelist: wl, } @@ -164,5 +164,5 @@ } } - logger.HandleWebhookSend(message, "sftp", s.Webhook) + logger.HandleWebhookSend(message, "ftp", s.Webhook) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.8/utils/utils.go new/goshs-2.0.9/utils/utils.go --- old/goshs-2.0.8/utils/utils.go 2026-05-13 15:57:24.000000000 +0200 +++ new/goshs-2.0.9/utils/utils.go 2026-05-27 15:49:51.000000000 +0200 @@ -98,7 +98,7 @@ return string(bytes) } -func RegisterZeroconfMDNS(ssl bool, webPort int, webdav bool, webdavPort int, sftp bool, sftpPort int, smtp bool, smtpPort int, dns bool, dnsPort int, smb bool, smbPort int, ldap bool, ldapPort int) error { +func RegisterZeroconfMDNS(ssl bool, webPort int, webdav bool, webdavPort int, smtp bool, smtpPort int, dns bool, dnsPort int, smb bool, smbPort int, ldap bool, ldapPort int, ftp bool, ftpSFTPMode bool, ftpPort int) error { // Register zeroconf mDNS hostname, err := os.Hostname() if err != nil { @@ -159,24 +159,6 @@ logger.Infof("mDNS service registered as %s://%s.local:%d", out, hostname, webdavPort) } - // Register sftp if enabled - if sftp { - zeroSFTP, err := zeroconf.Register( - "goshs SFTP", - "_ssh._tcp", - "local.", - sftpPort, - []string{fmt.Sprintf("host=%s.local", hostname), "subsystem=sftp", "path=/", fmt.Sprintf("version=%s", goshsversion.GoshsVersion)}, - nil, - ) - if err != nil { - return fmt.Errorf("zeroconf mDNS did not register successfully: %+v", err) - } - defer zeroSFTP.Shutdown() - - logger.Infof("mDNS service registered as ssh://%s.local:%d", hostname, sftpPort) - } - // Register smtp if enabled if smtp { zeroDav, err := zeroconf.Register( @@ -249,5 +231,36 @@ logger.Infof("mDNS service registered as ldap://%s.local:%d", hostname, ldapPort) } + // Register ftp/sftp if enabled + if ftp { + var ftpServiceType, ftpProto, ftpName string + var ftpTxtRecords []string + if ftpSFTPMode { + ftpServiceType = "_ssh._tcp" + ftpProto = "ssh" + ftpName = "goshs SFTP" + ftpTxtRecords = []string{fmt.Sprintf("host=%s.local", hostname), "subsystem=sftp", "path=/", fmt.Sprintf("version=%s", goshsversion.GoshsVersion)} + } else { + ftpServiceType = "_ftp._tcp" + ftpProto = "ftp" + ftpName = "goshs FTP" + ftpTxtRecords = []string{fmt.Sprintf("host=%s.local", hostname), fmt.Sprintf("version=%s", goshsversion.GoshsVersion)} + } + zeroFTP, err := zeroconf.Register( + ftpName, + ftpServiceType, + "local.", + ftpPort, + ftpTxtRecords, + nil, + ) + if err != nil { + return fmt.Errorf("zeroconf mDNS did not register successfully: %+v", err) + } + defer zeroFTP.Shutdown() + + logger.Infof("mDNS service registered as %s://%s.local:%d", ftpProto, hostname, ftpPort) + } + return nil } ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/goshs/vendor.tar.gz /work/SRC/openSUSE:Factory/.goshs.new.1937/vendor.tar.gz differ: char 15, line 1
