Compare commits

..

No commits in common. "b2e43f4bad256f24aa2c4ebb02c759c22108ad47" and "0f28d2c023283838d79c5a94be5ff1dba7d332f0" have entirely different histories.

4 changed files with 100 additions and 128 deletions

View file

@ -25,8 +25,6 @@ linters-settings:
#- style #- style
#- opinionated #- opinionated
disabled-checks: disabled-checks:
- commentFormatting
- commentedOutCode
- dupImport # https://github.com/go-critic/go-critic/issues/845 - dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain - ifElseChain
- octalLiteral - octalLiteral

View file

@ -31,7 +31,7 @@ func handleTermResizes(conn *xsnet.Conn) {
log.Println(err) log.Println(err)
} }
termSzPacket := fmt.Sprintf("%d %d", rows, cols) termSzPacket := fmt.Sprintf("%d %d", rows, cols)
conn.WritePacket([]byte(termSzPacket), xsnet.CSOTermSize) //nolint:errcheck conn.WritePacket([]byte(termSzPacket), xsnet.CSOTermSize)
} }
}() }()
ch <- syscall.SIGWINCH // Initial resize. ch <- syscall.SIGWINCH // Initial resize.

183
xs/xs.go
View file

@ -14,6 +14,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"os" "os"
@ -56,18 +57,6 @@ var (
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
const (
CmdExitedEarly = 2
XSNetDialFailed = 3
ErrReadingAuthReply = 253
ServerRejectedSecureProposal = 254
GeneralProtocolErr = 255
)
const (
DeadCharPrefix = 0x1d
)
// Praise Bob. Do not remove, lest ye lose Slack. // Praise Bob. Do not remove, lest ye lose Slack.
const bob = string("\r\n\r\n" + const bob = string("\r\n\r\n" +
"@@@@@@@^^~~~~~~~~~~~~~~~~~~~~^@@@@@@@@@\r\n" + "@@@@@@@^^~~~~~~~~~~~~~~~~~~~~^@@@@@@@@@\r\n" +
@ -165,7 +154,7 @@ func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err er
if rt, ok := dst.(io.ReaderFrom); ok { if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src) return rt.ReadFrom(src)
} }
*/ //nolint:gocritic,nolintlint */
if buf == nil { if buf == nil {
size := 32 * 1024 size := 32 * 1024
if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N { if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {
@ -186,8 +175,8 @@ func copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err er
// A repeat of 4 keys (conveniently 'dead' chars for most // A repeat of 4 keys (conveniently 'dead' chars for most
// interactive shells; here CTRL-]) shall introduce // interactive shells; here CTRL-]) shall introduce
// some special responses or actions on the client side. // some special responses or actions on the client side.
if seqPos < 4 { //nolint:gomnd if seqPos < 4 {
if buf[0] == DeadCharPrefix { if buf[0] == 0x1d {
seqPos++ seqPos++
} }
} else { } else {
@ -234,7 +223,7 @@ func GetSize() (cols, rows int, err error) {
if err != nil { if err != nil {
log.Println(err) log.Println(err)
cols, rows = 80, 24 // failsafe cols, rows = 80, 24 //failsafe
} else { } else {
n, err := fmt.Sscanf(string(out), "%d %d\n", &rows, &cols) n, err := fmt.Sscanf(string(out), "%d %d\n", &rows, &cols)
if n < 2 || if n < 2 ||
@ -268,7 +257,7 @@ func buildCmdRemoteToLocal(copyQuiet bool, copyLimitBPS uint, destPath string) (
} else { } else {
// TODO: Query remote side for total file/dir size // TODO: Query remote side for total file/dir size
bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d ", copyLimitBPS) bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d ", copyLimitBPS)
displayOpts := " -pre " //nolint:goconst displayOpts := " -pre "
cmd = xs.GetTool("bash") cmd = xs.GetTool("bash")
args = []string{"-c", "pv " + displayOpts + bandwidthInBytesPerSec + "| tar -xz -C " + destPath} args = []string{"-c", "pv " + displayOpts + bandwidthInBytesPerSec + "| tar -xz -C " + destPath}
} }
@ -316,7 +305,7 @@ func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (cap
} else { } else {
captureStderr = copyQuiet captureStderr = copyQuiet
bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d", copyLimitBPS) bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d", copyLimitBPS)
displayOpts := " -pre " //nolint:goconst,nolintlint displayOpts := " -pre "
cmd = xs.GetTool("bash") cmd = xs.GetTool("bash")
args = []string{"-c", xs.GetTool("tar") + " -cz -f /dev/stdout "} args = []string{"-c", xs.GetTool("tar") + " -cz -f /dev/stdout "}
files = strings.TrimSpace(files) files = strings.TrimSpace(files)
@ -366,9 +355,9 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool,
var c *exec.Cmd var c *exec.Cmd
// os.Clearenv() //os.Clearenv()
// os.Setenv("HOME", u.HomeDir) //os.Setenv("HOME", u.HomeDir)
// os.Setenv("TERM", "vt102") // TODO: server or client option? //os.Setenv("TERM", "vt102") // TODO: server or client option?
captureStderr, cmdName, cmdArgs := buildCmdLocalToRemote(copyQuiet, copyLimitBPS, strings.TrimSpace(files)) captureStderr, cmdName, cmdArgs := buildCmdLocalToRemote(copyQuiet, copyLimitBPS, strings.TrimSpace(files))
c = exec.Command(cmdName, cmdArgs...) // #nosec c = exec.Command(cmdName, cmdArgs...) // #nosec
@ -401,7 +390,7 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool,
if err != nil { if err != nil {
fmt.Println("cmd exited immediately. Cannot get cmd.Wait().ExitStatus()") fmt.Println("cmd exited immediately. Cannot get cmd.Wait().ExitStatus()")
err = errors.New("cmd exited prematurely") err = errors.New("cmd exited prematurely")
exitStatus = uint32(CmdExitedEarly) exitStatus = uint32(2)
} else { } else {
if err = c.Wait(); err != nil { if err = c.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok { if exiterr, ok := err.(*exec.ExitError); ok {
@ -421,7 +410,7 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool,
} }
// send CSOExitStatus to inform remote (server) end cp is done // send CSOExitStatus to inform remote (server) end cp is done
log.Println("Sending local exitStatus:", exitStatus) log.Println("Sending local exitStatus:", exitStatus)
r := make([]byte, 4) //nolint:gomnd r := make([]byte, 4)
binary.BigEndian.PutUint32(r, exitStatus) binary.BigEndian.PutUint32(r, exitStatus)
_, we := conn.WritePacket(r, xsnet.CSOExitStatus) _, we := conn.WritePacket(r, xsnet.CSOExitStatus)
if we != nil { if we != nil {
@ -429,7 +418,7 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool,
} }
// Do a final read for remote's exit status // Do a final read for remote's exit status
s := make([]byte, 4) //nolint:gomnd s := make([]byte, 4)
_, remErr := conn.Read(s) _, remErr := conn.Read(s)
if remErr != io.EOF && if remErr != io.EOF &&
!strings.Contains(remErr.Error(), "use of closed network") && !strings.Contains(remErr.Error(), "use of closed network") &&
@ -489,8 +478,8 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool,
// doShellMode begins an xs shell session (one-shot command or // doShellMode begins an xs shell session (one-shot command or
// interactive). // interactive).
func doShellMode(isInteractive bool, conn *xsnet.Conn, oldState *xs.State, rec *xs.Session) { func doShellMode(isInteractive bool, conn *xsnet.Conn, oldState *xs.State, rec *xs.Session) {
// Client reader (from server) goroutine //client reader (from server) goroutine
// Read remote end's stdout //Read remote end's stdout
wg.Add(1) wg.Add(1)
// #gv:s/label=\"doShellMode\$1\"/label=\"shellRemoteToStdin\"/ // #gv:s/label=\"doShellMode\$1\"/label=\"shellRemoteToStdin\"/
@ -613,7 +602,7 @@ func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, other
if strings.Contains(arg, ":") || strings.Contains(arg, "@") { if strings.Contains(arg, ":") || strings.Contains(arg, "@") {
fancyArg := strings.Split(flag.Arg(i), "@") fancyArg := strings.Split(flag.Arg(i), "@")
var fancyHostPath []string var fancyHostPath []string
if len(fancyArg) < 2 { //nolint:gomnd if len(fancyArg) < 2 {
//TODO: no user specified, use current //TODO: no user specified, use current
fancyUser = "[default:getUser]" fancyUser = "[default:getUser]"
fancyHostPath = strings.Split(fancyArg[0], ":") fancyHostPath = strings.Split(fancyArg[0], ":")
@ -639,8 +628,8 @@ func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, other
return fancyUser, fancyHost, fancyPath, isDest, otherArgs return fancyUser, fancyHost, fancyPath, isDest, otherArgs
} }
func launchTuns(conn *xsnet.Conn /*remoteHost string,*/, tuns string) { func launchTuns(conn *xsnet.Conn, remoteHost string, tuns string) {
/*remAddrs, _ := net.LookupHost(remoteHost)*/ //nolint:gocritic,nolintlint /*remAddrs, _ := net.LookupHost(remoteHost)*/
if tuns == "" { if tuns == "" {
return return
@ -689,12 +678,12 @@ func main() { //nolint: funlen, gocyclo
var ( var (
isInteractive bool isInteractive bool
vopt bool vopt bool
gopt bool // true: login via password, asking server to generate authToken gopt bool //login via password, asking server to generate authToken
dbg bool dbg bool
shellMode bool // true: act as shell, false: file copier shellMode bool // if true act as shell, else file copier
cipherAlg string cipherAlg string //cipher alg
hmacAlg string hmacAlg string //hmac alg
kexAlg string kexAlg string //KEX/KEM alg
server string server string
port uint port uint
cmdStr string cmdStr string
@ -714,7 +703,7 @@ func main() { //nolint: funlen, gocyclo
op []byte op []byte
) )
// === Common (xs and xc) option parsing //=== Common (xs and xc) option parsing
flag.BoolVar(&vopt, "v", false, "show version") flag.BoolVar(&vopt, "v", false, "show version")
flag.BoolVar(&dbg, "d", false, "debug logging") flag.BoolVar(&dbg, "d", false, "debug logging")
@ -742,18 +731,18 @@ func main() { //nolint: funlen, gocyclo
KEX_FRODOKEM_1344SHAKE KEX_FRODOKEM_1344SHAKE
KEX_FRODOKEM_976AES KEX_FRODOKEM_976AES
KEX_FRODOKEM_976SHAKE`) KEX_FRODOKEM_976SHAKE`)
flag.StringVar(&kcpMode, "K", "unused", "KCP `alg`, one of [KCP_NONE | KCP_AES | KCP_BLOWFISH | KCP_CAST5 | KCP_SM4 | KCP_SALSA20 | KCP_SIMPLEXOR | KCP_TEA | KCP_3DES | KCP_TWOFISH | KCP_XTEA] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP") //nolint:lll flag.StringVar(&kcpMode, "K", "unused", "KCP `alg`, one of [KCP_NONE | KCP_AES | KCP_BLOWFISH | KCP_CAST5 | KCP_SM4 | KCP_SALSA20 | KCP_SIMPLEXOR | KCP_TEA | KCP_3DES | KCP_TWOFISH | KCP_XTEA] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP")
flag.UintVar(&port, "p", 2000, "``port") //nolint:gomnd,lll flag.UintVar(&port, "p", 2000, "``port")
//nolint:gocritic,nolintlint // flag.StringVar(&authCookie, "a", "", "auth cookie") //flag.StringVar(&authCookie, "a", "", "auth cookie")
flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts") flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min `msecs`") //nolint:gomnd flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min `msecs`")
flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max `msecs`") //nolint:gomnd flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max `msecs`")
flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max `bytes`") //nolint:gomnd flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max `bytes`")
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to <`file`>") flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to <`file`>")
flag.StringVar(&memprofile, "memprofile", "", "write memory profile to <`file`>") flag.StringVar(&memprofile, "memprofile", "", "write memory profile to <`file`>")
// === xc vs. xs option parsing //=== xc vs. xs option parsing
// Find out what program we are (shell or copier) // Find out what program we are (shell or copier)
myPath := strings.Split(os.Args[0], string(os.PathSeparator)) myPath := strings.Split(os.Args[0], string(os.PathSeparator))
@ -770,7 +759,7 @@ func main() { //nolint: funlen, gocyclo
flag.Usage = usageShell flag.Usage = usageShell
} else { } else {
flag.BoolVar(&copyQuiet, "q", false, "do not output progress bar during copy") flag.BoolVar(&copyQuiet, "q", false, "do not output progress bar during copy")
flag.UintVar(&copyLimitBPS, "L", 8589934592, "copy max rate in bytes per sec") //nolint:gomnd flag.UintVar(&copyLimitBPS, "L", 8589934592, "copy max rate in bytes per sec")
flag.Usage = usageCp flag.Usage = usageCp
} }
flag.Parse() flag.Parse()
@ -780,7 +769,7 @@ func main() { //nolint: funlen, gocyclo
exitWithStatus(0) exitWithStatus(0)
} }
// === Profiling instrumentation //=== Profiling instrumentation
if cpuprofile != "" { if cpuprofile != "" {
f, err := os.Create(cpuprofile) f, err := os.Create(cpuprofile)
@ -790,19 +779,19 @@ func main() { //nolint: funlen, gocyclo
defer f.Close() defer f.Close()
fmt.Println("StartCPUProfile()") fmt.Println("StartCPUProfile()")
if err := pprof.StartCPUProfile(f); err != nil { if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err) //nolint:gocritic log.Fatal("could not start CPU profile: ", err)
} else { } else {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
go func() { http.ListenAndServe("localhost:6060", nil) }() //nolint:errcheck,gosec go func() { http.ListenAndServe("localhost:6060", nil) }()
} }
// === User, host, port and path args for file operations, if applicable //=== User, host, port and path args for file operations, if applicable
remoteUser, remoteHost, tmpPath, pathIsDest, otherArgs := remoteUser, remoteHost, tmpPath, pathIsDest, otherArgs :=
parseNonSwitchArgs(flag.Args()) parseNonSwitchArgs(flag.Args())
//nolint:gocritic,nolintlint // fmt.Println("otherArgs:", otherArgs) //fmt.Println("otherArgs:", otherArgs)
// Set defaults if user doesn't specify user, path or port // Set defaults if user doesn't specify user, path or port
var uname string var uname string
@ -820,7 +809,7 @@ func main() { //nolint: funlen, gocyclo
tmpPath = "." tmpPath = "."
} }
// === Copy mode arg and copy src/dest setup //=== Copy mode arg and copy src/dest setup
var fileArgs string var fileArgs string
if !shellMode /*&& tmpPath != ""*/ { if !shellMode /*&& tmpPath != ""*/ {
@ -853,15 +842,15 @@ func main() { //nolint: funlen, gocyclo
} }
} }
// === Do some final option consistency checks //=== Do some final option consistency checks
//nolint:gocritic,nolintlint // fmt.Println("server finally is:", server) //fmt.Println("server finally is:", server)
if flag.NFlag() == 0 && server == "" { if flag.NFlag() == 0 && server == "" {
flag.Usage() flag.Usage()
exitWithStatus(0) exitWithStatus(0)
} }
if cmdStr != "" && (len(copySrc) != 0 || copyDst != "") { if len(cmdStr) != 0 && (len(copySrc) != 0 || len(copyDst) != 0) {
log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both") log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both")
} }
@ -874,18 +863,18 @@ func main() { //nolint: funlen, gocyclo
if dbg { if dbg {
log.SetOutput(Log) log.SetOutput(Log)
} else { } else {
log.SetOutput(io.Discard) log.SetOutput(ioutil.Discard)
} }
// === Auth token fetch for login //=== Auth token fetch for login
if !gopt { if !gopt {
// See if we can log in via an auth token // See if we can log in via an auth token
u, _ := user.Current() u, _ := user.Current()
ab, aerr := os.ReadFile(fmt.Sprintf("%s/.xs_id", u.HomeDir)) ab, aerr := ioutil.ReadFile(fmt.Sprintf("%s/.xs_id", u.HomeDir))
if aerr == nil { if aerr == nil {
for _, line := range strings.Split(string(ab), "\n") { for _, line := range strings.Split(string(ab), "\n") {
line += "\n" line = line + "\n"
idx := strings.Index(line, remoteHost+":"+uname) idx := strings.Index(line, remoteHost+":"+uname)
if idx >= 0 { if idx >= 0 {
line = line[idx:] line = line[idx:]
@ -905,8 +894,8 @@ func main() { //nolint: funlen, gocyclo
} }
runtime.GC() runtime.GC()
// === Enforce some sane min/max vals on chaff flags //=== Enforce some sane min/max vals on chaff flags
if chaffFreqMin < 2 { //nolint:gomnd if chaffFreqMin < 2 {
chaffFreqMin = 2 chaffFreqMin = 2
} }
if chaffFreqMax == 0 { if chaffFreqMax == 0 {
@ -916,7 +905,7 @@ func main() { //nolint: funlen, gocyclo
chaffBytesMax = 64 chaffBytesMax = 64
} }
// === Shell vs. Copy mode chaff and cmd setup //=== Shell vs. Copy mode chaff and cmd setup
if shellMode { if shellMode {
// We must make the decision about interactivity before Dial() // We must make the decision about interactivity before Dial()
@ -926,7 +915,7 @@ func main() { //nolint: funlen, gocyclo
op = []byte{'A'} op = []byte{'A'}
chaffFreqMin = 2 chaffFreqMin = 2
chaffFreqMax = 10 chaffFreqMax = 10
} else if cmdStr == "" { } else if len(cmdStr) == 0 {
op = []byte{'s'} op = []byte{'s'}
isInteractive = true isInteractive = true
} else { } else {
@ -947,18 +936,18 @@ func main() { //nolint: funlen, gocyclo
// client->server file copy // client->server file copy
// src file list is in copySrc // src file list is in copySrc
op = []byte{'D'} op = []byte{'D'}
//nolint:gocritic,nolintlint // fmt.Println("client->server copy:", string(copySrc), "->", copyDst) //fmt.Println("client->server copy:", string(copySrc), "->", copyDst)
cmdStr = copyDst cmdStr = copyDst
} else { } else {
// server->client file copy // server->client file copy
// remote src file(s) in copyDsr // remote src file(s) in copyDsr
op = []byte{'S'} op = []byte{'S'}
//nolint:gocritic,nolintlint // fmt.Println("server->client copy:", string(copySrc), "->", copyDst) //fmt.Println("server->client copy:", string(copySrc), "->", copyDst)
cmdStr = string(copySrc) cmdStr = string(copySrc)
} }
} }
// === TCP / KCP Dial setup //=== TCP / KCP Dial setup
proto := "tcp" proto := "tcp"
if kcpMode != "unused" { if kcpMode != "unused" {
@ -967,10 +956,10 @@ func main() { //nolint: funlen, gocyclo
conn, err := xsnet.Dial(proto, server, cipherAlg, hmacAlg, kexAlg, kcpMode) conn, err := xsnet.Dial(proto, server, cipherAlg, hmacAlg, kexAlg, kcpMode)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
exitWithStatus(XSNetDialFailed) exitWithStatus(3)
} }
// === Shell terminal mode (Shell vs. Copy) setup //=== Shell terminal mode (Shell vs. Copy) setup
// Set stdin in raw mode if it's an interactive session // Set stdin in raw mode if it's an interactive session
// TODO: send flag to server side indicating this // TODO: send flag to server side indicating this
@ -978,7 +967,7 @@ func main() { //nolint: funlen, gocyclo
var oldState *xs.State var oldState *xs.State
defer conn.Close() defer conn.Close()
// === From this point on, conn is a secure encrypted channel //=== From this point on, conn is a secure encrypted channel
if shellMode { if shellMode {
if isatty.IsTerminal(os.Stdin.Fd()) { if isatty.IsTerminal(os.Stdin.Fd()) {
@ -994,20 +983,20 @@ func main() { //nolint: funlen, gocyclo
} }
} }
// === Login phase //=== Login phase
// Start login timeout here and disconnect if user/pass phase stalls // Start login timeout here and disconnect if user/pass phase stalls
// iloginImpatience := time.AfterFunc(20*time.Second, func() { //iloginImpatience := time.AfterFunc(20*time.Second, func() {
// i fmt.Printf(" .. [you still there? Waiting for a password.]") //i fmt.Printf(" .. [you still there? Waiting for a password.]")
// i}) //i})
loginTimeout := time.AfterFunc(30*time.Second, func() { //nolint:gomnd loginTimeout := time.AfterFunc(30*time.Second, func() {
restoreTermState(oldState) restoreTermState(oldState)
fmt.Printf(" .. [login timeout]\n") fmt.Printf(" .. [login timeout]\n")
exitWithStatus(xsnet.CSOLoginTimeout) exitWithStatus(xsnet.CSOLoginTimeout)
}) })
if authCookie == "" { if len(authCookie) == 0 {
// No auth token, prompt for password //No auth token, prompt for password
fmt.Printf("Gimme cookie:") fmt.Printf("Gimme cookie:")
ab, e := xs.ReadPassword(os.Stdin.Fd()) ab, e := xs.ReadPassword(os.Stdin.Fd())
fmt.Printf("\r\n") fmt.Printf("\r\n")
@ -1017,43 +1006,43 @@ func main() { //nolint: funlen, gocyclo
authCookie = string(ab) authCookie = string(ab)
} }
//nolint:gocritic,nolintlint // i_ = loginImpatience.Stop() //i_ = loginImpatience.Stop()
_ = loginTimeout.Stop() _ = loginTimeout.Stop()
// Security scrub // Security scrub
runtime.GC() runtime.GC()
// === Session param and TERM setup //=== Session param and TERM setup
// Set up session params and send over to server // Set up session params and send over to server
rec := xs.NewSession(op, []byte(uname), []byte(remoteHost), []byte(os.Getenv("TERM")), []byte(cmdStr), []byte(authCookie), 0) rec := xs.NewSession(op, []byte(uname), []byte(remoteHost), []byte(os.Getenv("TERM")), []byte(cmdStr), []byte(authCookie), 0)
sendErr := sendSessionParams(&conn, rec) sendErr := sendSessionParams(&conn, rec)
if sendErr != nil { if sendErr != nil {
restoreTermState(oldState) restoreTermState(oldState)
rec.SetStatus(ServerRejectedSecureProposal) rec.SetStatus(254)
fmt.Fprintln(os.Stderr, "Error: server rejected secure proposal params or login timed out") fmt.Fprintln(os.Stderr, "Error: server rejected secure proposal params or login timed out")
exitWithStatus(int(rec.Status())) exitWithStatus(int(rec.Status()))
//nolint:gocritic,nolintlint // log.Fatal(sendErr) //log.Fatal(sendErr)
} }
// Security scrub //Security scrub
authCookie = "" //nolint: ineffassign authCookie = "" //nolint: ineffassign
runtime.GC() runtime.GC()
// === Login Auth //=== Login Auth
// === Read auth reply from server //=== Read auth reply from server
authReply := make([]byte, 1) // bool: 0 = fail, 1 = pass authReply := make([]byte, 1) // bool: 0 = fail, 1 = pass
_, err = conn.Read(authReply) _, err = conn.Read(authReply)
if err != nil { if err != nil {
// === Exit if auth reply not received //=== Exit if auth reply not received
fmt.Fprintln(os.Stderr, "Error reading auth reply") fmt.Fprintln(os.Stderr, "Error reading auth reply")
rec.SetStatus(ErrReadingAuthReply) rec.SetStatus(255)
} else if authReply[0] == 0 { } else if authReply[0] == 0 {
// === .. or if auth failed //=== .. or if auth failed
fmt.Fprintln(os.Stderr, rejectUserMsg()) fmt.Fprintln(os.Stderr, rejectUserMsg())
rec.SetStatus(GeneralProtocolErr) rec.SetStatus(255)
} else { } else {
// === Set up chaffing to server //=== Set up chaffing to server
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
if chaffEnabled { if chaffEnabled {
// #gv:s/label=\"main\$2\"/label=\"deferCloseChaff\"/ // #gv:s/label=\"main\$2\"/label=\"deferCloseChaff\"/
@ -1063,16 +1052,16 @@ func main() { //nolint: funlen, gocyclo
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
} }
// === (goroutine) Start keepAliveWorker for tunnels //=== (goroutine) Start keepAliveWorker for tunnels
// #gv:s/label=\"main\$1\"/label=\"tunKeepAlive\"/ // #gv:s/label=\"main\$1\"/label=\"tunKeepAlive\"/
// TODO:.gv:main:1:tunKeepAlive // TODO:.gv:main:1:tunKeepAlive
// [1]: better to always send tunnel keepAlives even if client didn't specify //[1]: better to always send tunnel keepAlives even if client didn't specify
// any, to prevent listeners from knowing this. // any, to prevent listeners from knowing this.
// [1] if tunSpecStr != "" { //[1] if tunSpecStr != "" {
keepAliveWorker := func() { keepAliveWorker := func() {
for { for {
// Add a bit of jitter to keepAlive so it doesn't stand out quite as much // Add a bit of jitter to keepAlive so it doesn't stand out quite as much
time.Sleep(time.Duration(2000-rand.Intn(200)) * time.Millisecond) //nolint:gosec,gomnd time.Sleep(time.Duration(2000-rand.Intn(200)) * time.Millisecond) //nolint:gosec
// FIXME: keepAlives should probably have small random packet len/data as well // FIXME: keepAlives should probably have small random packet len/data as well
// to further obscure them vs. interactive or tunnel data // to further obscure them vs. interactive or tunnel data
// keepAlives must be >=2 bytes, due to processing elsewhere // keepAlives must be >=2 bytes, due to processing elsewhere
@ -1080,15 +1069,15 @@ func main() { //nolint: funlen, gocyclo
} }
} }
go keepAliveWorker() go keepAliveWorker()
// [1]} //[1]}
// === Session entry (shellMode or copyMode) //=== Session entry (shellMode or copyMode)
if shellMode { if shellMode {
// === (shell) launch tunnels //=== (shell) launch tunnels
launchTuns(&conn /*remoteHost,*/, tunSpecStr) launchTuns(&conn, remoteHost, tunSpecStr)
doShellMode(isInteractive, &conn, oldState, rec) doShellMode(isInteractive, &conn, oldState, rec)
} else { } else {
// === (.. or file copy) //=== (.. or file copy)
s, _ := doCopyMode(&conn, pathIsDest, fileArgs, copyQuiet, copyLimitBPS, rec) s, _ := doCopyMode(&conn, pathIsDest, fileArgs, copyQuiet, copyLimitBPS, rec)
rec.SetStatus(s) rec.SetStatus(s)
} }
@ -1104,7 +1093,7 @@ func main() { //nolint: funlen, gocyclo
oldState = nil oldState = nil
} }
// === Exit //=== Exit
exitWithStatus(int(rec.Status())) exitWithStatus(int(rec.Status()))
} }
@ -1139,7 +1128,7 @@ func exitWithStatus(status int) {
defer f.Close() defer f.Close()
runtime.GC() // get up-to-date statistics runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil { if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err) //nolint:gocritic log.Fatal("could not write memory profile: ", err)
} }
} }

View file

@ -1195,7 +1195,6 @@ func (hc Conn) Read(b []byte) (n int, err error) {
var hmacIn [HMAC_CHK_SZ]uint8 var hmacIn [HMAC_CHK_SZ]uint8
var payloadLen uint32 var payloadLen uint32
//------------- Read ctrl/status opcode --------------------
// Read ctrl/status opcode (CSOHmacInvalid on hmac mismatch) // Read ctrl/status opcode (CSOHmacInvalid on hmac mismatch)
err = binary.Read(*hc.c, binary.BigEndian, &ctrlStatOp) err = binary.Read(*hc.c, binary.BigEndian, &ctrlStatOp)
if err != nil { if err != nil {
@ -1216,9 +1215,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hc.Close() hc.Close()
return 0, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **") return 0, errors.New("** ALERT - remote end detected HMAC mismatch - possible channel tampering **")
} }
//----------------------------------------------------------
//------------------ Read HMAC len ------------------------
// Read the hmac and payload len first // Read the hmac and payload len first
err = binary.Read(*hc.c, binary.BigEndian, &hmacIn) err = binary.Read(*hc.c, binary.BigEndian, &hmacIn)
if err != nil { if err != nil {
@ -1233,9 +1230,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(etxt) logger.LogDebug(etxt)
return 0, errors.New(etxt) return 0, errors.New(etxt)
} }
//----------------------------------------------------------
//------------------ Read Payload len ---------------------
err = binary.Read(*hc.c, binary.BigEndian, &payloadLen) err = binary.Read(*hc.c, binary.BigEndian, &payloadLen)
if err != nil { if err != nil {
if err.Error() == "EOF" { if err.Error() == "EOF" {
@ -1249,7 +1244,6 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(etxt) logger.LogDebug(etxt)
return 0, errors.New(etxt) return 0, errors.New(etxt)
} }
//----------------------------------------------------------
if payloadLen > MAX_PAYLOAD_LEN { if payloadLen > MAX_PAYLOAD_LEN {
logger.LogDebug(fmt.Sprintf("[Insane payloadLen:%v]\n", payloadLen)) logger.LogDebug(fmt.Sprintf("[Insane payloadLen:%v]\n", payloadLen))
@ -1257,7 +1251,6 @@ func (hc Conn) Read(b []byte) (n int, err error) {
return 1, errors.New("Insane payloadLen") return 1, errors.New("Insane payloadLen")
} }
//-------------------- Read Payload ------------------------
var payloadBytes = make([]byte, payloadLen) var payloadBytes = make([]byte, payloadLen)
n, err = io.ReadFull(*hc.c, payloadBytes) n, err = io.ReadFull(*hc.c, payloadBytes)
if err != nil { if err != nil {
@ -1272,14 +1265,12 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(etxt) logger.LogDebug(etxt)
return 0, errors.New(etxt) return 0, errors.New(etxt)
} }
//----------------------------------------------------------
if hc.logCipherText { if hc.logCipherText {
log.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(payloadBytes[:n])) log.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(payloadBytes[:n]))
} }
//fmt.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(payloadBytes[:n])) //fmt.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(payloadBytes[:n]))
//---------------- Verify Payload via HMAC -----------------
hc.rm.Write(payloadBytes) // Calc hmac on received data hc.rm.Write(payloadBytes) // Calc hmac on received data
hTmp := hc.rm.Sum(nil)[0:HMAC_CHK_SZ] hTmp := hc.rm.Sum(nil)[0:HMAC_CHK_SZ]
//log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp) //log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
@ -1289,9 +1280,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(fmt.Sprintln("** ALERT - detected HMAC mismatch, possible channel tampering **")) logger.LogDebug(fmt.Sprintln("** ALERT - detected HMAC mismatch, possible channel tampering **"))
_, _ = (*hc.c).Write([]byte{CSOHmacInvalid}) _, _ = (*hc.c).Write([]byte{CSOHmacInvalid})
} }
//----------------------------------------------------------
//------------------- Decrypt Payload ----------------------
db := bytes.NewBuffer(payloadBytes[:n]) //copying payloadBytes to db db := bytes.NewBuffer(payloadBytes[:n]) //copying payloadBytes to db
// The StreamReader acts like a pipe, decrypting // The StreamReader acts like a pipe, decrypting
// whatever is available and forwarding the result // whatever is available and forwarding the result
@ -1300,7 +1289,6 @@ func (hc Conn) Read(b []byte) (n int, err error) {
// The caller isn't necessarily reading the full payload so we need // The caller isn't necessarily reading the full payload so we need
// to decrypt to an intermediate buffer, draining it on demand of caller // to decrypt to an intermediate buffer, draining it on demand of caller
decryptN, err := rs.Read(payloadBytes) decryptN, err := rs.Read(payloadBytes)
//----------------------------------------------------------
if hc.logPlainText { if hc.logPlainText {
log.Printf(" <:ptext:\r\n%s\r\n", hex.Dump(payloadBytes[:n])) log.Printf(" <:ptext:\r\n%s\r\n", hex.Dump(payloadBytes[:n]))
@ -1309,7 +1297,6 @@ func (hc Conn) Read(b []byte) (n int, err error) {
log.Println("xsnet.Read():", err) log.Println("xsnet.Read():", err)
//panic(err) //panic(err)
} else { } else {
//------------ Discard Padding ---------------------
// Padding: Read padSide, padLen, (padding | d) or (d | padding) // Padding: Read padSide, padLen, (padding | d) or (d | padding)
padSide := payloadBytes[0] padSide := payloadBytes[0]
padLen := payloadBytes[1] padLen := payloadBytes[1]
@ -1320,17 +1307,15 @@ func (hc Conn) Read(b []byte) (n int, err error) {
} else { } else {
payloadBytes = payloadBytes[0 : len(payloadBytes)-int(padLen)] payloadBytes = payloadBytes[0 : len(payloadBytes)-int(padLen)]
} }
//--------------------------------------------------
switch ctrlStatOp { // Throw away pkt if it's chaff (ie., caller to Read() won't see this data)
case CSOChaff: if ctrlStatOp == CSOChaff {
// Throw away pkt if it's chaff (ie., caller to Read() won't see this data)
log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN) log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN)
case CSOTermSize: } else if ctrlStatOp == CSOTermSize {
fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols) fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols)
log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols) log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
hc.WinCh <- WinSize{hc.Rows, hc.Cols} hc.WinCh <- WinSize{hc.Rows, hc.Cols}
case CSOExitStatus: } else if ctrlStatOp == CSOExitStatus {
if len(payloadBytes) > 0 { if len(payloadBytes) > 0 {
hc.SetStatus(CSOType(binary.BigEndian.Uint32(payloadBytes))) hc.SetStatus(CSOType(binary.BigEndian.Uint32(payloadBytes)))
} else { } else {
@ -1338,7 +1323,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hc.SetStatus(CSETruncCSO) hc.SetStatus(CSETruncCSO)
} }
hc.Close() hc.Close()
case CSOTunSetup: } else if ctrlStatOp == CSOTunSetup {
// server side tunnel setup in response to client // server side tunnel setup in response to client
lport := binary.BigEndian.Uint16(payloadBytes[0:2]) lport := binary.BigEndian.Uint16(payloadBytes[0:2])
rport := binary.BigEndian.Uint16(payloadBytes[2:4]) rport := binary.BigEndian.Uint16(payloadBytes[2:4])
@ -1350,7 +1335,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(fmt.Sprintf("[Server] Got CSOTunSetup [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Server] Got CSOTunSetup [%d:%d]", lport, rport))
} }
(*hc.tuns)[rport].Ctl <- 'd' // Dial() rport (*hc.tuns)[rport].Ctl <- 'd' // Dial() rport
case CSOTunSetupAck: } else if ctrlStatOp == CSOTunSetupAck {
lport := binary.BigEndian.Uint16(payloadBytes[0:2]) lport := binary.BigEndian.Uint16(payloadBytes[0:2])
rport := binary.BigEndian.Uint16(payloadBytes[2:4]) rport := binary.BigEndian.Uint16(payloadBytes[2:4])
if _, ok := (*hc.tuns)[rport]; !ok { if _, ok := (*hc.tuns)[rport]; !ok {
@ -1361,7 +1346,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
logger.LogDebug(fmt.Sprintf("[Client] Got CSOTunSetupAck [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Client] Got CSOTunSetupAck [%d:%d]", lport, rport))
} }
(*hc.tuns)[rport].Ctl <- 'a' // Listen() for lport connection (*hc.tuns)[rport].Ctl <- 'a' // Listen() for lport connection
case CSOTunRefused: } else if ctrlStatOp == CSOTunRefused {
// client side receiving CSOTunRefused means the remote side // client side receiving CSOTunRefused means the remote side
// could not dial() rport. So we cannot yet listen() // could not dial() rport. So we cannot yet listen()
// for client-side on lport. // for client-side on lport.
@ -1373,7 +1358,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
} else { } else {
logger.LogDebug(fmt.Sprintf("[Client] CSOTunRefused on already-closed tun [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Client] CSOTunRefused on already-closed tun [%d:%d]", lport, rport))
} }
case CSOTunDisconn: } else if ctrlStatOp == CSOTunDisconn {
// server side's rport has disconnected (server lost) // server side's rport has disconnected (server lost)
lport := binary.BigEndian.Uint16(payloadBytes[0:2]) lport := binary.BigEndian.Uint16(payloadBytes[0:2])
rport := binary.BigEndian.Uint16(payloadBytes[2:4]) rport := binary.BigEndian.Uint16(payloadBytes[2:4])
@ -1383,7 +1368,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
} else { } else {
logger.LogDebug(fmt.Sprintf("[Client] CSOTunDisconn on already-closed tun [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Client] CSOTunDisconn on already-closed tun [%d:%d]", lport, rport))
} }
case CSOTunHangup: } else if ctrlStatOp == CSOTunHangup {
// client side's lport has hung up // client side's lport has hung up
lport := binary.BigEndian.Uint16(payloadBytes[0:2]) lport := binary.BigEndian.Uint16(payloadBytes[0:2])
rport := binary.BigEndian.Uint16(payloadBytes[2:4]) rport := binary.BigEndian.Uint16(payloadBytes[2:4])
@ -1393,7 +1378,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
} else { } else {
logger.LogDebug(fmt.Sprintf("[Server] CSOTunHangup to already-closed tun [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Server] CSOTunHangup to already-closed tun [%d:%d]", lport, rport))
} }
case CSOTunData: } else if ctrlStatOp == CSOTunData {
lport := binary.BigEndian.Uint16(payloadBytes[0:2]) lport := binary.BigEndian.Uint16(payloadBytes[0:2])
rport := binary.BigEndian.Uint16(payloadBytes[2:4]) rport := binary.BigEndian.Uint16(payloadBytes[2:4])
//fmt.Printf("[Got CSOTunData: [lport %d:rport %d] data:%v\n", lport, rport, payloadBytes[4:]) //fmt.Printf("[Got CSOTunData: [lport %d:rport %d] data:%v\n", lport, rport, payloadBytes[4:])
@ -1406,7 +1391,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
} else { } else {
logger.LogDebug(fmt.Sprintf("[Attempt to write data to closed tun [%d:%d]", lport, rport)) logger.LogDebug(fmt.Sprintf("[Attempt to write data to closed tun [%d:%d]", lport, rport))
} }
case CSOTunKeepAlive: } else if ctrlStatOp == CSOTunKeepAlive {
// client side has sent keepalive for tunnels -- if client // client side has sent keepalive for tunnels -- if client
// dies or exits unexpectedly the absence of this will // dies or exits unexpectedly the absence of this will
// let the server know to hang up on Dial()ed server rports. // let the server know to hang up on Dial()ed server rports.
@ -1417,9 +1402,9 @@ func (hc Conn) Read(b []byte) (n int, err error) {
t.KeepAlive = 0 t.KeepAlive = 0
hc.Unlock() hc.Unlock()
} }
case CSONone: } else if ctrlStatOp == CSONone {
hc.dBuf.Write(payloadBytes) hc.dBuf.Write(payloadBytes)
default: } else {
logger.LogDebug(fmt.Sprintf("[Unknown CSOType:%d]", ctrlStatOp)) logger.LogDebug(fmt.Sprintf("[Unknown CSOType:%d]", ctrlStatOp))
} }
} }