Revert "AUTH-1941: Adds initial SSH server implementation"
This reverts commit e9c9bf3cbd.
			
			
This commit is contained in:
		
							parent
							
								
									026f2d1cc9
								
							
						
					
					
						commit
						47254113ee
					
				|  | @ -25,14 +25,6 @@ | ||||||
|   revision = "3f9954f6f6697845b082ca57995849ddf614f450" |   revision = "3f9954f6f6697845b082ca57995849ddf614f450" | ||||||
|   version = "v1.3.3" |   version = "v1.3.3" | ||||||
| 
 | 
 | ||||||
| [[projects]] |  | ||||||
|   branch = "master" |  | ||||||
|   digest = "1:1a200e7e73293b75eb8e5c93d023b5472663432da0b663e1532624fcfede9ca8" |  | ||||||
|   name = "github.com/anmitsu/go-shlex" |  | ||||||
|   packages = ["."] |  | ||||||
|   pruneopts = "UT" |  | ||||||
|   revision = "648efa622239a2f6ff949fed78ee37b48d499ba4" |  | ||||||
| 
 |  | ||||||
| [[projects]] | [[projects]] | ||||||
|   digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" |   digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" | ||||||
|   name = "github.com/beorn7/perks" |   name = "github.com/beorn7/perks" | ||||||
|  | @ -120,14 +112,6 @@ | ||||||
|   revision = "95778dfbb74eb7e4dbaf43bf7d71809650ef8076" |   revision = "95778dfbb74eb7e4dbaf43bf7d71809650ef8076" | ||||||
|   version = "v19" |   version = "v19" | ||||||
| 
 | 
 | ||||||
| [[projects]] |  | ||||||
|   digest = "1:385dff5bf83bf1c9d40ef2b0f2e697f0d1973fc9e24ae9401cf41698612bbebc" |  | ||||||
|   name = "github.com/creack/pty" |  | ||||||
|   packages = ["."] |  | ||||||
|   pruneopts = "UT" |  | ||||||
|   revision = "2769f65a3a94eb8f876f44a0459d24ae7ad2e488" |  | ||||||
|   version = "v1.1.7" |  | ||||||
| 
 |  | ||||||
| [[projects]] | [[projects]] | ||||||
|   digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" |   digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" | ||||||
|   name = "github.com/davecgh/go-spew" |   name = "github.com/davecgh/go-spew" | ||||||
|  | @ -193,14 +177,6 @@ | ||||||
|   pruneopts = "UT" |   pruneopts = "UT" | ||||||
|   revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa" |   revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa" | ||||||
| 
 | 
 | ||||||
| [[projects]] |  | ||||||
|   digest = "1:0a01355005024757ae2a1f62e8fe68a30b4f5fde7a08ac88b3685e7999ac354b" |  | ||||||
|   name = "github.com/gliderlabs/ssh" |  | ||||||
|   packages = ["."] |  | ||||||
|   pruneopts = "UT" |  | ||||||
|   revision = "5b6cc7030f17095c0cf23bb063b0bfe824fe5f8b" |  | ||||||
|   version = "v0.2.2" |  | ||||||
| 
 |  | ||||||
| [[projects]] | [[projects]] | ||||||
|   digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" |   digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" | ||||||
|   name = "github.com/go-sql-driver/mysql" |   name = "github.com/go-sql-driver/mysql" | ||||||
|  | @ -757,12 +733,10 @@ | ||||||
|     "github.com/coredns/coredns/request", |     "github.com/coredns/coredns/request", | ||||||
|     "github.com/coreos/go-oidc", |     "github.com/coreos/go-oidc", | ||||||
|     "github.com/coreos/go-systemd/daemon", |     "github.com/coreos/go-systemd/daemon", | ||||||
|     "github.com/creack/pty", |  | ||||||
|     "github.com/denisenkom/go-mssqldb", |     "github.com/denisenkom/go-mssqldb", | ||||||
|     "github.com/equinox-io/equinox", |     "github.com/equinox-io/equinox", | ||||||
|     "github.com/facebookgo/grace/gracenet", |     "github.com/facebookgo/grace/gracenet", | ||||||
|     "github.com/getsentry/raven-go", |     "github.com/getsentry/raven-go", | ||||||
|     "github.com/gliderlabs/ssh", |  | ||||||
|     "github.com/go-sql-driver/mysql", |     "github.com/go-sql-driver/mysql", | ||||||
|     "github.com/golang-collections/collections/queue", |     "github.com/golang-collections/collections/queue", | ||||||
|     "github.com/google/uuid", |     "github.com/google/uuid", | ||||||
|  |  | ||||||
|  | @ -115,11 +115,3 @@ | ||||||
| [[constraint]] | [[constraint]] | ||||||
|   branch = "v2" |   branch = "v2" | ||||||
|   name = "github.com/coreos/go-oidc" |   name = "github.com/coreos/go-oidc" | ||||||
| 
 |  | ||||||
| [[constraint]] |  | ||||||
|   name = "github.com/gliderlabs/ssh" |  | ||||||
|   version = "0.2.2" |  | ||||||
| 
 |  | ||||||
| [[constraint]] |  | ||||||
|   name = "github.com/creack/pty" |  | ||||||
|   version = "1.1.7" |  | ||||||
|  |  | ||||||
|  | @ -1,9 +1,3 @@ | ||||||
| 2019.8.2 |  | ||||||
| - 2019-08-20 STOR-519: Add db-connect, a SQL over HTTPS server |  | ||||||
| - 2019-08-11 TUN-2163: Add GrapQLType method to Scope interface |  | ||||||
| - 2019-08-06 TUN-2152: Requests with a query in the URL are erroneously escaped |  | ||||||
| - 2019-07-18 AUTH-1941: Adds initial SSH server implementation |  | ||||||
| 
 |  | ||||||
| 2019.8.1 | 2019.8.1 | ||||||
| - 2019-08-05 TUN-2111: Implement custom serialization logic for FallibleConfig and OriginConfig | - 2019-08-05 TUN-2111: Implement custom serialization logic for FallibleConfig and OriginConfig | ||||||
| - 2019-08-06 Revert "TUN-1736: Missing headers when passing an invalid path" | - 2019-08-06 Revert "TUN-1736: Missing headers when passing an invalid path" | ||||||
|  |  | ||||||
|  | @ -8,32 +8,31 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"runtime" |  | ||||||
| 	"runtime/trace" | 	"runtime/trace" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/cloudflare/cloudflared/dbconnect" | ||||||
|  | 	"github.com/cloudflare/cloudflared/tunnelrpc/pogs" | ||||||
|  | 
 | ||||||
|  | 	"github.com/cloudflare/cloudflared/connection" | ||||||
|  | 	"github.com/cloudflare/cloudflared/supervisor" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 
 | ||||||
|  | 	"github.com/getsentry/raven-go" | ||||||
|  | 
 | ||||||
| 	"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo" | 	"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo" | ||||||
| 	"github.com/cloudflare/cloudflared/cmd/cloudflared/config" | 	"github.com/cloudflare/cloudflared/cmd/cloudflared/config" | ||||||
| 	"github.com/cloudflare/cloudflared/cmd/cloudflared/updater" | 	"github.com/cloudflare/cloudflared/cmd/cloudflared/updater" | ||||||
| 	"github.com/cloudflare/cloudflared/connection" |  | ||||||
| 	"github.com/cloudflare/cloudflared/dbconnect" |  | ||||||
| 	"github.com/cloudflare/cloudflared/hello" | 	"github.com/cloudflare/cloudflared/hello" | ||||||
| 	"github.com/cloudflare/cloudflared/metrics" | 	"github.com/cloudflare/cloudflared/metrics" | ||||||
| 	"github.com/cloudflare/cloudflared/origin" | 	"github.com/cloudflare/cloudflared/origin" | ||||||
| 	"github.com/cloudflare/cloudflared/signal" | 	"github.com/cloudflare/cloudflared/signal" | ||||||
| 	"github.com/cloudflare/cloudflared/sshserver" |  | ||||||
| 	"github.com/cloudflare/cloudflared/supervisor" |  | ||||||
| 	"github.com/cloudflare/cloudflared/tlsconfig" | 	"github.com/cloudflare/cloudflared/tlsconfig" | ||||||
| 	"github.com/cloudflare/cloudflared/tunneldns" | 	"github.com/cloudflare/cloudflared/tunneldns" | ||||||
| 	"github.com/cloudflare/cloudflared/tunnelrpc/pogs" |  | ||||||
| 	"github.com/cloudflare/cloudflared/websocket" | 	"github.com/cloudflare/cloudflared/websocket" | ||||||
| 
 |  | ||||||
