Class: Wafer::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/wafer.rb,
lib/wafer/ctl_server.rb,
lib/wafer/auth_server.rb

Constant Summary collapse

NO_LOG_CMDS =
["passlogin", "passwordauth"]
NO_USER_VERIFY_CMDS =
["emailused", "emaillookup"]
KEYCODE_VERIFY_CMDS =
["checkaccess", "convertaccount", "getping", "getprop", "pinguser"]
HASH_VERIFY_CMDS =
["md5login", "md5auth"]
PASSWORD_VERIFY_CMDS =
["passwordlogin", "passwordauth"]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repo:, settings: {}) ⇒ Server

Returns a new instance of Server.



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/wafer.rb', line 26

def initialize(repo:, settings: {})
  @repo = repo

  # Make a copy of default settings to allow modification
  @settings = copy_of_default_settings
  @settings.merge! settings

  @read_sockets = []
  @err_sockets = []
  @socket_types = {}
  @seq_numbers = {}
  @socket_buffer = {}
end

Instance Attribute Details

#repoObject (readonly)

Returns the value of attribute repo.



23
24
25
# File 'lib/wafer.rb', line 23

def repo
  @repo
end

#settingsObject (readonly)

Returns the value of attribute settings.



24
25
26
# File 'lib/wafer.rb', line 24

def settings
  @settings
end

Instance Method Details

#auth_respond(conn, first_message) ⇒ Object



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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/wafer/auth_server.rb', line 8

def auth_respond(conn, first_message)
    if first_message[0][0] == ":"
        secure_auth = true
        user_name = first_message[0][1..-1]
        code = first_message[1]
        command = first_message[2]
        @seq_numbers[conn] = first_message[3]
        message = first_message[4..-1]
    else
        secure_auth = false
        command = first_message[0]
        @seq_numbers[conn] = first_message[1]
        user_name = first_message[2]
        code = false
        message = first_message[3..-1]
    end

    if command == "" || user_name == ""
        send_error(conn, "BAD INPUT")
    end

    uid = @repo.uid_by_name(user_name)

    if NO_LOG_CMDS.include?(command)
        # Don't log passwords
        log("Recorded command #{command.inspect} for user #{user_name.inspect}")
    else
        log("Auth server: #{first_message.inspect}")
    end

    # All commands but two require verifying the user isn't deleted, banned, etc.
    unless NO_USER_VERIFY_CMDS.include?(command)
        allowed, err = @repo.is_user_ok(uid)
        return send_error(conn, err) unless allowed
    end

    # Some commands need the keycode to be valid if supplied
    if KEYCODE_VERIFY_CMDS.include?(command) && code
        allowed, err = @repo.is_keycode_ok(uid, code)
        return send_error(conn, err) unless allowed
    end

    if HASH_VERIFY_CMDS.include?(command)
        allowed, err = @repo.is_hash_ok(uid, message[0])
        return send_error(conn, err) unless allowed
    end

    if PASSWORD_VERIFY_CMDS.include?(command)
        allowed, err = @repo.is_password_ok(uid, message[0])
        return send_error(conn, err) unless allowed
    end

    case command

    when "checkaccess"
        if @repo.user_has_access?(uid, message[0])
            return send_ok(conn, "ACCESS")
        else
            return send_error(conn, "NOAUTH")
        end

    when "convertaccount"
        if message[0] == "premium"
            @repo.user_set_flag(uid, "premium")
            return send_ok(conn, "premium")
        elsif message[0] == "basic"
            @repo.user_unset_flag(uid, "premium")
            return send_ok(conn, "basic")
        else
            return send_error(conn, "Unknown conversion (#{message[0]})")
        end

    when "emaillookup"
        user = @repo.user_by_field("email", user_name)
        if user
            return send_ok(conn, user["name"])
        else
            return send_error(conn, "no such email")
        end

    when "emailused"
        user = @repo.user_by_field("email", user_name)
        if user
            return send_ok(conn, "YES")
        else
            return send_error(conn, "no such email")
        end

    when "getping"
        # We don't do real email pings with this server
        user = @repo.user_by_id(uid)
        if user
            return send_ok(conn, "#{uid} #{user["email"]} 17171717171717171717")
        else
            return send_error(conn, "NO PING")
        end

    when "getprop"
        if secure_auth
            prop = message[1]
        else
            prop = message[0]
        end

        user = @repo.user_by_id(uid)
        return send_ok(conn, user[prop])

    when "keycodeauth"
        code = message[0] unless code

        allowed, err = @repo.is_keycode_ok(uid, code)
        STDERR.puts "keycodeauth(#{uid.inspect}, #{code.inspect}) = [#{allowed.inspect}, #{err.inspect}]"
        return send_error(conn, err) unless allowed

        return send_error(conn, "TOS") unless @repo.user_has_tos?(uid)
        return send_error(conn, "USER HAS NO EMAIL") unless @repo.user_has_verified_email?(uid)

        return send_auth_status(conn, uid)

    when "md5login"
        return send_ok(conn, @repo.user_keycode(uid))

    when "md5auth"
        return send_auth_status(conn, uid)

    when "passwordlogin"
        return send_ok(conn, @repo.user_keycode(uid))

    when "passwordauth"
        return send_auth_status(conn, uid)

    when "pinguser"
        return send_ok(conn, "OK")

    when "setemail"
        @repo.user_set_email(uid, message[0])
        return send_ok(conn, "YES")

    when "tempkeycode"
        return send_ok(conn, @repo.user_keycode(uid))

    when "tempguarantee"
        return send_error(conn, "DOES NOT SUPPORT")
    end

    return send_error(conn, "BAD COMMAND(#{command.inspect})")
