Class: SFTPServer::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/sftp_server/server.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Server

Returns a new instance of Server.



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/sftp_server/server.rb', line 16

def initialize(options = {})
  @user_name = options[:user_name]
  @password = options[:password]
  @rsa_key = options[:rsa_key]
  @dsa_key = options[:dsa_key]
  @port = options[:port]
  @listen_address = options[:listen_address]
  @verbose = options[:verbose]

  @authenticated = false
  @handles = {}
end

Instance Attribute Details

#dsa_keyObject

Returns the value of attribute dsa_key.



11
12
13
# File 'lib/sftp_server/server.rb', line 11

def dsa_key
  @dsa_key
end

#listen_addressObject

Returns the value of attribute listen_address.



13
14
15
# File 'lib/sftp_server/server.rb', line 13

def listen_address
  @listen_address
end

#passwordObject

Returns the value of attribute password.



9
10
11
# File 'lib/sftp_server/server.rb', line 9

def password
  @password
end

#portObject

Returns the value of attribute port.



12
13
14
# File 'lib/sftp_server/server.rb', line 12

def port
  @port
end

#rsa_keyObject

Returns the value of attribute rsa_key.



10
11
12
# File 'lib/sftp_server/server.rb', line 10

def rsa_key
  @rsa_key
end

#user_nameObject

Returns the value of attribute user_name.



8
9
10
# File 'lib/sftp_server/server.rb', line 8

def user_name
  @user_name
end

#verboseObject

Returns the value of attribute verbose.



14
15
16
# File 'lib/sftp_server/server.rb', line 14

def verbose
  @verbose
end

Instance Method Details

#authenticate(session) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/sftp_server/server.rb', line 126

def authenticate(session)
  authenticated = false
  while !authenticated
    message = SSH::API.ssh_message_get(session)
    next unless message

    message_type = SSH::API.ssh_message_type(message)
    log "message_type: #{message_type}"
    next unless message_type > -1

    case message_type
    when SSH::API::MessageTypes::SSH_REQUEST_AUTH
      log "auth"
      message_subtype = SSH::API.ssh_message_subtype(message)
      log "auth message_subtype: #{message_subtype}"
      case message_subtype
      when SSH::API::MessageAuthTypes::SSH_AUTH_METHOD_PASSWORD
        request_user_name = SSH::API.ssh_message_auth_user(message)
        request_password = SSH::API.ssh_message_auth_password(message)
        log user_name
        log password
        if user_name == request_user_name && password == request_password
          SSH::API.ssh_message_auth_reply_success(message, 0)
          SSH::API.ssh_message_free(message)
          authenticated = true
          break
        else
          SSH::API.ssh_message_reply_default(message)
          next
        end
      else
        respond_auth_required(message) unless @authenticated
      end
    else
      SSH::API.ssh_message_reply_default(message)
    end
  end
  authenticated
end

#bind_accept(bind, session) ⇒ Object



41
42
43
44
# File 'lib/sftp_server/server.rb', line 41

def bind_accept(bind, session)
  result = SSH::API.ssh_bind_accept(bind, session)
  fail SSH::API.ssh_get_error(bind) if result < 0
end

#bind_listen(bind) ⇒ Object



36
37
38
39
# File 'lib/sftp_server/server.rb', line 36

def bind_listen(bind)
  result = SSH::API.ssh_bind_listen(bind)
  fail SSH::API.ssh_get_error(bind) if result < 0
end

#close_channel(channel) ⇒ Object



401
402
403
404
# File 'lib/sftp_server/server.rb', line 401

def close_channel(channel)
  result = SSH::API.ssh_channel_close(channel)
  fail SSH::API.ssh_get_error(channel) if result < 0
end

#disconnect_session(session) ⇒ Object



411
412
413
414
# File 'lib/sftp_server/server.rb', line 411

def disconnect_session(session)
  result = SSH::API.ssh_disconnect(session)
  fail SSH::API.ssh_get_error(session) if result < 0
end

#free_bind(bind) ⇒ Object



416
417
418
419
# File 'lib/sftp_server/server.rb', line 416

def free_bind(bind)
  result = SSH::API.ssh_bind_free(bind)
  fail SSH::API.ssh_get_error(bind) if result < 0
end

#free_channel(channel) ⇒ Object



406
407
408
409
# File 'lib/sftp_server/server.rb', line 406

def free_channel(channel)
  result = SSH::API.ssh_channel_free(channel)
  fail SSH::API.ssh_get_error(channel) if result < 0
end

#handle_auth(message) ⇒ Object



51
52
# File 'lib/sftp_server/server.rb', line 51

