em-ftpd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ module EM::FTPD
2
+ module Authentication
3
+
4
+ def initialize
5
+ @user = nil
6
+ @requested_user = nil
7
+ super
8
+ end
9
+
10
+ def logged_in?
11
+ @user.nil? ? false : true
12
+ end
13
+
14
+ # handle the USER FTP command. This is a user attempting to login.
15
+ # we simply store the requested user name as an instance variable
16
+ # and wait for the password to be submitted before doing anything
17
+ def cmd_user(param)
18
+ send_param_required and return if param.nil?
19
+ send_response("500 Already logged in") and return unless @user.nil?
20
+ @requested_user = param
21
+ send_response "331 OK, password required"
22
+ end
23
+
24
+ # handle the PASS FTP command. This is the second stage of a user logging in
25
+ def cmd_pass(param)
26
+ send_response "202 User already logged in" and return unless @user.nil?
27
+ send_param_required and return if param.nil?
28
+ send_response "530 password with no username" and return if @requested_user.nil?
29
+
30
+ # return an error message if:
31
+ # - the specified username isn't in our system
32
+ # - the password is wrong
33
+
34
+ @driver.authenticate(@requested_user, param) do |result|
35
+ if result
36
+ @name_prefix = "/"
37
+ @user = @requested_user
38
+ @requested_user = nil
39
+ send_response "230 OK, password correct"
40
+ else
41
+ @user = nil
42
+ send_response "530 incorrect login. not logged in."
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ module BaseSocket
2
+
3
+ attr_reader :aborted
4
+
5
+ def initialize
6
+ @on_stream = nil
7
+ @aborted = false
8
+ end
9
+
10
+ def on_stream &blk
11
+ @on_stream = blk if block_given?
12
+ unless data.empty?
13
+ @on_stream.call(data) # send all data that was collected before the stream hanlder was set
14
+ @data = ""
15
+ end
16
+ @on_stream
17
+ end
18
+
19
+ def data
20
+ @data ||= ""
21
+ end
22
+
23
+ def receive_data(chunk)
24
+ if @on_stream
25
+ @on_stream.call(chunk)
26
+ else
27
+ data << chunk
28
+ end
29
+ end
30
+
31
+ def unbind
32
+ if @aborted
33
+ fail
34
+ else
35
+ if @on_stream
36
+ succeed
37
+ else
38
+ succeed data
39
+ end
40
+ end
41
+ end
42
+
43
+ def abort
44
+ @aborted = true
45
+ close_connection_after_writing
46
+ end
47
+ end
@@ -0,0 +1,120 @@
1
+ # coding: utf-8
2
+
3
+ module EM::FTPD
4
+
5
+ class Configurator
6
+
7
+ def initialize
8
+ @user = nil
9
+ @group = nil
10
+ @daemonise = false
11
+ @name = nil
12
+ @pid_file = nil
13
+ @port = 21
14
+
15
+ @driver = nil
16
+ @driver_args = []
17
+ end
18
+
19
+ def user(val = nil)
20
+ if val
21
+ @user = val.to_s
22
+ else
23
+ @user
24
+ end
25
+ end
26
+
27
+ def uid
28
+ return nil if @user.nil?
29
+
30
+ begin
31
+ detail = Etc.getpwnam(@user)
32
+ return detail.uid
33
+ rescue
34
+ $stderr.puts "user must be nil or a real account" if detail.nil?
35
+ end
36
+ end
37
+
38
+ def group(val = nil)
39
+ if val
40
+ @group = val.to_s
41
+ else
42
+ @group
43
+ end
44
+ end
45
+
46
+ def gid
47
+ return nil if @group.nil?
48
+
49
+ begin
50
+ detail = Etc.getpwnam(@group)
51
+ return detail.gid
52
+ rescue
53
+ $stderr.puts "group must be nil or a real group" if detail.nil?
54
+ end
55
+ end
56
+
57
+
58
+ def daemonise(val = nil)
59
+ if val
60
+ @daemonise = val
61
+ else
62
+ @daemonise
63
+ end
64
+ end
65
+
66
+ def driver(klass = nil)
67
+ if klass
68
+ @driver = klass
69
+ else
70
+ @driver
71
+ end
72
+ end
73
+
74
+ def driver_args(*args)
75
+ if args.empty?
76
+ @driver_args
77
+ else
78
+ @driver_args = args
79
+ end
80
+ end
81
+
82
+ def name(val = nil)
83
+ if val
84
+ @name = val.to_s
85
+ else
86
+ @name
87
+ end
88
+ end
89
+
90
+ def pid_file(val = nil)
91
+ if val
92
+ @pid_file = val.to_s
93
+ else
94
+ @pid_file
95
+ end
96
+ end
97
+
98
+ def port(val = nil)
99
+ if val
100
+ @port = val.to_i
101
+ else
102
+ @port
103
+ end
104
+ end
105
+
106
+ def check!
107
+ if @driver.nil?
108
+ die("driver MUST be specified in the config file")
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def die(msg)
115
+ $stderr.puts msg
116
+ exit 1
117
+ end
118
+ end
119
+
120
+ end
@@ -0,0 +1,110 @@
1
+ module EM::FTPD
2
+ module Directories
3
+ # go up a directory, really just an alias
4
+ def cmd_cdup(param)
5
+ send_unauthorised and return unless logged_in?
6
+ cmd_cwd("..")
7
+ end
8
+
9
+ # As per RFC1123, XCUP is a synonym for CDUP
10
+ alias cmd_xcup cmd_cdup
11
+
12
+
13
+ # change directory
14
+ def cmd_cwd(param)
15
+ send_unauthorised and return unless logged_in?
16
+ path = build_path(param)
17
+
18
+ @driver.change_dir(path) do |result|
19
+ if result
20
+ @name_prefix = path
21
+ send_response "250 Directory changed to #{path}"
22
+ else
23
+ send_permission_denied
24
+ end
25
+ end
26
+ end
27
+
28
+ # As per RFC1123, XCWD is a synonym for CWD
29
+ alias cmd_xcwd cmd_cwd
30
+
31
+ # make directory
32
+ def cmd_mkd(param)
33
+ send_unauthorised and return unless logged_in?
34
+ send_param_required and return if param.nil?
35
+
36
+ @driver.make_dir(build_path(param)) do |result|
37
+ if result
38
+ send_response "257 Directory created"
39
+ else
40
+ send_action_not_taken
41
+ end
42
+ end
43
+ end
44
+
45
+ # return a listing of the current directory, one per line, each line
46
+ # separated by the standard FTP EOL sequence. The listing is returned
47
+ # to the client over a data socket.
48
+ #
49
+ def cmd_nlst(param)
50
+ send_unauthorised and return unless logged_in?
51
+ send_response "150 Opening ASCII mode data connection for file list"
52
+
53
+ @driver.dir_contents(build_path(param)) do |files|
54
+ send_outofband_data(files.map(&:name))
55
+ end
56
+ end
57
+
58
+
59
+ def default_files(dir)
60
+ [
61
+ DirectoryItem.new(:name => '.', :permissions => 'rwxrwxrwx', :directory => true),
62
+ DirectoryItem.new(:name => '..', :permissions => 'rwxrwxrwx', :directory => true),
63
+ ]
64
+ end
65
+
66
+ # return a detailed list of files and directories
67
+ def cmd_list(param)
68
+ send_unauthorised and return unless logged_in?
69
+ send_response "150 Opening ASCII mode data connection for file list"
70
+
71
+ param = '' if param.to_s == '-a'
72
+
73
+ @driver.dir_contents(build_path(param)) do |files|
74
+ now = Time.now
75
+ lines = files.map { |item|
76
+ sizestr = (item.size || 0).to_s.rjust(12)
77
+ "#{item.directory ? 'd' : '-'}#{item.permissions || 'rwxrwxrwx'} 1 #{item.owner || 'owner'} #{item.group || 'group'} #{sizestr} #{(item.time || now).strftime("%b %d %H:%M")} #{item.name}"
78
+ }
79
+ send_outofband_data(lines)
80
+ end
81
+ end
82
+
83
+ # return the current directory
84
+ def cmd_pwd(param)
85
+ send_unauthorised and return unless logged_in?
86
+ send_response "257 \"#{@name_prefix}\" is the current directory"
87
+ end
88
+
89
+ # As per RFC1123, XPWD is a synonym for PWD
90
+ alias cmd_xpwd cmd_pwd
91
+
92
+ # delete a directory
93
+ def cmd_rmd(param)
94
+ send_unauthorised and return unless logged_in?
95
+ send_param_required and return if param.nil?
96
+
97
+ @driver.delete_dir(build_path(param)) do |result|
98
+ if result
99
+ send_response "250 Directory deleted."
100
+ else
101
+ send_action_not_taken
102
+ end
103
+ end
104
+ end
105
+
106
+ # As per RFC1123, XRMD is a synonym for RMD
107
+ alias cmd_xrmd cmd_rmd
108
+
109
+ end
110
+ end
@@ -0,0 +1,12 @@
1
+ module EM::FTPD
2
+ class DirectoryItem
3
+ ATTRS = [:name, :owner, :group, :size, :time, :permissions, :directory]
4
+ attr_accessor(*ATTRS)
5
+
6
+ def initialize(options)
7
+ options.each do |attr, value|
8
+ self.send("#{attr}=", value)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,141 @@
1
+ require 'tempfile'
2
+
3
+ module EM::FTPD
4
+ module Files
5
+
6
+ # delete a file
7
+ def cmd_dele(param)
8
+ send_unauthorised and return unless logged_in?
9
+ send_param_required and return if param.nil?
10
+
11
+ path = build_path(param)
12
+
13
+ @driver.delete_file(path) do |result|
14
+ if result
15
+ send_response "250 File deleted"
16
+ else
17
+ send_action_not_taken
18
+ end
19
+ end
20
+ end
21
+
22
+ # resume downloads
23
+ def cmd_rest(param)
24
+ send_response "500 Feature not implemented"
25
+ end
26
+
27
+ # send a file to the client
28
+ def cmd_retr(param)
29
+ send_unauthorised and return unless logged_in?
30
+ send_param_required and return if param.nil?
31
+
32
+ path = build_path(param)
33
+
34
+ @driver.get_file(path) do |data|
35
+ if data
36
+ send_response "150 Data transfer starting #{data.size} bytes"
37
+ send_outofband_data(data)
38
+ else
39
+ send_response "551 file not available"
40
+ end
41
+ end
42
+ end
43
+
44
+ # rename a file
45
+ def cmd_rnfr(param)
46
+ send_unauthorised and return unless logged_in?
47
+ send_param_required and return if param.nil?
48
+
49
+ @from_filename = build_path(param)
50
+ send_response "350 Requested file action pending further information."
51
+ end
52
+
53
+ # rename a file
54
+ def cmd_rnto(param)
55
+ send_unauthorised and return unless logged_in?
56
+ send_param_required and return if param.nil?
57
+
58
+ @driver.rename(@from_filename, build_path(param)) do |result|
59
+ if result
60
+ send_response "250 File renamed."
61
+ else
62
+ send_action_not_taken
63
+ end
64
+ end
65
+ end
66
+
67
+ # return the size of a file in bytes
68
+ def cmd_size(param)
69
+ send_unauthorised and return unless logged_in?
70
+ send_param_required and return if param.nil?
71
+
72
+ @driver.bytes(build_path(param)) do |bytes|
73
+ if bytes
74
+ send_response "213 #{bytes}"
75
+ else
76
+ send_response "450 file not available"
77
+ end
78
+ end
79
+ end
80
+
81
+ # save a file from a client
82
+ def cmd_stor(param)
83
+ send_unauthorised and return unless logged_in?
84
+ send_param_required and return if param.nil?
85
+
86
+ path = build_path(param)
87
+
88
+ if @driver.respond_to?(:put_file_streamed)
89
+ cmd_stor_streamed(path)
90
+ elsif @driver.respond_to?(:put_file)
91
+ cmd_stor_tempfile(path)
92
+ else
93
+ raise "driver MUST respond to put_file OR put_file_streamed"
94
+ end
95
+ end
96
+
97
+ def cmd_stor_streamed(target_path)
98
+ wait_for_datasocket do |datasocket|
99
+ if datasocket
100
+ send_response "150 Data transfer starting"
101
+ @driver.put_file_streamed(target_path, datasocket) do |bytes|
102
+ if bytes
103
+ send_response "200 OK, received #{bytes} bytes"
104
+ else
105
+ send_action_not_taken
106
+ end
107
+ end
108
+ else
109
+ send_response "425 Error establishing connection"
110
+ end
111
+ end
112
+ end
113
+
114
+ def cmd_stor_tempfile(target_path)
115
+ tmpfile = Tempfile.new("em-ftp")
116
+
117
+ wait_for_datasocket do |datasocket|
118
+ datasocket.on_stream { |chunk|
119
+ tmpfile.write chunk
120
+ }
121
+ send_response "150 Data transfer starting"
122
+ datasocket.callback {
123
+ puts "data transfer finished"
124
+ tmpfile.flush
125
+ @driver.put_file(target_path, tmpfile.path) do |bytes|
126
+ if bytes
127
+ send_response "200 OK, received #{bytes} bytes"
128
+ else
129
+ send_action_not_taken
130
+ end
131
+ end
132
+ tmpfile.unlink
133
+ }
134
+ datasocket.errback {
135
+ tmpfile.unlink
136
+ }
137
+ end
138
+ end
139
+
140
+ end
141
+ end