#!/usr/bin/env ruby require 'eventmachine' require 'digest/md5' require 'fiber' require 'geoip' # .. allowed commands .. ROLES = CAPABILITIES # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config. $version = "0.2EventMachineSocketServer" $debug = 0 $role_commands = Hash[ "everyone" => ["PING", "WHO", "C", "PART"], "broadcast_admin" => ["BC_ID", "BC", "BC_ENDCOUNT"], "broadcast" => ["REQ_BC", "BC_RE"], "specbot_admin" => ["REQ_ASSIGN", "REQ_UNASSIGN", "REQ_PING", "REQ_ASSIGNMENTS"], "specbot" => ["ASSIGN_RE", "UNASSIGN_RE", "PING_RE", "ASSIGNMENTS_RE"], ] $default_role = "everyone" # which role is talking to which role? # effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role. $role_dialogs = Hash[ "everyone" => ["everyone"], "broadcast_admin" => ["broadcast"], "broadcast" => ["broadcast_admin"], "specbot_admin" => ["specbot"], "specbot" => ["specbot_admin"], ] $user_roles = Hash[ "paul_dev_eggdrop" => ["everyone", "broadcast"], "paul_eggdrop" => ["everyone", "broadcast"], "paul_dev_specbot" => ["everyone", "broadcast", "specbot"], "paul_specbot" => ["everyone", "broadcast", "specbot"], "qw.nu" => ["everyone", "broadcast"], "qw.nu_poster" => ["everyone", "broadcast"], "mihawk_dev_specbot" => ["everyone", "broadcast", "specbot"], "mihawk_specbot" => ["everyone", "broadcast", "specbot"], "central_brain" => ["everyone", "broadcast_admin", "specbot_admin"], ] module InterconnectionPointProtocolHandler @@connected_clients = Array.new @@broadcasts = Hash.new # default method that is being run on connection! def post_init # connection of someone starts here... @username = nil end ### getters def entered_username? !@username.nil? && !@username.empty? # then it's true end def username @username end def my_roles return $user_roles[@username] end def my_cmds return my_roles.collect {|v| $role_commands[v]}.flatten end # returns online users by default by searching through saved connections def ousers users = Array.new @@connected_clients.each {|c| users.push(c.username)} return users end # of ousers ### checkers def allowed_cmd(inputmessage) my_cmds.each{|c| #print "A #{c} B #{inputmessage}\n" if inputmessage =~ /^#{c}/ return true end } return false end def online(user) if ousers.include? user return true else return false end end ### actions def put_log(msg) puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}" end # searches through our hash of saved connections and writes them a messages. def write_user(input, username = nil) if not username username = @username end if connection = @@connected_clients.find { |c| c.username == username } sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}" line = "#{sometime}: #{input}\n" connection.send_data line end end # of write_user # searches through roles and writes those users on their connections def write_role(role, input, *exempts) #find users with role users = Array.new $user_roles.each {|k, v| if v.include? role users.push(k) end } # find users that are online and inside Array "users" lala = Array.new users.each {|v| if ousers.include? v lala.push(v) end } # now write to them lala.each {|user| if not exempts.include? user write_user(input, user) end } end # of write_role() # what happens with what input?! def inputting(input) write_user("SYS You typed: #{input}") if input =~ /^([A-Z_]+)/ cmd = $1 end # now we have the cmd .. or not ;) if input =~ /^[A-Z_]+ (.+)/ payload = $1 end # now we can use payload if allowed_cmd(cmd) ### typical system commands follow if cmd == "PING" write_user("PONG") elsif cmd == "C" if payload =~ /^(.+)$/ write_role($default_role, "C #{@username}: #{$1}") else write_user("SYS Format is C ") end elsif cmd == "WHO" ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")} elsif cmd == "PART" write_user("SYS Goodbye '#{@username}'.") write_role($default_role, "PARTED User '#{@username}' just left the party.", @username) return "bye" ###now for the good stuff, broadcasting elsif cmd == "REQ_BC" # help with format .. but send the raw payload if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/ # n f s ni txt error = 0 # $& # The string matched by the last successful pattern match in this scope, or nil if the last pattern match failed. (Mnemonic: like & in some editors.) This variable is r network = $1 freqname = $2 source = $3 nickname = $4 text = $5 if not network =~ /^(QWalt)|(QDEV)/ write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.") error = 1 end if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/ write_user("SYS Frequency name is unknown #{freqname}") error = 1 end if source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/ else write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}") error = 1 end if not nickname =~ /^.+/ write_user("SYS Nickname string is not in the form ^.+ #{nickname}") error = 1 end if not text =~ /^.+/ write_user("SYS Message string is not in the form ^.+ #{text}") error = 1 end else # of check syntax write_user("SYS Command format is REQ_BC ,,,'',''") error = 1 end if error == 0 # send REQ_BC to the corresponding role. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text]) # so it only reaches the issuer of REQ_BC write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}") @@broadcasts[bcid] = @username # qw:// ip resolving if source =~ /^qw:\/\/(.+):(\d+)(.*)$/ # ip address is 15 in length iphost = $1 ipport = $2 rest = $3 if iphost =~ /^\d+\.\d+\.\d+\.\d+/ # find country code c = GeoIP.new('GeoIP.dat').country(iphost)[3].to_s.downcase rest.prepend " (#{c}) " unless c.nil? # resolve it to a dns name f = Fiber.new do Fiber.yield Resolv.getname(iphost) end domainname = f.resume #domainname = "" if domainname.nil? || domainname.empty? domainname = iphost else put_log "domainname: #{domainname}" # if the resulting dns is too long, use ip-address instead. # if the resulting dns has too many dots, use ip-address instead. if domainname.length > 23 || domainname.split(".").size > 3 || domainname.scan(/\d/).size > 3 put_log "cutting down host_name: #{domainname}, because:" put_log "#{domainname.length}" put_log "#{domainname.split(".").size}" put_log "#{domainname.scan(/\d/).size}" domainname = iphost end end source = "qw://#{domainname}:#{ipport}#{rest}" end end # of if source qw://x.x.x.x:portzzz # resolve finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text] write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself. end #end of REQ_BC here.. elsif cmd == "BC_RE" if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/ bcid = $1 user_string = $2 user_count = $3 item_string = $4 item_count = $5 payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count] # according bcid it is possible to get the originating user.. so send only to his topic! if @@broadcasts[bcid] to_user = @@broadcasts[bcid] write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.") write_user("#{cmd} #{payload}", to_user) else write_user("SYS Broadcast reply bcid: #{bcid} underway.") write_role("broadcast", "#{cmd} #{payload}", @username) end else # of bc_re format check put_log "SYS Format is BC_RE =,=" end end else # of if allowed command write_user("SYS Command #{input} not allowed, your commands are: #{my_cmds.join(", ")}.") end # of if allowed command end # of inputting! # default method that is being run on receiving data! def receive_data(data) # connection receives a line on the server end line = data.chomp unless data.chomp.nil? if line if entered_username? # oh, it's a known user # it's alive! inputter = inputting(line) if inputter == "bye" close_connection end else # of username known, then it seems to be the first connect # and this line is the user name coming in! username = line if online(username) put_log "SYS User '#{username}' tried to double connect. Say bye bye." send_data "SYS Hey '#{username}', only one connection allowed. Bye.\n" EventMachine.add_timer 1, proc { close_connection } return false end if $user_roles.any? {|k| k.include? username} @username = username @@connected_clients.push(self) put_log("SYS '#{@username}' connected. Online now: #{ousers.join(", ")}") write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'") write_user("ROLES #{my_roles.join(", ")}") write_user("COMMANDS #{my_cmds.join(", ")}") write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username) else put_log "SYS User '#{username}' unknown to me. Saying bye." send_data "SYS User '#{username}' unknown to me. Bye.\n" # disconnect him close_connection end end # of username known end # of line end # default method that is being run on disconnection! def unbind # a connection goes bye bye if @username @@connected_clients.delete(self) put_log "SYS '#{@username}' disconnected. Online now: #{ousers.join(", ")}" end end end def main # #run: Note that this will block current thread. EventMachine.run do EventMachine.start_server("0.0.0.0", 7337, InterconnectionPointProtocolHandler) end end main