summaryrefslogtreecommitdiff
path: root/lib/nickserver/server.rb
blob: 32afdaee9bdaba0713d9e98ac5c3e6dc26700de3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
132
133
134
require 'eventmachine'
require 'evma_httpserver'
require 'json'

#
# This is the main HTTP server that clients connect to in order to fetch keys
#
# For info on EM::HttpServer, see https://github.com/eventmachine/evma_httpserver
#
module Nickserver
  class Server < EM::Connection
    include EM::HttpServer

    #
    # Starts the Nickserver. Must be run inside an EM.run block.
    #
    # Available options:
    #
    #   * :port (default Nickserver::Config.port)
    #   * :host (default 127.0.0.1)
    #
    def self.start(opts={})
      Nickserver::Config.load
      options = {host: '127.0.0.1', port: Nickserver::Config.port.to_i}.merge(opts)
      unless defined?(TESTING)
        puts "Starting nickserver #{options[:host]}:#{options[:port]}"
      end
      EM.start_server options[:host], options[:port], Nickserver::Server
    end

    def post_init
      super
      no_environment_strings
    end

    def process_http_request
      uid = get_uid_from_request
      if uid.nil?
        send_not_found
      elsif uid !~ EmailAddress
        send_error("Not a valid address")
      else
        send_key(uid)
      end
    rescue RuntimeError => exc
      puts "Error: #{exc}"
      puts exc.backtrace
      send_error(exc.to_s)
    end

    private

    def send_error(msg = "not supported")
      send_response(status: 500, content: "500 #{msg}\n")
    end

    def send_not_found(msg = "Not Found")
      send_response(status: 404, content: "404 #{msg}\n")
    end

    def send_response(opts = {})
      options = {status: 200, content_type: 'text/plain', content: ''}.merge(opts)
      response = EM::DelegatedHttpResponse.new(self)
      response.status = options[:status]
      response.content_type options[:content_type]
      response.content = options[:content]
      response.send_response
    end

    def send_key(uid)
      get_key_from_uid(uid) do |key|
        send_response content: format_response(address: uid, openpgp: key)
      end
    end

    def get_uid_from_request
      if @http_query_string
        params = CGI.parse(@http_query_string)
      elsif @http_post_content
        params = CGI.parse(@http_post_content)
      end
      if params && params["address"] && params["address"].any?
        return params["address"].first
      else
        return nil
      end
    end

    def get_key_from_uid(uid)
      fetcher = if local_address?(uid)
        Nickserver::Couch::FetchKey.new
      else
        Nickserver::HKP::FetchKey.new
      end
      fetcher.get(uid).callback {|key|
        yield key
      }.errback {|status, msg|
        if status == 404
          send_not_found
        else
          send_response(status: status, content: msg)
        end
      }
    end

    def format_response(map)
      map.to_json
    end

    #
    # Return true if the user address is for a user of this service provider.
    # e.g. if the provider is example.org, then alice@example.org returns true.
    #
    # If 'domain' is not configured, we rely on the Host header of the HTTP request.
    #
    def local_address?(uid)
      uid_domain = uid.sub(/^.*@(.*)$/, "\\1")
      if Config.domain
        return uid_domain == Config.domain
      else
        # no domain configured, use Host header
        host_header = @http_headers.split(/\0/).grep(/^Host: /).first
        if host_header.nil?
          send_error("HTTP request must include a Host header.")
        else
          host = host_header.split(':')[1].strip.sub(/^nicknym\./, '')
          return uid_domain == host
        end
      end
    rescue
      return false
    end
  end
end