end

#conn_connect(conn_type) ⇒ Object



49
50
51
52
53
54
55
56
57
58
# File 'lib/wafer.rb', line 49

def conn_connect(conn_type)
  port = @settings["dgd"]["portbase"] + (conn_type == :auth ? 70 : 71)
  sock = TCPSocket.open @settings["dgd"]["serverIP"], port
  @socket_types[sock] = conn_type
  @read_sockets.push sock
  @err_sockets.push sock
  @socket_buffer[sock] = ""

  return sock
end

#conn_reconnect(conn) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/wafer.rb', line 60

def conn_reconnect(conn)
  @read_sockets -= [ conn ]
  @err_sockets -= [ conn ]
  socket_type = @socket_types[conn]
  @socket_types.delete(conn)
  @seq_numbers.delete(conn)
  @socket_buffer.delete(conn)
  begin
    log("Closing connection of type #{socket_type.inspect}...")
    conn.close
  rescue
    STDERR.puts $!.inspect
    log("Closing connection of type #{socket_type.inspect}... (But got an error, failing - this is common.)")
  end

  STDERR.puts "Reconnecting outgoing connection of type #{socket_type.inspect}..."
  conn_connect(socket_type)
end

#copy_of_default_settingsObject



40
41
42
# File 'lib/wafer.rb', line 40

def copy_of_default_settings
  Hash[DEFAULT_SETTINGS.map { |key, value| [key, value.dup] }]
end

#ctl_respond(conn, parts) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
# File 'lib/wafer/ctl_server.rb', line 2

def ctl_respond(conn, parts)
    return send_error(conn, "BAD INPUT") if parts.size < 3 || parts.size > 9

    @seq_numbers[conn] = parts[1]
    command = parts[0]

    if command == "announce"
        return send_ok(conn, "OK")
    end

    return send_error(conn, "UNIMPLEMENTED")
end

#event_loopObject



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
135
136
137
138
139
140
141
142
143
# File 'lib/wafer.rb', line 91

def event_loop
  puts "Settings:"
  puts JSON.pretty_generate(@settings)

  conn_connect(:auth)
  conn_connect(:ctl)

  loop do
    sleep 0.1
    readable, _, errorable, = IO.select @read_sockets, [], @err_sockets, @settings["authServer"]["selectTimeout"]

    readable ||= []
    errorable ||= []

    puts "Selected... R: #{readable.size} / #{@read_sockets.size} E: #{errorable.size} / #{@err_sockets.size}"

    # Close connections on error
    errorable.each { |errant_conn| conn_reconnect(errant_conn) }

    (readable - errorable).each do |conn|
      STDERR.puts "Preparing for read..."
      data = conn.recv_nonblock(2048)
      if !data || data == ""
        STDERR.puts "No data - need to reconnect?"
        sleep 0.5
        #conn_reconnect(conn)
        next
      end
      STDERR.puts "Successful read: #{data.inspect}"
      @socket_buffer[conn] += data

      while @socket_buffer[conn]["\r\n"]
        line, remainder = @socket_buffer[conn].split("\r\n", 2)
        @socket_buffer[conn] = remainder

        parts = line.chomp.strip.split(" ").map { |part| CGI::unescape(part) }
        next if parts == []  # No-op

        STDERR.puts "Successful parse: #{parts.inspect}"

        case @socket_types[conn]
        when :ctl
          ctl_respond(conn, parts)
        when :auth
          auth_respond(conn, parts)
        else
          log("Wrong socket type #{@socket_types[conn].inspect} for connection!")
          conn_disconnect(conn)
        end
      end
    end
  end
end

#log(message) ⇒ Object



44
45
46
47
# File 'lib/wafer.rb', line 44

def log(message)
  pre = "[#{Time.now}] "
  puts pre + message
end

#send_auth_status(conn, uid) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/wafer/auth_server.rb', line 156

def send_auth_status(conn, uid)
    user_type = @repo.(uid)
    user_status = @repo.(uid)
    user_string = "(#{user_type};#{user_status})"

    if @repo.user_is_paid?(uid)
        if user_type == "trial"
            return send_ok(conn, "TRIAL #{@repo.user_next_stamp(uid)} #{user_string}")
        elsif ["developer", "staff", "free"].include?(user_type)
            return send_ok(conn, "PAID 0 #{user_string}")
        else
            return send_ok(conn, "PAID #{@repo.user_next_stamp(uid)} #{user_string}")
        end
    else
        return send_ok(conn, "UNPAID #{user_string}")
    end
end

#send_error(conn, message) ⇒ Object



79
80
81
82
83
# File 'lib/wafer.rb', line 79

def send_error(conn, message)
  seq = @seq_numbers[conn]
  log("Error on conn (#{seq}): #{message}")
  conn.write "#{seq} ERR #{message}\n"
end

#send_ok(conn, message) ⇒ Object



85
86
87
88
89
# File 'lib/wafer.rb', line 85

def send_ok(conn, message)
  ok_message = "#{@seq_numbers[conn]} OK #{message}\n"
  STDERR.puts "Sending OK message: #{ok_message.inspect}"
  conn.write ok_message
end