sftp_server 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6579fc72d42db229fdc52951724810a74e81661
4
+ data.tar.gz: a73bb16cb690daa050997e727c195740e70c841b
5
+ SHA512:
6
+ metadata.gz: c27a18a11ed53b0c762c48d6c43f2ba495bcb8ca18f85ce561ae8550be0d84df7d1220bd4ec0d1795dc32fb2abcd42cdacfc3ce20d4b346b574c456f20def542
7
+ data.tar.gz: 840fa2cfe4cc9ae5de894a94b03e07d1d8b98679d1d8f996226db9ff382feb025fb5fd561555b79f3a278789e6e7807f72b25d18a6256ffac70babcdd2783d8b
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
@@ -0,0 +1 @@
1
+ 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sftp_server.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 M. Scott Ford
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # SFTPServer
2
+
3
+ A simple SFTP server for testing clients:
4
+
5
+ Uses libssh via FFI to create an SFTP server. Useful for testing interactions with a remote SFTP server.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'sftp_server'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install sftp_server
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/corgibytes/sftp_server/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,2 @@
1
+ require 'sftp_server/version'
2
+ require 'sftp_server/server'
@@ -0,0 +1,13 @@
1
+ require 'ffi'
2
+
3
+ module SFTPServer
4
+ module C
5
+ module API
6
+ extend FFI::Library
7
+ ffi_lib_flags :now, :global
8
+ ffi_lib 'c'
9
+
10
+ attach_function :opendir, [:string], :pointer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,417 @@
1
+ require 'sftp_server/ssh/api'
2
+ require 'sftp_server/c/api'
3
+
4
+ require 'pry'
5
+
6
+ module SFTPServer
7
+ class Server
8
+ attr_accessor :user_name
9
+ attr_accessor :password
10
+ attr_accessor :rsa_key
11
+ attr_accessor :dsa_key
12
+ attr_accessor :port
13
+ attr_accessor :listen_address
14
+ attr_accessor :verbose
15
+
16
+ def initialize(options = {})
17
+ @user_name = options[:user_name]
18
+ @password = options[:password]
19
+ @rsa_key = options[:rsa_key]
20
+ @dsa_key = options[:dsa_key]
21
+ @port = options[:port]
22
+ @listen_address = options[:listen_address]
23
+ @verbose = options[:verbose]
24
+
25
+ @authenticated = false
26
+ @handles = {}
27
+ end
28
+
29
+ def set_bind_option(sshbind, key_type, key_value, value_type, value_value)
30
+ result = SSH::API.ssh_bind_options_set(
31
+ sshbind, key_type, key_value, value_type, value_value
32
+ )
33
+ fail SSH::API.ssh_get_error(sshbind) if result < 0
34
+ end
35
+
36
+ def bind_listen(bind)
37
+ result = SSH::API.ssh_bind_listen(bind)
38
+ fail SSH::API.ssh_get_error(bind) if result < 0
39
+ end
40
+
41
+ def bind_accept(bind, session)
42
+ result = SSH::API.ssh_bind_accept(bind, session)
43
+ fail SSH::API.ssh_get_error(bind) if result < 0
44
+ end
45
+
46
+ def handle_key_exchange(session)
47
+ result = SSH::API.ssh_handle_key_exchange(session)
48
+ fail SSH::API.ssh_get_error(session) if result < 0
49
+ end
50
+
51
+ def handle_auth(message)
52
+ end
53
+
54
+ def respond_auth_required(message)
55
+ SSH::API.ssh_message_auth_set_methods(message, SSH::API::MessageAuthTypes::SSH_AUTH_METHOD_PASSWORD)
56
+ SSH::API.ssh_message_reply_default(message)
57
+ end
58
+
59
+ def sftp_channel_request(session)
60
+ sftp_channel_requested = false
61
+ while !sftp_channel_requested
62
+ message = SSH::API.ssh_message_get(session)
63
+ if message
64
+ message_type = SSH::API.ssh_message_type(message)
65
+ log "open sftp session message_type: #{message_type}"
66
+ if message_type == SSH::API::MessageTypes::SSH_REQUEST_CHANNEL
67
+ message_subtype = SSH::API.ssh_message_subtype(message)
68
+ log "open sftp session message_subtype: #{message_subtype}"
69
+
70
+ case message_subtype
71
+ when SSH::API::ChannelRequestTypes::SSH_CHANNEL_REQUEST_ENV
72
+ env_name = SSH::API.ssh_message_channel_request_env_name(message)
73
+ log "request env name: #{env_name}"
74
+
75
+ env_value = SSH::API.ssh_message_channel_request_env_value(message)
76
+ log "request env value: #{env_value}"
77
+ when SSH::API::ChannelRequestTypes::SSH_CHANNEL_REQUEST_SUBSYSTEM
78
+ subsystem_name = SSH::API.ssh_message_channel_request_subsystem(message)
79
+ log "request subsystem: #{subsystem_name}"
80
+ if subsystem_name == 'sftp'
81
+ SSH::API.ssh_message_channel_request_reply_success(message)
82
+ sftp_channel_requested = true
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ unless sftp_channel_requested
89
+ SSH::API.ssh_message_reply_default(message)
90
+ end
91
+ SSH::API.ssh_message_free(message)
92
+ end
93
+ sftp_channel_requested
94
+ end
95
+
96
+ def log(message)
97
+ puts(message) if verbose
98
+ end
99
+
100
+ def open_channel(session)
101
+ channel = nil
102
+ while channel.nil?
103
+ message = SSH::API.ssh_message_get(session)
104
+ next unless message
105
+
106
+ message_type = SSH::API.ssh_message_type(message)
107
+ log "channel message_type: #{message_type}"
108
+ next unless message_type > -1
109
+
110
+ case message_type
111
+ when SSH::API::MessageTypes::SSH_REQUEST_CHANNEL_OPEN
112
+ message_subtype = SSH::API.ssh_message_subtype(message)
113
+ log "channel message_subtype: #{message_subtype}"
114
+ if message_subtype == SSH::API::ChannelTypes::SSH_CHANNEL_SESSION
115
+ channel = SSH::API.ssh_message_channel_request_open_reply_accept(message)
116
+ break
117
+ end
118
+ else
119
+ SSH::API.ssh_message_reply_default(message)
120
+ end
121
+ SSH::API.ssh_message_free(message)
122
+ end
123
+ channel
124
+ end
125
+
126
+ def authenticate(session)
127
+ authenticated = false
128
+ while !authenticated
129
+ message = SSH::API.ssh_message_get(session)
130
+ next unless message
131
+
132
+ message_type = SSH::API.ssh_message_type(message)
133
+ log "message_type: #{message_type}"
134
+ next unless message_type > -1
135
+
136
+ case message_type
137
+ when SSH::API::MessageTypes::SSH_REQUEST_AUTH
138
+ log "auth"
139
+ message_subtype = SSH::API.ssh_message_subtype(message)
140
+ log "auth message_subtype: #{message_subtype}"
141
+ case message_subtype
142
+ when SSH::API::MessageAuthTypes::SSH_AUTH_METHOD_PASSWORD
143
+ request_user_name = SSH::API.ssh_message_auth_user(message)
144
+ request_password = SSH::API.ssh_message_auth_password(message)
145
+ log user_name
146
+ log password
147
+ if user_name == request_user_name && password == request_password
148
+ SSH::API.ssh_message_auth_reply_success(message, 0)
149
+ SSH::API.ssh_message_free(message)
150
+ authenticated = true
151
+ break
152
+ else
153
+ SSH::API.ssh_message_reply_default(message)
154
+ next
155
+ end
156
+ else
157
+ respond_auth_required(message) unless @authenticated
158
+ end
159
+ else
160
+ SSH::API.ssh_message_reply_default(message)
161
+ end
162
+ end
163
+ authenticated
164
+ end
165
+
166
+ def init_sftp_session(sftp_session)
167
+ result = SSH::API.sftp_server_init(sftp_session)
168
+ fail SSH::API.ssh_get_error(sftp_session) unless result == 0
169
+ end
170
+
171
+ def sftp_message_loop(sftp_session)
172
+ while true
173
+ client_message = SSH::API.sftp_get_client_message(sftp_session)
174
+ log "client_message: #{client_message}"
175
+ return if client_message.null?
176
+
177
+ client_message_type = SSH::API.sftp_client_message_get_type(client_message)
178
+ next unless client_message_type
179
+ log "client_message_type: #{client_message_type}"
180
+
181
+ case client_message_type
182
+ when SSH::API::SFTPCommands::SSH_FXP_REALPATH
183
+ log "realpath"
184
+
185
+ file_name = SSH::API.sftp_client_message_get_filename(client_message)
186
+ log "file_name: #{file_name}"
187
+
188
+ long_file_name = File.expand_path(file_name)
189
+
190
+ SSH::API.sftp_reply_names_add(client_message, long_file_name, long_file_name, SSH::API::SFTPAttributes.new.to_ptr)
191
+ SSH::API.sftp_reply_names(client_message)
192
+ when SSH::API::SFTPCommands::SSH_FXP_OPENDIR
193
+ log "opendir"
194
+
195
+ dir_name = SSH::API.sftp_client_message_get_filename(client_message)
196
+ long_dir_name = File.expand_path(dir_name)
197
+ log "long_dir_name: #{long_dir_name}"
198
+
199
+ @handles[long_dir_name] = :open
200
+
201
+ long_dir_name_pointer = FFI::MemoryPointer.from_string(long_dir_name)
202
+ handle = SSH::API.sftp_handle_alloc(sftp_session, long_dir_name_pointer)
203
+
204
+ SSH::API.sftp_reply_handle(client_message, handle)
205
+ when SSH::API::SFTPCommands::SSH_FXP_READDIR
206
+ log "readdir"
207
+
208
+ client_message_data = SSH::API::SFTPClientMessage.new(client_message)
209
+ handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
210
+ long_dir_name = handle.read_string
211
+ log "long_dir_name: #{long_dir_name}"
212
+
213
+ if @handles[long_dir_name] == :open
214
+ Dir.entries(long_dir_name).each do |entry|
215
+ SSH::API.sftp_reply_names_add(
216
+ client_message,
217
+ entry,
218
+ entry,
219
+ SSH::API::SFTPAttributes.new.to_ptr
220
+ )
221
+ end
222
+ @handles[long_dir_name] = :read
223
+ SSH::API.sftp_reply_names(client_message)
224
+ else
225
+ SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_EOF, 'End-of-file encountered')
226
+ end
227
+ when SSH::API::SFTPCommands::SSH_FXP_CLOSE
228
+ log 'close'
229
+
230
+ client_message_data = SSH::API::SFTPClientMessage.new(client_message)
231
+ handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
232
+ long_dir_name = handle.read_string
233
+ log "long_dir_name: #{long_dir_name}"
234
+
235
+ @handles.delete(long_dir_name)
236
+
237
+ SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_OK, 'Success')
238
+ when SSH::API::SFTPCommands::SSH_FXP_LSTAT
239
+ log 'lstat'
240
+
241
+ file_name = SSH::API.sftp_client_message_get_filename(client_message)
242
+ log "file_name: #{file_name}"
243
+
244
+ long_file_name = File.expand_path(file_name)
245
+
246
+ file_stat = File.lstat(long_file_name)
247
+
248
+ attributes = SSH::API::SFTPAttributes.new
249
+
250
+ attributes[:flags] = 0
251
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_SIZE
252
+ attributes[:size] = file_stat.size
253
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_UIDGID
254
+ attributes[:uid] = file_stat.uid
255
+ attributes[:gid] = file_stat.gid
256
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_PERMISSIONS
257
+ attributes[:permissions] = file_stat.mode
258
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_ACMODTIME
259
+ attributes[:atime] = file_stat.atime.to_i
260
+ attributes[:mtime] = file_stat.mtime.to_i
261
+
262
+ SSH::API.sftp_reply_attr(client_message, attributes.to_ptr)
263
+ when SSH::API::SFTPCommands::SSH_FXP_STAT
264
+ log 'stat'
265
+
266
+ file_name = SSH::API.sftp_client_message_get_filename(client_message)
267
+ log "file_name: #{file_name}"
268
+
269
+ long_file_name = File.expand_path(file_name)
270
+
271
+ file_stat = File.stat(long_file_name)
272
+
273
+ attributes = SSH::API::SFTPAttributes.new
274
+
275
+ attributes[:flags] = 0
276
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_SIZE
277
+ attributes[:size] = file_stat.size
278
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_UIDGID
279
+ attributes[:uid] = file_stat.uid
280
+ attributes[:gid] = file_stat.gid
281
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_PERMISSIONS
282
+ attributes[:permissions] = file_stat.mode
283
+ attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_ACMODTIME
284
+ attributes[:atime] = file_stat.atime.to_i
285
+ attributes[:mtime] = file_stat.mtime.to_i
286
+
287
+ SSH::API.sftp_reply_attr(client_message, attributes.to_ptr)
288
+ when SSH::API::SFTPCommands::SSH_FXP_OPEN
289
+ log 'open'
290
+
291
+ file_name = SSH::API.sftp_client_message_get_filename(client_message)
292
+ long_file_name = File.expand_path(file_name)
293
+ log "long_file_name: #{long_file_name}"
294
+
295
+ client_message_data = SSH::API::SFTPClientMessage.new(client_message)
296
+ message_flags = client_message_data[:flags]
297
+ flags = 0
298
+ if (message_flags & SSH::API::Flags::SSH_FXF_READ == SSH::API::Flags::SSH_FXF_READ) &&
299
+ (message_flags & SSH::API::Flags::SSH_FXF_WRITE == SSH::API::Flags::SSH_FXF_WRITE)
300
+ flags = File::Constants::RDWR
301
+ elsif (message_flags & SSH::API::Flags::SSH_FXF_READ == SSH::API::Flags::SSH_FXF_READ)
302
+ flags = File::Constants::RDONLY
303
+ elsif (message_flags & SSH::API::Flags::SSH_FXF_WRITE == SSH::API::Flags::SSH_FXF_WRITE)
304
+ flags = File::Constants::WRONLY
305
+ end
306
+
307
+ if (message_flags & SSH::API::Flags::SSH_FXF_APPEND == SSH::API::Flags::SSH_FXF_APPEND)
308
+ flags |= File::Constants::APPEND
309
+ end
310
+
311
+ if (message_flags & SSH::API::Flags::SSH_FXF_CREAT == SSH::API::Flags::SSH_FXF_CREAT)
312
+ flags |= File::Constants::CREAT
313
+ end
314
+
315
+ if (message_flags & SSH::API::Flags::SSH_FXF_TRUNC == SSH::API::Flags::SSH_FXF_TRUNC)
316
+ flags |= File::Constants::TRUNC
317
+ end
318
+
319
+ if (message_flags & SSH::API::Flags::SSH_FXF_EXCL == SSH::API::Flags::SSH_FXF_EXCL)
320
+ flags |= File::Constants::EXCL
321
+ end
322
+
323
+ @handles[long_file_name] = File.open(long_file_name, flags)
324
+
325
+ long_file_name_pointer = FFI::MemoryPointer.from_string(long_file_name)
326
+ handle = SSH::API.sftp_handle_alloc(sftp_session, long_file_name_pointer)
327
+
328
+ SSH::API.sftp_reply_handle(client_message, handle)
329
+ when SSH::API::SFTPCommands::SSH_FXP_READ
330
+ log 'read'
331
+
332
+ client_message_data = SSH::API::SFTPClientMessage.new(client_message)
333
+ handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
334
+ long_file_name = handle.read_string
335
+ log "long_file_name: #{long_file_name}"
336
+
337
+ file = @handles[long_file_name]
338
+ if file
339
+ file.seek(client_message_data[:offset])
340
+ data = file.read(client_message_data[:len])
341
+ if data
342
+ buffer = FFI::MemoryPointer.new(:char, data.size)
343
+ buffer.put_bytes(0, data)
344
+ SSH::API.sftp_reply_data(client_message, buffer, data.size)
345
+ else
346
+ SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_EOF, 'End-of-file encountered')
347
+ end
348
+ end
349
+ when SSH::API::SFTPCommands::SSH_FXP_WRITE
350
+ log 'write'
351
+
352
+ client_message_data = SSH::API::SFTPClientMessage.new(client_message)
353
+ handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
354
+ long_file_name = handle.read_string
355
+ log "long_file_name: #{long_file_name}"
356
+
357
+ file = @handles[long_file_name]
358
+ if file
359
+ file.seek(client_message_data[:offset])
360
+ buffer = SSH::API.sftp_client_message_get_data(client_message)
361
+ file.write(buffer.read_string)
362
+ SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_OK, 'Success')
363
+ end
364
+ end
365
+
366
+ SSH::API.sftp_client_message_free(client_message)
367
+ end
368
+ end
369
+
370
+ def close_channel(channel)
371
+ result = SSH::API.ssh_channel_close(channel)
372
+ fail SSH::API.ssh_get_error(channel) if result < 0
373
+ end
374
+
375
+ def free_channel(channel)
376
+ result = SSH::API.ssh_channel_free(channel)
377
+ fail SSH::API.ssh_get_error(channel) if result < 0
378
+ end
379
+
380
+ def disconnect_session(session)
381
+ result = SSH::API.ssh_disconnect(session)
382
+ fail SSH::API.ssh_get_error(session) if result < 0
383
+ end
384
+
385
+ def free_bind(bind)
386
+ result = SSH::API.ssh_bind_free(bind)
387
+ fail SSH::API.ssh_get_error(bind) if result < 0
388
+ end
389
+
390
+ def open
391
+ ssh_bind = SSH::API.ssh_bind_new
392
+ session = SSH::API.ssh_new
393
+
394
+ set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_BINDADDR, :string, listen_address) if listen_address
395
+ set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_BINDPORT_STR, :string, port) if port
396
+ set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_RSAKEY, :string, rsa_key) if rsa_key
397
+ set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_DSAKEY, :string, dsa_key) if dsa_key
398
+
399
+ bind_listen(ssh_bind)
400
+ bind_accept(ssh_bind, session)
401
+ handle_key_exchange(session)
402
+
403
+ if authenticate(session)
404
+ channel = open_channel(session)
405
+ if channel
406
+ if sftp_channel_request(session)
407
+ sftp_session = SSH::API.sftp_server_new(session, channel)
408
+ init_sftp_session(sftp_session)
409
+ sftp_message_loop(sftp_session)
410
+ end
411
+ end
412
+ close_channel(channel)
413
+ free_channel(channel)
414
+ end
415
+ end
416
+ end
417
+ end