socket_central.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. #!/usr/bin/ruby
  2. # vim: expandtab tabstop=2 shiftwidth=2 softtabstop=2 autoindent:
  3. require 'rubygems'
  4. require 'socket'
  5. require 'digest/md5'
  6. require 'timeout'
  7. # .. allowed commands .. ROLES = CAPABILITIES
  8. # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config.
  9. $version = "0.15TCPServer"
  10. $debug = 0
  11. $role_commands = Hash[
  12. "everyone" => ["PING", "WHO", "C", "PART"],
  13. "broadcast_admin" => ["BC_ID", "BC", "BC_ENDCOUNT"],
  14. "broadcast" => ["REQ_BC", "BC_RE"],
  15. "specbot_admin" => ["REQ_ASSIGN", "REQ_UNASSIGN", "REQ_PING", "REQ_ASSIGNMENTS"],
  16. "specbot" => ["ASSIGN_RE", "UNASSIGN_RE", "PING_RE", "ASSIGNMENTS_RE"],
  17. ]
  18. $default_role = "everyone"
  19. # which role is talking to which role?
  20. # effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role.
  21. $role_dialogs = Hash[
  22. "everyone" => ["everyone"],
  23. "broadcast_admin" => ["broadcast"],
  24. "broadcast" => ["broadcast_admin"],
  25. "specbot_admin" => ["specbot"],
  26. "specbot" => ["specbot_admin"],
  27. ]
  28. $user_roles = Hash[
  29. "paul_dev_eggdrop" => ["everyone", "broadcast"],
  30. "paul_eggdrop" => ["everyone", "broadcast"],
  31. "paul_dev_specbot" => ["everyone", "broadcast", "specbot"],
  32. "paul_specbot" => ["everyone", "broadcast", "specbot"],
  33. "qw.nu" => ["everyone", "broadcast"],
  34. "qw.nu_poster" => ["everyone", "broadcast"],
  35. "mihawk_dev_specbot" => ["everyone", "broadcast", "specbot"],
  36. "mihawk_specbot" => ["everyone", "broadcast", "specbot"],
  37. "central_brain" => ["everyone", "broadcast_admin", "specbot_admin"],
  38. ]
  39. STDOUT.sync = true
  40. # display loop ... it is displaying the display_queue
  41. def displaying(c, user)
  42. begin
  43. loop do
  44. while line = $display_queue[user][0]
  45. c.puts line
  46. $display_queue[user].delete_at(0)
  47. end
  48. sleep 0.1
  49. end
  50. #rescue Errno::EPIPE
  51. rescue Exception => e
  52. puts e.message
  53. puts e.backtrace.inspect
  54. kill_conn(c, user, Thread.current)
  55. put_log "Failed while writing to #{user}. Online users now: #{online_users.join(", ")}"
  56. end
  57. end
  58. def kill_conn(c, user, t_display)
  59. sleep 0.5
  60. # delete the key out off $display_queue
  61. $display_queue.delete(user)
  62. # shut down displaying thread and connection
  63. t_display.exit
  64. # shut down connection
  65. c.close unless c.nil?
  66. end
  67. def online_users
  68. def user(user)
  69. if online_users.include? user
  70. return true
  71. else
  72. return false
  73. end
  74. end
  75. ousers = Array.new
  76. $display_queue.each_key {|k| ousers.push(k)}
  77. return ousers
  78. end
  79. def write_user(user, input)
  80. if online_users.user(user)
  81. sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}"
  82. line = "#{sometime}: #{input}"
  83. $display_queue[user].push(line)
  84. end
  85. end
  86. def write_role(role, input, *exempts)
  87. #find users with role
  88. users = Array.new
  89. $user_roles.each {|k, v|
  90. if v.include? role
  91. users.push(k)
  92. end
  93. }
  94. # find users that are online and inside Array "users"
  95. lala = Array.new
  96. users.each {|v|
  97. if $display_queue.has_key? v
  98. lala.push(v)
  99. end
  100. }
  101. # now write to them
  102. lala.each {|user|
  103. if not exempts.include? user then
  104. write_user(user, input)
  105. end
  106. }
  107. end
  108. def allowed_cmd(user, inputmessage)
  109. my_cmds(user).each{|c|
  110. #print "A #{c} B #{inputmessage}\n"
  111. if inputmessage =~ /^#{c}/
  112. return true
  113. end
  114. }
  115. return false
  116. end
  117. # what happens with what input?!
  118. def inputting(user, input)
  119. write_user(user, "SYS You typed: #{input}")
  120. if input =~ /^([A-Z_]+)/
  121. cmd = $1
  122. end
  123. # now we have the cmd .. or not ;)
  124. if input =~ /^[A-Z_]+ (.+)/
  125. payload = $1
  126. end
  127. # now we can use payload
  128. if allowed_cmd(user, cmd)
  129. ### typical system commands follow
  130. if cmd == "PING"
  131. write_user(user, "PONG")
  132. elsif cmd == "C"
  133. if payload =~ /^(.+)$/
  134. write_role($default_role, "C #{user}: #{$1}")
  135. else
  136. write_user(user, "SYS Format is C <chattext>")
  137. end
  138. elsif cmd == "WHO"
  139. online_users.each {|ouser|
  140. write_user(user, "WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")
  141. }
  142. elsif cmd == "PART"
  143. write_user(user, "SYS Goodbye '#{user}'.")
  144. write_role($default_role, "PARTED User '#{user}' just left the party.", user)
  145. return false
  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(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(user, "SYS Frequency name is unknown #{freqname}")
  165. error = 1
  166. end
  167. if not source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/
  168. write_user(user, "SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}")
  169. error = 1
  170. end
  171. if not nickname =~ /^.+/
  172. write_user(user, "SYS Nickname string is not in the form ^.+ #{nickname}")
  173. error = 1
  174. end
  175. if not text =~ /^.+/
  176. write_user(user, "SYS Message string is not in the form ^.+ #{text}")
  177. error = 1
  178. end
  179. else # of check syntax
  180. write_user(user, "SYS Command format is REQ_BC <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
  181. error = 1
  182. end
  183. if error == 0
  184. # send REQ_BC to the corresponding role.
  185. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text])
  186. # so it only reaches the issuer of REQ_BC
  187. write_user(user, "BC_ID #{bcid} for: #{network},#{freqname},#{source}")
  188. $broadcasts[bcid] = user
  189. finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text]
  190. write_role("broadcast", finalmessage, user) # write to the role with the exempt of myself.
  191. end
  192. #end of REQ_BC here..
  193. elsif cmd == "BC_RE"
  194. if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/
  195. bcid = $1
  196. user_string = $2
  197. user_count = $3
  198. item_string = $4
  199. item_count = $5
  200. payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count]
  201. # according bcid it is possible to get the originating user.. so send only to his topic!
  202. if $broadcasts[bcid]
  203. to_user = $broadcasts[bcid]
  204. write_user(user, "SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.")
  205. write_user(to_user, "#{cmd} #{payload}")
  206. else
  207. write_user(user, "SYS Broadcast reply bcid: #{bcid} underway.")
  208. write_role("broadcast", "#{cmd} #{payload}", user)
  209. end
  210. else # of bc_re format check
  211. put_log "SYS Format is BC_RE <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
  212. end
  213. end
  214. else # of if allowed command
  215. write_user(user, "SYS Command #{input} not allowed, your commands are: #{my_cmds(user).join(", ")}.")
  216. end # of if allowed command
  217. return true
  218. end
  219. def my_roles(user)
  220. return $user_roles[user]
  221. end
  222. def my_cmds(user)
  223. myroles = my_roles(user)
  224. mycmds = myroles.collect {|v| $role_commands[v]}.flatten
  225. return mycmds
  226. end
  227. def thinking(c, user, t_display)
  228. put_log "#{user} connected. Online now: #{online_users.join(", ")}"
  229. if not $user_roles.any? {|k, v| k.include? user}
  230. put_log "SYS User '#{user}' unknown to me. Saying bye."
  231. write_user(user, "SYS User '#{user}' unknown to me. Bye.")
  232. return false
  233. end
  234. write_user(user, "HELLO Hi user '#{user}'! How are you? I'm '#{$version}'")
  235. write_user(user, "ROLES #{my_roles(user).join(", ")}")
  236. write_user(user, "COMMANDS #{my_cmds(user).join(", ")}")
  237. write_role($default_role, "JOINED User '#{user}' just joined the party.", user)
  238. # reading the client input ... to write it somewhere, possibly to the client himself.
  239. input = nil
  240. while not input == "PART" do
  241. begin
  242. status = Timeout::timeout(300) do
  243. begin
  244. input = c.gets.chomp # waits for user input
  245. staying = inputting(user, input)
  246. if not staying
  247. put_log "'#{user}' parting normally.."
  248. return false
  249. end
  250. rescue Exception => e
  251. puts e.message
  252. puts e.backtrace.inspect
  253. put_log "'#{user}' lost connection."
  254. #kill_conn(c, user, t_display)
  255. return false
  256. end
  257. end
  258. rescue Timeout::Error => e
  259. write_user(user, "PING Still alive?") # if writing fails, then it may be a broken pipe .. so it will lose the connection and delete the user from the online user list.
  260. rescue Exception => e
  261. puts e.message
  262. puts e.backtrace.inspect
  263. end
  264. end
  265. return false
  266. end
  267. def pingtimer(c, user)
  268. end
  269. def spawn_server
  270. $display_queue = Hash.new
  271. $broadcasts = Hash.new
  272. server = TCPServer.new 7337
  273. Thread.abort_on_exception = true
  274. loop do
  275. Thread.start(server.accept) do |c|
  276. user = c.gets.chomp # waits for user name input
  277. # only one connection from a user allowed - we don't handle multiple display_queues for one user!!!!!11111
  278. if online_users.user(user)
  279. user = c.puts "Only one connection allowed per user."
  280. c.close unless c.nil?
  281. else
  282. $display_queue[user] = Array.new
  283. #p $display_queue # ok we can take $display_queue to check for online users.
  284. # this thread reads what others want you to read ..
  285. t_user_display = Thread.new{ displaying(c, user) }
  286. begin
  287. execution = thinking(c, user, t_user_display)
  288. # shut down the displaying thread and connection
  289. kill_conn(c, user, t_user_display)
  290. rescue Exception => e
  291. puts e.message
  292. puts e.backtrace.inspect
  293. kill_conn(c, user, t_user_display)
  294. end # of rescue
  295. put_log "Online now: #{online_users.join(", ")}"
  296. end
  297. end
  298. end
  299. end
  300. def put_debug(msg)
  301. if $debug == 1
  302. puts msg
  303. end
  304. end
  305. def put_log(msg)
  306. puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S %z")}: #{msg}"
  307. end
  308. spawn_server