From bcc27ff52fcefb4ae1827f2813e5691443537eb9 Mon Sep 17 00:00:00 2001 From: Starainrt Date: Fri, 23 Apr 2021 11:10:18 +0800 Subject: [PATCH] init --- sftp.go | 134 ++++++++++++ ssh.go | 636 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 770 insertions(+) create mode 100644 sftp.go create mode 100644 ssh.go diff --git a/sftp.go b/sftp.go new file mode 100644 index 0000000..1d19907 --- /dev/null +++ b/sftp.go @@ -0,0 +1,134 @@ +package starssh + +import ( + "bytes" + "github.com/pkg/sftp" + "io" + "os" +) + +func SftpTransferOut(localFilePath, remotePath string, sftpClient *sftp.Client) error { + srcFile, err := os.Open(localFilePath) + if err != nil { + return err + } + defer srcFile.Close() + // var remoteFileName = filepath.Base(localFilePath) + dstFile, err := sftpClient.Create(remotePath) + if err != nil { + return err + } + defer dstFile.Close() + for { + buf := make([]byte, 1024) + n, err := srcFile.Read(buf) + dstFile.Write(buf[:n]) + if err == io.EOF { + break + } + } + return nil +} + +func SftpTransferOutByte(localData []byte, remotePath string, sftpClient *sftp.Client) error { + dstFile, err := sftpClient.Create(remotePath) + if err != nil { + return err + } + defer dstFile.Close() + _, err = dstFile.Write(localData) + return err +} + +func SftpTransferOutFunc(localFilePath, remotePath string, bufcap int, rtefunc func(float64), sftpClient *sftp.Client) error { + num := 0 + srcFile, err := os.Open(localFilePath) + if err != nil { + return err + } + defer srcFile.Close() + stat, _ := os.Stat(localFilePath) + filebig := float64(stat.Size()) + //var remoteFileName = filepath.Base(localFilePath) + dstFile, err := sftpClient.Create(remotePath) + if err != nil { + return err + } + defer dstFile.Close() + for { + buf := make([]byte, bufcap) + n, err := srcFile.Read(buf) + num += n + go rtefunc(float64(num) / filebig * 100) + dstFile.Write(buf[:n]) + if err == io.EOF { + break + } + if err != nil { + return err + } + } + return nil +} + +func SftpTransferInByte(remotePath string, sftpClient *sftp.Client) ([]byte, error) { + dstFile, err := sftpClient.Open(remotePath) + if err != nil { + return []byte{}, err + } + defer dstFile.Close() + buf := new(bytes.Buffer) + _, err = dstFile.WriteTo(buf) + return buf.Bytes(), err +} + +func SftpTransferIn(src, dst string, sftpClient *sftp.Client) error { + srcFile, err := sftpClient.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + //var localFileName = filepath.Base(src) + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + if _, err = srcFile.WriteTo(dstFile); err != nil { + return err + } + + return nil +} + +func SftpTransferInFunc(src, dst string, bufcap int, rtefunc func(float64), sftpClient *sftp.Client) error { + num := 0 + srcFile, err := sftpClient.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + stat, _ := srcFile.Stat() + filebig := float64(stat.Size()) + //var localFileName = filepath.Base(src) + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + for { + buf := make([]byte, bufcap) + n, err := srcFile.Read(buf) + num += n + go rtefunc(float64(num) / filebig * 100) + dstFile.Write(buf[:n]) + if err == io.EOF { + break + } + if err != nil { + return err + } + } + return nil +} \ No newline at end of file diff --git a/ssh.go b/ssh.go new file mode 100644 index 0000000..e700640 --- /dev/null +++ b/ssh.go @@ -0,0 +1,636 @@ +package starssh + +import ( + "bufio" + "encoding/base64" + "errors" + "fmt" + "golang.org/x/crypto/ssh" + "io" + "io/ioutil" + "net" + "regexp" + "strings" + "sync" + "time" +) + +type StarSSH struct { + Client *ssh.Client + PublicKey ssh.PublicKey + PubkeyBase64 string + Hostname string + RemoteAddr net.Addr + Banner string + LoginInfo LoginInput + online bool +} + +type LoginInput struct { + KeyExchanges []string + Ciphers []string + MACs []string + User string + Password string + Prikey string + PrikeyPwd string + Addr string + Port int + Timeout time.Duration + HostKeyCallback func(string, net.Addr, ssh.PublicKey) error + BannerCallback func(string) error +} +type StarShell struct { + Keyword string + UseWaitDefault bool + Session *ssh.Session + in io.Writer + out *bufio.Reader + er *bufio.Reader + outbyte []byte + errbyte []byte + lastout int64 + errors error + isprint bool + isfuncs bool + iscolor bool + isecho bool + rw sync.RWMutex + funcs func(string) +} + +func Login(info LoginInput) (*StarSSH, error) { + var ( + auth []ssh.AuthMethod + clientConfig *ssh.ClientConfig + config ssh.Config + err error + ) + sshInfo := new(StarSSH) + // get auth method + auth = make([]ssh.AuthMethod, 0) + if info.Prikey == "" { + keyboardInteractiveChallenge := func( + user, + instruction string, + questions []string, + echos []bool, + ) (answers []string, err error) { + if len(questions) == 0 { + return []string{}, nil + } + return []string{info.Password}, nil + } + auth = append(auth, ssh.Password(info.Password)) + auth = append(auth, ssh.KeyboardInteractive(keyboardInteractiveChallenge)) + } else { + pemBytes := []byte(info.Prikey) + var signer ssh.Signer + if info.PrikeyPwd == "" { + signer, err = ssh.ParsePrivateKey(pemBytes) + } else { + signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(info.PrikeyPwd)) + } + if err != nil { + return nil, err + } + auth = append(auth, ssh.PublicKeys(signer)) + } + + if len(info.Ciphers) == 0 { + config = ssh.Config{ + Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc", "chacha20-poly1305@openssh.com"}, + } + } else { + config = ssh.Config{ + Ciphers: info.Ciphers, + } + } + + if len(info.MACs) != 0 { + config.MACs = info.MACs + } + if len(info.KeyExchanges) != 0 { + config.KeyExchanges = info.KeyExchanges + } + + if info.Timeout == 0 { + info.Timeout = time.Second * 5 + } + hostKeycbfunc := func(hostname string, remote net.Addr, key ssh.PublicKey) error { + + sshInfo.PublicKey = key + sshInfo.RemoteAddr = remote + sshInfo.Hostname = hostname + if info.HostKeyCallback != nil { + return info.HostKeyCallback(hostname, remote, key) + } + return nil + } + + bannercbfunc := func(banner string) error { + sshInfo.Banner = banner + if info.BannerCallback != nil { + return info.BannerCallback(banner) + } + return nil + } + + clientConfig = &ssh.ClientConfig{ + User: info.User, + Auth: auth, + Timeout: info.Timeout, + Config: config, + HostKeyCallback: hostKeycbfunc, + BannerCallback: bannercbfunc, + } + + // connet to ssh + + sshInfo.LoginInfo = info + sshInfo.Client, err = ssh.Dial("tcp", fmt.Sprintf("%s:%d", info.Addr, info.Port), clientConfig) + if err == nil && sshInfo.PublicKey != nil { + sshInfo.online = true + sshInfo.PubkeyBase64 = base64.StdEncoding.EncodeToString(sshInfo.PublicKey.Marshal()) + } + return sshInfo, err +} + +func LoginSimple(host string, user string, passwd string, prikeyPath string, port int, timeout time.Duration) (*StarSSH, error) { + var info = LoginInput{ + Addr: host, + Port: port, + Timeout: timeout, + User: user, + } + if prikeyPath != "" { + prikey, err := ioutil.ReadFile(prikeyPath) + if err != nil { + return nil, err + } + info.Prikey = string(prikey) + if passwd != "" { + info.PrikeyPwd = passwd + } + } else { + info.Password = passwd + } + return Login(info) +} + +func (this *StarShell) ShellWait(cmd string) (string, string, error) { + var outc, errc string = " ", " " + this.Clear() + defer this.Clear() + echo := "echo b7Y85R56TUY6R5UTb612" + err := this.WriteCommand(cmd) + if err != nil { + return "", "", err + } + time.Sleep(time.Millisecond * 20) + err = this.WriteCommand(echo) + if err != nil { + return "", "", err + } + for { + time.Sleep(time.Millisecond * 120) + outs := string(this.outbyte) + errs := string(this.errbyte) + outs = strings.TrimSpace(strings.ReplaceAll(outs, "\r\n", "\n")) + errs = strings.TrimSpace(strings.ReplaceAll(errs, "\r\n", "\n")) + if len(outs) >= len(cmd+"\n"+echo) && outs[0:len(cmd+"\n"+echo)] == cmd+"\n"+echo { + outs = outs[len(cmd+"\n"+echo):] + } else if len(outs) >= len(cmd) && outs[0:len(cmd)] == cmd { + outs = outs[len(cmd):] + } + if len(errs) >= len(cmd) && errs[0:len(cmd)] == cmd { + errs = errs[len(cmd):] + } + if this.UseWaitDefault { + if strings.Index(string(outs), "b7Y85R56TUY6R5UTb612") >= 0 { + list := strings.Split(string(outs), "\n") + for _, v := range list { + if strings.Index(v, "b7Y85R56TUY6R5UTb612") < 0 { + outc += v + "\n" + } + } + break + } + if strings.Index(string(errs), "b7Y85R56TUY6R5UTb612") >= 0 { + list := strings.Split(string(errs), "\n") + for _, v := range list { + if strings.Index(v, "b7Y85R56TUY6R5UTb612") < 0 { + errc += v + "\n" + } + } + break + } + } + if this.Keyword != "" { + if strings.Index(string(outs), this.Keyword) >= 0 { + list := strings.Split(string(outs), "\n") + for _, v := range list { + if strings.Index(v, this.Keyword) < 0 && strings.Index(v, "b7Y85R56TUY6R5UTb612") < 0 { + outc += v + "\n" + } + } + break + } + if strings.Index(string(errs), this.Keyword) >= 0 { + list := strings.Split(string(errs), "\n") + for _, v := range list { + if strings.Index(v, this.Keyword) < 0 && strings.Index(v, "b7Y85R56TUY6R5UTb612") < 0 { + errc += v + "\n" + } + } + break + } + } + } + return this.TrimColor(strings.TrimSpace(outc)), this.TrimColor(strings.TrimSpace(errc)), err +} + +func (this *StarShell) Close() error { + return this.Session.Close() +} + +func (this *StarShell) SwitchNoColor(is bool) { + this.iscolor = is +} + +func (this *StarShell) SwitchEcho(is bool) { + this.isecho = is +} + +func (this *StarShell) TrimColor(str string) string { + if this.iscolor { + return SedColor(str) + } + return str +} + +/* +本函数控制是否在本地屏幕上打印远程Shell的输出内容[true|false] +*/ +func (this *StarShell) SwitchPrint(run bool) { + this.isprint = run +} + +/* +本函数控制是否立即处理远程Shell输出每一行内容[true|false] +*/ +func (this *StarShell) SwitchFunc(run bool) { + this.isfuncs = run +} + +func (this *StarShell) SetFunc(funcs func(string)) { + this.funcs = funcs +} + +func (this *StarShell) Clear() { + defer this.rw.Unlock() + this.rw.Lock() + this.outbyte = []byte{} + this.errbyte = []byte{} + time.Sleep(time.Millisecond * 15) +} + +func (this *StarShell) ShellClear(cmd string, sleep int) (string, string, error) { + defer this.Clear() + this.Clear() + return this.Shell(cmd, sleep) +} + +func (this *StarShell) Shell(cmd string, sleep int) (string, string, error) { + if err := this.WriteCommand(cmd); err != nil { + return "", "", err + } + tmp1, tmp2, err := this.GetResult(sleep) + tmps := this.TrimColor(strings.TrimSpace(string(tmp1))) + if this.isecho { + n := len(strings.Split(cmd, "\n")) + if n == 1 { + list := strings.SplitN(tmps, "\n", 2) + if len(list) == 2 { + tmps = list[1] + } + } else { + list := strings.Split(tmps, "\n") + cmds := strings.Split(cmd, "\n") + for _, v := range cmds { + for k, v2 := range list { + if strings.TrimSpace(v2) == strings.TrimSpace(v) { + list[k] = "" + break + } + } + } + tmps = "" + for _, v := range list { + if v != "" { + tmps += v + "\n" + } + } + tmps = tmps[0 : len(tmps)-1] + } + } + return tmps, this.TrimColor(strings.TrimSpace(string(tmp2))), err +} + +func (this *StarShell) GetResult(sleep int) ([]byte, []byte, error) { + if this.errors != nil { + this.Session.Close() + return this.outbyte, this.errbyte, this.errors + } + if sleep > 0 { + time.Sleep(time.Millisecond * time.Duration(sleep)) + } + return this.outbyte, this.errbyte, nil +} + +func (this *StarShell) WriteCommand(cmd string) error { + return this.Write([]byte(cmd + "\n")) +} + +func (this *StarShell) Write(bstr []byte) error { + if this.errors != nil { + this.Session.Close() + return this.errors + } + _, err := this.in.Write(bstr) + return err +} + +func (this *StarShell) gohub() { + go func() { + var cache []byte + for { + read, err := this.er.ReadByte() + if err != nil { + this.errors = err + return + } + this.errbyte = append(this.errbyte, read) + if this.isprint { + fmt.Print(string([]byte{read})) + } + cache = append(cache, read) + if read == '\n' { + if this.isfuncs { + go this.funcs(this.TrimColor(strings.TrimSpace(string(cache)))) + cache = []byte{} + } + } + } + }() + var cache []byte + for { + read, err := this.out.ReadByte() + if err != nil { + this.errors = err + return + } + this.rw.Lock() + this.outbyte = append(this.outbyte, read) + cache = append(cache, read) + this.rw.Unlock() + if read == '\n' { + if this.isfuncs { + go this.funcs(strings.TrimSpace(string(cache))) + cache = []byte{} + } + } + if this.isprint { + fmt.Print(string([]byte{read})) + } + } +} + +func (this *StarShell) GetUid() string { + res, _, _ := this.ShellWait(`id | grep -oP "(?<=uid\=)\d+"`) + return strings.TrimSpace(res) +} +func (this *StarShell) GetGid() string { + res, _, _ := this.ShellWait(`id | grep -oP "(?<=gid\=)\d+"`) + return strings.TrimSpace(res) +} +func (this *StarShell) GetUser() string { + res, _, _ := this.ShellWait(`id | grep -oP "(?<=\().*?(?=\))" | head -n 1`) + return strings.TrimSpace(res) +} +func (this *StarShell) GetGroup() string { + res, _, _ := this.ShellWait(`id | grep -oP "(?<=\().*?(?=\))" | head -n 2 | tail -n 1`) + return strings.TrimSpace(res) +} + +func (this *StarSSH) NewShell() (shell *StarShell, err error) { + shell = new(StarShell) + shell.Session, err = this.NewSession() + if err != nil { + return + } + shell.in, _ = shell.Session.StdinPipe() + tmp, _ := shell.Session.StdoutPipe() + shell.out = bufio.NewReader(tmp) + tmp, _ = shell.Session.StderrPipe() + shell.er = bufio.NewReader(tmp) + err = shell.Session.Shell() + shell.isecho = true + go shell.Session.Wait() + shell.UseWaitDefault = true + shell.WriteCommand("bash") + time.Sleep(500 * time.Millisecond) + shell.WriteCommand("export PS1= ") + shell.WriteCommand("export PS2= ") + go shell.gohub() + time.Sleep(500 * time.Millisecond) + shell.Clear() + shell.Clear() + return +} + +func (this *StarSSH) Close() error { + if this.online { + return this.Client.Close() + } + return nil +} + +func (this *StarSSH) NewSession() (*ssh.Session, error) { + return NewSession(this.Client) +} + +func (this *StarSSH) ShellOne(cmd string) (string, error) { + newsess, err := this.NewSession() + if err != nil { + return "", err + } + data, err := newsess.CombinedOutput(cmd) + newsess.Close() + return strings.TrimSpace(string(data)), err +} + +func (this *StarSSH) Exists(filepath string) bool { + res, _ := this.ShellOne(`echo 1 && [ ! -e "` + filepath + `" ] && echo 2`) + if res == "1" { + return true + } else { + return false + } +} + +func (this *StarSSH) GetUid() string { + res, _ := this.ShellOne(`id | grep -oP "(?<=uid\=)\d+"`) + return strings.TrimSpace(res) +} +func (this *StarSSH) GetGid() string { + res, _ := this.ShellOne(`id | grep -oP "(?<=gid\=)\d+"`) + return strings.TrimSpace(res) +} +func (this *StarSSH) GetUser() string { + res, _ := this.ShellOne(`id | grep -oP "(?<=\().*?(?=\))" | head -n 1`) + return strings.TrimSpace(res) +} +func (this *StarSSH) GetGroup() string { + res, _ := this.ShellOne(`id | grep -oP "(?<=\().*?(?=\))" | head -n 2 | tail -n 1`) + return strings.TrimSpace(res) +} + +func (this *StarSSH) IsFile(filepath string) bool { + res, _ := this.ShellOne(`echo 1 && [ ! -f "` + filepath + `" ] && echo 2`) + if res == "1" { + return true + } else { + return false + } +} + +func (this *StarSSH) IsFolder(filepath string) bool { + res, _ := this.ShellOne(`echo 1 && [ ! -d "` + filepath + `" ] && echo 2`) + if res == "1" { + return true + } else { + return false + } +} + +func (this *StarSSH) ShellOneShowScreen(cmd string) (string, error) { + newsess, err := this.NewSession() + if err != nil { + return "", err + } + var bytes, errbytes []byte + tmp, _ := newsess.StdoutPipe() + reader := bufio.NewReader(tmp) + tmp, _ = newsess.StderrPipe() + errder := bufio.NewReader(tmp) + err = newsess.Start(cmd) + if err != nil { + return "", err + } + c := make(chan int, 1) + go newsess.Wait() + go func() { + for { + byt, err := reader.ReadByte() + if err != nil { + break + } + fmt.Print(string([]byte{byt})) + bytes = append(bytes, byt) + } + c <- 1 + }() + for { + byt, err := errder.ReadByte() + if err != nil { + break + } + fmt.Print(string([]byte{byt})) + errbytes = append(errbytes, byt) + } + _ = <-c + newsess.Close() + if len(errbytes) != 0 { + err = errors.New(strings.TrimSpace(string(errbytes))) + } else { + err = nil + } + return strings.TrimSpace(string(bytes)), err +} + +func (this *StarSSH) ShellOneToFunc(cmd string, callback func(string)) (string, error) { + newsess, err := this.NewSession() + if err != nil { + return "", err + } + var bytes, errbytes []byte + tmp, _ := newsess.StdoutPipe() + reader := bufio.NewReader(tmp) + tmp, _ = newsess.StderrPipe() + errder := bufio.NewReader(tmp) + err = newsess.Start(cmd) + if err != nil { + return "", err + } + c := make(chan int, 1) + go newsess.Wait() + go func() { + for { + byt, err := reader.ReadByte() + if err != nil { + break + } + callback(string([]byte{byt})) + bytes = append(bytes, byt) + } + c <- 1 + }() + for { + byt, err := errder.ReadByte() + if err != nil { + break + } + callback(string([]byte{byt})) + errbytes = append(errbytes, byt) + } + _ = <-c + newsess.Close() + if len(errbytes) != 0 { + err = errors.New(strings.TrimSpace(string(errbytes))) + } else { + err = nil + } + return strings.TrimSpace(string(bytes)), err +} + +func NewTransferSession(client *ssh.Client) (*ssh.Session, error) { + session, err := client.NewSession() + return session, err +} + +func NewSession(client *ssh.Client) (*ssh.Session, error) { + var session *ssh.Session + var err error + // create session + if session, err = client.NewSession(); err != nil { + return nil, err + } + modes := ssh.TerminalModes{ + ssh.ECHO: 1, // 还是要强制开启 + //ssh.IGNCR: 0, + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + + if err := session.RequestPty("xterm", 500, 250, modes); err != nil { + return nil, err + } + return session, nil +} + +func SedColor(str string) string { + reg := regexp.MustCompile(`\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]`) + //fmt.Println("regexp:", reg.Match([]byte(str))) + return string(reg.ReplaceAll([]byte(str), []byte(""))) +}