dd8e306e31
When our go.mod file was introduced in commit 114e85c2002091eb415040923d872f8e4a4bc636 in PR #3208, the module path chosen did not include a trailing /v2 component. However, the Go modules specification now advises that module paths must have a "major version suffix" which matches the release version. We therefore add a /v2 suffix to our module path and all its instances in import paths. See also https://golang.org/ref/mod#major-version-suffixes for details regarding the Go module system's major version suffix rule.
198 lines
5.4 KiB
Go
198 lines
5.4 KiB
Go
package ssh
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/git-lfs/git-lfs/v2/config"
|
|
"github.com/git-lfs/git-lfs/v2/subprocess"
|
|
"github.com/git-lfs/git-lfs/v2/tools"
|
|
"github.com/rubyist/tracerx"
|
|
)
|
|
|
|
type sshVariant string
|
|
|
|
const (
|
|
variantSSH = sshVariant("ssh")
|
|
variantSimple = sshVariant("simple")
|
|
variantPutty = sshVariant("putty")
|
|
variantTortoise = sshVariant("tortoiseplink")
|
|
)
|
|
|
|
type SSHMetadata struct {
|
|
UserAndHost string
|
|
Port string
|
|
Path string
|
|
}
|
|
|
|
func FormatArgs(cmd string, args []string, needShell bool) (string, []string) {
|
|
if !needShell {
|
|
return cmd, args
|
|
}
|
|
|
|
return subprocess.FormatForShellQuotedArgs(cmd, args)
|
|
}
|
|
|
|
func GetLFSExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, command, operation string, multiplexDesired bool) (string, []string) {
|
|
exe, args, needShell := GetExeAndArgs(osEnv, gitEnv, meta, multiplexDesired)
|
|
args = append(args, fmt.Sprintf("%s %s %s", command, meta.Path, operation))
|
|
exe, args = FormatArgs(exe, args, needShell)
|
|
tracerx.Printf("run_command: %s %s", exe, strings.Join(args, " "))
|
|
return exe, args
|
|
}
|
|
|
|
// Parse command, and if it looks like a valid command, return the ssh binary
|
|
// name, the command to run, and whether we need a shell. If not, return
|
|
// existing as the ssh binary name.
|
|
func parseShellCommand(command string, existing string) (ssh string, cmd string, needShell bool) {
|
|
ssh = existing
|
|
if cmdArgs := tools.QuotedFields(command); len(cmdArgs) > 0 {
|
|
needShell = true
|
|
ssh = cmdArgs[0]
|
|
cmd = command
|
|
}
|
|
return
|
|
}
|
|
|
|
func findVariant(variant string) (bool, sshVariant) {
|
|
switch variant {
|
|
case "ssh", "simple", "putty", "tortoiseplink":
|
|
return false, sshVariant(variant)
|
|
case "plink":
|
|
return false, variantPutty
|
|
case "auto":
|
|
return true, ""
|
|
default:
|
|
return false, variantSSH
|
|
}
|
|
}
|
|
|
|
func autodetectVariant(osEnv config.Environment, gitEnv config.Environment, basessh string) sshVariant {
|
|
if basessh != defaultSSHCmd {
|
|
// Strip extension for easier comparison
|
|
if ext := filepath.Ext(basessh); len(ext) > 0 {
|
|
basessh = basessh[:len(basessh)-len(ext)]
|
|
}
|
|
if strings.EqualFold(basessh, "plink") {
|
|
return variantPutty
|
|
}
|
|
if strings.EqualFold(basessh, "tortoiseplink") {
|
|
return variantTortoise
|
|
}
|
|
}
|
|
return "ssh"
|
|
}
|
|
|
|
func getVariant(osEnv config.Environment, gitEnv config.Environment, basessh string) sshVariant {
|
|
variant, ok := osEnv.Get("GIT_SSH_VARIANT")
|
|
if !ok {
|
|
variant, ok = gitEnv.Get("ssh.variant")
|
|
}
|
|
autodetect, val := findVariant(variant)
|
|
if ok && !autodetect {
|
|
return val
|
|
}
|
|
return autodetectVariant(osEnv, gitEnv, basessh)
|
|
}
|
|
|
|
// findRuntimeDir returns a path to the runtime directory if one exists and is
|
|
// guaranteed to be private.
|
|
func findRuntimeDir(osEnv config.Environment) string {
|
|
if dir, ok := osEnv.Get("XDG_RUNTIME_DIR"); ok {
|
|
return dir
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getControlDir(osEnv config.Environment) (string, error) {
|
|
dir := findRuntimeDir(osEnv)
|
|
if dir == "" {
|
|
return ioutil.TempDir("", "sock-*")
|
|
}
|
|
dir = filepath.Join(dir, "git-lfs")
|
|
err := os.Mkdir(dir, 0700)
|
|
if err != nil {
|
|
// Ideally we would use errors.Is here to check against
|
|
// os.ErrExist, but that's not available on Go 1.11.
|
|
perr, ok := err.(*os.PathError)
|
|
if !ok || perr.Err != syscall.EEXIST {
|
|
return ioutil.TempDir("", "sock-*")
|
|
}
|
|
}
|
|
return dir, nil
|
|
}
|
|
|
|
// Return the executable name for ssh on this machine and the base args
|
|
// Base args includes port settings, user/host, everything pre the command to execute
|
|
func GetExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, multiplexDesired bool) (exe string, baseargs []string, needShell bool) {
|
|
var cmd string
|
|
|
|
ssh, _ := osEnv.Get("GIT_SSH")
|
|
sshCmd, _ := osEnv.Get("GIT_SSH_COMMAND")
|
|
ssh, cmd, needShell = parseShellCommand(sshCmd, ssh)
|
|
|
|
if ssh == "" {
|
|
sshCmd, _ := gitEnv.Get("core.sshcommand")
|
|
ssh, cmd, needShell = parseShellCommand(sshCmd, defaultSSHCmd)
|
|
}
|
|
|
|
if cmd == "" {
|
|
cmd = ssh
|
|
}
|
|
|
|
basessh := filepath.Base(ssh)
|
|
variant := getVariant(osEnv, gitEnv, basessh)
|
|
|
|
args := make([]string, 0, 7)
|
|
|
|
if variant == variantTortoise {
|
|
// TortoisePlink requires the -batch argument to behave like ssh/plink
|
|
args = append(args, "-batch")
|
|
}
|
|
|
|
multiplexEnabled := gitEnv.Bool("lfs.ssh.automultiplex", true)
|
|
if variant == variantSSH && multiplexDesired && multiplexEnabled {
|
|
controlPath, err := getControlDir(osEnv)
|
|
if err != nil {
|
|
controlPath = filepath.Join(controlPath, "sock-%C")
|
|
args = append(args, "-oControlMaster=auto", fmt.Sprintf("-oControlPath=%s", controlPath))
|
|
}
|
|
}
|
|
|
|
if len(meta.Port) > 0 {
|
|
if variant == variantPutty || variant == variantTortoise {
|
|
args = append(args, "-P")
|
|
} else {
|
|
args = append(args, "-p")
|
|
}
|
|
args = append(args, meta.Port)
|
|
}
|
|
|
|
if variant == variantSSH {
|
|
// inserts a separator between cli -options and host/cmd commands
|
|
// example: $ ssh -p 12345 -- user@host.com git-lfs-authenticate ...
|
|
args = append(args, "--", meta.UserAndHost)
|
|
} else {
|
|
// no prefix supported, strip leading - off host to prevent cmd like:
|
|
// $ git config lfs.url ssh://-proxycmd=whatever
|
|
// $ plink -P 12345 -proxycmd=foo git-lfs-authenticate ...
|
|
//
|
|
// Instead, it'll attempt this, and eventually return an error
|
|
// $ plink -P 12345 proxycmd=foo git-lfs-authenticate ...
|
|
args = append(args, sshOptPrefixRE.ReplaceAllString(meta.UserAndHost, ""))
|
|
}
|
|
|
|
return cmd, args, needShell
|
|
}
|
|
|
|
const defaultSSHCmd = "ssh"
|
|
|
|
var (
|
|
sshOptPrefixRE = regexp.MustCompile(`\A\-+`)
|
|
)
|