Class: RCS::Updater::Server

Inherits:
EM::HttpServer::Server
  • Object
show all
Extended by:
Tracer
Includes:
MonitorMixin, Tracer
Defined in:
lib/rcs-common/updater/server.rb

Constant Summary

Constants included from Tracer

Tracer::TRACE_YAML_NAME

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Tracer

thread_name, trace, trace_ensure_log_folders, trace_init, trace_named_put, trace_named_remove, trace_nested_pop, trace_nested_push, trace_setup

Constructor Details

#initialize(*args) ⇒ Server

Returns a new instance of Server.



19
20
21
22
# File 'lib/rcs-common/updater/server.rb', line 19

def initialize(*args)
  @shared_key = SharedKey.new
  super
end

Class Method Details

.add_firewall_rule(port) ⇒ Object



101
102
103
104
105
106
107
# File 'lib/rcs-common/updater/server.rb', line 101

def self.add_firewall_rule(port)
  if WinFirewall.exists?
    rule_name = "RCS_FWD Updater"
    WinFirewall.del_rule(rule_name)
    WinFirewall.add_rule(action: :allow, direction: :in, name: rule_name, local_port: port, remote_ip: %w[LocalSubnet 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16], protocol: :tcp)
  end
end

.start(port: 6677, address: "0.0.0.0") ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rcs-common/updater/server.rb', line 109

def self.start(port: 6677, address: "0.0.0.0")
  EM::run do
    trace_setup rescue $stderr.puts("trace_setup failed - logging only to stdout")
    add_firewall_rule(port)

    trace(:info, "Starting RCS Updater server on #{address}:#{port}")
    EM::start_server(address, port, self)
  end
rescue Interrupt
  trace(:fatal, "Interrupted by the user")
end

Instance Method Details

#http_request_errback(ex) ⇒ Object



82
83
84
# File 'lib/rcs-common/updater/server.rb', line 82

def http_request_errback(ex)
  print_exception(ex)
end

#payload_to_hash(payload) ⇒ Object



78
79
80
# File 'lib/rcs-common/updater/server.rb', line 78

def payload_to_hash(payload)
  {path: payload.filepath, output: payload.output, return_code: payload.return_code, stored: payload.stored} if payload
end


86
87
88
89
90
# File 'lib/rcs-common/updater/server.rb', line 86

def print_exception(ex, backtrace: true)
  text = "[#{ex.class}] #{ex.message}"
  text << "\n\t#{ex.backtrace.join("\n\t")}" if ex.backtrace and backtrace
  trace(:error, text)
end

#private_ipv4?Boolean

Returns:

  • (Boolean)


33
34
35
36
37
38
39
40
# File 'lib/rcs-common/updater/server.rb', line 33

def private_ipv4?
  a,b,c,d = remote_addr.split(".").map(&:to_i)
  return true if a==127 && b==0 && c==0 && d==1 # localhost
  return true if a==192 && b==168 && c.between?(0,255) && d.between?(0,255) # 192.168.0.0/16
  return true if a==172 && b.between?(16,31) && c.between?(0,255) && d.between?(0,255) # 172.16.0.0/12
  return true if a==10 && b.between?(0,255) && c.between?(0,255) && d.between?(0,255)  # 10.0.0.0/8
  return false
end

#process_http_requestObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rcs-common/updater/server.rb', line 42

def process_http_request
  EM.defer do
    begin
      trace(:info, "[#{@http[:host]}] REQ #{@http_protocol} #{@http_request_method} #{@http_content.size} bytes from #{remote_addr}")

      raise AuthError.new("Invalid http method") if @http_request_method != "POST"
      raise AuthError.new("No content") unless @http_content
      raise AuthError.new("Missing server signature") unless @shared_key.read_key_from_file
      raise AuthError.new("remote_addr is not private") unless private_ipv4?
      raise AuthError.new("Invalid signature") unless x_options
      raise AuthError.new("Payload checksum failed") if x_options['md5'] != Digest::MD5.hexdigest(@http_content)

      synchronize do
        @@x_options_last_tm ||= nil
        raise AuthError.new("Reply attack") if @@x_options_last_tm and x_options['tm'] <= @@x_options_last_tm
        @@x_options_last_tm = x_options['tm']
      end

      payload = Payload.new(@http_content, x_options)

      set_comm_inactivity_timeout(payload.timeout + 30)

      payload.store if payload.storable?
      payload.run if payload.runnable?

      send_response(200, payload_to_hash(payload))
    rescue AuthError => ex
      print_exception(ex, backtrace: false)
      close_connection
    rescue Exception => ex
      print_exception(ex)
      send_response(500, payload_to_hash(payload))
    end
  end
end

#remote_addrObject



28
29
30
31
# File 'lib/rcs-common/updater/server.rb', line 28

def remote_addr
  ary = get_peername[2,6].unpack("nC4")
  ary[1..-1].join(".")
end

#send_response(status_code, content = nil) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/rcs-common/updater/server.rb', line 92

def send_response(status_code, content = nil)
  response = EM::DelegatedHttpResponse.new(self)
  response.status = status_code
  response.content_type('application/json')
  response.content = content.to_json if content
  response.send_response
  trace(:info, "[#{@http[:host]}] REP #{status_code} #{response.content.size} bytes")
end

#x_optionsObject



24
25
26
# File 'lib/rcs-common/updater/server.rb', line 24

def x_options
  @x_options ||= @shared_key.decrypt_hash(@http[:x_options]) rescue nil
end