123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- #!/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 <chattext>")
- 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 <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
- 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
- # 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 <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
- 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
|