def handle_auth(message)
end

#handle_key_exchange(session) ⇒ Object



46
47
48
49
# File 'lib/sftp_server/server.rb', line 46

def handle_key_exchange(session)
  result = SSH::API.ssh_handle_key_exchange(session)
  fail SSH::API.ssh_get_error(session) if result < 0
end

#init_sftp_session(sftp_session) ⇒ Object



166
167
168
169
# File 'lib/sftp_server/server.rb', line 166

def init_sftp_session(sftp_session)
  result = SSH::API.sftp_server_init(sftp_session)
  fail SSH::API.ssh_get_error(sftp_session) unless result == 0
end

#log(message) ⇒ Object



96
97
98
# File 'lib/sftp_server/server.rb', line 96

def log(message)
  puts(message) if verbose
end

#openObject



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/sftp_server/server.rb', line 421

def open
  ssh_bind = SSH::API.ssh_bind_new

  set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_BINDADDR, :string, listen_address) if listen_address
  set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_BINDPORT_STR, :string, port) if port
  set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_RSAKEY, :string, rsa_key) if rsa_key
  set_bind_option(ssh_bind, :int, SSH::API::BindOptions::SSH_BIND_OPTIONS_DSAKEY, :string, dsa_key) if dsa_key

  bind_listen(ssh_bind)
  loop do
    session = SSH::API.ssh_new
    bind_accept(ssh_bind, session)
    handle_key_exchange(session)

    if authenticate(session)
      channel = open_channel(session)
      if channel
        if sftp_channel_request(session)
          sftp_session = SSH::API.sftp_server_new(session, channel)
          init_sftp_session(sftp_session)
          sftp_message_loop(sftp_session)
        end
      end
      close_channel(channel)
      free_channel(channel)
    end
  end
end

#open_channel(session) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/sftp_server/server.rb', line 100

def open_channel(session)
  channel = nil
  while channel.nil?
    message = SSH::API.ssh_message_get(session)
    next unless message

    message_type = SSH::API.ssh_message_type(message)
    log "channel message_type: #{message_type}"
    next unless message_type > -1

    case message_type
    when SSH::API::MessageTypes::SSH_REQUEST_CHANNEL_OPEN
      message_subtype = SSH::API.ssh_message_subtype(message)
      log "channel message_subtype: #{message_subtype}"
      if message_subtype == SSH::API::ChannelTypes::SSH_CHANNEL_SESSION
        channel = SSH::API.ssh_message_channel_request_open_reply_accept(message)
        break
      end
    else
      SSH::API.ssh_message_reply_default(message)
    end
    SSH::API.ssh_message_free(message)
  end
  channel
end

#respond_auth_required(message) ⇒ Object



54
55
56
57
# File 'lib/sftp_server/server.rb', line 54

def respond_auth_required(message)
  SSH::API.ssh_message_auth_set_methods(message, SSH::API::MessageAuthTypes::SSH_AUTH_METHOD_PASSWORD)
  SSH::API.ssh_message_reply_default(message)
end

#set_bind_option(sshbind, key_type, key_value, value_type, value_value) ⇒ Object



29
30
31
32
33
34
# File 'lib/sftp_server/server.rb', line 29

def set_bind_option(sshbind, key_type, key_value, value_type, value_value)
  result = SSH::API.ssh_bind_options_set(
    sshbind, key_type, key_value, value_type, value_value
  )
  fail SSH::API.ssh_get_error(sshbind) if result < 0
end

#sftp_channel_request(session) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sftp_server/server.rb', line 59

def sftp_channel_request(session)
  sftp_channel_requested = false
  while !sftp_channel_requested
    message = SSH::API.ssh_message_get(session)
    if message
      message_type = SSH::API.ssh_message_type(message)
      log "open sftp session message_type: #{message_type}"
      if message_type == SSH::API::MessageTypes::SSH_REQUEST_CHANNEL
        message_subtype = SSH::API.ssh_message_subtype(message)
        log "open sftp session message_subtype: #{message_subtype}"

        case message_subtype
        when SSH::API::ChannelRequestTypes::SSH_CHANNEL_REQUEST_ENV
          env_name = SSH::API.ssh_message_channel_request_env_name(message)
          log "request env name: #{env_name}"

          env_value = SSH::API.ssh_message_channel_request_env_value(message)
          log "request env value: #{env_value}"
        when SSH::API::ChannelRequestTypes::SSH_CHANNEL_REQUEST_SUBSYSTEM
          subsystem_name = SSH::API.ssh_message_channel_request_subsystem(message)
          log "request subsystem: #{subsystem_name}"
          if subsystem_name == 'sftp'
            SSH::API.ssh_message_channel_request_reply_success(message)
            sftp_channel_requested = true
          end
        end
      end
    end

    unless sftp_channel_requested
      SSH::API.ssh_message_reply_default(message)
    end
    SSH::API.ssh_message_free(message)
  end
  sftp_channel_requested
