switch to use gliderlabs/ssh for builtin server (#7250)

resolves git conflicts from #3896 (credit to @belak, in case github doesn't keep original author during squash)

Co-Authored-By: Matti Ranta <techknowlogick@gitea.io>
pull/7286/head^2
techknowlogick 2019-07-06 21:28:09 -04:00 committed by GitHub
parent c44f0b1c76
commit d0ec940dd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2122 additions and 178 deletions

8
go.mod
View File

@ -40,7 +40,7 @@ require (
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/gliderlabs/ssh v0.1.4 // indirect
github.com/gliderlabs/ssh v0.2.2
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd // indirect
github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e // indirect
github.com/go-macaron/binding v0.0.0-20160711225916-9440f336b443
@ -110,11 +110,11 @@ require (
github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
go.etcd.io/bbolt v1.3.2 // indirect
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b
golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190618163018-fdf1049a943a // indirect
golang.org/x/tools v0.0.0-20190620154339-431033348dd0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect

16
go.sum
View File

@ -100,8 +100,8 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.1.4 h1:5N8AYXpaQAPy0L7linKa5aI+WRfyYagAhjksVzxh+mI=
github.com/gliderlabs/ssh v0.1.4/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd h1:r04MMPyLHj/QwZuMJ5+7tJcBr1AQjpiAK/rZWRrQT7o=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e h1:SiEs4J3BKVIeaWrH3tKaz3QLZhJ68iJ/A4xrzIoE5+Y=
@ -369,8 +369,8 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 h1:TMrx+Qdx7uJAeUbv15N72h5Hmyb5+VDjEiMufAEAM04=
@ -395,15 +395,15 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqY
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f h1:dHNZYIYdq2QuU6w73vZ/DzesPbVlZVYZTtTZmrnsbQ8=
golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 h1:Dl2hc890lrizvUppGbRWhnIh2f8jOTCQpY5IKWRS0oM=
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190618163018-fdf1049a943a h1:aQmaYPOmKItb96VioBrTlYay5tSNUdKAFEhPCWMeLSM=
golang.org/x/tools v0.0.0-20190618163018-fdf1049a943a/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190620154339-431033348dd0 h1:qUGDNmGEM+ZBtwF9vuzEv+9nQQPL+l/oNBZ+DCDTAyo=
golang.org/x/tools v0.0.0-20190620154339-431033348dd0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View File

@ -34,6 +34,7 @@ LFS_CONTENT_PATH = data/lfs-mssql
OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
APP_DATA_PATH = integrations/gitea-integration-mssql/data
BUILTIN_SSH_SERVER_USER = git
[mailer]
ENABLED = true

View File

@ -34,6 +34,7 @@ LFS_CONTENT_PATH = data/lfs-mysql
OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
APP_DATA_PATH = integrations/gitea-integration-mysql/data
BUILTIN_SSH_SERVER_USER = git
[mailer]
ENABLED = true

View File

@ -34,6 +34,7 @@ LFS_CONTENT_PATH = data/lfs-mysql8
OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
APP_DATA_PATH = integrations/gitea-integration-mysql8/data
BUILTIN_SSH_SERVER_USER = git
[mailer]
ENABLED = false

View File

@ -34,6 +34,7 @@ LFS_CONTENT_PATH = data/lfs-pgsql
OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
APP_DATA_PATH = integrations/gitea-integration-pgsql/data
BUILTIN_SSH_SERVER_USER = git
[mailer]
ENABLED = true

View File

@ -73,7 +73,7 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link")
assert.True(t, exists, "The template has changed")
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.RunUser, setting.SSH.Domain, setting.SSH.Port)
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.BuiltinServerUser, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link)
}

View File

@ -31,6 +31,7 @@ OFFLINE_MODE = false
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
APP_DATA_PATH = integrations/gitea-integration-sqlite/data
ENABLE_GZIP = true
BUILTIN_SSH_SERVER_USER = git
[mailer]
ENABLED = true

View File