| 	"github.com/coreos/go-systemd/daemon" | 	"github.com/coreos/go-systemd/daemon" | ||||||
| 	"github.com/facebookgo/grace/gracenet" | 	"github.com/facebookgo/grace/gracenet" | ||||||
| 	"github.com/getsentry/raven-go" |  | ||||||
| 	"github.com/gliderlabs/ssh" |  | ||||||
| 	"github.com/google/uuid" |  | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"gopkg.in/urfave/cli.v2" | 	"gopkg.in/urfave/cli.v2" | ||||||
| 	"gopkg.in/urfave/cli.v2/altsrc" | 	"gopkg.in/urfave/cli.v2/altsrc" | ||||||
|  | @ -291,31 +290,6 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan | ||||||
| 		c.Set("url", "https://"+helloListener.Addr().String()) | 		c.Set("url", "https://"+helloListener.Addr().String()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.IsSet("ssh-server") { |  | ||||||
| 		if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { |  | ||||||
| 			logger.Errorf("--ssh-server is not supported on %s", runtime.GOOS) |  | ||||||
| 			return errors.New(fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)) |  | ||||||
| 
 |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		logger.Infof("ssh-server set") |  | ||||||
| 
 |  | ||||||
| 		sshServerAddress := "127.0.0.1:" + c.String("local-ssh-port") |  | ||||||
| 		server, err := sshserver.New(logger, sshServerAddress, shutdownC) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logger.WithError(err).Error("Cannot create new SSH Server") |  | ||||||
| 			return errors.Wrap(err, "Cannot create new SSH Server") |  | ||||||
| 		} |  | ||||||
| 		wg.Add(1) |  | ||||||
| 		go func() { |  | ||||||
| 			defer wg.Done() |  | ||||||
| 			if err = server.Start(); err != nil && err != ssh.ErrServerClosed { |  | ||||||
| 				logger.WithError(err).Error("SSH server error") |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 		c.Set("url", "ssh://"+sshServerAddress) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if host := hostnameFromURI(c.String("url")); host != "" { | 	if host := hostnameFromURI(c.String("url")); host != "" { | ||||||
| 		listener, err := net.Listen("tcp", "127.0.0.1:") | 		listener, err := net.Listen("tcp", "127.0.0.1:") | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -339,7 +313,6 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan | ||||||
| 	go func() { | 	go func() { | ||||||
| 		defer wg.Done() | 		defer wg.Done() | ||||||
| 		errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID) | 		errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID) | ||||||
| 		errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID) |  | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period")) | 	return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period")) | ||||||
|  | @ -776,13 +749,6 @@ func tunnelFlags(shouldHide bool) []cli.Flag { | ||||||
| 			EnvVars: []string{"TUNNEL_HELLO_WORLD"}, | 			EnvVars: []string{"TUNNEL_HELLO_WORLD"}, | ||||||
| 			Hidden:  shouldHide, | 			Hidden:  shouldHide, | ||||||
| 		}), | 		}), | ||||||
| 		altsrc.NewBoolFlag(&cli.BoolFlag{ |  | ||||||
| 			Name:    "ssh-server", |  | ||||||
| 			Value:   false, |  | ||||||
| 			Usage:   "Run an SSH Server", |  | ||||||
| 			EnvVars: []string{"TUNNEL_SSH_SERVER"}, |  | ||||||
| 			Hidden:  true, // TODO: remove when feature is complete
 |  | ||||||
| 		}), |  | ||||||
| 		altsrc.NewStringFlag(&cli.StringFlag{ | 		altsrc.NewStringFlag(&cli.StringFlag{ | ||||||
| 			Name:    "pidfile", | 			Name:    "pidfile", | ||||||
| 			Usage:   "Write the application's PID to this file after first successful connection.", | 			Usage:   "Write the application's PID to this file after first successful connection.", | ||||||
|  | @ -925,12 +891,5 @@ func tunnelFlags(shouldHide bool) []cli.Flag { | ||||||
| 			EnvVars: []string{"DIAL_EDGE_TIMEOUT"}, | 			EnvVars: []string{"DIAL_EDGE_TIMEOUT"}, | ||||||
| 			Hidden:  true, | 			Hidden:  true, | ||||||
| 		}), | 		}), | ||||||
| 		altsrc.NewStringFlag(&cli.StringFlag{ |  | ||||||
| 			Name:    "local-ssh-port", |  | ||||||
| 			Usage:   "Localhost port that cloudflared SSH server will run on", |  | ||||||
| 			Value:   "22", |  | ||||||
| 			EnvVars: []string{"LOCAL_SSH_PORT"}, |  | ||||||
| 			Hidden:  true, |  | ||||||
| 		}), |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,168 +0,0 @@ | ||||||
| package sshserver |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"fmt" |  | ||||||
| 	"github.com/pkg/errors" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"os/user" |  | ||||||
| 	"strconv" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| 
 |  | ||||||
| 	"github.com/creack/pty" |  | ||||||
| 	"github.com/gliderlabs/ssh" |  | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type SSHServer struct { |  | ||||||
| 	ssh.Server |  | ||||||
| 	logger    *logrus.Logger |  | ||||||
| 	shutdownC chan struct{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const DefaultShellPrompt = `\e[0;31m[\u@\h \W]\$ \e[m ` |  | ||||||
| 
 |  | ||||||
| func New(logger *logrus.Logger, address string, shutdownC chan struct{}) (*SSHServer, error) { |  | ||||||
| 	currentUser, err := user.Current() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if currentUser.Uid != "0" { |  | ||||||
| 		return nil, errors.New("cloudflared ssh server needs to run as root") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	sshServer := SSHServer{ssh.Server{Addr: address}, logger, shutdownC} |  | ||||||
| 	return &sshServer, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *SSHServer) Start() error { |  | ||||||
| 	s.logger.Infof("Starting SSH server at %s", s.Addr) |  | ||||||
| 
 |  | ||||||
| 	go func() { |  | ||||||
| 		<-s.shutdownC |  | ||||||
| 		if err := s.Close(); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Cannot close SSH server") |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	s.Handle(s.connectionHandler) |  | ||||||
| 	return s.ListenAndServe() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *SSHServer) connectionHandler(session ssh.Session) { |  | ||||||
| 
 |  | ||||||
| 	// Get uid and gid of user attempting to login
 |  | ||||||
| 	uid, gid, err := getUser(session.User()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if _, err := io.WriteString(session, "Invalid credentials\n"); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Invalid credentials: Failed to write to SSH session") |  | ||||||
| 		} |  | ||||||
| 		if err := session.Exit(1); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Failed to close SSH session") |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Spawn shell under user
 |  | ||||||
| 	cmd := exec.Command("/bin/bash") |  | ||||||
| 	cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uid, Gid: gid}} |  | ||||||
| 
 |  | ||||||
| 	ptyReq, winCh, isPty := session.Pty() |  | ||||||
| 	if !isPty { |  | ||||||
| 		if _, err := io.WriteString(session, "No PTY requested.\n"); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("No PTY requested: Failed to write to SSH session") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err := session.Exit(1); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Failed to close SSH session") |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term)) |  | ||||||
| 	cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=%s", DefaultShellPrompt)) |  | ||||||
| 	psuedoTTY, err := pty.Start(cmd) |  | ||||||
| 	if err != nil { |  | ||||||
| 		s.logger.WithError(err).Error("Failed to start pty session") |  | ||||||
| 		if err := session.Exit(1); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Failed to close SSH session") |  | ||||||
| 		} |  | ||||||
| 		close(s.shutdownC) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Handle terminal window size changes
 |  | ||||||
| 	go func() { |  | ||||||
| 		for win := range winCh { |  | ||||||
| 			if errNo := setWinsize(psuedoTTY, win.Width, win.Height); errNo != 0 { |  | ||||||
| 				s.logger.WithError(err).Error("Failed to set pty window size: ", err.Error()) |  | ||||||
| 				if err := session.Exit(1); err != nil { |  | ||||||
| 					s.logger.WithError(err).Error("Failed to close SSH session") |  | ||||||
| 				} |  | ||||||
| 				close(s.shutdownC) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	// Write incoming commands to PTY
 |  | ||||||
| 	go func() { |  | ||||||
| 		if _, err := io.Copy(psuedoTTY, session); err != nil { |  | ||||||
| 			s.logger.WithError(err).Error("Failed to write incoming command to pty") |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 	pr, pw := io.Pipe() |  | ||||||
| 	scanner := bufio.NewScanner(pr) |  | ||||||
| 	go func() { |  | ||||||
| 		for scanner.Scan() { |  | ||||||
| 			s.logger.Info(scanner.Text()) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	// Write outgoing command output to both the command recorder, and remote user
 |  | ||||||
| 	mw := io.MultiWriter(pw, session) |  | ||||||
| 	if _, err := io.Copy(mw, psuedoTTY); err != nil { |  | ||||||
| 		s.logger.WithError(err).Error("Failed to write command output to user") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := pw.Close(); err != nil { |  | ||||||
| 		s.logger.WithError(err).Error("Failed to close pipe writer") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := pr.Close(); err != nil { |  | ||||||
| 		s.logger.WithError(err).Error("Failed to close pipe reader") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Wait for all resources associated with cmd to be released
 |  | ||||||
| 	// Returns error if shell exited with a non-zero status or received a signal
 |  | ||||||
| 	if err := cmd.Wait(); err != nil { |  | ||||||
| 		s.logger.WithError(err).Debug("Shell did not close correctly") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Sets PTY window size for terminal
 |  | ||||||
| func setWinsize(f *os.File, w, h int) syscall.Errno { |  | ||||||
| 	_, _, errNo := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ), |  | ||||||
| 		uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(h), uint16(w), 0, 0}))) |  | ||||||
| 	return errNo |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Only works on POSIX systems
 |  | ||||||
| func getUser(username string) (uint32, uint32, error) { |  | ||||||
| 	sshUser, err := user.Lookup(username) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, 0, err |  | ||||||
| 	} |  | ||||||
| 	uid, err := strconv.ParseUint(sshUser.Uid, 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, 0, err |  | ||||||
| 	} |  | ||||||
| 	gid, err := strconv.ParseUint(sshUser.Gid, 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, 0, err |  | ||||||
| 	} |  | ||||||
| 	return uint32(uid), uint32(gid), nil |  | ||||||
| } |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| shlex.test |  | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| Copyright (c) anmitsu <anmitsu.s@gmail.com> |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| a copy of this software and associated documentation files (the |  | ||||||
| "Software"), to deal in the Software without restriction, including |  | ||||||
| without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| permit persons to whom the Software is furnished to do so, subject to |  | ||||||
| the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall be |  | ||||||
| included in all copies or substantial portions of the Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |  | ||||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |  | ||||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |  | ||||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  | @ -1,38 +0,0 @@ | ||||||
| # go-shlex |  | ||||||
| 
 |  | ||||||
| go-shlex is a library to make a lexical analyzer like Unix shell for |  | ||||||
| Go. |  | ||||||
| 
 |  | ||||||
| ## Install |  | ||||||
| 
 |  | ||||||
|     go get -u "github.com/anmitsu/go-shlex" |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|     "fmt" |  | ||||||
|     "log" |  | ||||||
| 
 |  | ||||||
|     "github.com/anmitsu/go-shlex" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
|     cmd := `cp -Rdp "file name" 'file name2' dir\ name` |  | ||||||
|     words, err := shlex.Split(cmd, true) |  | ||||||
|     if err != nil { |  | ||||||
|         log.Fatal(err) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for _, w := range words { |  | ||||||
|         fmt.Println(w) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Documentation |  | ||||||
| 
 |  | ||||||
| http://godoc.org/github.com/anmitsu/go-shlex |  | ||||||
| 
 |  | ||||||
|  | @ -1,193 +0,0 @@ | ||||||
| // Package shlex provides a simple lexical analysis like Unix shell.
 |  | ||||||
| package shlex |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bufio" |  | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"strings" |  | ||||||
| 	"unicode" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	ErrNoClosing = errors.New("No closing quotation") |  | ||||||
| 	ErrNoEscaped = errors.New("No escaped character") |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Tokenizer is the interface that classifies a token according to
 |  | ||||||
| // words, whitespaces, quotations, escapes and escaped quotations.
 |  | ||||||
| type Tokenizer interface { |  | ||||||
| 	IsWord(rune) bool |  | ||||||
| 	IsWhitespace(rune) bool |  | ||||||
| 	IsQuote(rune) bool |  | ||||||
| 	IsEscape(rune) bool |  | ||||||
| 	IsEscapedQuote(rune) bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DefaultTokenizer implements a simple tokenizer like Unix shell.
 |  | ||||||
| type DefaultTokenizer struct{} |  | ||||||
| 
 |  | ||||||
| func (t *DefaultTokenizer) IsWord(r rune) bool { |  | ||||||
| 	return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r) |  | ||||||
| } |  | ||||||
| func (t *DefaultTokenizer) IsQuote(r rune) bool { |  | ||||||
| 	switch r { |  | ||||||
| 	case '\'', '"': |  | ||||||
| 		return true |  | ||||||
| 	default: |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| func (t *DefaultTokenizer) IsWhitespace(r rune) bool { |  | ||||||
| 	return unicode.IsSpace(r) |  | ||||||
| } |  | ||||||
| func (t *DefaultTokenizer) IsEscape(r rune) bool { |  | ||||||
| 	return r == '\\' |  | ||||||
| } |  | ||||||
| func (t *DefaultTokenizer) IsEscapedQuote(r rune) bool { |  | ||||||
| 	return r == '"' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Lexer represents a lexical analyzer.
 |  | ||||||
| type Lexer struct { |  | ||||||
| 	reader          *bufio.Reader |  | ||||||
| 	tokenizer       Tokenizer |  | ||||||
| 	posix           bool |  | ||||||
| 	whitespacesplit bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewLexer creates a new Lexer reading from io.Reader.  This Lexer
 |  | ||||||
| // has a DefaultTokenizer according to posix and whitespacesplit
 |  | ||||||
| // rules.
 |  | ||||||
| func NewLexer(r io.Reader, posix, whitespacesplit bool) *Lexer { |  | ||||||
| 	return &Lexer{ |  | ||||||
| 		reader:          bufio.NewReader(r), |  | ||||||
| 		tokenizer:       &DefaultTokenizer{}, |  | ||||||
| 		posix:           posix, |  | ||||||
| 		whitespacesplit: whitespacesplit, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewLexerString creates a new Lexer reading from a string.  This
 |  | ||||||
| // Lexer has a DefaultTokenizer according to posix and whitespacesplit
 |  | ||||||
| // rules.
 |  | ||||||
| func NewLexerString(s string, posix, whitespacesplit bool) *Lexer { |  | ||||||
| 	return NewLexer(strings.NewReader(s), posix, whitespacesplit) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Split splits a string according to posix or non-posix rules.
 |  | ||||||
| func Split(s string, posix bool) ([]string, error) { |  | ||||||
| 	return NewLexerString(s, posix, true).Split() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetTokenizer sets a Tokenizer.
 |  | ||||||
| func (l *Lexer) SetTokenizer(t Tokenizer) { |  | ||||||
| 	l.tokenizer = t |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *Lexer) Split() ([]string, error) { |  | ||||||
| 	result := make([]string, 0) |  | ||||||
| 	for { |  | ||||||
| 		token, err := l.readToken() |  | ||||||
| 		if token != "" { |  | ||||||
| 			result = append(result, token) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err == io.EOF { |  | ||||||
| 			break |  | ||||||
| 		} else if err != nil { |  | ||||||
| 			return result, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return result, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *Lexer) readToken() (string, error) { |  | ||||||
| 	t := l.tokenizer |  | ||||||
| 	token := "" |  | ||||||
| 	quoted := false |  | ||||||
| 	state := ' ' |  | ||||||
| 	escapedstate := ' ' |  | ||||||
| scanning: |  | ||||||
| 	for { |  | ||||||
| 		next, _, err := l.reader.ReadRune() |  | ||||||
| 		if err != nil { |  | ||||||
| 			if t.IsQuote(state) { |  | ||||||
| 				return token, ErrNoClosing |  | ||||||
| 			} else if t.IsEscape(state) { |  | ||||||
| 				return token, ErrNoEscaped |  | ||||||
| 			} |  | ||||||
| 			return token, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		switch { |  | ||||||
| 		case t.IsWhitespace(state): |  | ||||||
| 			switch { |  | ||||||
| 			case t.IsWhitespace(next): |  | ||||||
| 				break scanning |  | ||||||
| 			case l.posix && t.IsEscape(next): |  | ||||||
| 				escapedstate = 'a' |  | ||||||
| 				state = next |  | ||||||
| 			case t.IsWord(next): |  | ||||||
| 				token += string(next) |  | ||||||
| 				state = 'a' |  | ||||||
| 			case t.IsQuote(next): |  | ||||||
| 				if !l.posix { |  | ||||||
| 					token += string(next) |  | ||||||
| 				} |  | ||||||
| 				state = next |  | ||||||
| 			default: |  | ||||||
| 				token = string(next) |  | ||||||
| 				if l.whitespacesplit { |  | ||||||
| 					state = 'a' |  | ||||||
| 				} else if token != "" || (l.posix && quoted) { |  | ||||||
| 					break scanning |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		case t.IsQuote(state): |  | ||||||
| 			quoted = true |  | ||||||
| 			switch { |  | ||||||
| 			case next == state: |  | ||||||
| 				if !l.posix { |  | ||||||
| 					token += string(next) |  | ||||||
| 					break scanning |  | ||||||
| 				} else { |  | ||||||
| 					state = 'a' |  | ||||||
| 				} |  | ||||||
| 			case l.posix && t.IsEscape(next) && t.IsEscapedQuote(state): |  | ||||||
| 				escapedstate = state |  | ||||||
| 				state = next |  | ||||||
| 			default: |  | ||||||
| 				token += string(next) |  | ||||||
| 			} |  | ||||||
| 		case t.IsEscape(state): |  | ||||||
| 			if t.IsQuote(escapedstate) && next != state && next != escapedstate { |  | ||||||
| 				token += string(state) |  | ||||||
| 			} |  | ||||||
| 			token += string(next) |  | ||||||
| 			state = escapedstate |  | ||||||
| 		case t.IsWord(state): |  | ||||||
| 			switch { |  | ||||||
| 			case t.IsWhitespace(next): |  | ||||||
| 				if token != "" || (l.posix && quoted) { |  | ||||||
| 					break scanning |  | ||||||
| 				} |  | ||||||
| 			case l.posix && t.IsQuote(next): |  | ||||||
| 				state = next |  | ||||||
| 			case l.posix && t.IsEscape(next): |  | ||||||
| 				escapedstate = 'a' |  | ||||||
| 				state = next |  | ||||||
| 			case t.IsWord(next) || t.IsQuote(next): |  | ||||||
| 				token += string(next) |  | ||||||
| 			default: |  | ||||||
| 				if l.whitespacesplit { |  | ||||||
| 					token += string(next) |  | ||||||
| 				} else if token != "" { |  | ||||||
| 					l.reader.UnreadRune() |  | ||||||
| 					break scanning |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return token, nil |  | ||||||
| } |  | ||||||
|  | @ -1,4 +0,0 @@ | ||||||
| [568].out |  | ||||||
| _go* |  | ||||||
| _test* |  | ||||||
| _obj |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| FROM golang:1.12 |  | ||||||
| 
 |  | ||||||
| # Clone and complie a riscv compatible version of the go compiler. |  | ||||||
| RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go |  | ||||||
| # riscvdev branch HEAD as of 2019-06-29. |  | ||||||
| RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf |  | ||||||
| ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH |  | ||||||
| RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash |  | ||||||
| ENV GOROOT=/riscv-go |  | ||||||
| 
 |  | ||||||
| # Make sure we compile. |  | ||||||
| WORKDIR pty |  | ||||||
| ADD . . |  | ||||||
| RUN GOOS=linux GOARCH=riscv go build |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| Copyright (c) 2011 Keith Rarick |  | ||||||
| 
 |  | ||||||
| Permission is hereby granted, free of charge, to any person |  | ||||||
| obtaining a copy of this software and associated |  | ||||||
| documentation files (the "Software"), to deal in the |  | ||||||
| Software without restriction, including without limitation |  | ||||||
| the rights to use, copy, modify, merge, publish, distribute, |  | ||||||
| sublicense, and/or sell copies of the Software, and to |  | ||||||
| permit persons to whom the Software is furnished to do so, |  | ||||||
| subject to the following conditions: |  | ||||||
| 
 |  | ||||||
| The above copyright notice and this permission notice shall |  | ||||||
| be included in all copies or substantial portions of the |  | ||||||
| Software. |  | ||||||
| 
 |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |  | ||||||
| KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |  | ||||||
| WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |  | ||||||
| PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS |  | ||||||
| OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR |  | ||||||
| OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |  | ||||||
| OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |  | ||||||
| SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  | @ -1,100 +0,0 @@ | ||||||
| # pty |  | ||||||
| 
 |  | ||||||
| Pty is a Go package for using unix pseudo-terminals. |  | ||||||
| 
 |  | ||||||
| ## Install |  | ||||||
| 
 |  | ||||||
|     go get github.com/creack/pty |  | ||||||
| 
 |  | ||||||
| ## Example |  | ||||||
| 
 |  | ||||||
| ### Command |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"github.com/creack/pty" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
| 	c := exec.Command("grep", "--color=auto", "bar") |  | ||||||
| 	f, err := pty.Start(c) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	go func() { |  | ||||||
| 		f.Write([]byte("foo\n")) |  | ||||||
| 		f.Write([]byte("bar\n")) |  | ||||||
| 		f.Write([]byte("baz\n")) |  | ||||||
| 		f.Write([]byte{4}) // EOT |  | ||||||
| 	}() |  | ||||||
| 	io.Copy(os.Stdout, f) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Shell |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
|         "io" |  | ||||||
|         "log" |  | ||||||
|         "os" |  | ||||||
|         "os/exec" |  | ||||||
|         "os/signal" |  | ||||||
|         "syscall" |  | ||||||
| 
 |  | ||||||
|         "github.com/creack/pty" |  | ||||||
|         "golang.org/x/crypto/ssh/terminal" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func test() error { |  | ||||||
|         // Create arbitrary command. |  | ||||||
|         c := exec.Command("bash") |  | ||||||
| 
 |  | ||||||
|         // Start the command with a pty. |  | ||||||
|         ptmx, err := pty.Start(c) |  | ||||||
|         if err != nil { |  | ||||||
|                 return err |  | ||||||
|         } |  | ||||||
|         // Make sure to close the pty at the end. |  | ||||||
|         defer func() { _ = ptmx.Close() }() // Best effort. |  | ||||||
| 
 |  | ||||||
|         // Handle pty size. |  | ||||||
|         ch := make(chan os.Signal, 1) |  | ||||||
|         signal.Notify(ch, syscall.SIGWINCH) |  | ||||||
|         go func() { |  | ||||||
|                 for range ch { |  | ||||||
|                         if err := pty.InheritSize(os.Stdin, ptmx); err != nil { |  | ||||||
|                                 log.Printf("error resizing pty: %s", err) |  | ||||||
|                         } |  | ||||||
|                 } |  | ||||||
|         }() |  | ||||||
|         ch <- syscall.SIGWINCH // Initial resize. |  | ||||||
| 
 |  | ||||||
|         // Set stdin in raw mode. |  | ||||||
|         oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) |  | ||||||
|         if err != nil { |  | ||||||
|                 panic(err) |  | ||||||
|         } |  | ||||||
|         defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. |  | ||||||
| 
 |  | ||||||
|         // Copy stdin to the pty and the pty to stdout. |  | ||||||
|         go func() { _, _ = io.Copy(ptmx, os.Stdin) }() |  | ||||||
|         _, _ = io.Copy(os.Stdout, ptmx) |  | ||||||
| 
 |  | ||||||
|         return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func main() { |  | ||||||
|         if err := test(); err != nil { |  | ||||||
|                 log.Fatal(err) |  | ||||||
|         } |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| // Package pty provides functions for working with Unix terminals.
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ErrUnsupported is returned if a function is not
 |  | ||||||
| // available on the current platform.
 |  | ||||||
| var ErrUnsupported = errors.New("unsupported") |  | ||||||
| 
 |  | ||||||
| // Opens a pty and its corresponding tty.
 |  | ||||||
| func Open() (pty, tty *os.File, err error) { |  | ||||||
| 	return open() |  | ||||||
| } |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| // +build !windows,!solaris
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import "syscall" |  | ||||||
| 
 |  | ||||||
| func ioctl(fd, cmd, ptr uintptr) error { |  | ||||||
| 	_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) |  | ||||||
| 	if e != 0 { |  | ||||||
| 		return e |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | @ -1,39 +0,0 @@ | ||||||
| // +build darwin dragonfly freebsd netbsd openbsd
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| // from <sys/ioccom.h>
 |  | ||||||
| const ( |  | ||||||
| 	_IOC_VOID    uintptr = 0x20000000 |  | ||||||
| 	_IOC_OUT     uintptr = 0x40000000 |  | ||||||
| 	_IOC_IN      uintptr = 0x80000000 |  | ||||||
| 	_IOC_IN_OUT  uintptr = _IOC_OUT | _IOC_IN |  | ||||||
| 	_IOC_DIRMASK         = _IOC_VOID | _IOC_OUT | _IOC_IN |  | ||||||
| 
 |  | ||||||
| 	_IOC_PARAM_SHIFT = 13 |  | ||||||
| 	_IOC_PARAM_MASK  = (1 << _IOC_PARAM_SHIFT) - 1 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func _IOC_PARM_LEN(ioctl uintptr) uintptr { |  | ||||||
| 	return (ioctl >> 16) & _IOC_PARAM_MASK |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr { |  | ||||||
| 	return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func _IO(group byte, ioctl_num uintptr) uintptr { |  | ||||||
| 	return _IOC(_IOC_VOID, group, ioctl_num, 0) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { |  | ||||||
| 	return _IOC(_IOC_OUT, group, ioctl_num, param_len) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr { |  | ||||||
| 	return _IOC(_IOC_IN, group, ioctl_num, param_len) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { |  | ||||||
| 	return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len) |  | ||||||
| } |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"golang.org/x/sys/unix" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	// see /usr/include/sys/stropts.h
 |  | ||||||
| 	I_PUSH  = uintptr((int32('S')<<8 | 002)) |  | ||||||
| 	I_STR   = uintptr((int32('S')<<8 | 010)) |  | ||||||
| 	I_FIND  = uintptr((int32('S')<<8 | 013)) |  | ||||||
| 	// see /usr/include/sys/ptms.h
 |  | ||||||
| 	ISPTM   = (int32('P') << 8) | 1 |  | ||||||
| 	UNLKPT  = (int32('P') << 8) | 2 |  | ||||||
| 	PTSSTTY = (int32('P') << 8) | 3 |  | ||||||
| 	ZONEPT  = (int32('P') << 8) | 4 |  | ||||||
| 	OWNERPT = (int32('P') << 8) | 5 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type strioctl struct { |  | ||||||
| 	ic_cmd    int32 |  | ||||||
| 	ic_timout int32 |  | ||||||
| 	ic_len    int32 |  | ||||||
| 	ic_dp     unsafe.Pointer |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ioctl(fd, cmd, ptr uintptr) error { |  | ||||||
| 	return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr)) |  | ||||||
| } |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| #!/usr/bin/env bash |  | ||||||
| 
 |  | ||||||
| GOOSARCH="${GOOS}_${GOARCH}" |  | ||||||
| case "$GOOSARCH" in |  | ||||||
| _* | *_ | _) |  | ||||||
| 	echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2 |  | ||||||
| 	exit 1 |  | ||||||
| 	;; |  | ||||||
| esac |  | ||||||
| 
 |  | ||||||
| GODEFS="go tool cgo -godefs" |  | ||||||
| 
 |  | ||||||
| $GODEFS types.go |gofmt > ztypes_$GOARCH.go |  | ||||||
| 
 |  | ||||||
| case $GOOS in |  | ||||||
| freebsd|dragonfly|openbsd) |  | ||||||
| 	$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go |  | ||||||
| 	;; |  | ||||||
| esac |  | ||||||
|  | @ -1,65 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"os" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	p := os.NewFile(uintptr(pFD), "/dev/ptmx") |  | ||||||
| 	// In case of error after this point, make sure we close the ptmx fd.
 |  | ||||||
| 	defer func() { |  | ||||||
| 		if err != nil { |  | ||||||
| 			_ = p.Close() // Best effort.
 |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	sname, err := ptsname(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := grantpt(p); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := unlockpt(p); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t, err := os.OpenFile(sname, os.O_RDWR, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	return p, t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ptsname(f *os.File) (string, error) { |  | ||||||
| 	n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) |  | ||||||
| 
 |  | ||||||
| 	err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, c := range n { |  | ||||||
| 		if c == 0 { |  | ||||||
| 			return string(n[:i]), nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "", errors.New("TIOCPTYGNAME string not NUL-terminated") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func grantpt(f *os.File) error { |  | ||||||
| 	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func unlockpt(f *os.File) error { |  | ||||||
| 	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0) |  | ||||||
| } |  | ||||||
|  | @ -1,80 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // same code as pty_darwin.go
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	// In case of error after this point, make sure we close the ptmx fd.
 |  | ||||||
| 	defer func() { |  | ||||||
| 		if err != nil { |  | ||||||
| 			_ = p.Close() // Best effort.
 |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	sname, err := ptsname(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := grantpt(p); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := unlockpt(p); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t, err := os.OpenFile(sname, os.O_RDWR, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	return p, t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func grantpt(f *os.File) error { |  | ||||||
| 	_, err := isptmaster(f.Fd()) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func unlockpt(f *os.File) error { |  | ||||||
| 	_, err := isptmaster(f.Fd()) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isptmaster(fd uintptr) (bool, error) { |  | ||||||
| 	err := ioctl(fd, syscall.TIOCISPTMASTER, 0) |  | ||||||
| 	return err == nil, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	emptyFiodgnameArg fiodgnameArg |  | ||||||
| 	ioctl_FIODNAME    = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func ptsname(f *os.File) (string, error) { |  | ||||||
| 	name := make([]byte, _C_SPECNAMELEN) |  | ||||||
| 	fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}} |  | ||||||
| 
 |  | ||||||
| 	err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, c := range name { |  | ||||||
| 		if c == 0 { |  | ||||||
| 			s := "/dev/" + string(name[:i]) |  | ||||||
| 			return strings.Replace(s, "ptm", "pts", -1), nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "", errors.New("TIOCPTYGNAME string not NUL-terminated") |  | ||||||
| } |  | ||||||
|  | @ -1,78 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"os" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func posixOpenpt(oflag int) (fd int, err error) { |  | ||||||
| 	r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) |  | ||||||
| 	fd = int(r0) |  | ||||||
| 	if e1 != 0 { |  | ||||||
| 		err = e1 |  | ||||||
| 	} |  | ||||||
| 	return fd, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	p := os.NewFile(uintptr(fd), "/dev/pts") |  | ||||||
| 	// In case of error after this point, make sure we close the pts fd.
 |  | ||||||
| 	defer func() { |  | ||||||
| 		if err != nil { |  | ||||||
| 			_ = p.Close() // Best effort.
 |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	sname, err := ptsname(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	return p, t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func isptmaster(fd uintptr) (bool, error) { |  | ||||||
| 	err := ioctl(fd, syscall.TIOCPTMASTER, 0) |  | ||||||
| 	return err == nil, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	emptyFiodgnameArg fiodgnameArg |  | ||||||
| 	ioctlFIODGNAME    = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func ptsname(f *os.File) (string, error) { |  | ||||||
| 	master, err := isptmaster(f.Fd()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	if !master { |  | ||||||
| 		return "", syscall.EINVAL |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const n = _C_SPECNAMELEN + 1 |  | ||||||
| 	var ( |  | ||||||
| 		buf = make([]byte, n) |  | ||||||
| 		arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} |  | ||||||
| 	) |  | ||||||
| 	if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, c := range buf { |  | ||||||
| 		if c == 0 { |  | ||||||
| 			return string(buf[:i]), nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "", errors.New("FIODGNAME string not NUL-terminated") |  | ||||||
| } |  | ||||||
|  | @ -1,51 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	// In case of error after this point, make sure we close the ptmx fd.
 |  | ||||||
| 	defer func() { |  | ||||||
| 		if err != nil { |  | ||||||
| 			_ = p.Close() // Best effort.
 |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	sname, err := ptsname(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := unlockpt(p); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	return p, t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ptsname(f *os.File) (string, error) { |  | ||||||
| 	var n _C_uint |  | ||||||
| 	err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return "/dev/pts/" + strconv.Itoa(int(n)), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func unlockpt(f *os.File) error { |  | ||||||
| 	var u _C_int |  | ||||||
| 	// use TIOCSPTLCK with a pointer to zero to clear the lock
 |  | ||||||
| 	return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) |  | ||||||
| } |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	/* |  | ||||||
| 	 * from ptm(4): |  | ||||||
| 	 * The PTMGET command allocates a free pseudo terminal, changes its |  | ||||||
| 	 * ownership to the caller, revokes the access privileges for all previous |  | ||||||
| 	 * users, opens the file descriptors for the pty and tty devices and |  | ||||||
| 	 * returns them to the caller in struct ptmget. |  | ||||||
| 	 */ |  | ||||||
| 
 |  | ||||||
| 	p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	defer p.Close() |  | ||||||
| 
 |  | ||||||
| 	var ptm ptmget |  | ||||||
| 	if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm") |  | ||||||
| 	tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm") |  | ||||||
| 
 |  | ||||||
| 	return pty, tty, nil |  | ||||||
| } |  | ||||||
|  | @ -1,139 +0,0 @@ | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| /* based on: |  | ||||||
| http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"golang.org/x/sys/unix" |  | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const NODEV = ^uint64(0) |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0) |  | ||||||
| 	//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
 |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	p := os.NewFile(uintptr(masterfd), "/dev/ptmx") |  | ||||||
| 
 |  | ||||||
| 	sname, err := ptsname(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = grantpt(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = unlockpt(p) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
| 	t := os.NewFile(uintptr(slavefd), sname) |  | ||||||
| 
 |  | ||||||
| 	// pushing terminal driver STREAMS modules as per pts(7)
 |  | ||||||
| 	for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) { |  | ||||||
| 		err = streams_push(t, mod) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	return p, t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func minor(x uint64) uint64 { |  | ||||||
| 	return x & 0377 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ptsdev(fd uintptr) uint64 { |  | ||||||
| 	istr := strioctl{ISPTM, 0, 0, nil} |  | ||||||
| 	err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return NODEV |  | ||||||
| 	} |  | ||||||
| 	var status unix.Stat_t |  | ||||||
| 	err = unix.Fstat(int(fd), &status) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return NODEV |  | ||||||
| 	} |  | ||||||
| 	return uint64(minor(status.Rdev)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func ptsname(f *os.File) (string, error) { |  | ||||||
| 	dev := ptsdev(f.Fd()) |  | ||||||
| 	if dev == NODEV { |  | ||||||
| 		return "", errors.New("not a master pty") |  | ||||||
| 	} |  | ||||||
| 	fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10) |  | ||||||
| 	// access(2) creates the slave device (if the pty exists)
 |  | ||||||
| 	// F_OK == 0 (unistd.h)
 |  | ||||||
| 	err := unix.Access(fn, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return fn, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type pt_own struct { |  | ||||||
| 	pto_ruid int32 |  | ||||||
| 	pto_rgid int32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func grantpt(f *os.File) error { |  | ||||||
| 	if ptsdev(f.Fd()) == NODEV { |  | ||||||
| 		return errors.New("not a master pty") |  | ||||||
| 	} |  | ||||||
| 	var pto pt_own |  | ||||||
| 	pto.pto_ruid = int32(os.Getuid()) |  | ||||||
| 	// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
 |  | ||||||
| 	pto.pto_rgid = int32(os.Getgid()) |  | ||||||
| 	var istr strioctl |  | ||||||
| 	istr.ic_cmd = OWNERPT |  | ||||||
| 	istr.ic_timout = 0 |  | ||||||
| 	istr.ic_len = int32(unsafe.Sizeof(istr)) |  | ||||||
| 	istr.ic_dp = unsafe.Pointer(&pto) |  | ||||||
| 	err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.New("access denied") |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func unlockpt(f *os.File) error { |  | ||||||
| 	istr := strioctl{UNLKPT, 0, 0, nil} |  | ||||||
| 	return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr))) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // push STREAMS modules if not already done so
 |  | ||||||
| func streams_push(f *os.File, mod string) error { |  | ||||||
| 	var err error |  | ||||||
| 	buf := []byte(mod) |  | ||||||
| 	// XXX I_FIND is not returning an error when the module
 |  | ||||||
| 	// is already pushed even though truss reports a return
 |  | ||||||
| 	// value of 1. A bug in the Go Solaris syscall interface?
 |  | ||||||
| 	// XXX without this we are at risk of the issue
 |  | ||||||
| 	// https://www.illumos.org/issues/9042
 |  | ||||||
| 	// but since we are not using libc or XPG4.2, we should not be
 |  | ||||||
| 	// double-pushing modules
 |  | ||||||
| 	 |  | ||||||
| 	err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0]))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0]))) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func open() (pty, tty *os.File, err error) { |  | ||||||
| 	return nil, nil, ErrUnsupported |  | ||||||
| } |  | ||||||
|  | @ -1,57 +0,0 @@ | ||||||
| // +build !windows
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"syscall" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
 |  | ||||||
| // and c.Stderr, calls c.Start, and returns the File of the tty's
 |  | ||||||
| // corresponding pty.
 |  | ||||||
| func Start(c *exec.Cmd) (pty *os.File, err error) { |  | ||||||
| 	return StartWithSize(c, nil) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
 |  | ||||||
| // and c.Stderr, calls c.Start, and returns the File of the tty's
 |  | ||||||
| // corresponding pty.
 |  | ||||||
| //
 |  | ||||||
| // This will resize the pty to the specified size before starting the command
 |  | ||||||
| func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) { |  | ||||||
| 	pty, tty, err := Open() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	defer tty.Close() |  | ||||||
| 	if sz != nil { |  | ||||||
| 		err = Setsize(pty, sz) |  | ||||||
| 		if err != nil { |  | ||||||
| 			pty.Close() |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if c.Stdout == nil { |  | ||||||
| 		c.Stdout = tty |  | ||||||
| 	} |  | ||||||
| 	if c.Stderr == nil { |  | ||||||
| 		c.Stderr = tty |  | ||||||
| 	} |  | ||||||
| 	if c.Stdin == nil { |  | ||||||
| 		c.Stdin = tty |  | ||||||
| 	} |  | ||||||
| 	if c.SysProcAttr == nil { |  | ||||||
| 		c.SysProcAttr = &syscall.SysProcAttr{} |  | ||||||
| 	} |  | ||||||
| 	c.SysProcAttr.Setctty = true |  | ||||||
| 	c.SysProcAttr.Setsid = true |  | ||||||
| 	c.SysProcAttr.Ctty = int(tty.Fd()) |  | ||||||
| 	err = c.Start() |  | ||||||
| 	if err != nil { |  | ||||||
| 		pty.Close() |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return pty, err |  | ||||||
| } |  | ||||||
|  | @ -1,50 +0,0 @@ | ||||||
| #!/usr/bin/env sh |  | ||||||
| 
 |  | ||||||
| # Test script checking that all expected os/arch compile properly. |  | ||||||
| # Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib. |  | ||||||
| 
 |  | ||||||
| echo2() { |  | ||||||
|     echo $@ >&2 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| trap end 0 |  | ||||||
| end() { |  | ||||||
|     [ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| cross() { |  | ||||||
|     os=$1 |  | ||||||
|     shift |  | ||||||
|     echo2 "Build for $os." |  | ||||||
|     for arch in $@; do |  | ||||||
| 	echo2 "  - $os/$arch" |  | ||||||
| 	GOOS=$os GOARCH=$arch go build |  | ||||||
|     done |  | ||||||
|     echo2 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| 
 |  | ||||||
| cross linux     amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le |  | ||||||
| cross darwin    amd64 386 arm arm64 |  | ||||||
| cross freebsd   amd64 386 arm |  | ||||||
| cross netbsd    amd64 386 arm |  | ||||||
| cross openbsd   amd64 386 |  | ||||||
| cross dragonfly amd64 |  | ||||||
| cross solaris   amd64 |  | ||||||
| 
 |  | ||||||
| # Not expected to work but should still compile. |  | ||||||
| cross windows amd64 386 arm |  | ||||||
| 
 |  | ||||||
| # TODO: Fix compilation error on openbsd/arm. |  | ||||||
| # TODO: Merge the solaris PR. |  | ||||||
| 
 |  | ||||||
| # Some os/arch require a different compiler. Run in docker. |  | ||||||
| if ! hash docker; then |  | ||||||
|     # If docker is not present, stop here. |  | ||||||
|     return |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| echo2 "Build for linux." |  | ||||||
| echo2 "  - linux/riscv" |  | ||||||
| docker build -t test -f Dockerfile.riscv . |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| // +build ignore
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import "C" |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  C.int |  | ||||||
| 	_C_uint C.uint |  | ||||||
| ) |  | ||||||
|  | @ -1,17 +0,0 @@ | ||||||
| // +build ignore
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| #define _KERNEL |  | ||||||
| #include <sys/conf.h> |  | ||||||
| #include <sys/param.h> |  | ||||||
| #include <sys/filio.h> |  | ||||||
| */ |  | ||||||
| import "C" |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg C.struct_fiodname_args |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| // +build ignore
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| #include <sys/param.h> |  | ||||||
| #include <sys/filio.h> |  | ||||||
| */ |  | ||||||
| import "C" |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg C.struct_fiodgname_arg |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| // +build ignore
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| #include <sys/time.h> |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <sys/tty.h> |  | ||||||
| */ |  | ||||||
| import "C" |  | ||||||
| 
 |  | ||||||
| type ptmget C.struct_ptmget |  | ||||||
| 
 |  | ||||||
| var ioctl_PTMGET = C.PTMGET |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| // +build !windows,!solaris
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"syscall" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // InheritSize applies the terminal size of pty to tty. This should be run
 |  | ||||||
| // in a signal handler for syscall.SIGWINCH to automatically resize the tty when
 |  | ||||||
| // the pty receives a window size change notification.
 |  | ||||||
| func InheritSize(pty, tty *os.File) error { |  | ||||||
| 	size, err := GetsizeFull(pty) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = Setsize(tty, size) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Setsize resizes t to s.
 |  | ||||||
| func Setsize(t *os.File, ws *Winsize) error { |  | ||||||
| 	return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetsizeFull returns the full terminal size description.
 |  | ||||||
| func GetsizeFull(t *os.File) (size *Winsize, err error) { |  | ||||||
| 	var ws Winsize |  | ||||||
| 	err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ) |  | ||||||
| 	return &ws, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Getsize returns the number of rows (lines) and cols (positions
 |  | ||||||
| // in each line) in terminal t.
 |  | ||||||
| func Getsize(t *os.File) (rows, cols int, err error) { |  | ||||||
| 	ws, err := GetsizeFull(t) |  | ||||||
| 	return int(ws.Rows), int(ws.Cols), err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Winsize describes the terminal size.
 |  | ||||||
| type Winsize struct { |  | ||||||
| 	Rows uint16 // ws_row: Number of rows (in cells)
 |  | ||||||
| 	Cols uint16 // ws_col: Number of columns (in cells)
 |  | ||||||
| 	X    uint16 // ws_xpixel: Width in pixels
 |  | ||||||
| 	Y    uint16 // ws_ypixel: Height in pixels
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func windowRectCall(ws *Winsize, fd, a2 uintptr) error { |  | ||||||
| 	_, _, errno := syscall.Syscall( |  | ||||||
| 		syscall.SYS_IOCTL, |  | ||||||
| 		fd, |  | ||||||
| 		a2, |  | ||||||
| 		uintptr(unsafe.Pointer(ws)), |  | ||||||
| 	) |  | ||||||
| 	if errno != 0 { |  | ||||||
| 		return syscall.Errno(errno) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | @ -1,51 +0,0 @@ | ||||||
| //
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"os" |  | ||||||
| 	"golang.org/x/sys/unix" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	TIOCGWINSZ = 21608 // 'T' << 8 | 104
 |  | ||||||
| 	TIOCSWINSZ = 21607 // 'T' << 8 | 103
 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Winsize describes the terminal size.
 |  | ||||||
| type Winsize struct { |  | ||||||
| 	Rows uint16 // ws_row: Number of rows (in cells)
 |  | ||||||
| 	Cols uint16 // ws_col: Number of columns (in cells)
 |  | ||||||
| 	X    uint16 // ws_xpixel: Width in pixels
 |  | ||||||
| 	Y    uint16 // ws_ypixel: Height in pixels
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // GetsizeFull returns the full terminal size description.
 |  | ||||||
| func GetsizeFull(t *os.File) (size *Winsize, err error) { |  | ||||||
| 	var wsz *unix.Winsize |  | ||||||
| 	wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ) |  | ||||||
| 
 |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} else { |  | ||||||
| 		return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get Windows Size
 |  | ||||||
| func Getsize(t *os.File) (rows, cols int, err error) { |  | ||||||
| 	var wsz *unix.Winsize |  | ||||||
| 	wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ) |  | ||||||
| 
 |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 80, 25, err |  | ||||||
| 	} else { |  | ||||||
| 		return int(wsz.Row), int(wsz.Col), nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Setsize resizes t to s.
 |  | ||||||
| func Setsize(t *os.File, ws *Winsize) error { |  | ||||||
| 	wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y} |  | ||||||
| 	return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz) |  | ||||||
| } |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| // +build arm64
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_dragonfly.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = 0x3f |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg struct { |  | ||||||
| 	Name      *byte |  | ||||||
| 	Len       uint32 |  | ||||||
| 	Pad_cgo_0 [4]byte |  | ||||||
| } |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_freebsd.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = 0x3f |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg struct { |  | ||||||
| 	Len int32 |  | ||||||
| 	Buf *byte |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_freebsd.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = 0x3f |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg struct { |  | ||||||
| 	Len       int32 |  | ||||||
| 	Pad_cgo_0 [4]byte |  | ||||||
| 	Buf       *byte |  | ||||||
| } |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_freebsd.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	_C_SPECNAMELEN = 0x3f |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type fiodgnameArg struct { |  | ||||||
| 	Len int32 |  | ||||||
| 	Buf *byte |  | ||||||
| } |  | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| // +build linux
 |  | ||||||
| // +build mips mipsle mips64 mips64le
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_openbsd.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ptmget struct { |  | ||||||
| 	Cfd	int32 |  | ||||||
| 	Sfd	int32 |  | ||||||
| 	Cn	[16]int8 |  | ||||||
| 	Sn	[16]int8 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ioctl_PTMGET = 0x40287401 |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types_openbsd.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ptmget struct { |  | ||||||
| 	Cfd int32 |  | ||||||
| 	Sfd int32 |  | ||||||
| 	Cn  [16]int8 |  | ||||||
| 	Sn  [16]int8 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ioctl_PTMGET = 0x40287401 |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // +build ppc64
 |  | ||||||
| 
 |  | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // +build ppc64le
 |  | ||||||
| 
 |  | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // Code generated by cmd/cgo -godefs; DO NOT EDIT.
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| // +build riscv riscv64
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| // +build s390x
 |  | ||||||
| 
 |  | ||||||
| // Created by cgo -godefs - DO NOT EDIT
 |  | ||||||
| // cgo -godefs types.go
 |  | ||||||
| 
 |  | ||||||
| package pty |  | ||||||
| 
 |  | ||||||
| type ( |  | ||||||
| 	_C_int  int32 |  | ||||||
| 	_C_uint uint32 |  | ||||||
| ) |  | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| Copyright (c) 2016 Glider Labs. All rights reserved. |  | ||||||
| 
 |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are |  | ||||||
| met: |  | ||||||
| 
 |  | ||||||
|    * Redistributions of source code must retain the above copyright |  | ||||||
| notice, this list of conditions and the following disclaimer. |  | ||||||
|    * Redistributions in binary form must reproduce the above |  | ||||||
| copyright notice, this list of conditions and the following disclaimer |  | ||||||
| in the documentation and/or other materials provided with the |  | ||||||
| distribution. |  | ||||||
|    * Neither the name of Glider Labs nor the names of its |  | ||||||
| contributors may be used to endorse or promote products derived from |  | ||||||
| this software without specific prior written permission. |  | ||||||
| 
 |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |  | ||||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | ||||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |  | ||||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |  | ||||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |  | ||||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  | @ -1,96 +0,0 @@ | ||||||
| # gliderlabs/ssh |  | ||||||
| 
 |  | ||||||
| [](https://godoc.org/github.com/gliderlabs/ssh)  |  | ||||||
| [](https://circleci.com/gh/gliderlabs/ssh) |  | ||||||
| [](https://goreportcard.com/report/github.com/gliderlabs/ssh)  |  | ||||||
| [](#sponsors) |  | ||||||
| [](http://slack.gliderlabs.com)  |  | ||||||
| [](https://app.convertkit.com/landing_pages/243312) |  | ||||||
| 
 |  | ||||||
| > The Glider Labs SSH server package is dope.  —[@bradfitz](https://twitter.com/bradfitz), Go team member |  | ||||||
| 
 |  | ||||||
| This Go package wraps the [crypto/ssh |  | ||||||
| package](https://godoc.org/golang.org/x/crypto/ssh) with a higher-level API for |  | ||||||
| building SSH servers. The goal of the API was to make it as simple as using |  | ||||||
| [net/http](https://golang.org/pkg/net/http/), so the API is very similar: |  | ||||||
| 
 |  | ||||||
| ```go |  | ||||||
|  package main |  | ||||||
| 
 |  | ||||||
|  import ( |  | ||||||
|      "github.com/gliderlabs/ssh" |  | ||||||
|      "io" |  | ||||||
|      "log" |  | ||||||
|  ) |  | ||||||
| 
 |  | ||||||
|  func main() { |  | ||||||
|      ssh.Handle(func(s ssh.Session) { |  | ||||||
|          io.WriteString(s, "Hello world\n") |  | ||||||
|      })   |  | ||||||
| 
 |  | ||||||
|      log.Fatal(ssh.ListenAndServe(":2222", nil)) |  | ||||||
|  } |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| This package was built by [@progrium](https://twitter.com/progrium) after working on nearly a dozen projects at Glider Labs using SSH and collaborating with [@shazow](https://twitter.com/shazow) (known for [ssh-chat](https://github.com/shazow/ssh-chat)). |  | ||||||
| 
 |  | ||||||
| ## Examples |  | ||||||
| 
 |  | ||||||
| A bunch of great examples are in the `_examples` directory. |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| [See GoDoc reference.](https://godoc.org/github.com/gliderlabs/ssh) |  | ||||||
| 
 |  | ||||||
| ## Contributing |  | ||||||
| 
 |  | ||||||
| Pull requests are welcome! However, since this project is very much about API |  | ||||||
| design, please submit API changes as issues to discuss before submitting PRs. |  | ||||||
| 
 |  | ||||||
| Also, you can [join our Slack](http://slack.gliderlabs.com) to discuss as well. |  | ||||||
| 
 |  | ||||||
| ## Roadmap |  | ||||||
| 
 |  | ||||||
| * Non-session channel handlers |  | ||||||
| * Cleanup callback API |  | ||||||
| * 1.0 release |  | ||||||
| * High-level client? |  | ||||||
| 
 |  | ||||||
| ## Sponsors |  | ||||||
| 
 |  | ||||||
| Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ssh#sponsor)] |  | ||||||
| 
 |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/0/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/1/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/2/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/3/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/4/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/5/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/6/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/7/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/8/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/9/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/10/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/11/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/11/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/12/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/12/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/13/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/13/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/14/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/14/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/15/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/15/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/16/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/16/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/17/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/17/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/18/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/18/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/19/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/19/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/20/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/20/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/21/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/21/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/22/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/22/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/23/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/23/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/24/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/24/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/25/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/25/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/26/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/26/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/27/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/27/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/28/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/28/avatar.svg"></a> |  | ||||||
| <a href="https://opencollective.com/ssh/sponsor/29/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/29/avatar.svg"></a> |  | ||||||
| 
 |  | ||||||
| ## License |  | ||||||
| 
 |  | ||||||
| BSD |  | ||||||
|  | @ -1,83 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"io" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" |  | ||||||
| 	"path" |  | ||||||
| 	"sync" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	agentRequestType = "auth-agent-req@openssh.com" |  | ||||||
| 	agentChannelType = "auth-agent@openssh.com" |  | ||||||
| 
 |  | ||||||
| 	agentTempDir    = "auth-agent" |  | ||||||
| 	agentListenFile = "listener.sock" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // contextKeyAgentRequest is an internal context key for storing if the
 |  | ||||||
| // client requested agent forwarding
 |  | ||||||
| var contextKeyAgentRequest = &contextKey{"auth-agent-req"} |  | ||||||
| 
 |  | ||||||
| // SetAgentRequested sets up the session context so that AgentRequested
 |  | ||||||
| // returns true.
 |  | ||||||
| func SetAgentRequested(ctx Context) { |  | ||||||
| 	ctx.SetValue(contextKeyAgentRequest, true) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // AgentRequested returns true if the client requested agent forwarding.
 |  | ||||||
| func AgentRequested(sess Session) bool { |  | ||||||
| 	return sess.Context().Value(contextKeyAgentRequest) == true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewAgentListener sets up a temporary Unix socket that can be communicated
 |  | ||||||
| // to the session environment and used for forwarding connections.
 |  | ||||||
| func NewAgentListener() (net.Listener, error) { |  | ||||||
| 	dir, err := ioutil.TempDir("", agentTempDir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	l, err := net.Listen("unix", path.Join(dir, agentListenFile)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return l, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ForwardAgentConnections takes connections from a listener to proxy into the
 |  | ||||||
| // session on the OpenSSH channel for agent connections. It blocks and services
 |  | ||||||
| // connections until the listener stop accepting.
 |  | ||||||
| func ForwardAgentConnections(l net.Listener, s Session) { |  | ||||||
| 	sshConn := s.Context().Value(ContextKeyConn).(gossh.Conn) |  | ||||||
| 	for { |  | ||||||
| 		conn, err := l.Accept() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		go func(conn net.Conn) { |  | ||||||
| 			defer conn.Close() |  | ||||||
| 			channel, reqs, err := sshConn.OpenChannel(agentChannelType, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			defer channel.Close() |  | ||||||
| 			go gossh.DiscardRequests(reqs) |  | ||||||
| 			var wg sync.WaitGroup |  | ||||||
| 			wg.Add(2) |  | ||||||
| 			go func() { |  | ||||||
| 				io.Copy(conn, channel) |  | ||||||
| 				conn.(*net.UnixConn).CloseWrite() |  | ||||||
| 				wg.Done() |  | ||||||
| 			}() |  | ||||||
| 			go func() { |  | ||||||
| 				io.Copy(channel, conn) |  | ||||||
| 				channel.CloseWrite() |  | ||||||
| 				wg.Done() |  | ||||||
| 			}() |  | ||||||
| 			wg.Wait() |  | ||||||
| 		}(conn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,26 +0,0 @@ | ||||||
| version: 2 |  | ||||||
| jobs: |  | ||||||
|   build-go-latest: |  | ||||||
|     docker: |  | ||||||
|     - image: golang:latest |  | ||||||
|     working_directory: /go/src/github.com/gliderlabs/ssh |  | ||||||
|     steps: |  | ||||||
|     - checkout |  | ||||||
|     - run: go get |  | ||||||
|     - run: go test -v -race |  | ||||||
| 
 |  | ||||||
|   build-go-1.9: |  | ||||||
|     docker: |  | ||||||
|     - image: golang:1.9 |  | ||||||
|     working_directory: /go/src/github.com/gliderlabs/ssh |  | ||||||
|     steps: |  | ||||||
|     - checkout |  | ||||||
|     - run: go get |  | ||||||
|     - run: go test -v -race |  | ||||||
| 
 |  | ||||||
| workflows: |  | ||||||
|   version: 2 |  | ||||||
|   build: |  | ||||||
|     jobs: |  | ||||||
|       - build-go-latest |  | ||||||
|       - build-go-1.9 |  | ||||||
|  | @ -1,55 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"net" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type serverConn struct { |  | ||||||
| 	net.Conn |  | ||||||
| 
 |  | ||||||
| 	idleTimeout   time.Duration |  | ||||||
| 	maxDeadline   time.Time |  | ||||||
| 	closeCanceler context.CancelFunc |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *serverConn) Write(p []byte) (n int, err error) { |  | ||||||
| 	c.updateDeadline() |  | ||||||
| 	n, err = c.Conn.Write(p) |  | ||||||
| 	if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil { |  | ||||||
| 		c.closeCanceler() |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *serverConn) Read(b []byte) (n int, err error) { |  | ||||||
| 	c.updateDeadline() |  | ||||||
| 	n, err = c.Conn.Read(b) |  | ||||||
| 	if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil { |  | ||||||
| 		c.closeCanceler() |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *serverConn) Close() (err error) { |  | ||||||
| 	err = c.Conn.Close() |  | ||||||
| 	if c.closeCanceler != nil { |  | ||||||
| 		c.closeCanceler() |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *serverConn) updateDeadline() { |  | ||||||
| 	switch { |  | ||||||
| 	case c.idleTimeout > 0: |  | ||||||
| 		idleDeadline := time.Now().Add(c.idleTimeout) |  | ||||||
| 		if idleDeadline.Unix() < c.maxDeadline.Unix() || c.maxDeadline.IsZero() { |  | ||||||
| 			c.Conn.SetDeadline(idleDeadline) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		fallthrough |  | ||||||
| 	default: |  | ||||||
| 		c.Conn.SetDeadline(c.maxDeadline) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,152 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"net" |  | ||||||
| 	"sync" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // contextKey is a value for use with context.WithValue. It's used as
 |  | ||||||
| // a pointer so it fits in an interface{} without allocation.
 |  | ||||||
| type contextKey struct { |  | ||||||
| 	name string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	// ContextKeyUser is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type string.
 |  | ||||||
| 	ContextKeyUser = &contextKey{"user"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeySessionID is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type string.
 |  | ||||||
| 	ContextKeySessionID = &contextKey{"session-id"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyPermissions is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type *Permissions.
 |  | ||||||
| 	ContextKeyPermissions = &contextKey{"permissions"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyClientVersion is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type string.
 |  | ||||||
| 	ContextKeyClientVersion = &contextKey{"client-version"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyServerVersion is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type string.
 |  | ||||||
| 	ContextKeyServerVersion = &contextKey{"server-version"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyLocalAddr is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type net.Addr.
 |  | ||||||
| 	ContextKeyLocalAddr = &contextKey{"local-addr"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyRemoteAddr is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type net.Addr.
 |  | ||||||
| 	ContextKeyRemoteAddr = &contextKey{"remote-addr"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyServer is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type *Server.
 |  | ||||||
| 	ContextKeyServer = &contextKey{"ssh-server"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyConn is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type gossh.ServerConn.
 |  | ||||||
| 	ContextKeyConn = &contextKey{"ssh-conn"} |  | ||||||
| 
 |  | ||||||
| 	// ContextKeyPublicKey is a context key for use with Contexts in this package.
 |  | ||||||
| 	// The associated value will be of type PublicKey.
 |  | ||||||
| 	ContextKeyPublicKey = &contextKey{"public-key"} |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Context is a package specific context interface. It exposes connection
 |  | ||||||
| // metadata and allows new values to be easily written to it. It's used in
 |  | ||||||
| // authentication handlers and callbacks, and its underlying context.Context is
 |  | ||||||
| // exposed on Session in the session Handler. A connection-scoped lock is also
 |  | ||||||
| // embedded in the context to make it easier to limit operations per-connection.
 |  | ||||||
| type Context interface { |  | ||||||
| 	context.Context |  | ||||||
| 	sync.Locker |  | ||||||
| 
 |  | ||||||
| 	// User returns the username used when establishing the SSH connection.
 |  | ||||||
| 	User() string |  | ||||||
| 
 |  | ||||||
| 	// SessionID returns the session hash.
 |  | ||||||
| 	SessionID() string |  | ||||||
| 
 |  | ||||||
| 	// ClientVersion returns the version reported by the client.
 |  | ||||||
| 	ClientVersion() string |  | ||||||
| 
 |  | ||||||
| 	// ServerVersion returns the version reported by the server.
 |  | ||||||
| 	ServerVersion() string |  | ||||||
| 
 |  | ||||||
| 	// RemoteAddr returns the remote address for this connection.
 |  | ||||||
| 	RemoteAddr() net.Addr |  | ||||||
| 
 |  | ||||||
| 	// LocalAddr returns the local address for this connection.
 |  | ||||||
| 	LocalAddr() net.Addr |  | ||||||
| 
 |  | ||||||
| 	// Permissions returns the Permissions object used for this connection.
 |  | ||||||
| 	Permissions() *Permissions |  | ||||||
| 
 |  | ||||||
| 	// SetValue allows you to easily write new values into the underlying context.
 |  | ||||||
| 	SetValue(key, value interface{}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type sshContext struct { |  | ||||||
| 	context.Context |  | ||||||
| 	*sync.Mutex |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newContext(srv *Server) (*sshContext, context.CancelFunc) { |  | ||||||
| 	innerCtx, cancel := context.WithCancel(context.Background()) |  | ||||||
| 	ctx := &sshContext{innerCtx, &sync.Mutex{}} |  | ||||||
| 	ctx.SetValue(ContextKeyServer, srv) |  | ||||||
| 	perms := &Permissions{&gossh.Permissions{}} |  | ||||||
| 	ctx.SetValue(ContextKeyPermissions, perms) |  | ||||||
| 	return ctx, cancel |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // this is separate from newContext because we will get ConnMetadata
 |  | ||||||
| // at different points so it needs to be applied separately
 |  | ||||||
| func applyConnMetadata(ctx Context, conn gossh.ConnMetadata) { |  | ||||||
| 	if ctx.Value(ContextKeySessionID) != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	ctx.SetValue(ContextKeySessionID, hex.EncodeToString(conn.SessionID())) |  | ||||||
| 	ctx.SetValue(ContextKeyClientVersion, string(conn.ClientVersion())) |  | ||||||
| 	ctx.SetValue(ContextKeyServerVersion, string(conn.ServerVersion())) |  | ||||||
| 	ctx.SetValue(ContextKeyUser, conn.User()) |  | ||||||
| 	ctx.SetValue(ContextKeyLocalAddr, conn.LocalAddr()) |  | ||||||
| 	ctx.SetValue(ContextKeyRemoteAddr, conn.RemoteAddr()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) SetValue(key, value interface{}) { |  | ||||||
| 	ctx.Context = context.WithValue(ctx.Context, key, value) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) User() string { |  | ||||||
| 	return ctx.Value(ContextKeyUser).(string) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) SessionID() string { |  | ||||||
| 	return ctx.Value(ContextKeySessionID).(string) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) ClientVersion() string { |  | ||||||
| 	return ctx.Value(ContextKeyClientVersion).(string) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) ServerVersion() string { |  | ||||||
| 	return ctx.Value(ContextKeyServerVersion).(string) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) RemoteAddr() net.Addr { |  | ||||||
| 	return ctx.Value(ContextKeyRemoteAddr).(net.Addr) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) LocalAddr() net.Addr { |  | ||||||
| 	return ctx.Value(ContextKeyLocalAddr).(net.Addr) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ctx *sshContext) Permissions() *Permissions { |  | ||||||
| 	return ctx.Value(ContextKeyPermissions).(*Permissions) |  | ||||||
| } |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| /* |  | ||||||
| Package ssh wraps the crypto/ssh package with a higher-level API for building |  | ||||||
| SSH servers. The goal of the API was to make it as simple as using net/http, so |  | ||||||
| the API is very similar. |  | ||||||
| 
 |  | ||||||
| You should be able to build any SSH server using only this package, which wraps |  | ||||||
| relevant types and some functions from crypto/ssh. However, you still need to |  | ||||||
| use crypto/ssh for building SSH clients. |  | ||||||
| 
 |  | ||||||
| ListenAndServe starts an SSH server with a given address, handler, and options. The |  | ||||||
| handler is usually nil, which means to use DefaultHandler. Handle sets DefaultHandler: |  | ||||||
| 
 |  | ||||||
|   ssh.Handle(func(s ssh.Session) { |  | ||||||
|       io.WriteString(s, "Hello world\n") |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   log.Fatal(ssh.ListenAndServe(":2222", nil)) |  | ||||||
| 
 |  | ||||||
| If you don't specify a host key, it will generate one every time. This is convenient |  | ||||||
| except you'll have to deal with clients being confused that the host key is different. |  | ||||||
| It's a better idea to generate or point to an existing key on your system: |  | ||||||
| 
 |  | ||||||
|   log.Fatal(ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("/Users/progrium/.ssh/id_rsa"))) |  | ||||||
| 
 |  | ||||||
| Although all options have functional option helpers, another way to control the |  | ||||||
| server's behavior is by creating a custom Server: |  | ||||||
| 
 |  | ||||||
|   s := &ssh.Server{ |  | ||||||
|       Addr:             ":2222", |  | ||||||
|       Handler:          sessionHandler, |  | ||||||
|       PublicKeyHandler: authHandler, |  | ||||||
|   } |  | ||||||
|   s.AddHostKey(hostKeySigner) |  | ||||||
| 
 |  | ||||||
|   log.Fatal(s.ListenAndServe()) |  | ||||||
| 
 |  | ||||||
| This package automatically handles basic SSH requests like setting environment |  | ||||||
| variables, requesting PTY, and changing window size. These requests are |  | ||||||
| processed, responded to, and any relevant state is updated. This state is then |  | ||||||
| exposed to you via the Session interface. |  | ||||||
| 
 |  | ||||||
| The one big feature missing from the Session abstraction is signals. This was |  | ||||||
| started, but not completed. Pull Requests welcome! |  | ||||||
| */ |  | ||||||
| package ssh |  | ||||||
|  | @ -1,77 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"io/ioutil" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // PasswordAuth returns a functional option that sets PasswordHandler on the server.
 |  | ||||||
| func PasswordAuth(fn PasswordHandler) Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		srv.PasswordHandler = fn |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PublicKeyAuth returns a functional option that sets PublicKeyHandler on the server.
 |  | ||||||
| func PublicKeyAuth(fn PublicKeyHandler) Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		srv.PublicKeyHandler = fn |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HostKeyFile returns a functional option that adds HostSigners to the server
 |  | ||||||
| // from a PEM file at filepath.
 |  | ||||||
| func HostKeyFile(filepath string) Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		pemBytes, err := ioutil.ReadFile(filepath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		signer, err := gossh.ParsePrivateKey(pemBytes) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		srv.AddHostKey(signer) |  | ||||||
| 
 |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // HostKeyPEM returns a functional option that adds HostSigners to the server
 |  | ||||||
| // from a PEM file as bytes.
 |  | ||||||
| func HostKeyPEM(bytes []byte) Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		signer, err := gossh.ParsePrivateKey(bytes) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		srv.AddHostKey(signer) |  | ||||||
| 
 |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NoPty returns a functional option that sets PtyCallback to return false,
 |  | ||||||
| // denying PTY requests.
 |  | ||||||
| func NoPty() Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		srv.PtyCallback = func(ctx Context, pty Pty) bool { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // WrapConn returns a functional option that sets ConnCallback on the server.
 |  | ||||||
| func WrapConn(fn ConnCallback) Option { |  | ||||||
| 	return func(srv *Server) error { |  | ||||||
| 		srv.ConnCallback = fn |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,394 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // ErrServerClosed is returned by the Server's Serve, ListenAndServe,
 |  | ||||||
| // and ListenAndServeTLS methods after a call to Shutdown or Close.
 |  | ||||||
| var ErrServerClosed = errors.New("ssh: Server closed") |  | ||||||
| 
 |  | ||||||
| type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte) |  | ||||||
| 
 |  | ||||||
| var DefaultRequestHandlers = map[string]RequestHandler{} |  | ||||||
| 
 |  | ||||||
| type ChannelHandler func(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) |  | ||||||
| 
 |  | ||||||
| var DefaultChannelHandlers = map[string]ChannelHandler{ |  | ||||||
| 	"session": DefaultSessionHandler, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Server defines parameters for running an SSH server. The zero value for
 |  | ||||||
| // Server is a valid configuration. When both PasswordHandler and
 |  | ||||||
| // PublicKeyHandler are nil, no client authentication is performed.
 |  | ||||||
| type Server struct { |  | ||||||
| 	Addr        string   // TCP address to listen on, ":22" if empty
 |  | ||||||
| 	Handler     Handler  // handler to invoke, ssh.DefaultHandler if nil
 |  | ||||||
| 	HostSigners []Signer // private keys for the host key, must have at least one
 |  | ||||||
| 	Version     string   // server version to be sent before the initial handshake
 |  | ||||||
| 
 |  | ||||||
| 	KeyboardInteractiveHandler    KeyboardInteractiveHandler    // keyboard-interactive authentication handler
 |  | ||||||
| 	PasswordHandler               PasswordHandler               // password authentication handler
 |  | ||||||
| 	PublicKeyHandler              PublicKeyHandler              // public key authentication handler
 |  | ||||||
| 	PtyCallback                   PtyCallback                   // callback for allowing PTY sessions, allows all if nil
 |  | ||||||
| 	ConnCallback                  ConnCallback                  // optional callback for wrapping net.Conn before handling
 |  | ||||||
| 	LocalPortForwardingCallback   LocalPortForwardingCallback   // callback for allowing local port forwarding, denies all if nil
 |  | ||||||
| 	ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil
 |  | ||||||
| 	ServerConfigCallback          ServerConfigCallback          // callback for configuring detailed SSH options
 |  | ||||||
| 	SessionRequestCallback        SessionRequestCallback        // callback for allowing or denying SSH sessions
 |  | ||||||
| 
 |  | ||||||
| 	IdleTimeout time.Duration // connection timeout when no activity, none if empty
 |  | ||||||
| 	MaxTimeout  time.Duration // absolute connection timeout, none if empty
 |  | ||||||
| 
 |  | ||||||
| 	// ChannelHandlers allow overriding the built-in session handlers or provide
 |  | ||||||
| 	// extensions to the protocol, such as tcpip forwarding. By default only the
 |  | ||||||
| 	// "session" handler is enabled.
 |  | ||||||
| 	ChannelHandlers map[string]ChannelHandler |  | ||||||
| 
 |  | ||||||
| 	// RequestHandlers allow overriding the server-level request handlers or
 |  | ||||||
| 	// provide extensions to the protocol, such as tcpip forwarding. By default
 |  | ||||||
| 	// no handlers are enabled.
 |  | ||||||
| 	RequestHandlers map[string]RequestHandler |  | ||||||
| 
 |  | ||||||
| 	listenerWg sync.WaitGroup |  | ||||||
| 	mu         sync.Mutex |  | ||||||
| 	listeners  map[net.Listener]struct{} |  | ||||||
| 	conns      map[*gossh.ServerConn]struct{} |  | ||||||
| 	connWg     sync.WaitGroup |  | ||||||
| 	doneChan   chan struct{} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) ensureHostSigner() error { |  | ||||||
| 	if len(srv.HostSigners) == 0 { |  | ||||||
| 		signer, err := generateSigner() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		srv.HostSigners = append(srv.HostSigners, signer) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) ensureHandlers() { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	defer srv.mu.Unlock() |  | ||||||
| 	if srv.RequestHandlers == nil { |  | ||||||
| 		srv.RequestHandlers = map[string]RequestHandler{} |  | ||||||
| 		for k, v := range DefaultRequestHandlers { |  | ||||||
| 			srv.RequestHandlers[k] = v |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if srv.ChannelHandlers == nil { |  | ||||||
| 		srv.ChannelHandlers = map[string]ChannelHandler{} |  | ||||||
| 		for k, v := range DefaultChannelHandlers { |  | ||||||
| 			srv.ChannelHandlers[k] = v |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) config(ctx Context) *gossh.ServerConfig { |  | ||||||
| 	var config *gossh.ServerConfig |  | ||||||
| 	if srv.ServerConfigCallback == nil { |  | ||||||
| 		config = &gossh.ServerConfig{} |  | ||||||
| 	} else { |  | ||||||
| 		config = srv.ServerConfigCallback(ctx) |  | ||||||
| 	} |  | ||||||
| 	for _, signer := range srv.HostSigners { |  | ||||||
| 		config.AddHostKey(signer) |  | ||||||
| 	} |  | ||||||
| 	if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil { |  | ||||||
| 		config.NoClientAuth = true |  | ||||||
| 	} |  | ||||||
| 	if srv.Version != "" { |  | ||||||
| 		config.ServerVersion = "SSH-2.0-" + srv.Version |  | ||||||
| 	} |  | ||||||
| 	if srv.PasswordHandler != nil { |  | ||||||
| 		config.PasswordCallback = func(conn gossh.ConnMetadata, password []byte) (*gossh.Permissions, error) { |  | ||||||
| 			applyConnMetadata(ctx, conn) |  | ||||||
| 			if ok := srv.PasswordHandler(ctx, string(password)); !ok { |  | ||||||
| 				return ctx.Permissions().Permissions, fmt.Errorf("permission denied") |  | ||||||
| 			} |  | ||||||
| 			return ctx.Permissions().Permissions, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if srv.PublicKeyHandler != nil { |  | ||||||
| 		config.PublicKeyCallback = func(conn gossh.ConnMetadata, key gossh.PublicKey) (*gossh.Permissions, error) { |  | ||||||
| 			applyConnMetadata(ctx, conn) |  | ||||||
| 			if ok := srv.PublicKeyHandler(ctx, key); !ok { |  | ||||||
| 				return ctx.Permissions().Permissions, fmt.Errorf("permission denied") |  | ||||||
| 			} |  | ||||||
| 			ctx.SetValue(ContextKeyPublicKey, key) |  | ||||||
| 			return ctx.Permissions().Permissions, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if srv.KeyboardInteractiveHandler != nil { |  | ||||||
| 		config.KeyboardInteractiveCallback = func(conn gossh.ConnMetadata, challenger gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) { |  | ||||||
| 			if ok := srv.KeyboardInteractiveHandler(ctx, challenger); !ok { |  | ||||||
| 				return ctx.Permissions().Permissions, fmt.Errorf("permission denied") |  | ||||||
| 			} |  | ||||||
| 			return ctx.Permissions().Permissions, nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return config |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handle sets the Handler for the server.
 |  | ||||||
| func (srv *Server) Handle(fn Handler) { |  | ||||||
| 	srv.Handler = fn |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Close immediately closes all active listeners and all active
 |  | ||||||
| // connections.
 |  | ||||||
| //
 |  | ||||||
| // Close returns any error returned from closing the Server's
 |  | ||||||
| // underlying Listener(s).
 |  | ||||||
| func (srv *Server) Close() error { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	defer srv.mu.Unlock() |  | ||||||
| 	srv.closeDoneChanLocked() |  | ||||||
| 	err := srv.closeListenersLocked() |  | ||||||
| 	for c := range srv.conns { |  | ||||||
| 		c.Close() |  | ||||||
| 		delete(srv.conns, c) |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Shutdown gracefully shuts down the server without interrupting any
 |  | ||||||
| // active connections. Shutdown works by first closing all open
 |  | ||||||
| // listeners, and then waiting indefinitely for connections to close.
 |  | ||||||
| // If the provided context expires before the shutdown is complete,
 |  | ||||||
| // then the context's error is returned.
 |  | ||||||
| func (srv *Server) Shutdown(ctx context.Context) error { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	lnerr := srv.closeListenersLocked() |  | ||||||
| 	srv.closeDoneChanLocked() |  | ||||||
| 	srv.mu.Unlock() |  | ||||||
| 
 |  | ||||||
| 	finished := make(chan struct{}, 1) |  | ||||||
| 	go func() { |  | ||||||
| 		srv.listenerWg.Wait() |  | ||||||
| 		srv.connWg.Wait() |  | ||||||
| 		finished <- struct{}{} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	select { |  | ||||||
| 	case <-ctx.Done(): |  | ||||||
| 		return ctx.Err() |  | ||||||
| 	case <-finished: |  | ||||||
| 		return lnerr |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Serve accepts incoming connections on the Listener l, creating a new
 |  | ||||||
| // connection goroutine for each. The connection goroutines read requests and then
 |  | ||||||
| // calls srv.Handler to handle sessions.
 |  | ||||||
| //
 |  | ||||||
| // Serve always returns a non-nil error.
 |  | ||||||
| func (srv *Server) Serve(l net.Listener) error { |  | ||||||
| 	srv.ensureHandlers() |  | ||||||
| 	defer l.Close() |  | ||||||
| 	if err := srv.ensureHostSigner(); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if srv.Handler == nil { |  | ||||||
| 		srv.Handler = DefaultHandler |  | ||||||
| 	} |  | ||||||
| 	var tempDelay time.Duration |  | ||||||
| 
 |  | ||||||
| 	srv.trackListener(l, true) |  | ||||||
| 	defer srv.trackListener(l, false) |  | ||||||
| 	for { |  | ||||||
| 		conn, e := l.Accept() |  | ||||||
| 		if e != nil { |  | ||||||
| 			select { |  | ||||||
| 			case <-srv.getDoneChan(): |  | ||||||
| 				return ErrServerClosed |  | ||||||
| 			default: |  | ||||||
| 			} |  | ||||||
| 			if ne, ok := e.(net.Error); ok && ne.Temporary() { |  | ||||||
| 				if tempDelay == 0 { |  | ||||||
| 					tempDelay = 5 * time.Millisecond |  | ||||||
| 				} else { |  | ||||||
| 					tempDelay *= 2 |  | ||||||
| 				} |  | ||||||
| 				if max := 1 * time.Second; tempDelay > max { |  | ||||||
| 					tempDelay = max |  | ||||||
| 				} |  | ||||||
| 				time.Sleep(tempDelay) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			return e |  | ||||||
| 		} |  | ||||||
| 		go srv.handleConn(conn) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) handleConn(newConn net.Conn) { |  | ||||||
| 	if srv.ConnCallback != nil { |  | ||||||
| 		cbConn := srv.ConnCallback(newConn) |  | ||||||
| 		if cbConn == nil { |  | ||||||
| 			newConn.Close() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		newConn = cbConn |  | ||||||
| 	} |  | ||||||
| 	ctx, cancel := newContext(srv) |  | ||||||
| 	conn := &serverConn{ |  | ||||||
| 		Conn:          newConn, |  | ||||||
| 		idleTimeout:   srv.IdleTimeout, |  | ||||||
| 		closeCanceler: cancel, |  | ||||||
| 	} |  | ||||||
| 	if srv.MaxTimeout > 0 { |  | ||||||
| 		conn.maxDeadline = time.Now().Add(srv.MaxTimeout) |  | ||||||
| 	} |  | ||||||
| 	defer conn.Close() |  | ||||||
| 	sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// TODO: trigger event callback
 |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	srv.trackConn(sshConn, true) |  | ||||||
| 	defer srv.trackConn(sshConn, false) |  | ||||||
| 
 |  | ||||||
| 	ctx.SetValue(ContextKeyConn, sshConn) |  | ||||||
| 	applyConnMetadata(ctx, sshConn) |  | ||||||
| 	//go gossh.DiscardRequests(reqs)
 |  | ||||||
| 	go srv.handleRequests(ctx, reqs) |  | ||||||
| 	for ch := range chans { |  | ||||||
| 		handler := srv.ChannelHandlers[ch.ChannelType()] |  | ||||||
| 		if handler == nil { |  | ||||||
| 			handler = srv.ChannelHandlers["default"] |  | ||||||
| 		} |  | ||||||
| 		if handler == nil { |  | ||||||
| 			ch.Reject(gossh.UnknownChannelType, "unsupported channel type") |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		go handler(srv, sshConn, ch, ctx) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) handleRequests(ctx Context, in <-chan *gossh.Request) { |  | ||||||
| 	for req := range in { |  | ||||||
| 		handler := srv.RequestHandlers[req.Type] |  | ||||||
| 		if handler == nil { |  | ||||||
| 			handler = srv.RequestHandlers["default"] |  | ||||||
| 		} |  | ||||||
| 		if handler == nil { |  | ||||||
| 			req.Reply(false, nil) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		/*reqCtx, cancel := context.WithCancel(ctx) |  | ||||||
| 		defer cancel() */ |  | ||||||
| 		ret, payload := handler(ctx, srv, req) |  | ||||||
| 		req.Reply(ret, payload) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ListenAndServe listens on the TCP network address srv.Addr and then calls
 |  | ||||||
| // Serve to handle incoming connections. If srv.Addr is blank, ":22" is used.
 |  | ||||||
| // ListenAndServe always returns a non-nil error.
 |  | ||||||
| func (srv *Server) ListenAndServe() error { |  | ||||||
| 	addr := srv.Addr |  | ||||||
| 	if addr == "" { |  | ||||||
| 		addr = ":22" |  | ||||||
| 	} |  | ||||||
| 	ln, err := net.Listen("tcp", addr) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return srv.Serve(ln) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // AddHostKey adds a private key as a host key. If an existing host key exists
 |  | ||||||
| // with the same algorithm, it is overwritten. Each server config must have at
 |  | ||||||
| // least one host key.
 |  | ||||||
| func (srv *Server) AddHostKey(key Signer) { |  | ||||||
| 	// these are later added via AddHostKey on ServerConfig, which performs the
 |  | ||||||
| 	// check for one of every algorithm.
 |  | ||||||
| 	srv.HostSigners = append(srv.HostSigners, key) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SetOption runs a functional option against the server.
 |  | ||||||
| func (srv *Server) SetOption(option Option) error { |  | ||||||
| 	return option(srv) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) getDoneChan() <-chan struct{} { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	defer srv.mu.Unlock() |  | ||||||
| 	return srv.getDoneChanLocked() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) getDoneChanLocked() chan struct{} { |  | ||||||
| 	if srv.doneChan == nil { |  | ||||||
| 		srv.doneChan = make(chan struct{}) |  | ||||||
| 	} |  | ||||||
| 	return srv.doneChan |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) closeDoneChanLocked() { |  | ||||||
| 	ch := srv.getDoneChanLocked() |  | ||||||
| 	select { |  | ||||||
| 	case <-ch: |  | ||||||
| 		// Already closed. Don't close again.
 |  | ||||||
| 	default: |  | ||||||
| 		// Safe to close here. We're the only closer, guarded
 |  | ||||||
| 		// by srv.mu.
 |  | ||||||
| 		close(ch) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) closeListenersLocked() error { |  | ||||||
| 	var err error |  | ||||||
| 	for ln := range srv.listeners { |  | ||||||
| 		if cerr := ln.Close(); cerr != nil && err == nil { |  | ||||||
| 			err = cerr |  | ||||||
| 		} |  | ||||||
| 		delete(srv.listeners, ln) |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) trackListener(ln net.Listener, add bool) { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	defer srv.mu.Unlock() |  | ||||||
| 	if srv.listeners == nil { |  | ||||||
| 		srv.listeners = make(map[net.Listener]struct{}) |  | ||||||
| 	} |  | ||||||
| 	if add { |  | ||||||
| 		// If the *Server is being reused after a previous
 |  | ||||||
| 		// Close or Shutdown, reset its doneChan:
 |  | ||||||
| 		if len(srv.listeners) == 0 && len(srv.conns) == 0 { |  | ||||||
| 			srv.doneChan = nil |  | ||||||
| 		} |  | ||||||
| 		srv.listeners[ln] = struct{}{} |  | ||||||
| 		srv.listenerWg.Add(1) |  | ||||||
| 	} else { |  | ||||||
| 		delete(srv.listeners, ln) |  | ||||||
| 		srv.listenerWg.Done() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (srv *Server) trackConn(c *gossh.ServerConn, add bool) { |  | ||||||
| 	srv.mu.Lock() |  | ||||||
| 	defer srv.mu.Unlock() |  | ||||||
| 	if srv.conns == nil { |  | ||||||
| 		srv.conns = make(map[*gossh.ServerConn]struct{}) |  | ||||||
| 	} |  | ||||||
| 	if add { |  | ||||||
| 		srv.conns[c] = struct{}{} |  | ||||||
| 		srv.connWg.Add(1) |  | ||||||
| 	} else { |  | ||||||
| 		delete(srv.conns, c) |  | ||||||
| 		srv.connWg.Done() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,308 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net" |  | ||||||
| 	"sync" |  | ||||||
| 
 |  | ||||||
| 	"github.com/anmitsu/go-shlex" |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Session provides access to information about an SSH session and methods
 |  | ||||||
| // to read and write to the SSH channel with an embedded Channel interface from
 |  | ||||||
| // cypto/ssh.
 |  | ||||||
| //
 |  | ||||||
| // When Command() returns an empty slice, the user requested a shell. Otherwise
 |  | ||||||
| // the user is performing an exec with those command arguments.
 |  | ||||||
| //
 |  | ||||||
| // TODO: Signals
 |  | ||||||
| type Session interface { |  | ||||||
| 	gossh.Channel |  | ||||||
| 
 |  | ||||||
| 	// User returns the username used when establishing the SSH connection.
 |  | ||||||
| 	User() string |  | ||||||
| 
 |  | ||||||
| 	// RemoteAddr returns the net.Addr of the client side of the connection.
 |  | ||||||
| 	RemoteAddr() net.Addr |  | ||||||
| 
 |  | ||||||
| 	// LocalAddr returns the net.Addr of the server side of the connection.
 |  | ||||||
| 	LocalAddr() net.Addr |  | ||||||
| 
 |  | ||||||
| 	// Environ returns a copy of strings representing the environment set by the
 |  | ||||||
| 	// user for this session, in the form "key=value".
 |  | ||||||
| 	Environ() []string |  | ||||||
| 
 |  | ||||||
| 	// Exit sends an exit status and then closes the session.
 |  | ||||||
| 	Exit(code int) error |  | ||||||
| 
 |  | ||||||
| 	// Command returns a shell parsed slice of arguments that were provided by the
 |  | ||||||
| 	// user. Shell parsing splits the command string according to POSIX shell rules,
 |  | ||||||
| 	// which considers quoting not just whitespace.
 |  | ||||||
| 	Command() []string |  | ||||||
| 
 |  | ||||||
| 	// RawCommand returns the exact command that was provided by the user.
 |  | ||||||
| 	RawCommand() string |  | ||||||
| 
 |  | ||||||
| 	// PublicKey returns the PublicKey used to authenticate. If a public key was not
 |  | ||||||
| 	// used it will return nil.
 |  | ||||||
| 	PublicKey() PublicKey |  | ||||||
| 
 |  | ||||||
| 	// Context returns the connection's context. The returned context is always
 |  | ||||||
| 	// non-nil and holds the same data as the Context passed into auth
 |  | ||||||
| 	// handlers and callbacks.
 |  | ||||||
| 	//
 |  | ||||||
| 	// The context is canceled when the client's connection closes or I/O
 |  | ||||||
| 	// operation fails.
 |  | ||||||
| 	Context() context.Context |  | ||||||
| 
 |  | ||||||
| 	// Permissions returns a copy of the Permissions object that was available for
 |  | ||||||
| 	// setup in the auth handlers via the Context.
 |  | ||||||
| 	Permissions() Permissions |  | ||||||
| 
 |  | ||||||
| 	// Pty returns PTY information, a channel of window size changes, and a boolean
 |  | ||||||
| 	// of whether or not a PTY was accepted for this session.
 |  | ||||||
| 	Pty() (Pty, <-chan Window, bool) |  | ||||||
| 
 |  | ||||||
| 	// Signals registers a channel to receive signals sent from the client. The
 |  | ||||||
| 	// channel must handle signal sends or it will block the SSH request loop.
 |  | ||||||
| 	// Registering nil will unregister the channel from signal sends. During the
 |  | ||||||
| 	// time no channel is registered signals are buffered up to a reasonable amount.
 |  | ||||||
| 	// If there are buffered signals when a channel is registered, they will be
 |  | ||||||
| 	// sent in order on the channel immediately after registering.
 |  | ||||||
| 	Signals(c chan<- Signal) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // maxSigBufSize is how many signals will be buffered
 |  | ||||||
| // when there is no signal channel specified
 |  | ||||||
| const maxSigBufSize = 128 |  | ||||||
| 
 |  | ||||||
| func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) { |  | ||||||
| 	ch, reqs, err := newChan.Accept() |  | ||||||
| 	if err != nil { |  | ||||||
| 		// TODO: trigger event callback
 |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	sess := &session{ |  | ||||||
| 		Channel:   ch, |  | ||||||
| 		conn:      conn, |  | ||||||
| 		handler:   srv.Handler, |  | ||||||
| 		ptyCb:     srv.PtyCallback, |  | ||||||
| 		sessReqCb: srv.SessionRequestCallback, |  | ||||||
| 		ctx:       ctx, |  | ||||||
| 	} |  | ||||||
| 	sess.handleRequests(reqs) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type session struct { |  | ||||||
| 	sync.Mutex |  | ||||||
| 	gossh.Channel |  | ||||||
| 	conn      *gossh.ServerConn |  | ||||||
| 	handler   Handler |  | ||||||
| 	handled   bool |  | ||||||
| 	exited    bool |  | ||||||
| 	pty       *Pty |  | ||||||
| 	winch     chan Window |  | ||||||
| 	env       []string |  | ||||||
| 	ptyCb     PtyCallback |  | ||||||
| 	sessReqCb SessionRequestCallback |  | ||||||
| 	rawCmd    string |  | ||||||
| 	ctx       Context |  | ||||||
| 	sigCh     chan<- Signal |  | ||||||
| 	sigBuf    []Signal |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Write(p []byte) (n int, err error) { |  | ||||||
| 	if sess.pty != nil { |  | ||||||
| 		m := len(p) |  | ||||||
| 		// normalize \n to \r\n when pty is accepted.
 |  | ||||||
| 		// this is a hardcoded shortcut since we don't support terminal modes.
 |  | ||||||
| 		p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1) |  | ||||||
| 		p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1) |  | ||||||
| 		n, err = sess.Channel.Write(p) |  | ||||||
| 		if n > m { |  | ||||||
| 			n = m |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	return sess.Channel.Write(p) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) PublicKey() PublicKey { |  | ||||||
| 	sessionkey := sess.ctx.Value(ContextKeyPublicKey) |  | ||||||
| 	if sessionkey == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return sessionkey.(PublicKey) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Permissions() Permissions { |  | ||||||
| 	// use context permissions because its properly
 |  | ||||||
| 	// wrapped and easier to dereference
 |  | ||||||
| 	perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions) |  | ||||||
| 	return *perms |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Context() context.Context { |  | ||||||
| 	return sess.ctx |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Exit(code int) error { |  | ||||||
| 	sess.Lock() |  | ||||||
| 	defer sess.Unlock() |  | ||||||
| 	if sess.exited { |  | ||||||
| 		return errors.New("Session.Exit called multiple times") |  | ||||||
| 	} |  | ||||||
| 	sess.exited = true |  | ||||||
| 
 |  | ||||||
| 	status := struct{ Status uint32 }{uint32(code)} |  | ||||||
| 	_, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return sess.Close() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) User() string { |  | ||||||
| 	return sess.conn.User() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) RemoteAddr() net.Addr { |  | ||||||
| 	return sess.conn.RemoteAddr() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) LocalAddr() net.Addr { |  | ||||||
| 	return sess.conn.LocalAddr() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Environ() []string { |  | ||||||
| 	return append([]string(nil), sess.env...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) RawCommand() string { |  | ||||||
| 	return sess.rawCmd |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Command() []string { |  | ||||||
| 	cmd, _ := shlex.Split(sess.rawCmd, true) |  | ||||||
| 	return append([]string(nil), cmd...) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Pty() (Pty, <-chan Window, bool) { |  | ||||||
| 	if sess.pty != nil { |  | ||||||
| 		return *sess.pty, sess.winch, true |  | ||||||
| 	} |  | ||||||
| 	return Pty{}, sess.winch, false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) Signals(c chan<- Signal) { |  | ||||||
| 	sess.Lock() |  | ||||||
| 	defer sess.Unlock() |  | ||||||
| 	sess.sigCh = c |  | ||||||
| 	if len(sess.sigBuf) > 0 { |  | ||||||
| 		go func() { |  | ||||||
| 			for _, sig := range sess.sigBuf { |  | ||||||
| 				sess.sigCh <- sig |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (sess *session) handleRequests(reqs <-chan *gossh.Request) { |  | ||||||
| 	for req := range reqs { |  | ||||||
| 		switch req.Type { |  | ||||||
| 		case "shell", "exec": |  | ||||||
| 			if sess.handled { |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			var payload = struct{ Value string }{} |  | ||||||
| 			gossh.Unmarshal(req.Payload, &payload) |  | ||||||
| 			sess.rawCmd = payload.Value |  | ||||||
| 
 |  | ||||||
| 			// If there's a session policy callback, we need to confirm before
 |  | ||||||
| 			// accepting the session.
 |  | ||||||
| 			if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) { |  | ||||||
| 				sess.rawCmd = "" |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			sess.handled = true |  | ||||||
| 			req.Reply(true, nil) |  | ||||||
| 
 |  | ||||||
| 			go func() { |  | ||||||
| 				sess.handler(sess) |  | ||||||
| 				sess.Exit(0) |  | ||||||
| 			}() |  | ||||||
| 		case "env": |  | ||||||
| 			if sess.handled { |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			var kv struct{ Key, Value string } |  | ||||||
| 			gossh.Unmarshal(req.Payload, &kv) |  | ||||||
| 			sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value)) |  | ||||||
| 			req.Reply(true, nil) |  | ||||||
| 		case "signal": |  | ||||||
| 			var payload struct{ Signal string } |  | ||||||
| 			gossh.Unmarshal(req.Payload, &payload) |  | ||||||
| 			sess.Lock() |  | ||||||
| 			if sess.sigCh != nil { |  | ||||||
| 				sess.sigCh <- Signal(payload.Signal) |  | ||||||
| 			} else { |  | ||||||
| 				if len(sess.sigBuf) < maxSigBufSize { |  | ||||||
| 					sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal)) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			sess.Unlock() |  | ||||||
| 		case "pty-req": |  | ||||||
| 			if sess.handled || sess.pty != nil { |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			ptyReq, ok := parsePtyRequest(req.Payload) |  | ||||||
| 			if !ok { |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if sess.ptyCb != nil { |  | ||||||
| 				ok := sess.ptyCb(sess.ctx, ptyReq) |  | ||||||
| 				if !ok { |  | ||||||
| 					req.Reply(false, nil) |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			sess.pty = &ptyReq |  | ||||||
| 			sess.winch = make(chan Window, 1) |  | ||||||
| 			sess.winch <- ptyReq.Window |  | ||||||
| 			defer func() { |  | ||||||
| 				// when reqs is closed
 |  | ||||||
| 				close(sess.winch) |  | ||||||
| 			}() |  | ||||||
| 			req.Reply(ok, nil) |  | ||||||
| 		case "window-change": |  | ||||||
| 			if sess.pty == nil { |  | ||||||
| 				req.Reply(false, nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			win, ok := parseWinchRequest(req.Payload) |  | ||||||
| 			if ok { |  | ||||||
| 				sess.pty.Window = win |  | ||||||
| 				sess.winch <- win |  | ||||||
| 			} |  | ||||||
| 			req.Reply(ok, nil) |  | ||||||
| 		case agentRequestType: |  | ||||||
| 			// TODO: option/callback to allow agent forwarding
 |  | ||||||
| 			SetAgentRequested(sess.ctx) |  | ||||||
| 			req.Reply(true, nil) |  | ||||||
| 		default: |  | ||||||
| 			// TODO: debug log
 |  | ||||||
| 			req.Reply(false, nil) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,123 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/subtle" |  | ||||||
| 	"net" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Signal string |  | ||||||
| 
 |  | ||||||
| // POSIX signals as listed in RFC 4254 Section 6.10.
 |  | ||||||
| const ( |  | ||||||
| 	SIGABRT Signal = "ABRT" |  | ||||||
| 	SIGALRM Signal = "ALRM" |  | ||||||
| 	SIGFPE  Signal = "FPE" |  | ||||||
| 	SIGHUP  Signal = "HUP" |  | ||||||
| 	SIGILL  Signal = "ILL" |  | ||||||
| 	SIGINT  Signal = "INT" |  | ||||||
| 	SIGKILL Signal = "KILL" |  | ||||||
| 	SIGPIPE Signal = "PIPE" |  | ||||||
| 	SIGQUIT Signal = "QUIT" |  | ||||||
| 	SIGSEGV Signal = "SEGV" |  | ||||||
| 	SIGTERM Signal = "TERM" |  | ||||||
| 	SIGUSR1 Signal = "USR1" |  | ||||||
| 	SIGUSR2 Signal = "USR2" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // DefaultHandler is the default Handler used by Serve.
 |  | ||||||
| var DefaultHandler Handler |  | ||||||
| 
 |  | ||||||
| // Option is a functional option handler for Server.
 |  | ||||||
| type Option func(*Server) error |  | ||||||
| 
 |  | ||||||
| // Handler is a callback for handling established SSH sessions.
 |  | ||||||
| type Handler func(Session) |  | ||||||
| 
 |  | ||||||
| // PublicKeyHandler is a callback for performing public key authentication.
 |  | ||||||
| type PublicKeyHandler func(ctx Context, key PublicKey) bool |  | ||||||
| 
 |  | ||||||
| // PasswordHandler is a callback for performing password authentication.
 |  | ||||||
| type PasswordHandler func(ctx Context, password string) bool |  | ||||||
| 
 |  | ||||||
| // KeyboardInteractiveHandler is a callback for performing keyboard-interactive authentication.
 |  | ||||||
| type KeyboardInteractiveHandler func(ctx Context, challenger gossh.KeyboardInteractiveChallenge) bool |  | ||||||
| 
 |  | ||||||
| // PtyCallback is a hook for allowing PTY sessions.
 |  | ||||||
| type PtyCallback func(ctx Context, pty Pty) bool |  | ||||||
| 
 |  | ||||||
| // SessionRequestCallback is a callback for allowing or denying SSH sessions.
 |  | ||||||
| type SessionRequestCallback func(sess Session, requestType string) bool |  | ||||||
| 
 |  | ||||||
| // ConnCallback is a hook for new connections before handling.
 |  | ||||||
| // It allows wrapping for timeouts and limiting by returning
 |  | ||||||
| // the net.Conn that will be used as the underlying connection.
 |  | ||||||
| type ConnCallback func(conn net.Conn) net.Conn |  | ||||||
| 
 |  | ||||||
| // LocalPortForwardingCallback is a hook for allowing port forwarding
 |  | ||||||
| type LocalPortForwardingCallback func(ctx Context, destinationHost string, destinationPort uint32) bool |  | ||||||
| 
 |  | ||||||
| // ReversePortForwardingCallback is a hook for allowing reverse port forwarding
 |  | ||||||
| type ReversePortForwardingCallback func(ctx Context, bindHost string, bindPort uint32) bool |  | ||||||
| 
 |  | ||||||
| // ServerConfigCallback is a hook for creating custom default server configs
 |  | ||||||
| type ServerConfigCallback func(ctx Context) *gossh.ServerConfig |  | ||||||
| 
 |  | ||||||
| // Window represents the size of a PTY window.
 |  | ||||||
| type Window struct { |  | ||||||
| 	Width  int |  | ||||||
| 	Height int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Pty represents a PTY request and configuration.
 |  | ||||||
| type Pty struct { |  | ||||||
| 	Term   string |  | ||||||
| 	Window Window |  | ||||||
| 	// HELP WANTED: terminal modes!
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Serve accepts incoming SSH connections on the listener l, creating a new
 |  | ||||||
| // connection goroutine for each. The connection goroutines read requests and
 |  | ||||||
| // then calls handler to handle sessions. Handler is typically nil, in which
 |  | ||||||
| // case the DefaultHandler is used.
 |  | ||||||
| func Serve(l net.Listener, handler Handler, options ...Option) error { |  | ||||||
| 	srv := &Server{Handler: handler} |  | ||||||
| 	for _, option := range options { |  | ||||||
| 		if err := srv.SetOption(option); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return srv.Serve(l) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ListenAndServe listens on the TCP network address addr and then calls Serve
 |  | ||||||
| // with handler to handle sessions on incoming connections. Handler is typically
 |  | ||||||
| // nil, in which case the DefaultHandler is used.
 |  | ||||||
| func ListenAndServe(addr string, handler Handler, options ...Option) error { |  | ||||||
| 	srv := &Server{Addr: addr, Handler: handler} |  | ||||||
| 	for _, option := range options { |  | ||||||
| 		if err := srv.SetOption(option); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return srv.ListenAndServe() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Handle registers the handler as the DefaultHandler.
 |  | ||||||
| func Handle(handler Handler) { |  | ||||||
| 	DefaultHandler = handler |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // KeysEqual is constant time compare of the keys to avoid timing attacks.
 |  | ||||||
| func KeysEqual(ak, bk PublicKey) bool { |  | ||||||
| 
 |  | ||||||
| 	//avoid panic if one of the keys is nil, return false instead
 |  | ||||||
| 	if ak == nil || bk == nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	a := ak.Marshal() |  | ||||||
| 	b := bk.Marshal() |  | ||||||
| 	return (len(a) == len(b) && subtle.ConstantTimeCompare(a, b) == 1) |  | ||||||
| } |  | ||||||
|  | @ -1,193 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"io" |  | ||||||
| 	"log" |  | ||||||
| 	"net" |  | ||||||
| 	"strconv" |  | ||||||
| 	"sync" |  | ||||||
| 
 |  | ||||||
| 	gossh "golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	forwardedTCPChannelType = "forwarded-tcpip" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // direct-tcpip data struct as specified in RFC4254, Section 7.2
 |  | ||||||
| type localForwardChannelData struct { |  | ||||||
| 	DestAddr string |  | ||||||
| 	DestPort uint32 |  | ||||||
| 
 |  | ||||||
| 	OriginAddr string |  | ||||||
| 	OriginPort uint32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DirectTCPIPHandler can be enabled by adding it to the server's
 |  | ||||||
| // ChannelHandlers under direct-tcpip.
 |  | ||||||
| func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) { |  | ||||||
| 	d := localForwardChannelData{} |  | ||||||
| 	if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil { |  | ||||||
| 		newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error()) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestAddr, d.DestPort) { |  | ||||||
| 		newChan.Reject(gossh.Prohibited, "port forwarding is disabled") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dest := net.JoinHostPort(d.DestAddr, strconv.FormatInt(int64(d.DestPort), 10)) |  | ||||||
| 
 |  | ||||||
| 	var dialer net.Dialer |  | ||||||
| 	dconn, err := dialer.DialContext(ctx, "tcp", dest) |  | ||||||
| 	if err != nil { |  | ||||||
| 		newChan.Reject(gossh.ConnectionFailed, err.Error()) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ch, reqs, err := newChan.Accept() |  | ||||||
| 	if err != nil { |  | ||||||
| 		dconn.Close() |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	go gossh.DiscardRequests(reqs) |  | ||||||
| 
 |  | ||||||
| 	go func() { |  | ||||||
| 		defer ch.Close() |  | ||||||
| 		defer dconn.Close() |  | ||||||
| 		io.Copy(ch, dconn) |  | ||||||
| 	}() |  | ||||||
| 	go func() { |  | ||||||
| 		defer ch.Close() |  | ||||||
| 		defer dconn.Close() |  | ||||||
| 		io.Copy(dconn, ch) |  | ||||||
| 	}() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type remoteForwardRequest struct { |  | ||||||
| 	BindAddr string |  | ||||||
| 	BindPort uint32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type remoteForwardSuccess struct { |  | ||||||
| 	BindPort uint32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type remoteForwardCancelRequest struct { |  | ||||||
| 	BindAddr string |  | ||||||
| 	BindPort uint32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type remoteForwardChannelData struct { |  | ||||||
| 	DestAddr   string |  | ||||||
| 	DestPort   uint32 |  | ||||||
| 	OriginAddr string |  | ||||||
| 	OriginPort uint32 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ForwardedTCPHandler can be enabled by creating a ForwardedTCPHandler and
 |  | ||||||
| // adding the HandleSSHRequest callback to the server's RequestHandlers under
 |  | ||||||
| // tcpip-forward and cancel-tcpip-forward.
 |  | ||||||
| type ForwardedTCPHandler struct { |  | ||||||
| 	forwards map[string]net.Listener |  | ||||||
| 	sync.Mutex |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (h *ForwardedTCPHandler) HandleSSHRequest(ctx Context, srv *Server, req *gossh.Request) (bool, []byte) { |  | ||||||
| 	h.Lock() |  | ||||||
| 	if h.forwards == nil { |  | ||||||
| 		h.forwards = make(map[string]net.Listener) |  | ||||||
| 	} |  | ||||||
| 	h.Unlock() |  | ||||||
| 	conn := ctx.Value(ContextKeyConn).(*gossh.ServerConn) |  | ||||||
| 	switch req.Type { |  | ||||||
| 	case "tcpip-forward": |  | ||||||
| 		var reqPayload remoteForwardRequest |  | ||||||
| 		if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil { |  | ||||||
| 			// TODO: log parse failure
 |  | ||||||
| 			return false, []byte{} |  | ||||||
| 		} |  | ||||||
| 		if srv.ReversePortForwardingCallback == nil || !srv.ReversePortForwardingCallback(ctx, reqPayload.BindAddr, reqPayload.BindPort) { |  | ||||||
| 			return false, []byte("port forwarding is disabled") |  | ||||||
| 		} |  | ||||||
| 		addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort))) |  | ||||||
| 		ln, err := net.Listen("tcp", addr) |  | ||||||
| 		if err != nil { |  | ||||||
| 			// TODO: log listen failure
 |  | ||||||
| 			return false, []byte{} |  | ||||||
| 		} |  | ||||||
| 		_, destPortStr, _ := net.SplitHostPort(ln.Addr().String()) |  | ||||||
| 		destPort, _ := strconv.Atoi(destPortStr) |  | ||||||
| 		h.Lock() |  | ||||||
| 		h.forwards[addr] = ln |  | ||||||
| 		h.Unlock() |  | ||||||
| 		go func() { |  | ||||||
| 			<-ctx.Done() |  | ||||||
| 			h.Lock() |  | ||||||
| 			ln, ok := h.forwards[addr] |  | ||||||
| 			h.Unlock() |  | ||||||
| 			if ok { |  | ||||||
| 				ln.Close() |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
| 		go func() { |  | ||||||
| 			for { |  | ||||||
| 				c, err := ln.Accept() |  | ||||||
| 				if err != nil { |  | ||||||
| 					// TODO: log accept failure
 |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 				originAddr, orignPortStr, _ := net.SplitHostPort(c.RemoteAddr().String()) |  | ||||||
| 				originPort, _ := strconv.Atoi(orignPortStr) |  | ||||||
| 				payload := gossh.Marshal(&remoteForwardChannelData{ |  | ||||||
| 					DestAddr:   reqPayload.BindAddr, |  | ||||||
| 					DestPort:   uint32(destPort), |  | ||||||
| 					OriginAddr: originAddr, |  | ||||||
| 					OriginPort: uint32(originPort), |  | ||||||
| 				}) |  | ||||||
| 				go func() { |  | ||||||
| 					ch, reqs, err := conn.OpenChannel(forwardedTCPChannelType, payload) |  | ||||||
| 					if err != nil { |  | ||||||
| 						// TODO: log failure to open channel
 |  | ||||||
| 						log.Println(err) |  | ||||||
| 						c.Close() |  | ||||||
| 						return |  | ||||||
| 					} |  | ||||||
| 					go gossh.DiscardRequests(reqs) |  | ||||||
| 					go func() { |  | ||||||
| 						defer ch.Close() |  | ||||||
| 						defer c.Close() |  | ||||||
| 						io.Copy(ch, c) |  | ||||||
| 					}() |  | ||||||
| 					go func() { |  | ||||||
| 						defer ch.Close() |  | ||||||
| 						defer c.Close() |  | ||||||
| 						io.Copy(c, ch) |  | ||||||
| 					}() |  | ||||||
| 				}() |  | ||||||
| 			} |  | ||||||
| 			h.Lock() |  | ||||||
| 			delete(h.forwards, addr) |  | ||||||
| 			h.Unlock() |  | ||||||
| 		}() |  | ||||||
| 		return true, gossh.Marshal(&remoteForwardSuccess{uint32(destPort)}) |  | ||||||
| 
 |  | ||||||
| 	case "cancel-tcpip-forward": |  | ||||||
| 		var reqPayload remoteForwardCancelRequest |  | ||||||
| 		if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil { |  | ||||||
| 			// TODO: log parse failure
 |  | ||||||
| 			return false, []byte{} |  | ||||||
| 		} |  | ||||||
| 		addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort))) |  | ||||||
| 		h.Lock() |  | ||||||
| 		ln, ok := h.forwards[addr] |  | ||||||
| 		h.Unlock() |  | ||||||
| 		if ok { |  | ||||||
| 			ln.Close() |  | ||||||
| 		} |  | ||||||
| 		return true, nil |  | ||||||
| 	default: |  | ||||||
| 		return false, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,83 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 
 |  | ||||||
| 	"golang.org/x/crypto/ssh" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func generateSigner() (ssh.Signer, error) { |  | ||||||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return ssh.NewSignerFromKey(key) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parsePtyRequest(s []byte) (pty Pty, ok bool) { |  | ||||||
| 	term, s, ok := parseString(s) |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	width32, s, ok := parseUint32(s) |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	height32, _, ok := parseUint32(s) |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	pty = Pty{ |  | ||||||
| 		Term: term, |  | ||||||
| 		Window: Window{ |  | ||||||
| 			Width:  int(width32), |  | ||||||
| 			Height: int(height32), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseWinchRequest(s []byte) (win Window, ok bool) { |  | ||||||
| 	width32, s, ok := parseUint32(s) |  | ||||||
| 	if width32 < 1 { |  | ||||||
| 		ok = false |  | ||||||
| 	} |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	height32, _, ok := parseUint32(s) |  | ||||||
| 	if height32 < 1 { |  | ||||||
| 		ok = false |  | ||||||
| 	} |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	win = Window{ |  | ||||||
| 		Width:  int(width32), |  | ||||||
| 		Height: int(height32), |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseString(in []byte) (out string, rest []byte, ok bool) { |  | ||||||
| 	if len(in) < 4 { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	length := binary.BigEndian.Uint32(in) |  | ||||||
| 	if uint32(len(in)) < 4+length { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	out = string(in[4 : 4+length]) |  | ||||||
| 	rest = in[4+length:] |  | ||||||
| 	ok = true |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseUint32(in []byte) (uint32, []byte, bool) { |  | ||||||
| 	if len(in) < 4 { |  | ||||||
| 		return 0, nil, false |  | ||||||
| 	} |  | ||||||
| 	return binary.BigEndian.Uint32(in), in[4:], true |  | ||||||
| } |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| package ssh |  | ||||||
| 
 |  | ||||||
| import gossh "golang.org/x/crypto/ssh" |  | ||||||
| 
 |  | ||||||
| // PublicKey is an abstraction of different types of public keys.
 |  | ||||||
| type PublicKey interface { |  | ||||||
| 	gossh.PublicKey |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // The Permissions type holds fine-grained permissions that are specific to a
 |  | ||||||
| // user or a specific authentication method for a user. Permissions, except for
 |  | ||||||
| // "source-address", must be enforced in the server application layer, after
 |  | ||||||
| // successful authentication.
 |  | ||||||
| type Permissions struct { |  | ||||||
| 	*gossh.Permissions |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A Signer can create signatures that verify against a public key.
 |  | ||||||
| type Signer interface { |  | ||||||
| 	gossh.Signer |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParseAuthorizedKey parses a public key from an authorized_keys file used in
 |  | ||||||
| // OpenSSH according to the sshd(8) manual page.
 |  | ||||||
| func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { |  | ||||||
| 	return gossh.ParseAuthorizedKey(in) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ParsePublicKey parses an SSH public key formatted for use in
 |  | ||||||
| // the SSH wire protocol according to RFC 4253, section 6.6.
 |  | ||||||
| func ParsePublicKey(in []byte) (out PublicKey, err error) { |  | ||||||
| 	return gossh.ParsePublicKey(in) |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue