Skip to content

Commit a437071

Browse files
committed
perf: sftp command
1 parent c6bbe42 commit a437071

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed

cmd/sftp.go

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"fmt"
20+
"io"
21+
"log"
22+
"net"
23+
"os"
24+
"strings"
25+
"time"
26+
27+
"github.com/pkg/sftp"
28+
"github.com/spf13/cobra"
29+
gossh "golang.org/x/crypto/ssh"
30+
"golang.org/x/term"
31+
"gopkg.in/yaml.v3"
32+
)
33+
34+
// sshCmd represents the ssh command
35+
var sftpCmd = &cobra.Command{
36+
Use: "sftp",
37+
Short: "JMS KoKo sftp debug tool",
38+
Long: `JMS KoKo sftp tool
39+
For example:
40+
jmstool sftp [email protected] -p 2222 -d /tmp/file.txt
41+
`,
42+
Run: func(cmd *cobra.Command, args []string) {
43+
var (
44+
username = ""
45+
host = ""
46+
port = "2222"
47+
privateFile = ""
48+
password = ""
49+
)
50+
for i := range args {
51+
if strings.Contains(args[i], "@") {
52+
usernameHost := strings.Split(args[i], "@")
53+
if len(usernameHost) != 2 {
54+
fmt.Println("error format: ", args[i])
55+
os.Exit(1)
56+
}
57+
username, host = usernameHost[0], usernameHost[1]
58+
break
59+
}
60+
}
61+
if username == "" || host == "" {
62+
_ = cmd.Help()
63+
os.Exit(1)
64+
}
65+
auths := make([]gossh.AuthMethod, 0, 2)
66+
67+
if flagPort, err := cmd.PersistentFlags().GetString("port"); err == nil {
68+
port = flagPort
69+
}
70+
if flagIdentity, err := cmd.PersistentFlags().GetString("identity"); err == nil && flagIdentity != "" {
71+
privateFile = flagIdentity
72+
}
73+
if flagPassword, err := cmd.PersistentFlags().GetString("password"); err == nil && flagPassword != "" {
74+
password = flagPassword
75+
auths = append(auths, gossh.Password(password))
76+
}
77+
var sshConfig SSHConfig
78+
79+
defaultConfig := gossh.Config{}
80+
defaultConfig.SetDefaults()
81+
82+
if flagConfig, err := cmd.PersistentFlags().GetString("config"); err == nil && flagConfig != "" {
83+
raw, err := os.ReadFile(flagConfig)
84+
if err != nil {
85+
log.Fatal(err)
86+
}
87+
if err := yaml.Unmarshal(raw, &sshConfig); err != nil {
88+
log.Fatal(err)
89+
}
90+
fmt.Printf("from config: %+v\n", sshConfig)
91+
if len(sshConfig.Ciphers) == 0 {
92+
sshConfig.Ciphers = defaultConfig.Ciphers
93+
}
94+
if len(sshConfig.KexAlgos) == 0 {
95+
sshConfig.KexAlgos = defaultConfig.KeyExchanges
96+
}
97+
if len(sshConfig.HostKeyAlgos) == 0 {
98+
sshConfig.HostKeyAlgos = nil
99+
}
100+
if len(sshConfig.MACs) == 0 {
101+
sshConfig.MACs = defaultConfig.MACs
102+
}
103+
}
104+
105+
if password == "" && privateFile == "" {
106+
if _, err := fmt.Fprintf(os.Stdout, "%s@%s password: ", username, host); err != nil {
107+
log.Fatal(err)
108+
}
109+
if result, err := term.ReadPassword(int(os.Stdout.Fd())); err != nil {
110+
log.Fatal(err)
111+
} else {
112+
auths = append(auths, gossh.Password(string(result)))
113+
}
114+
115+
}
116+
if privateFile != "" {
117+
raw, err := os.ReadFile(privateFile)
118+
if err != nil {
119+
log.Fatal(err)
120+
}
121+
signer, err := gossh.ParsePrivateKey(raw)
122+
if err != nil {
123+
log.Fatal(err)
124+
}
125+
126+
auths = append(auths, gossh.PublicKeys(signer))
127+
}
128+
config := &gossh.ClientConfig{
129+
User: username,
130+
Auth: auths,
131+
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
132+
Config: gossh.Config{Ciphers: sshConfig.Ciphers, KeyExchanges: sshConfig.KexAlgos, MACs: sshConfig.MACs},
133+
Timeout: 30 * time.Second,
134+
HostKeyAlgorithms: sshConfig.HostKeyAlgos,
135+
}
136+
client, err := gossh.Dial("tcp", net.JoinHostPort(host, port), config)
137+
if err != nil {
138+
log.Fatalf("dial err: %s", err)
139+
}
140+
defer client.Close()
141+
sess, err := client.NewSession()
142+
if err != nil {
143+
log.Fatalf("Session err: %s", err)
144+
}
145+
defer sess.Close()
146+
downloadFile := ""
147+
if flagDownload, err := cmd.PersistentFlags().GetString("download"); err == nil && flagDownload != "" {
148+
downloadFile = flagDownload
149+
150+
}
151+
if downloadFile == "" {
152+
log.Println("download file is required")
153+
return
154+
}
155+
sftpClient, err := sftp.NewClient(client)
156+
if err != nil {
157+
log.Printf("sftpClient failed: %s\n", err)
158+
return
159+
}
160+
161+
defer sftpClient.Close()
162+
163+
srcFile, err := sftpClient.Open(downloadFile)
164+
if err != nil {
165+
log.Printf("Open failed: %s\n", err)
166+
return
167+
}
168+
defer srcFile.Close()
169+
dstName := srcFile.Name()
170+
if strings.Contains(dstName, "/") {
171+
dstName = dstName[strings.LastIndex(dstName, "/")+1:]
172+
}
173+
dstFile, err := os.Create(dstName)
174+
if err != nil {
175+
log.Printf("Create failed: %s\n", err)
176+
return
177+
}
178+
defer dstFile.Close()
179+
srcReader := &fileReader{read: srcFile}
180+
if _, err := io.Copy(dstFile, srcReader); err != nil {
181+
log.Printf("Copy file failed: %s\n", err)
182+
return
183+
}
184+
log.Printf("Download file %s to %s success\n", downloadFile, dstName)
185+
},
186+
}
187+
188+
func init() {
189+
rootCmd.AddCommand(sftpCmd)
190+
sftpCmd.PersistentFlags().StringP("port", "p", "22", "ssh port")
191+
sftpCmd.PersistentFlags().StringP("password", "P", "", "ssh password")
192+
sftpCmd.PersistentFlags().StringP("identity", "i", "", "identity_file")
193+
sftpCmd.PersistentFlags().StringP("config", "c", "", "config file for cipher, kex, hostkey, macs")
194+
sftpCmd.PersistentFlags().StringP("download", "d", "", "sftp download file")
195+
// Here you will define your flags and configuration settings.
196+
197+
// Cobra supports Persistent Flags which will work for this command
198+
// and all subcommands, e.g.:
199+
// sshCmd.PersistentFlags().String("foo", "", "A help for foo")
200+
201+
// Cobra supports local flags which will only run when this command
202+
// is called directly, e.g.:
203+
// sshCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
204+
}
205+
206+
type fileReader struct {
207+
read io.ReadCloser
208+
}
209+
210+
func (f *fileReader) Read(p []byte) (nr int, err error) {
211+
return f.read.Read(p)
212+
}
213+
214+
func (f *fileReader) Close() error {
215+
return f.read.Close()
216+
}

0 commit comments

Comments
 (0)