class Wafer::Server

Constants

HASH_VERIFY_CMDS
KEYCODE_VERIFY_CMDS
NO_LOG_CMDS
NO_USER_VERIFY_CMDS
PASSWORD_VERIFY_CMDS

Attributes

repo[R]
settings[R]

Public Class Methods

new(repo:, settings: {}) click to toggle source
# 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

Public Instance Methods

auth_respond(conn, first_message) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_settings() click to toggle source
# 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) click to toggle source
# 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_loop() click to toggle source
# 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) click to toggle source
# File lib/wafer.rb, line 44
def log(message)
  pre = "[#{Time.now}] "
  puts pre + message
end
send_auth_status(conn, uid) click to toggle source
# File lib/wafer/auth_server.rb, line 156
def send_auth_status(conn, uid)
    user_type = @repo.user_account_type(uid)
    user_status = @repo.user_account_status(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) click to toggle source
# 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) click to toggle source
# 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