em_server.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #!/usr/bin/env ruby
  2. require 'eventmachine'
  3. require 'digest/md5'
  4. require 'fiber'
  5. require 'geoip'
  6. # .. allowed commands .. ROLES = CAPABILITIES
  7. # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config.
  8. $version = "0.2EventMachineSocketServer"
  9. $debug = 0
  10. $role_commands = Hash[
  11. "everyone" => ["PING", "WHO", "C", "PART"],
  12. "broadcast_admin" => ["BC_ID", "BC", "BC_ENDCOUNT"],
  13. "broadcast" => ["REQ_BC", "BC_RE"],
  14. "specbot_admin" => ["REQ_ASSIGN", "REQ_UNASSIGN", "REQ_PING", "REQ_ASSIGNMENTS"],
  15. "specbot" => ["ASSIGN_RE", "UNASSIGN_RE", "PING_RE", "ASSIGNMENTS_RE"],
  16. ]
  17. $default_role = "everyone"
  18. # which role is talking to which role?
  19. # effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role.
  20. $role_dialogs = Hash[
  21. "everyone" => ["everyone"],
  22. "broadcast_admin" => ["broadcast"],
  23. "broadcast" => ["broadcast_admin"],
  24. "specbot_admin" => ["specbot"],
  25. "specbot" => ["specbot_admin"],
  26. ]
  27. $user_roles = Hash[
  28. "paul_dev_eggdrop" => ["everyone", "broadcast"],
  29. "paul_eggdrop" => ["everyone", "broadcast"],
  30. "paul_dev_specbot" => ["everyone", "broadcast", "specbot"],
  31. "paul_specbot" => ["everyone", "broadcast", "specbot"],
  32. "qw.nu" => ["everyone", "broadcast"],
  33. "qw.nu_poster" => ["everyone", "broadcast"],
  34. "mihawk_dev_specbot" => ["everyone", "broadcast", "specbot"],
  35. "mihawk_specbot" => ["everyone", "broadcast", "specbot"],
  36. "central_brain" => ["everyone", "broadcast_admin", "specbot_admin"],
  37. ]
  38. module InterconnectionPointProtocolHandler
  39. @@connected_clients = Array.new
  40. @@broadcasts = Hash.new
  41. # default method that is being run on connection!
  42. def post_init # connection of someone starts here...
  43. @username = nil
  44. end
  45. ### getters
  46. def entered_username?
  47. !@username.nil? && !@username.empty? # then it's true
  48. end
  49. def username
  50. @username
  51. end
  52. def my_roles
  53. return $user_roles[@username]
  54. end
  55. def my_cmds
  56. return my_roles.collect {|v| $role_commands[v]}.flatten
  57. end
  58. # returns online users by default by searching through saved connections
  59. def ousers
  60. users = Array.new
  61. @@connected_clients.each {|c| users.push(c.username)}
  62. return users
  63. end # of ousers
  64. ### checkers
  65. def allowed_cmd(inputmessage)
  66. my_cmds.each{|c|
  67. #print "A #{c} B #{inputmessage}\n"
  68. if inputmessage =~ /^#{c}/
  69. return true
  70. end
  71. }
  72. return false
  73. end
  74. def online(user)
  75. if ousers.include? user
  76. return true
  77. else
  78. return false
  79. end
  80. end
  81. ### actions
  82. def put_log(msg)
  83. puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
  84. end
  85. # searches through our hash of saved connections and writes them a messages.
  86. def write_user(input, username = nil)
  87. if not username
  88. username = @username
  89. end
  90. if connection = @@connected_clients.find { |c| c.username == username }
  91. sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}"
  92. line = "#{sometime}: #{input}\n"
  93. connection.send_data line
  94. end
  95. end # of write_user
  96. # searches through roles and writes those users on their connections
  97. def write_role(role, input, *exempts)
  98. #find users with role
  99. users = Array.new
  100. $user_roles.each {|k, v|
  101. if v.include? role
  102. users.push(k)
  103. end
  104. }
  105. # find users that are online and inside Array "users"
  106. lala = Array.new
  107. users.each {|v|
  108. if ousers.include? v
  109. lala.push(v)
  110. end
  111. }
  112. # now write to them
  113. lala.each {|user|
  114. if not exempts.include? user
  115. write_user(input, user)
  116. end
  117. }
  118. end # of write_role()
  119. # what happens with what input?!
  120. def inputting(input)
  121. write_user("SYS You typed: #{input}")
  122. if input =~ /^([A-Z_]+)/
  123. cmd = $1
  124. end
  125. # now we have the cmd .. or not ;)
  126. if input =~ /^[A-Z_]+ (.+)/
  127. payload = $1
  128. end
  129. # now we can use payload
  130. if allowed_cmd(cmd)
  131. ### typical system commands follow
  132. if cmd == "PING"
  133. write_user("PONG")
  134. elsif cmd == "C"
  135. if payload =~ /^(.+)$/
  136. write_role($default_role, "C #{@username}: #{$1}")
  137. else
  138. write_user("SYS Format is C <chattext>")
  139. end
  140. elsif cmd == "WHO"
  141. ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")}
  142. elsif cmd == "PART"
  143. write_user("SYS Goodbye '#{@username}'.")
  144. write_role($default_role, "PARTED User '#{@username}' just left the party.", @username)
  145. return "bye"
  146. ###now for the good stuff, broadcasting
  147. elsif cmd == "REQ_BC"
  148. # help with format .. but send the raw payload
  149. if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/
  150. # n f s ni txt
  151. error = 0
  152. # $&
  153. # 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
  154. network = $1
  155. freqname = $2
  156. source = $3
  157. nickname = $4
  158. text = $5
  159. if not network =~ /^(QWalt)|(QDEV)/
  160. write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.")
  161. error = 1
  162. end
  163. if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/
  164. write_user("SYS Frequency name is unknown #{freqname}")
  165. error = 1
  166. end
  167. if source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/
  168. else
  169. write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}")
  170. error = 1
  171. end
  172. if not nickname =~ /^.+/
  173. write_user("SYS Nickname string is not in the form ^.+ #{nickname}")
  174. error = 1
  175. end
  176. if not text =~ /^.+/
  177. write_user("SYS Message string is not in the form ^.+ #{text}")
  178. error = 1
  179. end
  180. else # of check syntax
  181. write_user("SYS Command format is REQ_BC <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
  182. error = 1
  183. end
  184. if error == 0
  185. # send REQ_BC to the corresponding role.
  186. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text])
  187. # so it only reaches the issuer of REQ_BC
  188. write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}")
  189. @@broadcasts[bcid] = @username
  190. # qw:// ip resolving
  191. if source =~ /^qw:\/\/(.+):(\d+)(.*)$/
  192. # ip address is 15 in length
  193. iphost = $1
  194. ipport = $2
  195. rest = $3
  196. if iphost =~ /^\d+\.\d+\.\d+\.\d+/
  197. # find country code
  198. c = GeoIP.new('GeoIP.dat').country(iphost)[3].to_s.downcase
  199. rest.prepend " (#{c}) " unless c.nil?
  200. # resolve it to a dns name
  201. f = Fiber.new do
  202. Fiber.yield Resolv.getname(iphost)
  203. end
  204. domainname = f.resume
  205. #domainname = ""
  206. if domainname.nil? || domainname.empty?
  207. domainname = iphost
  208. else
  209. # if the resulting dns is too long, use ip-address instead.
  210. # if the resulting dns has too many dots, use ip-address instead.
  211. if domainname.length > 23 || domainname.split(".").size > 3 || domainname.scan(/\d/).size > 3
  212. put_log "cutting down host_name: #{domainname}, because:"
  213. put_log "#{domainname.length}"
  214. put_log "#{domainname.split(".").size}"
  215. put_log "#{domainname.scan(/\d/).size}"
  216. domainname = iphost
  217. end
  218. end
  219. source = "qw://#{domainname}:#{ipport}#{rest}"
  220. end
  221. end # of if source qw://x.x.x.x:portzzz
  222. # resolve
  223. finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text]
  224. write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself.
  225. end
  226. #end of REQ_BC here..
  227. elsif cmd == "BC_RE"
  228. if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/
  229. bcid = $1
  230. user_string = $2
  231. user_count = $3
  232. item_string = $4
  233. item_count = $5
  234. payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count]
  235. # according bcid it is possible to get the originating user.. so send only to his topic!
  236. if @@broadcasts[bcid]
  237. to_user = @@broadcasts[bcid]
  238. write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.")
  239. write_user("#{cmd} #{payload}", to_user)
  240. else
  241. write_user("SYS Broadcast reply bcid: #{bcid} underway.")
  242. write_role("broadcast", "#{cmd} #{payload}", @username)
  243. end
  244. else # of bc_re format check
  245. put_log "SYS Format is BC_RE <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
  246. end
  247. end
  248. else # of if allowed command
  249. write_user("SYS Command #{input} not allowed, your commands are: #{my_cmds.join(", ")}.")
  250. end # of if allowed command
  251. end # of inputting!
  252. # default method that is being run on receiving data!
  253. def receive_data(data) # connection receives a line on the server end
  254. line = data.chomp unless data.chomp.nil?
  255. if line
  256. if entered_username? # oh, it's a known user
  257. # it's alive!
  258. inputter = inputting(line)
  259. if inputter == "bye"
  260. close_connection
  261. end
  262. else # of username known, then it seems to be the first connect
  263. # and this line is the user name coming in!
  264. username = line
  265. if online(username)
  266. put_log "SYS User '#{username}' tried to double connect. Say bye bye."
  267. send_data "SYS Hey '#{username}', only one connection allowed. Bye.\n"
  268. EventMachine.add_timer 1, proc { close_connection }
  269. return false
  270. end
  271. if $user_roles.any? {|k| k.include? username}
  272. @username = username
  273. @@connected_clients.push(self)
  274. put_log("SYS '#{@username}' connected. Online now: #{ousers.join(", ")}")
  275. write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'")
  276. write_user("ROLES #{my_roles.join(", ")}")
  277. write_user("COMMANDS #{my_cmds.join(", ")}")
  278. write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username)
  279. else
  280. put_log "SYS User '#{username}' unknown to me. Saying bye."
  281. send_data "SYS User '#{username}' unknown to me. Bye.\n"
  282. # disconnect him
  283. close_connection
  284. end
  285. end # of username known
  286. end # of line
  287. end
  288. # default method that is being run on disconnection!
  289. def unbind # a connection goes bye bye
  290. if @username
  291. @@connected_clients.delete(self)
  292. put_log "SYS '#{@username}' disconnected. Online now: #{ousers.join(", ")}"
  293. end
  294. end
  295. end
  296. def main
  297. # #run: Note that this will block current thread.
  298. EventMachine.run do
  299. EventMachine.start_server("0.0.0.0", 7337, InterconnectionPointProtocolHandler)
  300. end
  301. end
  302. main