diff --git a/Makefile b/Makefile index 7b3d4c6..93d3186 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ SRCS=\ client-ctl.go \ client-metrics.go \ client-peer.go \ + client-pts.go \ hodu.go \ hodu.pb.go \ hodu_grpc.pb.go \ @@ -32,7 +33,8 @@ DATA = \ xterm.css \ xterm.js \ xterm-addon-fit.js \ - xterm.html + xterm.html \ + xterm-pts.html CMD_DATA=\ cmd/rsa.key \ diff --git a/README.md b/README.md index 6bcdd1b..f09e689 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## normal operation - ./hodu server --rpc-on=0.0.0.0:9999 --ctl-on=0.0.0.0:8888 --pxy-on=0.0.0.0:9998 --wpx-on=0.0.0.0:9997 -- ./hodu client --rpc-to=127.0.0.1:9999 --ctl-on=127.0.0.1:7777 "127.0.0.2:22,0.0.0.0:12345,ssh,Access SSH Server" +- ./hodu client --rpc-to=127.0.0.1:9999 --ctl-on=127.0.0.1:1107 "127.0.0.2:22,0.0.0.0:12345,ssh,Access SSH Server" ## server.json ``` @@ -37,5 +37,5 @@ Run this command: ``` -curl -X POST --data-binary @client-route.json http://127.0.0.1:7777/_ctl/client-conns/1/routes +curl -X POST --data-binary @client-route.json http://127.0.0.1:1107/_ctl/client-conns/1/routes ``` diff --git a/client-ctl.go b/client-ctl.go index 98fb6a4..4975245 100644 --- a/client-ctl.go +++ b/client-ctl.go @@ -109,6 +109,7 @@ type json_out_client_stats struct { ClientConns int64 `json:"client-conns"` ClientRoutes int64 `json:"client-routes"` ClientPeers int64 `json:"client-peers"` + ClientPtsSessions int64 `json:"client-pts-sessions"` } // ------------------------------------ @@ -1136,6 +1137,7 @@ func (ctl *client_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request) stats.ClientConns = c.stats.conns.Load() stats.ClientRoutes = c.stats.routes.Load() stats.ClientPeers = c.stats.peers.Load() + stats.ClientPtsSessions = c.stats.pts_sessions.Load() status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(stats); err != nil { goto oops } diff --git a/client-metrics.go b/client-metrics.go index 9b15dc6..b2e4ec3 100644 --- a/client-metrics.go +++ b/client-metrics.go @@ -10,6 +10,7 @@ type ClientCollector struct { ClientConns *prometheus.Desc ClientRoutes *prometheus.Desc ClientPeers *prometheus.Desc + PtsSessions *prometheus.Desc } // NewClientCollector returns a new ClientCollector with all prometheus.Desc initialized @@ -46,6 +47,11 @@ func NewClientCollector(client *Client) ClientCollector { "Number of client-side peers", nil, nil, ), + PtsSessions: prometheus.NewDesc( + prefix + "pts_sessions", + "Number of pts sessions", + nil, nil, + ), } } @@ -84,4 +90,10 @@ func (c ClientCollector) Collect(ch chan<- prometheus.Metric) { prometheus.GaugeValue, float64(c.client.stats.peers.Load()), ) + + ch <- prometheus.MustNewConstMetric( + c.PtsSessions, + prometheus.GaugeValue, + float64(c.client.stats.pts_sessions.Load()), + ) } diff --git a/client-pts.go b/client-pts.go new file mode 100644 index 0000000..22dd87d --- /dev/null +++ b/client-pts.go @@ -0,0 +1,316 @@ +package hodu + +import "encoding/json" +import "errors" +import "fmt" +import "io" +import "net/http" +import "os" +import "os/exec" +import "os/user" +import "strconv" +import "sync" +import "syscall" + +import "github.com/creack/pty" +import "golang.org/x/net/websocket" +import "golang.org/x/sys/unix" + +type client_pts_ws struct { + C *Client + Id string + ws *websocket.Conn +} + +type client_pts_xterm_file struct { + client_ctl + file string +} + +/* +type json_ssh_ws_event struct { + Type string `json:"type"` + Data []string `json:"data"` +} +*/ + +// ------------------------------------------------------ + +func (pts *client_pts_ws) Identity() string { + return pts.Id +} + +func (pts *client_pts_ws) send_ws_data(ws *websocket.Conn, type_val string, data string) error { + var msg []byte + var err error + + msg, err = json.Marshal(json_ssh_ws_event{Type: type_val, Data: []string{ data } }) + if err == nil { err = websocket.Message.Send(ws, msg) } + return err +} + + +func (pts *client_pts_ws) connect_pts(username string, password string) (*exec.Cmd, *os.File, error) { + var c *Client + var cmd *exec.Cmd + var tty *os.File + var err error + + // username and password are not used yet. + c = pts.C + + if c.pts_shell == "" { + return nil, nil, fmt.Errorf("blank pts shell") + } + + cmd = exec.Command(c.pts_shell); + if c.pts_user != "" { + var uid int + var gid int + var u *user.User + + u, err = user.Lookup(c.pts_user) + if err != nil { return nil, nil, err } + + uid, _ = strconv.Atoi(u.Uid) + gid, _ = strconv.Atoi(u.Gid) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, + Setsid: true, + } + cmd.Dir = u.HomeDir + cmd.Env = append(cmd.Env, + "HOME=" + u.HomeDir, + "LOGNAME=" + u.Username, + "PATH=" + os.Getenv("PATH"), + "SHELL=" + c.pts_shell, + "TERM=xterm", + "USER=" + u.Username, + ) + } + + tty, err = pty.Start(cmd) + if err != nil { + return nil, nil, err + } + + //syscall.SetNonblock(int(tty.Fd()), true); + unix.SetNonblock(int(tty.Fd()), true); + + return cmd, tty, nil +} + +func (pts *client_pts_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { + var c *Client + var req *http.Request + var username string + var password string + var in *os.File + var out *os.File + var tty *os.File + var cmd *exec.Cmd + var wg sync.WaitGroup + var conn_ready_chan chan bool + var err error + + c = pts.C + req = ws.Request() + conn_ready_chan = make(chan bool, 3) + + wg.Add(1) + go func() { + var conn_ready bool + + defer wg.Done() + defer ws.Close() // dirty way to break the main loop + + conn_ready = <-conn_ready_chan + if conn_ready { // connected + var poll_fds []unix.PollFd; + var buf []byte + var n int + var err error + + + poll_fds = []unix.PollFd{ + unix.PollFd{Fd: int32(out.Fd()), Events: unix.POLLIN}, + } + + c.stats.pts_sessions.Add(1) + buf = make([]byte, 2048) + for { + n, err = unix.Poll(poll_fds, -1) // -1 means wait indefinitely + if err != nil { + if errors.Is(err, unix.EINTR) { continue } + c.log.Write("", LOG_ERROR, "[%s] Failed to poll pts stdout - %s", req.RemoteAddr, err.Error()) + break + } + if n == 0 { // timed out + continue + } + + if (poll_fds[0].Revents & (unix.POLLERR | unix.POLLHUP | unix.POLLNVAL)) != 0 { + c.log.Write(pts.Id, LOG_DEBUG, "[%s] EOF detected on pts stdout", req.RemoteAddr) + break; + } + + if (poll_fds[0].Revents & unix.POLLIN) != 0 { + n, err = out.Read(buf) + if err != nil { + if !errors.Is(err, io.EOF) { + c.log.Write(pts.Id, LOG_ERROR, "[%s] Failed to read pts stdout - %s", req.RemoteAddr, err.Error()) + } + break + } + if n > 0 { + err = pts.send_ws_data(ws, "iov", string(buf[:n])) + if err != nil { + c.log.Write(pts.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err.Error()) + break + } + } + } + } + c.stats.pts_sessions.Add(-1) + } + }() + +ws_recv_loop: + for { + var msg []byte + err = websocket.Message.Receive(ws, &msg) + if err != nil { goto done } + + if len(msg) > 0 { + var ev json_ssh_ws_event + err = json.Unmarshal(msg, &ev) + if err == nil { + switch ev.Type { + case "open": + if tty == nil && len(ev.Data) == 2 { + username = string(ev.Data[0]) + password = string(ev.Data[1]) + + wg.Add(1) + go func() { + var err error + + defer wg.Done() + cmd, tty, err = pts.connect_pts(username, password) + if err != nil { + c.log.Write(pts.Id, LOG_ERROR, "[%s] Failed to connect pts - %s", req.RemoteAddr, err.Error()) + pts.send_ws_data(ws, "error", err.Error()) + ws.Close() // dirty way to flag out the error + } else { + err = pts.send_ws_data(ws, "status", "opened") + if err != nil { + c.log.Write(pts.Id, LOG_ERROR, "[%s] Failed to write opened event to websocket - %s", req.RemoteAddr, err.Error()) + ws.Close() // dirty way to flag out the error + } else { + c.log.Write(pts.Id, LOG_DEBUG, "[%s] Opened pts session", req.RemoteAddr) + out = tty + in = tty + conn_ready_chan <- true + } + } + }() + } + + case "close": + if tty != nil { + tty.Close() + tty = nil + } + break ws_recv_loop + + case "iov": + if tty != nil { + var i int + for i, _ = range ev.Data { + in.Write([]byte(ev.Data[i])) + } + } + + case "size": + if tty != nil && len(ev.Data) == 2 { + var rows int + var cols int + rows, _ = strconv.Atoi(ev.Data[0]) + cols, _ = strconv.Atoi(ev.Data[1]) + pty.Setsize(tty, &pty.Winsize{Rows: uint16(rows), Cols: uint16(cols)}) + c.log.Write(pts.Id, LOG_DEBUG, "[%s] Resized terminal to %d,%d", req.RemoteAddr, rows, cols) + // ignore error + } + } + } + } + } + + if tty != nil { + err = pts.send_ws_data(ws, "status", "closed") + if err != nil { goto done } + } + +done: + conn_ready_chan <- false + ws.Close() + if cmd != nil { + // kill the child process underneath to close ptym(the master pty). + //cmd.Process.Signal(syscall.SIGTERM) + cmd.Process.Kill() + } + if tty != nil { tty.Close() } + if cmd != nil { cmd.Wait() } + wg.Wait() + c.log.Write(pts.Id, LOG_DEBUG, "[%s] Ended pts session", req.RemoteAddr) + + return http.StatusOK, err +} + + +// ------------------------------------------------------ + +func (pts *client_pts_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) { + var c *Client + var status_code int +// var err error + + c = pts.c + + switch pts.file { + case "xterm.js": + status_code = WriteJsRespHeader(w, http.StatusOK) + w.Write(xterm_js) + case "xterm-addon-fit.js": + status_code = WriteJsRespHeader(w, http.StatusOK) + w.Write(xterm_addon_fit_js) + case "xterm.css": + status_code = WriteCssRespHeader(w, http.StatusOK) + w.Write(xterm_css) + case "xterm-pts.html": + status_code = WriteHtmlRespHeader(w, http.StatusOK) + if c.xterm_pts_html != "" { + w.Write([]byte(c.xterm_pts_html)) + } else { + w.Write(xterm_pts_html) + } + + case "_forbidden": + status_code = WriteEmptyRespHeader(w, http.StatusForbidden) + + case "_notfound": + status_code = WriteEmptyRespHeader(w, http.StatusNotFound) + + default: + status_code = WriteEmptyRespHeader(w, http.StatusNotFound) + } + +//done: + return status_code, nil + +//oops: +// return status_code, err +} diff --git a/client.go b/client.go index 9613f85..88b7093 100644 --- a/client.go +++ b/client.go @@ -145,7 +145,12 @@ type Client struct { conns atomic.Int64 routes atomic.Int64 peers atomic.Int64 + pts_sessions atomic.Int64 } + + pts_user string + pts_shell string + xterm_pts_html string } type ClientConnState = int32 @@ -1624,9 +1629,31 @@ func NewClient(ctx context.Context, name string, logger Logger, cfg *ClientConfi c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/metrics", promhttp.HandlerFor(c.promreg, promhttp.HandlerOpts{ EnableOpenMetrics: true })) - c.ctl_mux.Handle("/_ctl/events", + c.ctl_mux.Handle("/_ctl/events", c.WrapWebsocketHandler(&client_ctl_ws{client_ctl{c: &c, id: HS_ID_CTL}})) + + c.ctl_mux.Handle("/_pts/ws", c.WrapWebsocketHandler(&client_pts_ws{C: &c, Id: HS_ID_CTL})) + + c.ctl_mux.Handle("/_pts/xterm.js", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.js"})) + c.ctl_mux.Handle("/_pts/xterm.js.map", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_notfound"})) + c.ctl_mux.Handle("/_pts/xterm-addon-fit.js", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm-addon-fit.js"})) + c.ctl_mux.Handle("/_pts/xterm-addon-fit.js.map", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_notfound"})) + c.ctl_mux.Handle("/_pts/xterm.css", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.css"})) + c.ctl_mux.Handle("/_pts/xterm-pts.html", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm-pts.html"})) + c.ctl_mux.Handle("/_pts/", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"})) + c.ctl_mux.Handle("/_pts/favicon.ico", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"})) + c.ctl_mux.Handle("/_pts/favicon.ico/", + c.WrapHttpHandler(&client_pts_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"})) + c.ctl_addr = make([]string, len(cfg.CtlAddrs)) c.ctl = make([]*http.Server, len(cfg.CtlAddrs)) copy(c.ctl_addr, cfg.CtlAddrs) @@ -1646,6 +1673,7 @@ func NewClient(ctx context.Context, name string, logger Logger, cfg *ClientConfi c.stats.conns.Store(0) c.stats.routes.Store(0) c.stats.peers.Store(0) + c.stats.pts_sessions.Store(0) return &c } @@ -1934,6 +1962,30 @@ func (c *Client) ReqStop() { } } +func (c *Client) SetXtermPtsHtml(html string) { + c.xterm_pts_html = html +} + +func (c *Client) GetXtermPtsHtml() string { + return c.xterm_pts_html +} + +func (c *Client) SetPtsUser(user string) { + c.pts_user = user +} + +func (c *Client) GetPtsUser() string { + return c.pts_user +} + +func (c *Client) SetPtsShell(user string) { + c.pts_shell = user +} + +func (c *Client) GetPtsShell() string { + return c.pts_shell +} + func (c *Client) RunCtlTask(wg *sync.WaitGroup) { var err error var ctl *http.Server diff --git a/cmd/config.go b/cmd/config.go index a8abce7..ef99c7f 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -105,6 +105,9 @@ type ClientAppConfig struct { MaxPeers int `yaml:"max-peer-conns"` // maximum number of connections from peers MaxRpcConns int `yaml:"max-rpc-conns"` // maximum number of rpc connections PeerConnTmout time.Duration `yaml:"peer-conn-timeout"` + PtsUser string `yaml:"pts-user"` + PtsShell string `yaml:"pts-shell"` + XtermPtsHtmlFile string `yaml:"xterm-pts-html-file"` } type ServerConfig struct { diff --git a/cmd/main.go b/cmd/main.go index ddf6ce9..0e02934 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -252,6 +252,10 @@ func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string, var logmask hodu.LogMask var logfile_maxsize int64 var logfile_rotate int + var pts_user string + var pts_shell string + var xterm_pts_html_file string + var xterm_pts_html string var i int var err error @@ -281,6 +285,9 @@ func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string, if logfile == "" { logfile = cfg.APP.LogFile } logfile_maxsize = cfg.APP.LogMaxSize logfile_rotate = cfg.APP.LogRotate + pts_user = cfg.APP.PtsUser + pts_shell = cfg.APP.PtsShell + xterm_pts_html_file = cfg.APP.XtermPtsHtmlFile config.RpcConnMax = cfg.APP.MaxRpcConns config.PeerConnMax = cfg.APP.MaxPeers config.PeerConnTmout = cfg.APP.PeerConnTmout @@ -305,8 +312,22 @@ func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string, return fmt.Errorf("failed to initialize logger - %s", err.Error()) } } + + if xterm_pts_html_file != "" { + var tmp []byte + tmp, err = os.ReadFile(xterm_pts_html_file) + if err != nil { + return fmt.Errorf("failed to read %s - %s", xterm_pts_html_file, err.Error()) + } + xterm_pts_html = string(tmp) + } + c = hodu.NewClient(context.Background(), HODU_NAME, logger, config) + if pts_user != "" { c.SetPtsUser(pts_user) } + if pts_shell != "" { c.SetPtsShell(pts_shell) } + if xterm_pts_html != "" { c.SetXtermPtsHtml(xterm_pts_html) } + c.StartService(&cc) c.StartCtlService() // control channel c.StartExtService(&signal_handler{svc:c}, nil) // signal handler task diff --git a/go.mod b/go.mod index cacfe4c..1e15fc5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module hodu go 1.22.0 require ( + github.com/creack/pty v1.1.24 github.com/goccy/go-yaml v1.17.1 github.com/prometheus/client_golang v1.20.5 golang.org/x/crypto v0.26.0 diff --git a/go.sum b/go.sum index 813583b..59211ec 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/hodu.go b/hodu.go index 2854b27..8a03480 100644 --- a/hodu.go +++ b/hodu.go @@ -1,6 +1,7 @@ package hodu import "crypto/rsa" +import _ "embed" import "encoding/base64" import "fmt" import "net" @@ -116,6 +117,22 @@ type json_out_go_stats struct { OtherSysBytes uint64 `json:"memory-other-sys-bytes"` } + +// --------------------------------------------------------- + +//go:embed xterm.js +var xterm_js []byte +//go:embed xterm-addon-fit.js +var xterm_addon_fit_js []byte +//go:embed xterm.css +var xterm_css []byte +//go:embed xterm.html +var xterm_html string +//go:embed xterm-pts.html +var xterm_pts_html []byte + +// --------------------------------------------------------- + func (n *Named) SetName(name string) { n.name = name } diff --git a/server-pxy.go b/server-pxy.go index 62f2217..48866fa 100644 --- a/server-pxy.go +++ b/server-pxy.go @@ -3,8 +3,8 @@ package hodu import "bufio" import "context" import "crypto/tls" -import _ "embed" import "encoding/json" +import "errors" import "fmt" import "io" import "net" @@ -21,15 +21,6 @@ import "golang.org/x/crypto/ssh" import "golang.org/x/net/http/httpguts" import "golang.org/x/net/websocket" -//go:embed xterm.js -var xterm_js []byte -//go:embed xterm-addon-fit.js -var xterm_addon_fit_js []byte -//go:embed xterm.css -var xterm_css []byte -//go:embed xterm.html -var xterm_html string - type server_pxy struct { S *Server Id string @@ -584,7 +575,7 @@ func (pxy *server_pxy_ssh_ws) send_ws_data(ws *websocket.Conn, type_val string, return err } -func (pxy *server_pxy_ssh_ws) connect_ssh (ctx context.Context, username string, password string, r *ServerRoute) ( *ssh.Client, *ssh.Session, io.Writer, io.Reader, error) { +func (pxy *server_pxy_ssh_ws) connect_ssh (ctx context.Context, username string, password string, r *ServerRoute) (*ssh.Client, *ssh.Session, io.Writer, io.Reader, error) { var cc *ssh.ClientConfig var addr *net.TCPAddr var dialer *net.Dialer @@ -712,15 +703,15 @@ func (pxy *server_pxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { for { n, err = out.Read(buf) if err != nil { - if err != io.EOF { - s.log.Write(pxy.Id, LOG_ERROR, "Read from SSH stdout error - %s", err.Error()) + if !errors.Is(err, io.EOF) { + s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to read from SSH stdout - %s", req.RemoteAddr, err.Error()) } break } if n > 0 { err = pxy.send_ws_data(ws, "iov", string(buf[:n])) if err != nil { - s.log.Write(pxy.Id, LOG_ERROR, "Failed to send to websocket - %s", err.Error()) + s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err.Error()) break } } @@ -757,26 +748,27 @@ ws_recv_loop: defer wg.Done() c, sess, in, out, err = pxy.connect_ssh(connect_ssh_ctx, username, password, r) if err != nil { - s.log.Write(pxy.Id, LOG_ERROR, "failed to connect ssh - %s", err.Error()) + s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to connect ssh - %s", req.RemoteAddr, err.Error()) pxy.send_ws_data(ws, "error", err.Error()) ws.Close() // dirty way to flag out the error } else { err = pxy.send_ws_data(ws, "status", "opened") if err != nil { - s.log.Write(pxy.Id, LOG_ERROR, "Failed to write opened event to websocket - %s", err.Error()) + s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to write opened event to websocket - %s", req.RemoteAddr, err.Error()) ws.Close() // dirty way to flag out the error } else { + s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Opened SSH session - %s", req.RemoteAddr, err.Error()) conn_ready_chan <- true } } (connect_ssh_cancel.Get())() - connect_ssh_cancel.Set(nil) + connect_ssh_cancel.Set(nil) // @@@ use atomic }() } case "close": var cancel context.CancelFunc - cancel = connect_ssh_cancel.Get() // is it a good way to avoid mutex? + cancel = connect_ssh_cancel.Get() // is it a good way to avoid mutex against Set() marked with @@@ above? if cancel != nil { cancel() } break ws_recv_loop @@ -795,7 +787,7 @@ ws_recv_loop: rows, _ = strconv.Atoi(ev.Data[0]) cols, _ = strconv.Atoi(ev.Data[1]) sess.WindowChange(rows, cols) - s.log.Write(pxy.Id, LOG_DEBUG, "Resized terminal to %d,%d", rows, cols) + s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Resized terminal to %d,%d", req.RemoteAddr, rows, cols) // ignore error } } @@ -814,6 +806,7 @@ done: if sess != nil { sess.Close() } if c != nil { c.Close() } wg.Wait() + s.log.Write(pxy.Id, LOG_DEBUG, "[%s] Ended SSH Session", req.RemoteAddr) return http.StatusOK, err } diff --git a/server.go b/server.go index 74a708e..211df85 100644 --- a/server.go +++ b/server.go @@ -1364,7 +1364,7 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi /* // this part is duplcate of pxy_mux. - s.ctl_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", + s.ctl_mux.Handle("/_ssh/ws/{conn_id}/{route_id}", s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_PXY_WS})) s.ctl_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl{S: &s, Id: HS_ID_CTL, NoAuth: true}})) @@ -1406,8 +1406,8 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi // --------------------------------------------------------- - s.pxy_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh_ws,_http configurable... - s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", + s.pxy_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable... + s.pxy_mux.Handle("/_ssh/ws/{conn_id}/{route_id}", s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_PXY_WS})) s.pxy_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl{S: &s, Id: HS_ID_PXY, NoAuth: true}})) @@ -1451,8 +1451,8 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi s.wpx_mux = http.NewServeMux() - s.wpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh_ws,_http configurable... - s.wpx_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", + s.wpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable... + s.wpx_mux.Handle("/_ssh/ws/{conn_id}/{route_id}", s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: "wpx-ssh"})) s.wpx_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", diff --git a/xterm-pts.html b/xterm-pts.html new file mode 100644 index 0000000..b7afa37 --- /dev/null +++ b/xterm-pts.html @@ -0,0 +1,285 @@ + + +
+ + + +