From 987af39e27f5bb3ec56ad516527f6a0dfea2dbe3 Mon Sep 17 00:00:00 2001 From: Dmitry Kim Date: Fri, 21 Sep 2007 18:07:09 +0000 Subject: + (botuser + maskdb) fast netmask lookup + supplemental fixes for transient users --- lib/rbot/botuser.rb | 83 +++++++++++++++------------ lib/rbot/maskdb.rb | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 35 deletions(-) create mode 100644 lib/rbot/maskdb.rb (limited to 'lib/rbot') diff --git a/lib/rbot/botuser.rb b/lib/rbot/botuser.rb index 83fb2624..8f5af858 100644 --- a/lib/rbot/botuser.rb +++ b/lib/rbot/botuser.rb @@ -9,6 +9,7 @@ require 'singleton' require 'set' +require 'rbot/maskdb' # This would be a good idea if it was failproof, but the truth # is that other methods can indirectly modify the hash. *sigh* @@ -240,9 +241,18 @@ class Bot # when modifying data attr_reader :data attr_writer :login_by_mask - attr_writer :autologin attr_writer :transient + def autologin=(vnew) + vold = @autologin + @autologin = vnew + if vold && !vnew + @netmasks.each { |n| Auth.manager.maskdb.remove(self, n) } + elsif vnew && !vold + @netmasks.each { |n| Auth.manager.maskdb.add(self, n) } + end + end + # Checks if the BotUser is transient def transient? @transient @@ -250,7 +260,7 @@ class Bot # Checks if the BotUser is permanent (not transient) def permanent? - !@permanent + !@transient end # Sets if the BotUser is permanent or not @@ -363,10 +373,13 @@ class Bot def from_hash(h) @username = h[:username] if h.has_key?(:username) @password = h[:password] if h.has_key?(:password) - @netmasks = h[:netmasks] if h.has_key?(:netmasks) - @perm = h[:perm] if h.has_key?(:perm) @login_by_mask = h[:login_by_mask] if h.has_key?(:login_by_mask) @autologin = h[:autologin] if h.has_key?(:autologin) + if h.has_key?(:netmasks) + @netmasks = h[:netmasks] + @netmasks.each { |n| Auth.manager.maskdb.add(self, n) } if @autologin + end + @perm = h[:perm] if h.has_key?(:perm) @data.replace(h[:data]) if h.has_key?(:data) end @@ -427,7 +440,12 @@ class Bot # Adds a Netmask # def add_netmask(mask) - @netmasks << mask.to_irc_netmask + m = mask.to_irc_netmask + @netmasks << m + if self.autologin? + Auth.manager.maskdb.add(self, m) + Auth.manager.logout_transients(m) if self.permanent? + end end # Removes a Netmask @@ -435,26 +453,14 @@ class Bot def delete_netmask(mask) m = mask.to_irc_netmask @netmasks.delete(m) - end - - # Removes all Netmasks - # - def reset_netmasks - @netmasks = NetmaskList.new + Auth.manager.maskdb.remove(self, m) if self.autologin? end # This method checks if BotUser has a Netmask that matches _user_ # def knows?(usr) user = usr.to_irc_user - known = false - @netmasks.each { |n| - if user.matches?(n) - known = true - break - end - } - return known + !!@netmasks.find { |n| user.matches? n } end # This method gets called when User _user_ wants to log in. @@ -550,12 +556,6 @@ class Bot return true end - # Resets the NetmaskList - def reset_netmasks - super - add_netmask("*!*@*") - end - # DefaultBotUser will check the default_perm after checking # the global ones # or on all channels if _chan_ is nil @@ -608,6 +608,7 @@ class Bot include Singleton + attr_reader :maskdb attr_reader :everyone attr_reader :botowner attr_reader :bot @@ -649,10 +650,11 @@ class Bot # resets the hashes def reset_hashes @botusers = Hash.new + @maskdb = NetmaskDb.new @allbotusers = Hash.new - [everyone, botowner].each { |x| + [everyone, botowner].each do |x| @allbotusers[x.username.to_sym] = x - } + end @transients = Set.new end @@ -740,17 +742,17 @@ class Bot ircuser = user.to_irc_user debug "Trying to autologin #{ircuser}" return @botusers[ircuser] if @botusers.has_key?(ircuser) - @allbotusers.each { |n, bu| - debug "Checking with #{n}" - return bu if bu.autologin? and login(ircuser, n) - } - # Check with transient users - @transients.each { |bu| - return bu if bu.login(ircuser) - } + bu = maskdb.find(ircuser) + if bu + debug "trying #{bu}" + bu.login(ircuser) or raise '...what?!' + @botusers[ircuser] = bu + return bu + end # Finally, create a transient if we're set to allow it if @bot.config['auth.autouser'] bu = create_transient_botuser(ircuser) + @botusers[ircuser] = bu return bu end return everyone @@ -775,6 +777,17 @@ class Bot return bu end + def logout_transients(m) + debug "to check: #{@botusers.keys.join ' '}" + @botusers.keys.each do |iu| + debug "checking #{iu.fullform} agains #{m.fullform}" + bu = @botusers[iu] + bu.transient? or next + iu.matches?(m) or next + @botusers.delete(iu).autologin = false + end + end + # Checks if User _user_ can do _cmd_ on _chan_. # # Permission are checked in this order, until a true or false diff --git a/lib/rbot/maskdb.rb b/lib/rbot/maskdb.rb new file mode 100644 index 00000000..38eb0cce --- /dev/null +++ b/lib/rbot/maskdb.rb @@ -0,0 +1,160 @@ +module Irc + class NetmaskDb + # helper backend class: generic nested radix tree + class Tree + attr_reader :pre, :chi + + def initialize(pre = '', chi = Hash.new) + @pre = pre + @chi = chi + end + + def add(val, *prefs) + str = prefs.shift or raise 'empty prefs' + @pre = str if @chi.empty? + + n = 0 + @pre.size.times do + break if @pre[n] != str[n] + n += 1 + end + + rest = str.slice(n .. -1) + + if n != @pre.size + prest = @pre.slice!(n .. -1) + pc = prest.slice! 0 + @chi = {pc => Tree.new(prest, @chi)} + end + + c = rest.slice!(0) + + if c + (@chi[c] ||= Tree.new).add(val, rest, *prefs) + else + if prefs.empty? + (@chi[''] ||= Array.new).push val + else + (@chi[''] ||= Tree.new).add(val, *prefs) + end + end + end + + def empty? + @chi.empty? + end + + def remove(*prefs, &block) + str = prefs.shift or raise 'empty prefs?' + return nil unless @pre.empty? or str.index(@pre) == 0 + c = str.slice(@pre.size) || '' + return nil unless @chi.include? c + if c == '' + if prefs.empty? + @chi[c].reject!(&block) + else + @chi[c].remove(*prefs, &block) + end + else + @chi[c].remove(str.slice((@pre.size + 1) .. -1), *prefs, &block) + end + @chi.delete(c) if @chi[c].empty? + + if @chi.size == 1 + k = @chi.keys.shift + return nil if k == '' + @pre << k << @chi[k].pre + @chi = @chi[k].chi + end + end + + def find(*prefs) + str = prefs.shift or raise 'empty prefs?' + self.find_helper(str, *prefs) + self.find_helper(str.reverse, *prefs) + end + + protected + def find_helper(*prefs) + str = prefs.shift or raise 'empty prefs?' + return [] unless @pre.empty? or str.index(@pre) == 0 + # puts "#{self.inspect}: #{str} == #{@pre} pfx matched" + if !@chi.include? '' + matches = [] + elsif Array === @chi[''] + matches = @chi[''] + else + matches = @chi[''].find(*prefs) + end + + c = str.slice(@pre.size) + + more = [] + if c and @chi.include?(c) + more = @chi[c].find_helper(str.slice((@pre.size + 1) .. -1), *prefs) + end + return more + matches + end + end + + # api wrapper for netmasks + + def initialize + @tree = Tree.new + end + + def cook_component(str) + s = (str && !str.empty?) ? str : '*' + l = s.index(/[\?\*]/) + if l + l2 = s.size - s.rindex(/[\?\*]/) - 1 + if l2 > l + s = s.reverse + l = l2 + end + + return (l > 0) ? s.slice(0 .. (l - 1)) : '' + else + return s + end + end + + def mask2keys(m) + [m.host, m.user, m.nick].map { |c| cook_component(c) } + end + + def add(user, *masks) + masks.each do |m| + debug "adding user #{user} with mask #{m}" + @tree.add([user, m], *mask2keys(m)) + end + end + + def remove(user, mask) + debug "trying to remove user #{user} with mask #{mask}" + @tree.remove(*mask2keys(mask)) do |val| + val[0] == user and val[1].fullform == mask.fullform + end + end + + def metric(iu, bu, mask) + ret = nil + if iu.matches? mask + ret = iu.fullform.length - mask.fullform.length + ret += 10 if bu.transient? + end + return ret + end + + def find(iu) + debug "find(#{iu.fullform})" + matches = @tree.find(iu.host, iu.user, iu.nick).uniq.map do |val| + m = metric(iu, *val) + m ? [val[0], m] : nil + end.compact.sort { |a, b| a[1] <=> a[1] } + debug "matches: " + (matches.map do |m| + "#{m[0].username}: [#{m[1]}]" + end.join(', ')) + return matches.empty? ? nil : matches[0][0] + end + end +end -- cgit v1.2.3