@ -1,4 +1,3 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -10,178 +9,157 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/Unknwon/com"
"golang.org/x/crypto/ssh"
"sync"
"syscall"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
)
func cleanCommand(cmd string) string {
i := strings.Index(cmd, "git")
if i == -1 {
return cmd
}
return cmd[i:]
}
type contextKey string
func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
for newChan := range chans {
if newChan.ChannelType() != "session" {
err := newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
if err != nil {
log.Error("Error rejecting channel: %v", err)
}
continue
const giteaKeyID = contextKey("gitea-key-id")
func getExitStatusFromError(err error) int {
if err == nil {
return 0
}
exitErr, ok := err.(*exec.ExitError)
if !ok {
return 1
}
waitStatus, ok := exitErr.Sys().(syscall.WaitStatus)
if !ok {
// This is a fallback and should at least let us return something useful
// when running on Windows, even if it isn't completely accurate.
if exitErr.Success() {
return 0
}
ch, reqs, err := newChan.Accept()
if err != nil {
log.Error("Error accepting channel: %v", err)
continue
}
go func(in <-chan *ssh.Request) {
defer func() {
if err = ch.Close(); err != nil {
log.Error("Close: %v", err)
}
}()
for req := range in {
payload := cleanCommand(string(req.Payload))
switch req.Type {
case "exec":
cmdName := strings.TrimLeft(payload, "'()")
log.Trace("SSH: Payload: %v", cmdName)
args := []string{"serv", "key-" + keyID, "--config=" + setting.CustomConf}
log.Trace("SSH: Arguments: %v", args)
cmd := exec.Command(setting.AppPath, args...)
cmd.Env = append(
os.Environ(),
"SSH_ORIGINAL_COMMAND="+cmdName,
"SKIP_MINWINSVC=1",
)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Error("SSH: StdoutPipe: %v", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Error("SSH: StderrPipe: %v", err)
return
}
input, err := cmd.StdinPipe()
if err != nil {
log.Error("SSH: StdinPipe: %v", err)
return
}
// FIXME: check timeout
if err = cmd.Start(); err != nil {
log.Error("SSH: Start: %v", err)
return
}
err = req.Reply(true, nil)
if err != nil {
log.Error("SSH: Reply: %v", err)
}
go func() {
_, err = io.Copy(input, ch)
if err != nil {
log.Error("SSH: Copy: %v", err)
}
}()
_, err = io.Copy(ch, stdout)
if err != nil {
log.Error("SSH: Copy: %v", err)
}
_, err = io.Copy(ch.Stderr(), stderr)
if err != nil {
log.Error("SSH: Copy: %v", err)
}
if err = cmd.Wait(); err != nil {
log.Error("SSH: Wait: %v", err)
return
}
_, err = ch.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
if err != nil {
log.Error("SSH: SendRequest: %v", err)
}
return
default:
}
}
}(reqs)
return 1
}
return waitStatus.ExitStatus()
}
func listen(config *ssh.ServerConfig, host string, port int) {
listener, err := net.Listen("tcp", host+":"+com.ToStr(port))
func sessionHandler(session ssh.Session) {
keyID := session.Context().Value(giteaKeyID).(int64)
command := session.RawCommand()
log.Trace("SSH: Payload: %v", command)
args := []string{"serv", "key-" + com.ToStr(keyID), "--config=" + setting.CustomConf}
log.Trace("SSH: Arguments: %v", args)
cmd := exec.Command(setting.AppPath, args...)
cmd.Env = append(
os.Environ(),
"SSH_ORIGINAL_COMMAND="+command,
"SKIP_MINWINSVC=1",
)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal("Failed to start SSH server: %v", err)
log.Error("SSH: StdoutPipe: %v", err)
return
}
for {
// Once a ServerConfig has been configured, connections can be accepted.
conn, err := listener.Accept()
if err != nil {
log.Error("SSH: Error accepting incoming connection: %v", err)
continue
stderr, err := cmd.StderrPipe()
if err != nil {
log.Error("SSH: StderrPipe: %v", err)
return
}
stdin, err := cmd.StdinPipe()
if err != nil {
log.Error("SSH: StdinPipe: %v", err)
return
}
wg := &sync.WaitGroup{}
wg.Add(2)
if err = cmd.Start(); err != nil {
log.Error("SSH: Start: %v", err)
return
}
go func() {
defer stdin.Close()
if _, err := io.Copy(stdin, session); err != nil {
log.Error("Failed to write session to stdin. %s", err)
}
}()
// Before use, a handshake must be performed on the incoming net.Conn.
// It must be handled in a separate goroutine,
// otherwise one user could easily block entire loop.
// For example, user could be asked to trust server key fingerprint and hangs.
go func() {
log.Trace("SSH: Handshaking for %s", conn.RemoteAddr())
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
if err == io.EOF {
log.Warn("SSH: Handshaking with %s was terminated: %v", conn.RemoteAddr(), err)
} else {
log.Error("SSH: Error on handshaking with %s: %v", conn.RemoteAddr(), err)
}
return
}
go func() {
defer wg.Done()
if _, err := io.Copy(session, stdout); err != nil {
log.Error("Failed to write stdout to session. %s", err)
}
}()
log.Trace("SSH: Connection from %s (%s)", sConn.RemoteAddr(), sConn.ClientVersion())
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
go handleServerConn(sConn.Permissions.Extensions["key-id"], chans)
}()
go func() {
defer wg.Done()
if _, err := io.Copy(session.Stderr(), stderr); err != nil {
log.Error("Failed to write stderr to session. %s", err)
}
}()
// Ensure all the output has been written before we wait on the command
// to exit.
wg.Wait()
// Wait for the command to exit and log any errors we get
err = cmd.Wait()
if err != nil {
log.Error("SSH: Wait: %v", err)
}
if err := session.Exit(getExitStatusFromError(err)); err != nil {
log.Error("Session failed to exit. %s", err)
}
}
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
if ctx.User() != setting.SSH.BuiltinServerUser {
return false
}
pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
if err != nil {
log.Error("SearchPublicKeyByContent: %v", err)
return false
}
ctx.SetValue(giteaKeyID, pkey.ID)
return true
}
// Listen starts a SSH server listens on given port.
func Listen(host string, port int, ciphers []string, keyExchanges []string, macs []string) {
config := &ssh.ServerConfig{
Config: ssh.Config{
Ciphers: ciphers,
KeyExchanges: keyExchanges,
MACs: macs,
},
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))))
if err != nil {
log.Error("SearchPublicKeyByContent: %v", err)
return nil, err
}
return &ssh.Permissions{Extensions: map[string]string{"key-id": com.ToStr(pkey.ID)}}, nil
// TODO: Handle ciphers, keyExchanges, and macs
srv := ssh.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
PublicKeyHandler: publicKeyHandler,
Handler: sessionHandler,
// We need to explicitly disable the PtyCallback so text displays
// properly.
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {
return false
},
}
@ -197,20 +175,21 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs
if err != nil {
log.Fatal("Failed to generate private key: %v", err)
}
log.Trace("SSH: New private key is generateed: %s", keyPath)
log.Trace("New private key is generated: %s", keyPath)
}
privateBytes, err := ioutil.ReadFile(keyPath)
err := srv.SetOption(ssh.HostKeyFile(keyPath))
if err != nil {
log.Fatal("SSH: Failed to load private key")
log.Error("Failed to set Host Key. %s", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("SSH: Failed to parse private key")
}
config.AddHostKey(private)
go listen(config, host, port)
go func() {
err := srv.ListenAndServe()
if err != nil {
log.Error("Failed to serve with builtin SSH server. %s", err)
}
}()
}
// GenKeyPair make a pair of public and private keys for SSH access.
@ -238,12 +217,12 @@ func GenKeyPair(keyPath string) error {
}
// generate public key
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
pub, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return err
}
public := ssh.MarshalAuthorizedKey(pub)
public := gossh.MarshalAuthorizedKey(pub)
p, err := os.OpenFile(keyPath+".pub", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err

1
vendor/github.com/anmitsu/go-shlex/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
shlex.test

20
vendor/github.com/anmitsu/go-shlex/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
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.

38
vendor/github.com/anmitsu/go-shlex/README.md generated vendored Normal file
View File

@ -0,0 +1,38 @@
# 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

193
vendor/github.com/anmitsu/go-shlex/shlex.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
// 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
}

