libf %!s(int64=2) %!d(string=hai) anos
pai
achega
785188a55a
Modificáronse 7 ficheiros con 317 adicións e 72 borrados
  1. 16 0
      .vscode/launch.json
  2. 11 9
      go.mod
  3. 12 2
      go.sum
  4. 101 18
      msh/client.go
  5. 24 3
      msh/config.go
  6. 153 40
      msh/main.go
  7. BIN=BIN
      msh/msh

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch Package",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": "${fileDirname}",
+            "args":[ "p=p0-p0-p0-", "d=1", "pwd", "ls", "exit" ]
+        }
+    ]
+}

+ 11 - 9
go.mod

@@ -3,27 +3,30 @@ module test
 go 1.19
 
 require (
-	git.wecise.com/wecise/common v0.0.0-20221012050044-272bf5785d10 // indirect
-	git.wecise.com/wecise/odbserver v0.0.0-20221017135833-5936b510aa0f // indirect
-	github.com/alphadose/haxmap v1.0.0 // indirect
-	github.com/atrox/homedir v1.0.0 // indirect
+	git.wecise.com/wecise/common v0.0.0-20221012050044-272bf5785d10
+	git.wecise.com/wecise/odbserver v0.0.0-20221026163706-f70060221cba
+	github.com/alphadose/haxmap v1.0.0
+	github.com/atrox/homedir v1.0.0
+	github.com/cornelk/hashmap v1.0.8
+	github.com/kevinburke/ssh_config v1.2.0
+	golang.org/x/crypto v0.1.0
+	gopkg.in/yaml.v2 v2.4.0
+)
+
+require (
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
-	github.com/cornelk/hashmap v1.0.8 // indirect
 	github.com/fatih/color v1.9.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/kevinburke/ssh_config v1.2.0 // indirect
 	github.com/mattn/go-colorable v0.1.7 // indirect
 	github.com/mattn/go-isatty v0.0.12 // indirect
-	github.com/orcaman/concurrent-map v1.0.0 // indirect
 	go.etcd.io/etcd/api/v3 v3.5.1 // indirect
 	go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
 	go.etcd.io/etcd/client/v3 v3.5.1 // indirect
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.17.0 // indirect
-	golang.org/x/crypto v0.1.0 // indirect
 	golang.org/x/net v0.1.0 // indirect
 	golang.org/x/sys v0.1.0 // indirect
 	golang.org/x/term v0.1.0 // indirect
@@ -31,5 +34,4 @@ require (
 	google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79 // indirect
 	google.golang.org/grpc v1.39.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
 )

+ 12 - 2
go.sum

@@ -11,6 +11,8 @@ git.wecise.com/wecise/odb-go v0.0.0-20211124085409-0d3e38018145/go.mod h1:RUauge
 git.wecise.com/wecise/odb-go v0.0.0-20220825070427-e443cba6de6d/go.mod h1:RUaugeb49Q7CKD1szlFvoVmw657wrVGnt0QMMZrhMTg=
 git.wecise.com/wecise/odbserver v0.0.0-20221017135833-5936b510aa0f h1:C5nR+6yqciHqL1ZHdvRut2Gxp+/HfO79jX2lWkU9di0=
 git.wecise.com/wecise/odbserver v0.0.0-20221017135833-5936b510aa0f/go.mod h1:R+Zt92XIgakZndnhlV59E3weRPo9ZZAoAtIaFyYDQQs=
+git.wecise.com/wecise/odbserver v0.0.0-20221026163706-f70060221cba h1:2joDxINKdBImdLUU3kMrZyUhfgSU3o0sib0yeEYTeZQ=
+git.wecise.com/wecise/odbserver v0.0.0-20221026163706-f70060221cba/go.mod h1:R+Zt92XIgakZndnhlV59E3weRPo9ZZAoAtIaFyYDQQs=
 git.wecise.com/wecise/platform v0.0.0-20220218084958-57446e70d9ea/go.mod h1:9AJEeYzbrNe7vIl7v/3MArD6cJDCc37WX9S1hfot8XA=
 github.com/360EntSecGroup-Skylar/excelize/v2 v2.1.0/go.mod h1:NRW1nxuHjsv3AUisgUneVjItiDPsxKSBBypDwX46bf4=
 github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
@@ -114,6 +116,7 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs
 github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
 github.com/dave/stablegob v1.0.0/go.mod h1:YSkxg4P8gwXEcrk/LN4tj9379lOKCKgj+j5TNV7jRG8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dchest/siphash v1.1.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
 github.com/ddliu/go-httpclient v0.6.6/go.mod h1:zM9P0OxV4OGGz1pt/ibuj0ooX2SWH9a6MvXZLbT0JMc=
@@ -213,6 +216,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@@ -284,9 +288,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lebovski/simple_pathes v0.0.0-20180828142656-bacbb72efdc3/go.mod h1:fbYs7LQyjpp3yCxSD7yw2N22QRORuzDcKipufpgJxNQ=
 github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM=
@@ -346,8 +352,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
-github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
-github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
 github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701/go.mod h1:VtBIF1XX0c1nKkeAPk8i4aXkYopqQgfDqolHUIHPwNI=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
 github.com/otiai10/gosseract/v2 v2.2.4/go.mod h1:ahOp/kHojnOMGv1RaUnR0jwY5JVa6BYKhYAS8nbMLSo=
@@ -363,7 +367,9 @@ github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuR
 github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
@@ -427,6 +433,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
@@ -655,6 +662,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
@@ -698,6 +706,7 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -722,6 +731,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200602174320-3e3e88ca92fa/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 101 - 18
msh/client.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"bufio"
+	"encoding/base64"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -15,7 +16,7 @@ import (
 	"syscall"
 	"time"
 
-	"git.wecise.com/wecise/common/logger"
+	"git.wecise.com/wecise/odbserver/matrix/logger"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/terminal"
 )
@@ -39,6 +40,10 @@ var (
 	}
 )
 
+var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
+var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
+var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
+
 type Client interface {
 	Login()
 }
@@ -183,7 +188,9 @@ func (c *defaultClient) Login() {
 	}
 	defer client.Close()
 
-	logger.Warnf("connect server ssh -p %d %s@%s version: %s\n", c.node.port(), c.node.user(), host, string(client.ServerVersion()))
+	if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
+		logger.Trace("connect server ssh", fmt.Sprintf("-p %d %s@%s version: %s", c.node.port(), c.node.user(), host, string(client.ServerVersion())))
+	}
 
 	session, err := client.NewSession()
 	if err != nil {
@@ -233,8 +240,6 @@ func (c *defaultClient) Login() {
 	}
 
 	cmdidx := 0
-	regxpassword := regexp.MustCompile(`.*[Pp]assword: *$`)
-	regxprompt := regexp.MustCompile(`.*[%#\$\>]\s*$`)
 	outputproc := func(stdoutbtr *BTReader, localstdout io.Writer, stdinPipe io.Writer) {
 		as := ""
 		change := false
@@ -254,22 +259,98 @@ func (c *defaultClient) Login() {
 				as += s
 			} else if change {
 				change = false
-				if regxpassword.MatchString(as) {
-					p := c.node.Commands[cmdidx].Password
-					if p == "" {
-						p = c.node.Commands[0].Password
+				if cmdidx < len(c.node.Commands) {
+					if msi := c.node.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
+						match := as[msi[0]:msi[1]]
+						if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("match end:", "'"+match+"'")
+						}
+						as = "" // 全清,开始新的命令
+						cmdidx++
+						if cmdidx >= len(c.node.Commands) {
+							continue
+						}
+						if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("command:", c.node.Commands[cmdidx].Cmd)
+						}
+						localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
+						stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
+						continue
+					}
+					for _, regex := range c.node.Commands[cmdidx].Regexps {
+						if regex == nil {
+							continue
+						}
+						if regex.Regexp == nil {
+							// like match all
+							if regex.Output != "" {
+								localstdout.Write([]byte(regex.Output))
+								stdinPipe.Write([]byte(regex.Output))
+							}
+						} else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
+							match := as[msi[0]:msi[1]]
+							if len(msi) >= 4 {
+								match = as[msi[2]:msi[3]]
+								as = as[msi[3]:] // 清除已处理完的内容
+							} else {
+								as = as[msi[1]:] // 清除已处理完的内容
+							}
+							if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
+								logger.Trace("match regex:", "'"+match+"'")
+							}
+							if regex.Output != "" {
+								localstdout.Write([]byte(regex.Output))
+								stdinPipe.Write([]byte(regex.Output))
+							}
+						}
 					}
-					// don't echo password, os.Stdout.Write([]byte(p + "\n"))
-					stdinPipe.Write([]byte(p + "\n"))
 				}
