mirror of
https://github.com/nwtgck/handy-sshd.git
synced 2025-06-13 19:45:36 +00:00
Merge branch 'release/0.2.0'
This commit is contained in:
commit
58d3afaaee
11 changed files with 188 additions and 18 deletions
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -10,3 +10,11 @@ updates:
|
||||||
- nwtgck
|
- nwtgck
|
||||||
assignees:
|
assignees:
|
||||||
- nwtgck
|
- nwtgck
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
timezone: Asia/Tokyo
|
||||||
|
open-pull-requests-limit: 99
|
||||||
|
reviewers: [ nwtgck ]
|
||||||
|
assignees: [ nwtgck ]
|
||||||
|
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -6,9 +6,9 @@ jobs:
|
||||||
build_multi_platform:
|
build_multi_platform:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- name: Build for multi-platform
|
- name: Build for multi-platform
|
||||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -9,15 +9,15 @@ jobs:
|
||||||
goreleaser:
|
goreleaser:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
version: v1.3.1
|
version: v1.3.1
|
||||||
args: release --rm-dist
|
args: release --rm-dist
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -5,8 +5,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] - 2023-08-09
|
||||||
|
### Added
|
||||||
|
* Add permissions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Require one user at least
|
||||||
|
|
||||||
## 0.1.0 - 2023-08-08
|
## 0.1.0 - 2023-08-08
|
||||||
### Added
|
### Added
|
||||||
* Initial release
|
* Initial release
|
||||||
|
|
||||||
[Unreleased]: https://github.com/nwtgck/handy-sshd/compare/v0.1.0...HEAD
|
[Unreleased]: https://github.com/nwtgck/handy-sshd/compare/v0.2.0...HEAD
|
||||||
|
[0.2.0]: https://github.com/nwtgck/handy-sshd/compare/v0.1.0...v0.2.0
|
||||||
|
|
56
README.md
56
README.md
|
@ -1,6 +1,23 @@
|
||||||
# handy-sshd
|
# handy-sshd
|
||||||
|
[](https://github.com/nwtgck/handy-sshd/actions/workflows/ci.yml)
|
||||||
|
|
||||||
Portable SSH Server
|
Portable SSH Server
|
||||||
|
|
||||||
|
## Install on Ubuntu/Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://github.com/nwtgck/handy-sshd/releases/download/v0.1.0/handy-sshd-0.1.0-linux-amd64.deb
|
||||||
|
sudo dpkg -i handy-sshd-0.1.0-linux-amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install on Mac
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install nwtgck/handy-sshd/handy-sshd
|
||||||
|
```
|
||||||
|
|
||||||
|
Get more executables in [the releases](https://github.com/nwtgck/handy-sshd/releases).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -22,3 +39,42 @@ handy-sshd -p 2222 --user "john:" --user "alice:"
|
||||||
# Listen on unix domain socket
|
# Listen on unix domain socket
|
||||||
handy-sshd --unix-socket /tmp/my-unix-socket --user "john:"
|
handy-sshd --unix-socket /tmp/my-unix-socket --user "john:"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
**All permissions are allowed when nothing is specified.** There are some permissions.
|
||||||
|
* --allow-direct-tcpip
|
||||||
|
* --allow-execute
|
||||||
|
* --allow-sftp
|
||||||
|
* --allow-tcpip-forward
|
||||||
|
|
||||||
|
Specifying `--allow-direct-tcpip` and `--allow-execute` for example allows only them.
|
||||||
|
The log shows "allowed: " and "NOT allowed: " permissions as follows.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ handy-sshd --user "john:" --allow-direct-tcpip --allow-execute
|
||||||
|
2023/08/09 20:49:35 INFO listening on :2222...
|
||||||
|
2023/08/09 20:49:35 INFO allowed: "direct-tcpip", "execute"
|
||||||
|
2023/08/09 20:49:35 INFO NOT allowed: "tcpip-forward", "sftp"
|
||||||
|
```
|
||||||
|
|
||||||
|
## --help
|
||||||
|
|
||||||
|
```
|
||||||
|
Portable SSH server
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
handy-sshd [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--allow-direct-tcpip client can use local forwarding and SOCKS proxy
|
||||||
|
--allow-execute client can use shell/interactive shell
|
||||||
|
--allow-sftp client can use SFTP and SSHFS
|
||||||
|
--allow-tcpip-forward client can use remote forwarding
|
||||||
|
-h, --help help for handy-sshd
|
||||||
|
--host string SSH server host (e.g. 127.0.0.1)
|
||||||
|
-p, --port uint16 SSH server port (default 2222)
|
||||||
|
--shell string Shell
|
||||||
|
--unix-socket string Unix-domain socket
|
||||||
|
--user stringArray SSH user name (e.g. "john:mypassword")
|
||||||
|
-v, --version show version
|
||||||
|
```
|
||||||
|
|
70
cmd/root.go
70
cmd/root.go
|
@ -21,6 +21,21 @@ var flag struct {
|
||||||
sshUnixSocket string
|
sshUnixSocket string
|
||||||
sshShell string
|
sshShell string
|
||||||
sshUsers []string
|
sshUsers []string
|
||||||
|
|
||||||
|
allowTcpipForward bool
|
||||||
|
allowDirectTcpip bool
|
||||||
|
allowExecute bool
|
||||||
|
allowSftp bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPermissionFlags = []struct {
|
||||||
|
name string
|
||||||
|
flagPtr *bool
|
||||||
|
}{
|
||||||
|
{name: "tcpip-forward", flagPtr: &flag.allowTcpipForward},
|
||||||
|
{name: "direct-tcpip", flagPtr: &flag.allowDirectTcpip},
|
||||||
|
{name: "execute", flagPtr: &flag.allowExecute},
|
||||||
|
{name: "sftp", flagPtr: &flag.allowSftp},
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshUser struct {
|
type sshUser struct {
|
||||||
|
@ -38,6 +53,12 @@ func init() {
|
||||||
RootCmd.PersistentFlags().StringVarP(&flag.sshShell, "shell", "", "", "Shell")
|
RootCmd.PersistentFlags().StringVarP(&flag.sshShell, "shell", "", "", "Shell")
|
||||||
//RootCmd.PersistentFlags().StringVar(&flag.dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)")
|
//RootCmd.PersistentFlags().StringVar(&flag.dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)")
|
||||||
RootCmd.PersistentFlags().StringArrayVarP(&flag.sshUsers, "user", "", nil, `SSH user name (e.g. "john:mypassword")`)
|
RootCmd.PersistentFlags().StringArrayVarP(&flag.sshUsers, "user", "", nil, `SSH user name (e.g. "john:mypassword")`)
|
||||||
|
|
||||||
|
// Permission flags
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&flag.allowTcpipForward, "allow-tcpip-forward", "", false, "client can use remote forwarding")
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&flag.allowDirectTcpip, "allow-direct-tcpip", "", false, "client can use local forwarding and SOCKS proxy")
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&flag.allowExecute, "allow-execute", "", false, "client can use shell/interactive shell")
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&flag.allowSftp, "allow-sftp", "", false, "client can use SFTP and SSHFS")
|
||||||
}
|
}
|
||||||
|
|
||||||
var RootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
|
@ -51,8 +72,26 @@ var RootCmd = &cobra.Command{
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger := slog.Default()
|
logger := slog.Default()
|
||||||
|
|
||||||
|
// Allow all permissions if all permission is not set
|
||||||
|
{
|
||||||
|
allPermissionFalse := true
|
||||||
|
for _, permissionFlag := range allPermissionFlags {
|
||||||
|
allPermissionFalse = allPermissionFalse && !*permissionFlag.flagPtr
|
||||||
|
}
|
||||||
|
if allPermissionFalse {
|
||||||
|
for _, permissionFlag := range allPermissionFlags {
|
||||||
|
*permissionFlag.flagPtr = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sshServer := &handy_sshd.Server{
|
sshServer := &handy_sshd.Server{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
AllowTcpipForward: flag.allowTcpipForward,
|
||||||
|
AllowDirectTcpip: flag.allowDirectTcpip,
|
||||||
|
AllowExecute: flag.allowExecute,
|
||||||
|
AllowSftp: flag.allowSftp,
|
||||||
}
|
}
|
||||||
var sshUsers []sshUser
|
var sshUsers []sshUser
|
||||||
for _, u := range flag.sshUsers {
|
for _, u := range flag.sshUsers {
|
||||||
|
@ -62,7 +101,11 @@ var RootCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
sshUsers = append(sshUsers, sshUser{name: splits[0], password: splits[1]})
|
sshUsers = append(sshUsers, sshUser{name: splits[0], password: splits[1]})
|
||||||
}
|
}
|
||||||
|
if len(sshUsers) == 0 {
|
||||||
|
return fmt.Errorf(`No user specified
|
||||||
|
e.g. --user "john:mypassword"
|
||||||
|
e.g. --user "john:"`)
|
||||||
|
}
|
||||||
// (base: https://gist.github.com/jpillora/b480fde82bff51a06238)
|
// (base: https://gist.github.com/jpillora/b480fde82bff51a06238)
|
||||||
sshConfig := &ssh.ServerConfig{
|
sshConfig := &ssh.ServerConfig{
|
||||||
//Define a function to run when a client attempts a password login
|
//Define a function to run when a client attempts a password login
|
||||||
|
@ -109,6 +152,9 @@ var RootCmd = &cobra.Command{
|
||||||
logger.Info(fmt.Sprintf("listening on %s...", flag.sshUnixSocket))
|
logger.Info(fmt.Sprintf("listening on %s...", flag.sshUnixSocket))
|
||||||
}
|
}
|
||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
|
|
||||||
|
showPermissions(logger)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -121,9 +167,29 @@ var RootCmd = &cobra.Command{
|
||||||
conn.Close()
|
conn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.Info("new SSH connection", "client_version", string(sshConn.ClientVersion()))
|
logger.Info("new SSH connection", "remote_address", sshConn.RemoteAddr(), "client_version", string(sshConn.ClientVersion()))
|
||||||
go sshServer.HandleGlobalRequests(sshConn, reqs)
|
go sshServer.HandleGlobalRequests(sshConn, reqs)
|
||||||
go sshServer.HandleChannels(flag.sshShell, chans)
|
go sshServer.HandleChannels(flag.sshShell, chans)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showPermissions(logger *slog.Logger) {
|
||||||
|
var allowedList []string
|
||||||
|
var notAllowedList []string
|
||||||
|
for _, permissionFlag := range allPermissionFlags {
|
||||||
|
if *permissionFlag.flagPtr {
|
||||||
|
allowedList = append(allowedList, `"`+permissionFlag.name+`"`)
|
||||||
|
} else {
|
||||||
|
notAllowedList = append(notAllowedList, `"`+permissionFlag.name+`"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showList := func(l []string) string {
|
||||||
|
if len(l) == 0 {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return strings.Join(l, ", ")
|
||||||
|
}
|
||||||
|
logger.Info(fmt.Sprintf("allowed: %s", showList(allowedList)))
|
||||||
|
logger.Info(fmt.Sprintf("NOT allowed: %s", showList(notAllowedList)))
|
||||||
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/pkg/sftp v1.13.5
|
github.com/pkg/sftp v1.13.5
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de
|
golang.org/x/exp v0.0.0-20230809094429-853ea248256d
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -27,8 +27,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE=
|
golang.org/x/exp v0.0.0-20230809094429-853ea248256d h1:wu5bD43Ana/nF1ZmaLr3lW/FQeJU8CcI+Ln7yWHViXE=
|
||||||
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/exp v0.0.0-20230809094429-853ea248256d/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/nwtgck/handy-sshd/cmd"
|
"github.com/nwtgck/handy-sshd/cmd"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := cmd.RootCmd.Execute(); err != nil {
|
if err := cmd.RootCmd.Execute(); err != nil {
|
||||||
_, _ = fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
server.go
38
server.go
|
@ -28,6 +28,13 @@ import (
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
AllowTcpipForward bool
|
||||||
|
AllowDirectTcpip bool
|
||||||
|
AllowExecute bool // this should not be split into "allow-exec" and "allow-pty-req" for now because "pty-req" can be used not for shell execution.
|
||||||
|
AllowSftp bool
|
||||||
|
|
||||||
// TODO: DNS server ?
|
// TODO: DNS server ?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +54,10 @@ func (s *Server) handleChannel(shell string, newChannel ssh.NewChannel) {
|
||||||
case "session":
|
case "session":
|
||||||
s.handleSession(shell, newChannel)
|
s.handleSession(shell, newChannel)
|
||||||
case "direct-tcpip":
|
case "direct-tcpip":
|
||||||
|
if !s.AllowDirectTcpip {
|
||||||
|
newChannel.Reject(ssh.Prohibited, "direct-tcpip not allowed")
|
||||||
|
break
|
||||||
|
}
|
||||||
s.handleDirectTcpip(newChannel)
|
s.handleDirectTcpip(newChannel)
|
||||||
default:
|
default:
|
||||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType()))
|
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType()))
|
||||||
|
@ -67,6 +78,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) {
|
||||||
for req := range requests {
|
for req := range requests {
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
case "exec":
|
case "exec":
|
||||||
|
if !s.AllowExecute {
|
||||||
|
s.Logger.Info("execution not allowed (exec)")
|
||||||
|
req.Reply(false, nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
s.handleExecRequest(req, connection)
|
s.handleExecRequest(req, connection)
|
||||||
case "shell":
|
case "shell":
|
||||||
// We only accept the default shell
|
// We only accept the default shell
|
||||||
|
@ -75,6 +91,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) {
|
||||||
req.Reply(true, nil)
|
req.Reply(true, nil)
|
||||||
}
|
}
|
||||||
case "pty-req":
|
case "pty-req":
|
||||||
|
if !s.AllowExecute {
|
||||||
|
s.Logger.Info("execution not allowed (pty-req)")
|
||||||
|
req.Reply(false, nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
termLen := req.Payload[3]
|
termLen := req.Payload[3]
|
||||||
w, h := parseDims(req.Payload[termLen+4:])
|
w, h := parseDims(req.Payload[termLen+4:])
|
||||||
shf, err = s.createPty(shell, connection)
|
shf, err = s.createPty(shell, connection)
|
||||||
|
@ -140,9 +161,17 @@ func (s *Server) handleExecRequest(req *ssh.Request, connection ssh.Channel) {
|
||||||
|
|
||||||
func (s *Server) handleSessionSubSystem(req *ssh.Request, connection ssh.Channel) {
|
func (s *Server) handleSessionSubSystem(req *ssh.Request, connection ssh.Channel) {
|
||||||
// https://github.com/pkg/sftp/blob/42e9800606febe03f9cdf1d1283719af4a5e6456/examples/go-sftp-server/main.go#L111
|
// https://github.com/pkg/sftp/blob/42e9800606febe03f9cdf1d1283719af4a5e6456/examples/go-sftp-server/main.go#L111
|
||||||
ok := string(req.Payload[4:]) == "sftp"
|
if string(req.Payload[4:]) != "sftp" {
|
||||||
req.Reply(ok, nil)
|
req.Reply(false, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !s.AllowSftp {
|
||||||
|
s.Logger.Info("sftp not allowed")
|
||||||
|
req.Reply(false, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Reply(true, nil)
|
||||||
serverOptions := []sftp.ServerOption{
|
serverOptions := []sftp.ServerOption{
|
||||||
sftp.WithDebug(os.Stderr),
|
sftp.WithDebug(os.Stderr),
|
||||||
}
|
}
|
||||||
|
@ -232,6 +261,11 @@ func (s *Server) HandleGlobalRequests(sshConn *ssh.ServerConn, reqs <-chan *ssh.
|
||||||
for req := range reqs {
|
for req := range reqs {
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
case "tcpip-forward":
|
case "tcpip-forward":
|
||||||
|
if !s.AllowTcpipForward {
|
||||||
|
s.Logger.Info("tcpip-forward not allowed")
|
||||||
|
req.Reply(false, nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
s.handleTcpipForward(sshConn, req)
|
s.handleTcpipForward(sshConn, req)
|
||||||
default:
|
default:
|
||||||
// discard
|
// discard
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package version
|
package version
|
||||||
|
|
||||||
const Version = "0.1.0"
|
const Version = "0.2.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue