From af49d17e8eefcd9fbaf634811e555e7d96a54c8d Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Sun, 10 May 2026 13:56:03 +0300 Subject: [PATCH] feat(socks): add socks5 user and password --- cmd/olcrtc/main.go | 6 +++++ internal/app/session/session.go | 6 +++-- internal/client/client.go | 43 ++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/cmd/olcrtc/main.go b/cmd/olcrtc/main.go index ff46907..363154e 100644 --- a/cmd/olcrtc/main.go +++ b/cmd/olcrtc/main.go @@ -40,6 +40,8 @@ type config struct { clientID string socksPort int socksHost string + socksUser string + socksPass string keyHex string debug bool dataDir string @@ -172,6 +174,8 @@ func parseFlagsFrom(args []string, errorHandling flag.ErrorHandling) (config, er fs.StringVar(&cfg.clientID, "client-id", "", "Client ID: binds one srv to one cnc (required)") fs.IntVar(&cfg.socksPort, "socks-port", 0, "SOCKS5 port (client only)") fs.StringVar(&cfg.socksHost, "socks-host", "", "SOCKS5 listen host (client only)") + fs.StringVar(&cfg.socksUser, "socks-user", "", "SOCKS5 username for incoming connections (client only, optional)") + fs.StringVar(&cfg.socksPass, "socks-pass", "", "SOCKS5 password for incoming connections (client only, optional)") fs.StringVar(&cfg.keyHex, "key", "", "Shared encryption key (hex)") fs.BoolVar(&cfg.debug, "debug", false, "Enable verbose logging") fs.StringVar(&cfg.dataDir, "data", "", "Path to data directory") @@ -250,6 +254,8 @@ func toSessionConfig(cfg config) session.Config { KeyHex: cfg.keyHex, SOCKSHost: cfg.socksHost, SOCKSPort: cfg.socksPort, + SOCKSUser: cfg.socksUser, + SOCKSPass: cfg.socksPass, DNSServer: cfg.dnsServer, SOCKSProxyAddr: cfg.socksProxyAddr, SOCKSProxyPort: cfg.socksProxyPort, diff --git a/internal/app/session/session.go b/internal/app/session/session.go index a75fa4c..10dec96 100644 --- a/internal/app/session/session.go +++ b/internal/app/session/session.go @@ -118,6 +118,8 @@ type Config struct { KeyHex string SOCKSHost string SOCKSPort int + SOCKSUser string + SOCKSPass string DNSServer string SOCKSProxyAddr string SOCKSProxyPort int @@ -357,8 +359,8 @@ func Run(ctx context.Context, cfg Config) error { cfg.ClientID, fmt.Sprintf("%s:%d", cfg.SOCKSHost, cfg.SOCKSPort), cfg.DNSServer, - "", - "", + cfg.SOCKSUser, + cfg.SOCKSPass, cfg.VideoWidth, cfg.VideoHeight, cfg.VideoFPS, diff --git a/internal/client/client.go b/internal/client/client.go index 46791a2..5eefce2 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -36,6 +36,8 @@ var ( ErrUnsupportedAddressType = errors.New("unsupported address type") // ErrRemoteNotReady is returned when the server-side stream fails to signal readiness. ErrRemoteNotReady = errors.New("remote not ready") + // ErrSOCKSAuthFailed is returned when username/password authentication is rejected. + ErrSOCKSAuthFailed = errors.New("SOCKS5 authentication failed") ) // Client handles local SOCKS5 connections and tunnels them to the server. @@ -47,6 +49,8 @@ type Client struct { sessMu sync.RWMutex clientID string dnsServer string + socksUser string + socksPass string } // Run starts the client with the specified parameters. @@ -100,8 +104,8 @@ func RunWithReady( clientID string, localAddr string, dnsServer, - _ string, - _ string, + socksUser string, + socksPass string, onReady func(), videoWidth int, videoHeight int, @@ -128,7 +132,7 @@ func RunWithReady( return fmt.Errorf("setupCipher failed: %w", err) } - c := &Client{cipher: cipher, clientID: clientID, dnsServer: dnsServer} + c := &Client{cipher: cipher, clientID: clientID, dnsServer: dnsServer, socksUser: socksUser, socksPass: socksPass} if err := c.bringUpLink( runCtx, linkName, transportName, carrierName, roomURL, cancel, @@ -411,12 +415,45 @@ func (c *Client) socks5Handshake(conn net.Conn) error { if _, err := io.ReadFull(conn, methods); err != nil { return fmt.Errorf("read socks5 methods: %w", err) } + + if c.socksUser != "" { + // RFC 1929: method 0x02 = username/password auth. + if _, err := conn.Write([]byte{5, 2}); err != nil { + return fmt.Errorf("write socks5 auth method: %w", err) + } + if err := c.socks5UserPassAuth(conn); err != nil { + return err + } + return nil + } + if _, err := conn.Write([]byte{5, 0}); err != nil { return fmt.Errorf("write socks5 auth: %w", err) } return nil } +func (c *Client) socks5UserPassAuth(conn net.Conn) error { + user := []byte(c.socksUser) + pass := []byte(c.socksPass) + req := make([]byte, 0, 3+len(user)+len(pass)) + req = append(req, 0x01, byte(len(user))) + req = append(req, user...) + req = append(req, byte(len(pass))) + req = append(req, pass...) + if _, err := conn.Write(req); err != nil { + return fmt.Errorf("write socks5 user/pass: %w", err) + } + resp := make([]byte, 2) + if _, err := io.ReadFull(conn, resp); err != nil { + return fmt.Errorf("read socks5 auth response: %w", err) + } + if resp[1] != 0x00 { + return ErrSOCKSAuthFailed + } + return nil +} + func (c *Client) socks5Request(conn net.Conn) (string, int, error) { header := make([]byte, 4) if _, err := io.ReadFull(conn, header); err != nil {