-				if regxprompt.MatchString(as) {
-					cmdidx++
-					if cmdidx >= len(c.node.Commands) {
-						localstdout.Write([]byte("exit" + "\n"))
-						stdinPipe.Write([]byte("exit" + "\n"))
+				if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
 					} else {
-						localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
-						stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.node.Commands) &&
+							(strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match yesno:", "'"+match+"'")
+					}
+					os.Stdout.Write([]byte("yes\n"))
+					stdinPipe.Write([]byte("yes\n"))
+				}
+				if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					p := c.node.Commands[0].Password
+					if cmdidx < len(c.node.Commands) {
+						p = c.node.Commands[cmdidx].Password
+					}
+					if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.node.Commands) &&
+							(strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match password:", "'"+match+"'")
+					}
+					if p != "" {
+						if p[0:1] == "=" {
+							p = p[1:]
+						} else {
+							x, e := base64.RawStdEncoding.DecodeString(p)
+							if e == nil {
+								p = string(x)
+							}
+							// else 不是Base64编码,保持原值
+						}
+						// don't echo password
+						if c.node.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.node.Commands) && c.node.Commands[cmdidx].Regexps[0].Debug != "" {
+							os.Stdout.Write([]byte(p + "\n"))
+						}
+						stdinPipe.Write([]byte(p + "\n"))
 					}
 				}
 				if len(as) > 1024 {
@@ -331,7 +412,9 @@ func (c *defaultClient) Login() {
 	}()
 
 	session.Wait()
-	logger.Warnf("disconnected")
+	if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
+		logger.Trace("disconnected")
+	}
 }
 
 type BTReader struct {

+ 24 - 3
msh/config.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -9,7 +10,7 @@ import (
 	"regexp"
 	"strconv"
 
-	"git.wecise.com/wecise/common/logger"
+	"git.wecise.com/wecise/odbserver/matrix/logger"
 	"github.com/atrox/homedir"
 	"github.com/kevinburke/ssh_config"
 	"golang.org/x/crypto/ssh"
@@ -32,13 +33,33 @@ type Node struct {
 
 type Regexp struct {
 	*regexp.Regexp
-	Output string
+}
+
+func (r *Regexp) MarshalJSON() ([]byte, error) {
+	if r == nil || r.Regexp == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *Regexp) MarshalYAML() (interface{}, error) {
+	if r == nil || r.Regexp == nil {
+		return nil, nil
+	}
+	return r.String(), nil
+}
+
+type Matcher struct {
+	Regexp *Regexp
+	Output string `yaml:"output"`
+	Debug  string `yaml:"debug"`
 }
 
 type Command struct {
 	Cmd      string `yaml:"cmd"`
 	Password string `yaml:"password"`
-	Regexp   []*Regexp
+	Regexps  []*Matcher
+	Endregx  *Regexp
 }
 
 func (n *Node) String() string {

+ 153 - 40
msh/main.go

@@ -1,81 +1,194 @@
 package main
 
 import (
+	"encoding/base64"
 	"fmt"
 	"os"
 	"os/user"
 	"regexp"
 	"strings"
 
-	"git.wecise.com/wecise/common/logger"
+	"git.wecise.com/wecise/odbserver/matrix/logger"
+	"gopkg.in/yaml.v2"
 )
 
 func init() {
 	logger.SetConsole(true)
-	logger.SetLevel(logger.INFO)
+	logger.SetLevel(logger.TRACE)
+	logger.SetFormat("yyyy-MM-dd HH:mm:SS.sss [level] msg")
 }
 
-func uch() (u, c, h string, o bool) {
+func uchi() (u, c, h string, i int) {
 	if len(os.Args) < 2 {
 		return
 	}
-	if regexp.MustCompile(`p=.+`).MatchString(os.Args[1]) {
+	if msi := regexp.MustCompile(`^([^:]+):(.*)@([^@]*)$`).FindStringSubmatchIndex(os.Args[1]); msi != nil {
+		u = os.Args[1][msi[2]:msi[3]]
+		c = "=" + os.Args[1][msi[4]:msi[5]]
+		h = os.Args[1][msi[6]:msi[7]]
+		i = 2
+		return
+	}
+	if msi := regexp.MustCompile(`^([^:]+)@([^@]*)$`).FindStringSubmatchIndex(os.Args[1]); msi != nil {
+		u = os.Args[1][msi[2]:msi[3]]
+		h = os.Args[1][msi[6]:msi[7]]
+		i = 2
+	} else if os.Args[1][0] != '-' && strings.Index(os.Args[1], "=") < 0 {
+		h = os.Args[1]
+		i = 2
+	} else {
+		i = 1
+	}
+	if u == "" {
 		user, e := user.Current()
 		if e != nil {
 			logger.Error(e)
-			return
+			return "", "", "", 0
 		}
 		u = user.Username
-		c = os.Args[1][2:]
+	}
+	if h == "" {
 		h = "127.0.0.1"
-		o = true
-		return
 	}
-	uch := strings.SplitN(os.Args[1], ":", 2)
-	if len(uch) < 2 {
-		return
+	return
+}
+
+type KV struct{ Key, Val string }
+
+func parseArgs(args []string) (kvs []*KV) {
+	argk := ""
+	argv := ""
+	for _, arg := range args {
+		if argk != "" {
+			argv = arg
+		} else if regexp.MustCompile(`^\-\w+$`).MatchString(arg) {
+			argk = arg[1:]
+			continue
+		} else {
+			kv := strings.SplitN(arg, "=", 2)
+			if len(kv) == 2 {
+				argk = kv[0]
+				argv = kv[1]
+			} else {
+				argk = ""
+				argv = arg
+			}
+		}
+		kvs = append(kvs, &KV{argk, argv})
+		argk, argv = "", ""
 	}
-	n := strings.LastIndex(uch[1], "@")
-	if n < 0 {
-		return
+	if argk != "" {
+		kvs = append(kvs, &KV{argk, argv})
 	}
-	return uch[0], uch[1][:n], uch[1][n+1:], true
+	return
 }
 
 func main() {
-	u, c, h, o := uch()
-	if !o {
+	u, c, h, i := uchi()
+	if i == 0 {
 		fmt.Println("usage:")
-		fmt.Println("  msh [user:code@host]|[p=password] [[c=]command [p=password] [[r=regexp] [o=output]]...]...")
+		fmt.Println("  msh user:password@host|p=password|a=passcode [[c=]command [p=password|a=passcode] [x=cmd-end-regexp] [[r=regexp] [o=output|n=outputline]]...]...")
+		fmt.Println("  a=passcode should be base64 encoded, or use p=password")
+		fmt.Println("  debug info include: p(progress) a(argments) m(match) 1(all)")
 		return
 	}
-
-	cmds := []*Command{{Cmd: "", Password: c, Regexp: []*Regexp{{Regexp: nil, Output: ""}}}}
-	for i := 2; i < len(os.Args); i++ {
-		arg := os.Args[i]
-		kv := strings.SplitN(arg, "=", 2)
-		var cmd string
-		switch kv[0] {
-		case "cmd", "command", "c":
-			cmd = kv[1]
+	kvs := parseArgs(os.Args[i:])
+	if c == "" {
+		for _, kv := range kvs {
+			if kv.Key == "a" {
+				c = kv.Val
+				if c == "" {
+					c = "="
+				}
+				break
+			}
+			if kv.Key == "p" {
+				c = "=" + kv.Val
+				break
+			}
+		}
+	}
+	p := c
+	if p[0:1] == "=" {
+		p = p[1:]
+	} else {
+		x, e := base64.RawStdEncoding.DecodeString(p)
+		if e == nil {
+			p = string(x)
+		}
+		// else 不是Base64编码,保持原值
+	}
+	// explainArgs key, val
+	cmds := []*Command{{Cmd: "ssh " + u + ":" + p + "@" + h, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt}}
+	for _, kv := range kvs {
+		key, val := kv.Key, kv.Val
+		switch key {
+		case "", "cmd", "command", "c":
+			cmds = append(cmds, &Command{Cmd: val, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt})
+		case "ry":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, err)
+				return
+			} else {
+				regxyesno = &Regexp{re}
+			}
+		case "rc":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, err)
+				return
+			} else {
+				regxpassword = &Regexp{re}
+			}
+		case "rp":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, err)
+				return
+			} else {
+				regxprompt = &Regexp{re}
+			}
 		case "password", "code", "pass", "p":
-			cmds[len(cmds)-1].Password = kv[1]
-			continue
+			cmds[len(cmds)-1].Password = "=" + val
+		case "passcode", "b64code", "a":
+			cmds[len(cmds)-1].Password = val
 		case "re", "r", "regex":
-			re, err := regexp.Compile(kv[1])
-			if err != nil {
-				logger.Error(err)
+			if val == "" {
+				cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: nil})
 			} else {
-				cmds[len(cmds)-1].Regexp = append(cmds[len(cmds)-1].Regexp, &Regexp{Regexp: re})
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, err)
+					return
+				} else {
+					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
+				}
+			}
+		case "x", "end":
+			if val == "" {
+				cmds[len(cmds)-1].Endregx = regxprompt
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, err)
+					return
+				} else {
+					cmds[len(cmds)-1].Endregx = &Regexp{re}
+				}
 			}
-			continue
 		case "out", "o", "output":
-			cmds[len(cmds)-1].Regexp[len(cmds[len(cmds)-1].Regexp)-1].Output = kv[1]
-			continue
-		default:
-			cmd = arg
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val
+		case "outln", "n", "outputline":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val + "\n"
+		case "debug", "d":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Debug += val
 		}
-		cmds = append(cmds, &Command{Cmd: cmd, Password: c, Regexp: []*Regexp{{Regexp: nil, Output: ""}}})
+	}
+	if strings.Index(cmds[0].Regexps[0].Debug, "a") >= 0 || strings.Index(cmds[0].Regexps[0].Debug, "1") >= 0 {
+		//bs, _ := json.MarshalIndent(cmds, "", "  ")
+		bs, _ := yaml.Marshal(cmds)
+		logger.Debug("arguments:\n" + string(bs))
 	}
 	node := &Node{
 		Name:       "x",
@@ -85,7 +198,7 @@ func main() {
 		Port:       22,
 		KeyPath:    "",
 		Passphrase: "",
-		Password:   c,
+		Password:   p,
 		Commands:   cmds,
 		Children:   []*Node{},
 		Jump:       []*Node{},

BIN=BIN
msh/msh