当前位置:   article > 正文

基于golang实现ssh terminal

ssh terminal

基于golang实现ssh terminal

实现ssh terminal相对比较容易,简单来说需要初始化ssh连接后,通过ssh连接创建一个会话,定义好输入、输出,然后再请求pty(需要定义好modes)与远程会话进行关联。

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

type loginInfo struct {
	Address  string `json:"ipaddress"`
	Port     int    `json:"port"`
	UserName string `json:"username"`
	PassWord string `json:"password"`
}

//将字符串转换为int
func toInt(str string) int {
	data, err := strconv.Atoi(str)
	if err != nil {
		fmt.Println(err)
	}
	return data
}

//初始化ws,将http协议提升为ws协议
func InitialWS(c *gin.Context) (*websocket.Conn, error) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	//将http协议提升为ws
	ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	return ws, err
}

//实现write方法,保存ws连接
type WSoutput struct {
	ws *websocket.Conn
}

// Write: implement Write interface to write bytes from ssh server into bytes.Buffer.
func (w *WSoutput) Write(p []byte) (int, error) {
	// 处理非utf8字符
	if !utf8.Valid(p) {
		bufStr := string(p)
		buf := make([]rune, 0, len(bufStr))
		for _, r := range bufStr {
			if r == utf8.RuneError {
				buf = append(buf, []rune("@")...)
			} else {
				buf = append(buf, r)
			}
		}
		p = []byte(string(buf))
	}
	err := w.ws.WriteMessage(websocket.TextMessage, p)
	return len(p), err
}

type sshConnect struct {
	connect   *ssh.Client    //ssh连接
	session   *ssh.Session   //ssh会话
	stdinPipe io.WriteCloser //标准输入管道
}

// 初始化ssh连接
func (t *sshConnect) InitialSSH(login loginInfo) error {
	//初始化ssh登陆配置
	config := &ssh.ClientConfig{
		User:    login.UserName,
		Auth:    []ssh.AuthMethod{ssh.Password(login.PassWord)},
		Timeout: 5 * time.Second,
		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"},
		},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}
	//创建ssh连接
	sshConnect, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", login.Address, login.Port), config)
	if err != nil {
		return err
	}
	t.connect = sshConnect
	return nil
}

//初始化终端
func (t *sshConnect) InitialTerminal(ws *websocket.Conn, rows, cols int) error {
	session, err := t.connect.NewSession()
	if err != nil {
		log.Println(err)
		return err
	}
	t.session = session
	t.stdinPipe, _ = session.StdinPipe()

	wsOutput := WSoutput{
		ws: ws,
	}
	//ssh.stdout and stderr will write output into comboWriter
	session.Stdout = &wsOutput
	session.Stderr = &wsOutput

	//定义terminal模式
	modes := ssh.TerminalModes{
		ssh.ECHO:          1, //开启回显
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	//将pty与远程的会话关联起来
	if err := session.RequestPty("xterm", rows, cols, modes); err != nil {
		return err
	}
	// Shell starts a login shell on the remote host
	if err := session.Shell(); err != nil {
		return err
	}
	return nil
}

//处理连接
func (t *sshConnect) Connect(ws *websocket.Conn) {
	stopCh := make(chan struct{}, 1)

	//启用协程获取webTerminal的输入
	go func() {
		for {
			// 读取web socket信息,msg为用户输入的信息
			_, msg, err := ws.ReadMessage()
			if err != nil {
				log.Println(err)
				stopCh <- struct{}{}
			}
			//心跳检测
			if string(msg) == "ping" {
				continue
			}
			//terminal窗口调整
			if strings.Contains(string(msg), "resize") {
				resizeSlice := strings.Split(string(msg), ":")
				rows, _ := strconv.Atoi(resizeSlice[1])
				cols, _ := strconv.Atoi(resizeSlice[2])
				err := t.session.WindowChange(rows, cols)
				if err != nil {
					log.Println(err)
					stopCh <- struct{}{}
				}
				continue
			}
			_, err = t.stdinPipe.Write(msg)
			if err != nil {
				stopCh <- struct{}{}
				return
			}
		}
	}()

	timer := time.NewTimer(time.Minute * 30)

	defer func() {
		ws.Close()
		t.stdinPipe.Close()
		t.session.Close()
		t.connect.Close()
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()

	for {
		select {
		case <-stopCh:
			return
		case <-timer.C:
			return
		}
	}
}

func sshTerminal(c *gin.Context) {
	host := c.DefaultQuery("host", "")
	cols := c.DefaultQuery("cols", "150")
	rows := c.DefaultQuery("rows", "35")
	sshInfo := loginInfo{
		Address:  host,
		Port:     22,
		UserName: "cbic",
		PassWord: "AzWVeVjtF4EmkEd3",
	}

	//将http协议提升为ws协议
	ws, err := InitialWS(c)
	if err != nil {
		fmt.Println(err)
		return
	}
	connectAction := sshConnect{}
	err = connectAction.InitialSSH(sshInfo)
	err = connectAction.InitialTerminal(ws, toInt(rows), toInt(cols))
	if err != nil {
		ws.WriteMessage(1, []byte(err.Error()))
		ws.Close()
		log.Println(err)
		return
	}
	connectAction.Connect(ws)
}

func main() {
	r := gin.Default()
	r.GET("/terminal", sshTerminal)
	r.Run(":9090")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/624939
推荐阅读
相关标签
  

闽ICP备14008679号