#!/usr/bin/ruby # vim: expandtab tabstop=2 shiftwidth=2 softtabstop=2 autoindent: require 'rubygems' require 'socket' require 'digest/md5' require 'timeout' # .. allowed commands .. ROLES = CAPABILITIES # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config. $version = "0.15TCPServer" $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"], ] STDOUT.sync = true # display loop ... it is displaying the display_queue def displaying(c, user) begin loop do while line = $display_queue[user][0] c.puts line $display_queue[user].delete_at(0) end sleep 0.1 end #rescue Errno::EPIPE rescue Exception => e puts e.message puts e.backtrace.inspect kill_conn(c, user, Thread.current) put_log "Failed while writing to #{user}. Online users now: #{online_users.join(", ")}" end end def kill_conn(c, user, t_display) sleep 0.5 # delete the key out off $display_queue $display_queue.delete(user) # shut down displaying thread and connection t_display.exit # shut down connection c.close unless c.nil? end def online_users def user(user) if online_users.include? user return true else return false end end ousers = Array.new $display_queue.each_key {|k| ousers.push(k)} return ousers end def write_user(user, input) if online_users.user(user) sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}" line = "#{sometime}: #{input}" $display_queue[user].push(line) end end 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 $display_queue.has_key? v lala.push(v) end } # now write to them lala.each {|user| if not exempts.include? user then write_user(user, input) end } end def allowed_cmd(user, inputmessage) my_cmds(user).each{|c| #print "A #{c} B #{inputmessage}\n" if inputmessage =~ /^#{c}/ return true end } return false end # what happens with what input?! def inputting(user, input) write_user(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(user, cmd) ### typical system commands follow if cmd == "PING" write_user(user, "PONG") elsif cmd == "C" if payload =~ /^(.+)$/ write_role($default_role, "C #{user}: #{$1}") else write_user(user, "SYS Format is C ") end elsif cmd == "WHO" online_users.each {|ouser| write_user(user, "WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}") } elsif cmd == "PART" write_user(user, "SYS Goodbye '#{user}'.") write_role($default_role, "PARTED User '#{user}' just left the party.", user) return false ###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(user, "SYS Network name #{network} is unknown: QWalt or QDEV allowed.") error = 1 end if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/ write_user(user, "SYS Frequency name is unknown #{freqname}") error = 1 end if not source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/ write_user(user, "SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}") error = 1 end if not nickname =~ /^.+/ write_user(user, "SYS Nickname string is not in the form ^.+ #{nickname}") error = 1 end if not text =~ /^.+/ write_user(user, "SYS Message string is not in the form ^.+ #{text}") error = 1 end else # of check syntax write_user(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(user, "BC_ID #{bcid} for: #{network},#{freqname},#{source}") $broadcasts[bcid] = user finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text] write_role("broadcast", finalmessage, user) # 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(user, "SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.") write_user(to_user, "#{cmd} #{payload}") else write_user(user, "SYS Broadcast reply bcid: #{bcid} underway.") write_role("broadcast", "#{cmd} #{payload}", user) end else # of bc_re format check put_log "SYS Format is BC_RE =,=" end end else # of if allowed command write_user(user, "SYS Command #{input} not allowed, your commands are: #{my_cmds(user).join(", ")}.") end # of if allowed command return true end def my_roles(user) return $user_roles[user] end def my_cmds(user) myroles = my_roles(user) mycmds = myroles.collect {|v| $role_commands[v]}.flatten return mycmds end def thinking(c, user, t_display) put_log "#{user} connected. Online now: #{online_users.join(", ")}" if not $user_roles.any? {|k, v| k.include? user} put_log "SYS User '#{user}' unknown to me. Saying bye." write_user(user, "SYS User '#{user}' unknown to me. Bye.") return false end write_user(user, "HELLO Hi user '#{user}'! How are you? I'm '#{$version}'") write_user(user, "ROLES #{my_roles(user).join(", ")}") write_user(user, "COMMANDS #{my_cmds(user).join(", ")}") write_role($default_role, "JOINED User '#{user}' just joined the party.", user) # reading the client input ... to write it somewhere, possibly to the client himself. input = nil while not input == "PART" do begin status = Timeout::timeout(300) do begin input = c.gets.chomp # waits for user input staying = inputting(user, input) if not staying put_log "'#{user}' parting normally.." return false end rescue Exception => e puts e.message puts e.backtrace.inspect put_log "'#{user}' lost connection." #kill_conn(c, user, t_display) return false end end rescue Timeout::Error => e 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. rescue Exception => e puts e.message puts e.backtrace.inspect end end return false end def pingtimer(c, user) end def spawn_server $display_queue = Hash.new $broadcasts = Hash.new server = TCPServer.new 7337 Thread.abort_on_exception = true loop do Thread.start(server.accept) do |c| user = c.gets.chomp # waits for user name input # only one connection from a user allowed - we don't handle multiple display_queues for one user!!!!!11111 if online_users.user(user) user = c.puts "Only one connection allowed per user." c.close unless c.nil? else $display_queue[user] = Array.new #p $display_queue # ok we can take $display_queue to check for online users. # this thread reads what others want you to read .. t_user_display = Thread.new{ displaying(c, user) } begin execution = thinking(c, user, t_user_display) # shut down the displaying thread and connection kill_conn(c, user, t_user_display) rescue Exception => e puts e.message puts e.backtrace.inspect kill_conn(c, user, t_user_display) end # of rescue put_log "Online now: #{online_users.join(", ")}" end end end end def put_debug(msg) if $debug == 1 puts msg end end def put_log(msg) puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S %z")}: #{msg}" end spawn_server