1
2
3
4
5
6
7
8
9
10
11
12
13
14 package sftpfs
15
16 import (
17 _rand "crypto/rand"
18 "crypto/rsa"
19 "crypto/x509"
20 "encoding/pem"
21 "flag"
22 "fmt"
23 "io"
24 "log"
25 "net"
26 "os"
27 "testing"
28 "time"
29
30 "github.com/pkg/sftp"
31 "golang.org/x/crypto/ssh"
32 )
33
34 type SftpFsContext struct {
35 sshc *ssh.Client
36 sshcfg *ssh.ClientConfig
37 sftpc *sftp.Client
38 }
39
40
41
42 func SftpConnect(user, password, host string) (*SftpFsContext, error) {
43
62
63 sshcfg := &ssh.ClientConfig{
64 User: user,
65 Auth: []ssh.AuthMethod{
66 ssh.Password(password),
67 },
68 HostKeyCallback: ssh.InsecureIgnoreHostKey(),
69 }
70
71 sshc, err := ssh.Dial("tcp", host, sshcfg)
72 if err != nil {
73 return nil, err
74 }
75
76 sftpc, err := sftp.NewClient(sshc)
77 if err != nil {
78 return nil, err
79 }
80
81 ctx := &SftpFsContext{
82 sshc: sshc,
83 sshcfg: sshcfg,
84 sftpc: sftpc,
85 }
86
87 return ctx, nil
88 }
89
90 func (ctx *SftpFsContext) Disconnect() error {
91 ctx.sftpc.Close()
92 ctx.sshc.Close()
93 return nil
94 }
95
96
97 func RunSftpServer(rootpath string) {
98 var (
99 readOnly bool
100 debugLevelStr string
101 debugStderr bool
102 rootDir string
103 )
104
105 flag.BoolVar(&readOnly, "R", false, "read-only server")
106 flag.BoolVar(&debugStderr, "e", true, "debug to stderr")
107 flag.StringVar(&debugLevelStr, "l", "none", "debug level")
108 flag.StringVar(&rootDir, "root", rootpath, "root directory")
109 flag.Parse()
110
111 debugStream := io.Discard
112
113
114
115 config := &ssh.ServerConfig{
116 PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
117
118
119 fmt.Fprintf(debugStream, "Login: %s\n", c.User())
120 if c.User() == "test" && string(pass) == "test" {
121 return nil, nil
122 }
123 return nil, fmt.Errorf("password rejected for %q", c.User())
124 },
125 }
126
127 privateBytes, err := os.ReadFile("./test/id_rsa")
128 if err != nil {
129 log.Fatal("Failed to load private key", err)
130 }
131
132 private, err := ssh.ParsePrivateKey(privateBytes)
133 if err != nil {
134 log.Fatal("Failed to parse private key", err)
135 }
136
137 config.AddHostKey(private)
138
139
140
141 listener, err := net.Listen("tcp", "0.0.0.0:2022")
142 if err != nil {
143 log.Fatal("failed to listen for connection", err)
144 }
145
146 nConn, err := listener.Accept()
147 if err != nil {
148 log.Fatal("failed to accept incoming connection", err)
149 }
150
151
152
153 conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
154 if err != nil {
155 log.Fatal("failed to handshake", err)
156 }
157 defer conn.Close()
158
159
160 go ssh.DiscardRequests(reqs)
161
162
163 for newChannel := range chans {
164
165
166
167 fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
168 if newChannel.ChannelType() != "session" {
169 newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
170 fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
171 continue
172 }
173 channel, requests, err := newChannel.Accept()
174 if err != nil {
175 log.Fatal("could not accept channel.", err)
176 }
177 fmt.Fprintf(debugStream, "Channel accepted\n")
178
179
180
181
182 go func(in <-chan *ssh.Request) {
183 for req := range in {
184 fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
185 ok := false
186 switch req.Type {
187 case "subsystem":
188 fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
189 if string(req.Payload[4:]) == "sftp" {
190 ok = true
191 }
192 }
193 fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
194 req.Reply(ok, nil)
195 }
196 }(requests)
197
198 server, err := sftp.NewServer(channel, sftp.WithDebug(debugStream))
199 if err != nil {
200 log.Fatal(err)
201 }
202 _ = server.Serve()
203 return
204 }
205 }
206
207
208
209
210 func MakeSSHKeyPair(bits int, pubKeyPath, privateKeyPath string) error {
211 privateKey, err := rsa.GenerateKey(_rand.Reader, bits)
212 if err != nil {
213 return err
214 }
215
216
217 privateKeyFile, err := os.Create(privateKeyPath)
218 if err != nil {
219 return err
220 }
221 defer privateKeyFile.Close()
222 if err != nil {
223 return err
224 }
225
226 privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
227 if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
228 return err
229 }
230
231
232 pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
233 if err != nil {
234 return err
235 }
236
237 return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0o655)
238 }
239
240 func TestSftpCreate(t *testing.T) {
241 os.Mkdir("./test", 0o777)
242 MakeSSHKeyPair(1024, "./test/id_rsa.pub", "./test/id_rsa")
243
244 go RunSftpServer("./test/")
245 time.Sleep(5 * time.Second)
246
247 ctx, err := SftpConnect("test", "test", "localhost:2022")
248 if err != nil {
249 t.Fatal(err)
250 }
251 defer ctx.Disconnect()
252
253 fs := New(ctx.sftpc)
254
255 fs.MkdirAll("test/dir1/dir2/dir3", os.FileMode(0o777))
256 fs.Mkdir("test/foo", os.FileMode(0o000))
257 fs.Chmod("test/foo", os.FileMode(0o700))
258 fs.Mkdir("test/bar", os.FileMode(0o777))
259
260 file, err := fs.Create("file1")
261 if err != nil {
262 t.Error(err)
263 }
264 defer file.Close()
265
266 file.Write([]byte("hello "))
267 file.WriteString("world!\n")
268
269 f1, err := fs.Open("file1")
270 if err != nil {
271 log.Fatalf("open: %v", err)
272 }
273 defer f1.Close()
274
275 b := make([]byte, 100)
276
277 _, _ = f1.Read(b)
278 fmt.Println(string(b))
279
280 fmt.Println("done")
281
282 }
283
View as plain text