From e02764bf4b14235ed7602d41b30dc83dffa81440 Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Fri, 14 Sep 2018 11:58:10 -0700 Subject: [PATCH] .hkexsh_id file supports multiple authtokens (multi remote hosts, aliases for same remote host) --- TODO.txt | 7 ++++--- hkexauth.go | 23 ++++++++++++++++++++-- hkexsh/hkexsh.go | 48 +++++++++++++++++++++++++++++++++++----------- hkexshd/hkexshd.go | 9 ++++++--- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/TODO.txt b/TODO.txt index c9a0df3..6b187fe 100644 --- a/TODO.txt +++ b/TODO.txt @@ -20,9 +20,10 @@ Features - (IN PROGRESS) auth tokens to allow scripted hkexsh/hkexcp use * ~/.hkexsh_id file with multiple (host:token) entries (Currently only one supported - need to support multiple lines for - multiple dest servers; also consider client sending host/ip used - to connect to server, so it can ensure the auth token matches that - used as servers can potentially be reached by multiple hostnames/IPs) + multiple dest servers; client sends host/ip used to connect to server, + so multihomed servers can still be specified, with separate entries + stored in both server/client ~/.hkexsh_id files. Use connhost here to + match against proper entry.) - hktun - tunnelling - multiple tunnel sessions co-existing w/shell sessions Alternate transports for hkexsh.Conn - HTTP-mimicking traffic, ICMP, ... ? diff --git a/hkexauth.go b/hkexauth.go index da09c0b..7806512 100644 --- a/hkexauth.go +++ b/hkexauth.go @@ -17,6 +17,7 @@ import ( "log" "os/user" "runtime" + "strings" "github.com/jameskeane/bcrypt" ) @@ -69,6 +70,7 @@ func AuthUserByPasswd(username string, auth string, fname string) (valid bool, a } func AuthUserByToken(username string, connhostname string, auth string) (valid bool) { + auth = strings.TrimSpace(auth) u, ue := user.Lookup(username) if ue != nil { return false @@ -80,8 +82,25 @@ func AuthUserByToken(username string, connhostname string, auth string) (valid b return false } - if string(b) == auth { - return true + r := csv.NewReader(bytes.NewReader(b)) + + r.Comma = ':' + r.Comment = '#' + r.FieldsPerRecord = 2 // connhost:authtoken + for { + record, err := r.Read() + if err == io.EOF { + return false + } + record[0] = strings.TrimSpace(record[0]) + record[1] = strings.TrimSpace(record[1]) + fmt.Println("auth:", auth, "record:", + strings.Join([]string{record[0], record[1]}, ":")) + + if (connhostname == record[0]) && + (auth == strings.Join([]string{record[0], record[1]}, ":")) { + return true + } } return } diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index a7b9fd5..592fbe9 100755 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -320,7 +320,8 @@ func rejectUserMsg() string { func main() { version := "0.2pre (NO WARRANTY)" var vopt bool - var aopt bool + var aopt bool //login using authToken + var gopt bool //login via password, asking server to generate authToken var dbg bool var shellMode bool // if true act as shell, else file copier var cAlg string @@ -358,7 +359,8 @@ func main() { // hkexsh accepts a command (-x) but not // a srcpath (-r) or dstpath (-t) flag.StringVar(&cmdStr, "x", "", "`command` to run (if not specified run interactive shell)") - flag.BoolVar(&aopt, "a", false, "return autologin token from server") + flag.BoolVar(&aopt, "a", false, "login using auth token") + flag.BoolVar(&gopt, "g", false, "ask server to generate authtoken") shellMode = true flag.Usage = UsageShell } else { @@ -445,20 +447,44 @@ func main() { log.SetOutput(ioutil.Discard) } - // See if we can log in via an auth token - u, _ := user.Current() - ab, aerr := ioutil.ReadFile(fmt.Sprintf("%s/.hkexsh_id", u.HomeDir)) - if aerr == nil { - authCookie = string(ab) - // Security scrub - ab = nil - runtime.GC() + if aopt && gopt { + fmt.Fprintln(os.Stderr, + "Error: use -g first to generate an authtoken,", + " then -a to login using it.") + os.Exit(1) + } + + if !gopt { + // See if we can log in via an auth token + u, _ := user.Current() + ab, aerr := ioutil.ReadFile(fmt.Sprintf("%s/.hkexsh_id", u.HomeDir)) + if aerr == nil { + //authCookie = string(ab) + idx := strings.Index(string(ab), remoteHost) + if idx >= 0 { + ab = ab[idx:] + } else { + fmt.Fprintln(os.Stderr, "ERROR: no matching authtoken") + os.Exit(1) + } + entries := strings.SplitN(string(ab), "\n", -1) + //if len(entries) > 0 { + fmt.Println("entries[0]:", entries[0]) + authCookie = strings.TrimSpace(entries[0]) + //} else { + // fmt.Fprintln(os.Stderr, "ERROR: no matching authtoken") + // os.Exit(1) + //} + // Security scrub + ab = nil + runtime.GC() + } } if shellMode { // We must make the decision about interactivity before Dial() // as it affects chaffing behaviour. 20180805 - if aopt { + if gopt { op = []byte{'A'} chaffFreqMin = 2 chaffFreqMax = 10 diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index 650cbb8..fdc4174 100755 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -12,6 +12,7 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" + "errors" "flag" "fmt" "io" @@ -106,7 +107,8 @@ func runClientToServerCopyAs(who, ttype string, conn hkexnet.Conn, fpath string, // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { exitStatus = uint32(status.ExitStatus()) - log.Printf("Exit Status: %d", exitStatus) + err = errors.New("cmd returned nonzero status") + fmt.Printf("Exit Status: %d\n", exitStatus) } } } @@ -495,7 +497,7 @@ func main() { hname := strings.Split(addr.String(), ":")[0] log.Printf("[Generating autologin token for [%s@%s]]\n", rec.Who(), hname) token := GenAuthToken(string(rec.Who()), string(rec.ConnHost())) - tokenCmd := fmt.Sprintf("echo \"%s\" | tee ~/.hkexsh_id", token) + tokenCmd := fmt.Sprintf("echo \"%s\" | tee -a ~/.hkexsh_id", token) runErr, cmdStatus := runShellAs(string(rec.Who()), string(rec.TermType()), tokenCmd, false, hc, chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF @@ -554,10 +556,11 @@ func main() { // Clear current op so user can enter next, or EOF rec.SetOp([]byte{0}) if runErr != nil { - log.Printf("[Error spawning cp for %s@%s]\n", rec.Who(), hname) + log.Printf("[Error running cp for %s@%s]\n", rec.Who(), hname) } else { log.Printf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus) } + fmt.Println("cmdStatus:", cmdStatus) hc.SetStatus(cmdStatus) } else if rec.Op()[0] == 'S' { // File copy (src) operation - server copy to client