Module: Boat::Server::BoatServer
- Includes:
- EventMachine::Protocols::LineText2
- Defined in:
- lib/boat/server.rb
Constant Summary collapse
- NextCommand =
Class.new(StandardError)
- @@last_connection_id =
0
Instance Method Summary collapse
- #check_authenticated! ⇒ Object
- #command_confirm(args) ⇒ Object
- #command_data(args) ⇒ Object
- #command_get(args) ⇒ Object
- #command_pass(args) ⇒ Object
- #command_put(args) ⇒ Object
- #command_quit(args) ⇒ Object
- #command_user(args) ⇒ Object
- #complete_put ⇒ Object
- #initialize(configuration) ⇒ Object
- #post_init ⇒ Object
- #random_salt ⇒ Object
- #receive_binary_data(data) ⇒ Object
- #receive_end_of_binary_data ⇒ Object
- #receive_line(line) ⇒ Object
- #repository_path ⇒ Object
- #unbind ⇒ Object
Instance Method Details
#check_authenticated! ⇒ Object
213 214 215 216 217 218 |
# File 'lib/boat/server.rb', line 213 def check_authenticated! unless @authenticated send_data "500 not authenticated\n" raise NextCommand end end |
#command_confirm(args) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/boat/server.rb', line 149 def command_confirm(args) if @put.nil? || @put[:state] != "awaiting CONFIRM" send_data "500 no need to send CONFIRM\n" elsif (matches = args.match(/\A([0-9a-f]{64}) ([0-9a-f]{64})\z/i)).nil? send_data "500 invalid CONFIRM command line; requires hash and signature\n" else file_hash = matches[1].downcase signature = matches[2].downcase if signature != OpenSSL::HMAC.hexdigest(@digest, @user["key"], "#{@put.fetch(:server_salt)}#{@put.fetch(:filename)}#{@put.fetch(:size)}#{file_hash}#{@put.fetch(:client_salt)}") send_data "500 signature is invalid\n" @put = nil else @put[:hash] = file_hash complete_put end end end |
#command_data(args) ⇒ Object
92 93 94 95 96 97 98 99 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 125 126 127 128 129 130 131 |
# File 'lib/boat/server.rb', line 92 def command_data(args) check_authenticated! if @put.nil? send_data "500 PUT first\n" elsif @put[:state] != "PUT" send_data "500 DATA already sent\n" elsif (matches = args.match(/\A([0-9]+) ([0-9a-f]{64}|-) (\S+) ([0-9a-f]{64})\z/i)).nil? send_data "500 invalid DATA command line; requires size, hash, new salt and signature\n" else size = matches[1].to_i file_hash = matches[2].downcase client_salt = matches[3] signature = matches[4].downcase if size >= 1<<31 send_data "500 size too large\n" elsif signature != OpenSSL::HMAC.hexdigest(@digest, @user["key"], "#{@put.fetch(:server_salt)}#{@put.fetch(:filename)}#{size}#{file_hash}#{client_salt}") send_data "500 signature is invalid\n" elsif File.exists?(current_filename = "#{repository_path}/current.#{@put.fetch(:filename)}") && OpenSSL::Digest.new('sha256').file(current_filename).to_s == file_hash signature = OpenSSL::HMAC.hexdigest(@digest, @user["key"], "#{client_salt}#{file_hash}") send_data "255 accepted #{signature}\n" else @put[:temporary_id] = "#{Time.now.to_i}.#{Process.pid}.#{@connection_id}" @put[:temporary_filename] = "#{@configuration["storage_path"]}/tmp/#{@put.fetch(:temporary_id)}" @put.merge!( :state => "DATA", :size => size, :hash => (file_hash unless file_hash == '-'), :client_salt => client_salt, :file_handle => File.open(@put[:temporary_filename], "w"), :digest => OpenSSL::Digest.new('sha256')) @temporary_files << @put[:temporary_filename] send_data "253 send #{size} bytes now\n" set_binary_mode size end end end |
#command_get(args) ⇒ Object
203 204 205 206 |
# File 'lib/boat/server.rb', line 203 def command_get(args) check_authenticated! send_data "500 not implemented\n" end |
#command_pass(args) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/boat/server.rb', line 54 def command_pass(args) if @authenticated send_data "500 already authenticated\n" elsif @username.nil? || @login_salt.nil? send_data "500 USER first\n" else user = @configuration.fetch("users", {}).fetch(@username, nil) expected = OpenSSL::HMAC.hexdigest(@digest, user["key"], @login_salt) if user if user && expected && args == expected send_data "250 OK\n" @user = user @authenticated = true else @username = @login_salt = nil send_data "401 invalid username or password\n" end end end |
#command_put(args) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/boat/server.rb', line 73 def command_put(args) check_authenticated! if @user["access"] == "r" send_data "400 no write access\n" elsif @put send_data "500 PUT already sent\n" elsif !args.match(/\A[a-z0-9_.%+-]+\z/i) # filenames should be urlencoded send_data "500 invalid filename\n" else if @user.fetch("versioning", true) == false && File.exists?("#{repository_path}/current.#{args}") send_data "500 file already exists\n" else @put = {:state => "PUT", :filename => args, :server_salt => random_salt} send_data "250 #{@put[:server_salt]}\n" end end end |
#command_quit(args) ⇒ Object
208 209 210 211 |
# File 'lib/boat/server.rb', line 208 def command_quit(args) send_data "221 bye\n" close_connection_after_writing end |
#command_user(args) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/boat/server.rb', line 42 def command_user(args) if @authenticated send_data "500 already authenticated\n" elsif args.empty? || args.match(/[^a-z0-9_.]/i) send_data "500 invalid username\n" else @username = args @login_salt = random_salt send_data "251 HMAC-SHA256 #{@login_salt}\n" end end |
#complete_put ⇒ Object
168 169 170 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 |
# File 'lib/boat/server.rb', line 168 def complete_put calculated_hash = @put.fetch(:digest).hexdigest if @put.fetch(:hash) != calculated_hash send_data "500 file hash does not match hash supplied by client\n" File.unlink(@put.fetch(:temporary_filename)) @temporary_files.delete(@put.fetch(:temporary_filename)) return end FileUtils.mkdir_p(repository_path) version_filename = "#{repository_path}/#{@put.fetch(:temporary_id)}.#{@put.fetch(:filename)}" symlink_name = "#{repository_path}/current.#{@put.fetch(:filename)}" if @user.fetch("versioning", true) == false && File.exists?(symlink_name) send_data "500 file with same filename was uploaded before this upload completed\n" File.unlink(@put.fetch(:temporary_filename)) @temporary_files.delete(@put.fetch(:temporary_filename)) return end File.rename(@put.fetch(:temporary_filename), version_filename) @temporary_files.delete(@put.fetch(:temporary_filename)) begin File.unlink(symlink_name) if File.symlink?(symlink_name) rescue Errno::ENOENT end File.symlink(version_filename, symlink_name) signature = OpenSSL::HMAC.hexdigest(@digest, @user["key"], "#{@put.fetch(:client_salt)}#{@put.fetch(:hash)}") send_data "255 accepted #{signature}\n" ensure @put = nil end |
#initialize(configuration) ⇒ Object
15 16 17 |
# File 'lib/boat/server.rb', line 15 def initialize(configuration) @configuration = configuration end |
#post_init ⇒ Object
19 20 21 22 23 24 25 |
# File 'lib/boat/server.rb', line 19 def post_init @@last_connection_id += 1 @connection_id = @@last_connection_id @temporary_files = [] @digest = OpenSSL::Digest::Digest.new('sha256') send_data "220 Boat Server #{Boat::VERSION}\n" end |
#random_salt ⇒ Object
229 230 231 |
# File 'lib/boat/server.rb', line 229 def random_salt [OpenSSL::Digest.new('sha256').digest((0..64).inject("") {|r, i| r << rand(256).chr})].pack("m").strip end |
#receive_binary_data(data) ⇒ Object
133 134 135 136 |
# File 'lib/boat/server.rb', line 133 def receive_binary_data(data) @put[:file_handle].write data @put[:digest] << data end |
#receive_end_of_binary_data ⇒ Object
138 139 140 141 142 143 144 145 146 147 |
# File 'lib/boat/server.rb', line 138 def receive_end_of_binary_data @put[:file_handle].close if @put.fetch(:hash).nil? @put[:state] = "awaiting CONFIRM" send_data "254 send hash confirmation\n" else complete_put end end |
#receive_line(line) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/boat/server.rb', line 27 def receive_line(line) match = line.match(/\A(\S*)(.*)?/) command = match[1].downcase args = match[2].strip if match[2] && !match[2].strip.empty? begin if %w(user pass put get data confirm quit).include?(command) send("command_#{command}", args) else send_data "500 unknown command\n" end rescue NextCommand end end |
#repository_path ⇒ Object
233 234 235 |
# File 'lib/boat/server.rb', line 233 def repository_path @user && "#{@configuration.fetch("storage_path")}/repositories/#{@user.fetch("repository")}" end |
#unbind ⇒ Object
220 221 222 223 224 225 226 227 |
# File 'lib/boat/server.rb', line 220 def unbind @temporary_files.each do |filename| begin File.unlink(filename) rescue Errno::ENOENT end end end |