end

#sftp_message_loop(sftp_session) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/sftp_server/server.rb', line 171

def sftp_message_loop(sftp_session)
  while true
    client_message = SSH::API.sftp_get_client_message(sftp_session)
    log "client_message: #{client_message}"
    return if client_message.null?

    client_message_type = SSH::API.sftp_client_message_get_type(client_message)
    next unless client_message_type
    log "client_message_type: #{client_message_type}"

    case client_message_type
    when SSH::API::SFTPCommands::SSH_FXP_REALPATH
      log "realpath"

      file_name = SSH::API.sftp_client_message_get_filename(client_message)
      log "file_name: #{file_name}"

      long_file_name = File.expand_path(file_name)

      SSH::API.sftp_reply_names_add(client_message, long_file_name, long_file_name, SSH::API::SFTPAttributes.new.to_ptr)
      SSH::API.sftp_reply_names(client_message)
    when SSH::API::SFTPCommands::SSH_FXP_OPENDIR
      log "opendir"

      dir_name = SSH::API.sftp_client_message_get_filename(client_message)
      long_dir_name = File.expand_path(dir_name)
      log "long_dir_name: #{long_dir_name}"

      @handles[long_dir_name] = :open

      long_dir_name_pointer = FFI::MemoryPointer.from_string(long_dir_name)
      handle = SSH::API.sftp_handle_alloc(sftp_session, long_dir_name_pointer)

      SSH::API.sftp_reply_handle(client_message, handle)
    when SSH::API::SFTPCommands::SSH_FXP_READDIR
      log "readdir"

      client_message_data = SSH::API::SFTPClientMessage.new(client_message)
      handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
      long_dir_name = handle.read_string
      log "long_dir_name: #{long_dir_name}"

      if @handles[long_dir_name] == :open
        Dir.entries(long_dir_name).each do |entry|
          file_stat = File.lstat(File.join(long_dir_name, entry))

          attributes = SSH::API::SFTPAttributes.new

          attributes[:flags] = 0
          attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_SIZE
          attributes[:size] = file_stat.size
          attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_UIDGID
          attributes[:uid] = file_stat.uid
          attributes[:gid] = file_stat.gid
          attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_PERMISSIONS
          attributes[:permissions] = file_stat.mode
          attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_ACMODTIME
          attributes[:atime] = file_stat.atime.to_i
          attributes[:mtime] = file_stat.mtime.to_i

          SSH::API.sftp_reply_names_add(
            client_message,
            entry,
            entry,
            attributes.to_ptr
          )
        end
        @handles[long_dir_name] = :read
        SSH::API.sftp_reply_names(client_message)
      else
        SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_EOF, 'End-of-file encountered')
      end
    when SSH::API::SFTPCommands::SSH_FXP_CLOSE
      log 'close'

      client_message_data = SSH::API::SFTPClientMessage.new(client_message)
      handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
      long_dir_name = handle.read_string
      log "long_dir_name: #{long_dir_name}"

      entry = @handles[long_dir_name]
      if entry.respond_to?(:close)
        entry.close
      end

      @handles.delete(long_dir_name)

      SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_OK, 'Success')
    when SSH::API::SFTPCommands::SSH_FXP_LSTAT
      log 'lstat'

      file_name = SSH::API.sftp_client_message_get_filename(client_message)
      log "file_name: #{file_name}"

      long_file_name = File.expand_path(file_name)

      file_stat = File.lstat(long_file_name)

      attributes = SSH::API::SFTPAttributes.new

      attributes[:flags] = 0
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_SIZE
      attributes[:size] = file_stat.size
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_UIDGID
      attributes[:uid] = file_stat.uid
      attributes[:gid] = file_stat.gid
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_PERMISSIONS
      attributes[:permissions] = file_stat.mode
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_ACMODTIME
      attributes[:atime] = file_stat.atime.to_i
      attributes[:mtime] = file_stat.mtime.to_i

      SSH::API.sftp_reply_attr(client_message, attributes.to_ptr)
    when SSH::API::SFTPCommands::SSH_FXP_STAT
      log 'stat'

      file_name = SSH::API.sftp_client_message_get_filename(client_message)
      log "file_name: #{file_name}"

      long_file_name = File.expand_path(file_name)

      file_stat = File.stat(long_file_name)

      attributes = SSH::API::SFTPAttributes.new

      attributes[:flags] = 0
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_SIZE
      attributes[:size] = file_stat.size
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_UIDGID
      attributes[:uid] = file_stat.uid
      attributes[:gid] = file_stat.gid
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_PERMISSIONS
      attributes[:permissions] = file_stat.mode
      attributes[:flags] |= SSH::API::Attributes::SSH_FILEXFER_ATTR_ACMODTIME
      attributes[:atime] = file_stat.atime.to_i
      attributes[:mtime] = file_stat.mtime.to_i

      SSH::API.sftp_reply_attr(client_message, attributes.to_ptr)
    when SSH::API::SFTPCommands::SSH_FXP_OPEN
      log 'open'

      file_name = SSH::API.sftp_client_message_get_filename(client_message)
      long_file_name = File.expand_path(file_name)
      log "long_file_name: #{long_file_name}"

      client_message_data = SSH::API::SFTPClientMessage.new(client_message)
      message_flags = client_message_data[:flags]
      flags = 0
      if (message_flags & SSH::API::Flags::SSH_FXF_READ == SSH::API::Flags::SSH_FXF_READ) &&
        (message_flags & SSH::API::Flags::SSH_FXF_WRITE == SSH::API::Flags::SSH_FXF_WRITE)
        flags = File::Constants::RDWR
      elsif (message_flags & SSH::API::Flags::SSH_FXF_READ == SSH::API::Flags::SSH_FXF_READ)
        flags = File::Constants::RDONLY
      elsif (message_flags & SSH::API::Flags::SSH_FXF_WRITE == SSH::API::Flags::SSH_FXF_WRITE)
        flags = File::Constants::WRONLY
      end

      if (message_flags & SSH::API::Flags::SSH_FXF_APPEND == SSH::API::Flags::SSH_FXF_APPEND)
        flags |= File::Constants::APPEND
      end

      if (message_flags & SSH::API::Flags::SSH_FXF_CREAT == SSH::API::Flags::SSH_FXF_CREAT)
        flags |= File::Constants::CREAT
      end

      if (message_flags & SSH::API::Flags::SSH_FXF_TRUNC == SSH::API::Flags::SSH_FXF_TRUNC)
        flags |= File::Constants::TRUNC
      end

      if (message_flags & SSH::API::Flags::SSH_FXF_EXCL == SSH::API::Flags::SSH_FXF_EXCL)
        flags |= File::Constants::EXCL
      end

      @handles[long_file_name] = File.open(long_file_name, flags)

      long_file_name_pointer = FFI::MemoryPointer.from_string(long_file_name)
      handle = SSH::API.sftp_handle_alloc(sftp_session, long_file_name_pointer)

      SSH::API.sftp_reply_handle(client_message, handle)
    when SSH::API::SFTPCommands::SSH_FXP_READ
      log 'read'

      client_message_data = SSH::API::SFTPClientMessage.new(client_message)
      handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
      long_file_name = handle.read_string
      log "long_file_name: #{long_file_name}"

      file = @handles[long_file_name]
      if file
        file.seek(client_message_data[:offset])
        data = file.read(client_message_data[:len])
        if data
          buffer = FFI::MemoryPointer.new(:char, data.size)
          buffer.put_bytes(0, data)
          SSH::API.sftp_reply_data(client_message, buffer, data.size)
        else
          SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_EOF, 'End-of-file encountered')
        end
      end
    when SSH::API::SFTPCommands::SSH_FXP_WRITE
      log 'write'

      client_message_data = SSH::API::SFTPClientMessage.new(client_message)
      handle = SSH::API.sftp_handle(sftp_session, client_message_data[:handle])
      long_file_name = handle.read_string
      log "long_file_name: #{long_file_name}"

      file = @handles[long_file_name]
      if file
        file.seek(client_message_data[:offset])
        buffer = SSH::API.sftp_client_message_get_data(client_message)
        file.write(buffer.read_string)
        SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_OK, 'Success')
      end
    when SSH::API::SFTPCommands::SSH_FXP_REMOVE
      log 'remove'

      file_name = SSH::API.sftp_client_message_get_filename(client_message)
      long_file_name = File.expand_path(file_name)
      log "long_file_name: #{long_file_name}"

      File.unlink(long_file_name)

      SSH::API.sftp_reply_status(client_message, SSH::API::SFTPStatus::SSH_FX_OK, 'Success')
    end

    SSH::API.sftp_client_message_free(client_message)
  end
end