mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-11 05:48:17 +03:00
Add SSH inbound, log level. Update MTPROXY. Fixes
This commit is contained in:
165
protocol/ssh/service.go
Normal file
165
protocol/ssh/service.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/bufio/deadline"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
||||
PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
|
||||
UpdateUsers(users []option.SSHUser)
|
||||
Handle(ctx context.Context, serverConn *ssh.ServerConn, channels <-chan ssh.NewChannel, requests <-chan *ssh.Request, metadata adapter.InboundContext, user string)
|
||||
Close() error
|
||||
}
|
||||
|
||||
var _ Service = (*service)(nil)
|
||||
|
||||
type service struct {
|
||||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
users []option.SSHUser
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func newService(router adapter.ConnectionRouterEx, logger logger.ContextLogger, users []option.SSHUser) *service {
|
||||
return &service{
|
||||
router: router,
|
||||
logger: logger,
|
||||
users: users,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *service) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
h.mtx.RLock()
|
||||
users := h.users
|
||||
h.mtx.RUnlock()
|
||||
for _, user := range users {
|
||||
if user.Name != "" && user.Name != conn.User() {
|
||||
continue
|
||||
}
|
||||
if user.Password != "" && user.Password == string(password) {
|
||||
return &ssh.Permissions{Extensions: map[string]string{"user": user.Name}}, nil
|
||||
}
|
||||
}
|
||||
return nil, E.New("password authentication failed for user ", conn.User())
|
||||
}
|
||||
|
||||
func (h *service) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
h.mtx.RLock()
|
||||
users := h.users
|
||||
h.mtx.RUnlock()
|
||||
for _, user := range users {
|
||||
if user.Name != "" && user.Name != conn.User() {
|
||||
continue
|
||||
}
|
||||
for _, authorizedKey := range user.AuthorizedKeys {
|
||||
parsed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(authorizedKey))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(parsed.Marshal(), key.Marshal()) {
|
||||
return &ssh.Permissions{Extensions: map[string]string{"user": user.Name}}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, E.New("public key authentication failed for user ", conn.User())
|
||||
}
|
||||
|
||||
func (h *service) UpdateUsers(users []option.SSHUser) {
|
||||
h.mtx.Lock()
|
||||
h.users = users
|
||||
h.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (h *service) Handle(ctx context.Context, serverConn *ssh.ServerConn, channels <-chan ssh.NewChannel, requests <-chan *ssh.Request, metadata adapter.InboundContext, user string) {
|
||||
h.logger.InfoContext(ctx, "[", user, "] authenticated SSH connection from ", metadata.Source)
|
||||
go ssh.DiscardRequests(requests)
|
||||
for newChannel := range channels {
|
||||
switch newChannel.ChannelType() {
|
||||
case "direct-tcpip":
|
||||
go h.handleDirectChannel(ctx, metadata, newChannel)
|
||||
default:
|
||||
newChannel.Reject(ssh.UnknownChannelType, "only direct-tcpip is supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *service) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *service) handleDirectChannel(ctx context.Context, metadata adapter.InboundContext, newChannel ssh.NewChannel) {
|
||||
var payload directTCPIPData
|
||||
if err := ssh.Unmarshal(newChannel.ExtraData(), &payload); err != nil {
|
||||
newChannel.Reject(ssh.ConnectionFailed, "invalid direct-tcpip payload")
|
||||
h.logger.ErrorContext(ctx, E.Cause(err, "parse direct-tcpip payload"))
|
||||
return
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, E.Cause(err, "accept direct-tcpip channel"))
|
||||
return
|
||||
}
|
||||
go ssh.DiscardRequests(requests)
|
||||
connMetadata := metadata
|
||||
connMetadata.Destination = M.ParseSocksaddrHostPort(payload.HostToConnect, uint16(payload.PortToConnect))
|
||||
conn := deadline.NewConn(&channelConn{
|
||||
Channel: channel,
|
||||
localAddr: metadata.OriginDestination.TCPAddr(),
|
||||
remoteAddr: metadata.Source.TCPAddr(),
|
||||
})
|
||||
h.logger.InfoContext(ctx, "[", metadata.User, "] inbound connection to ", connMetadata.Destination)
|
||||
h.router.RouteConnectionEx(ctx, conn, connMetadata, N.OnceClose(func(it error) {
|
||||
channel.Close()
|
||||
}))
|
||||
}
|
||||
|
||||
type directTCPIPData struct {
|
||||
HostToConnect string
|
||||
PortToConnect uint32
|
||||
OriginatorAddress string
|
||||
OriginatorPort uint32
|
||||
}
|
||||
|
||||
type channelConn struct {
|
||||
ssh.Channel
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *channelConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *channelConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *channelConn) SetDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *channelConn) SetReadDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *channelConn) SetWriteDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
Reference in New Issue
Block a user