27
vendor/github.com/gliderlabs/ssh/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
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.

96
vendor/github.com/gliderlabs/ssh/README.md generated vendored Normal file
View File

@ -0,0 +1,96 @@
# gliderlabs/ssh
[![GoDoc](https://godoc.org/github.com/gliderlabs/ssh?status.svg)](https://godoc.org/github.com/gliderlabs/ssh)
[![CircleCI](https://img.shields.io/circleci/project/github/gliderlabs/ssh.svg)](https://circleci.com/gh/gliderlabs/ssh)
[![Go Report Card](https://goreportcard.com/badge/github.com/gliderlabs/ssh)](https://goreportcard.com/report/github.com/gliderlabs/ssh)
[![OpenCollective](https://opencollective.com/ssh/sponsors/badge.svg)](#sponsors)
[![Slack](http://slack.gliderlabs.com/badge.svg)](http://slack.gliderlabs.com)
[![Email Updates](https://img.shields.io/badge/updates-subscribe-yellow.svg)](https://app.convertkit.com/landing_pages/243312)
> The Glider Labs SSH server package is dope. &mdash;[@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

83
vendor/github.com/gliderlabs/ssh/agent.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
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)
}
}

26
vendor/github.com/gliderlabs/ssh/circle.yml generated vendored Normal file
View File

@ -0,0 +1,26 @@
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

55
vendor/github.com/gliderlabs/ssh/conn.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
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)
}
}

152
vendor/github.com/gliderlabs/ssh/context.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
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)
}

45
vendor/github.com/gliderlabs/ssh/doc.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
/*
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

77
vendor/github.com/gliderlabs/ssh/options.go generated vendored Normal file
View File

@ -0,0 +1,77 @@
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
}
}

394
vendor/github.com/gliderlabs/ssh/server.go generated vendored Normal file
View File

@ -0,0 +1,394 @@
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, <