X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Fircsocket.rb;h=c1bc36115c28af7b8347e02c1c81b109bf3fdbf9;hb=4a86158144a13bc901222442ccd2db9c2bbd6bb0;hp=408287a8f17ac1a0318d1cf0c13e37c2021133c7;hpb=d9f1c04e912d44c218a90b9b0debcb0a11b87631;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/ircsocket.rb b/lib/rbot/ircsocket.rb index 408287a8..c1bc3611 100644 --- a/lib/rbot/ircsocket.rb +++ b/lib/rbot/ircsocket.rb @@ -1,3 +1,67 @@ +class ::String + # Calculate the penalty which will be assigned to this message + # by the IRCd + def irc_send_penalty + # According to eggrdop, the initial penalty is + penalty = 1 + self.size/100 + # on everything but UnderNET where it's + # penalty = 2 + self.size/120 + + cmd, pars = self.split($;,2) + debug "cmd: #{cmd}, pars: #{pars.inspect}" + case cmd.to_sym + when :KICK + chan, nick, msg = pars.split + chan = chan.split(',') + nick = nick.split(',') + penalty += nick.size + penalty *= chan.size + when :MODE + chan, modes, argument = pars.split + extra = 0 + if modes + extra = 1 + if argument + extra += modes.split(/\+|-/).size + else + extra += 3 * modes.split(/\+|-/).size + end + end + if argument + extra += 2 * argument.split.size + end + penalty += extra * chan.split.size + when :TOPIC + penalty += 1 + penalty += 2 unless pars.split.size < 2 + when :PRIVMSG, :NOTICE + dests = pars.split($;,2).first + penalty += dests.split(',').size + when :WHO + # I'm too lazy to implement this one correctly + penalty += 5 + when :AWAY, :JOIN, :VERSION, :TIME, :TRACE, :WHOIS, :DNS + penalty += 2 + when :INVITE, :NICK + penalty += 3 + when :ISON + penalty += 1 + else # Unknown messages + penalty += 1 + end + if penalty > 99 + debug "Wow, more than 99 secs of penalty!" + penalty = 99 + end + if penalty < 2 + debug "Wow, less than 2 secs of penalty!" + penalty = 2 + end + debug "penalty: #{penalty}" + return penalty + end +end + module Irc require 'socket' @@ -21,12 +85,13 @@ module Irc end def length - length = 0 + len = 0 @storage.each {|c| - length += c[1].length + len += c[1].size } - return length + return len end + alias :size :length def empty? @storage.empty? @@ -49,7 +114,7 @@ module Irc return nil end save_idx = @last_idx - @last_idx = (@last_idx + 1) % @storage.length + @last_idx = (@last_idx + 1) % @storage.size mess = @storage[@last_idx][1].first @last_idx = save_idx return mess @@ -60,7 +125,7 @@ module Irc warning "trying to access empty ring" return nil end - @last_idx = (@last_idx + 1) % @storage.length + @last_idx = (@last_idx + 1) % @storage.size mess = @storage[@last_idx][1].shift @storage.delete(@storage[@last_idx]) if @storage[@last_idx][1] == [] return mess @@ -116,10 +181,11 @@ module Irc def length len = 0 @rings.each { |r| - len += r.length + len += r.size } len end + alias :size :length def next if empty? @@ -131,8 +197,8 @@ module Irc mess = @rings[0].first else save_ring = @last_ring - (@rings.length - 1).times { - @last_ring = (@last_ring % (@rings.length - 1)) + 1 + (@rings.size - 1).times { + @last_ring = (@last_ring % (@rings.size - 1)) + 1 if !@rings[@last_ring].empty? mess = @rings[@last_ring].next break @@ -153,8 +219,8 @@ module Irc if !@rings[0].empty? return @rings[0].shift end - (@rings.length - 1).times { - @last_ring = (@last_ring % (@rings.length - 1)) + 1 + (@rings.size - 1).times { + @last_ring = (@last_ring % (@rings.size - 1)) + 1 if !@rings[@last_ring].empty? return @rings[@last_ring].shift end @@ -192,28 +258,59 @@ module Irc # max lines to burst attr_reader :sendq_burst - # server:: server to connect to - # port:: IRCd port + # an optional filter object. we call @filter.in(data) for + # all incoming data and @filter.out(data) for all outgoing data + attr_reader :filter + + # normalized uri of the current server + attr_reader :server_uri + + # default trivial filter class + class IdentityFilter + def in(x) + x + end + + def out(x) + x + end + end + + # set filter to identity, not to nil + def filter=(f) + @filter = f || IdentityFilter.new + end + + # server_list:: list of servers to connect to # host:: optional local host to bind to (ruby 1.7+ required) # create a new IrcSocket - def initialize(server, port, host, sendq_delay=2, sendq_burst=4) + def initialize(server_list, host, sendq_delay=2, sendq_burst=4, opts={}) @timer = Timer::Timer.new @timer.add(0.2) do spool end - @server = server.dup - @port = port.to_i + @server_list = server_list.dup + @server_uri = nil + @conn_count = 0 @host = host @sock = nil + @filter = IdentityFilter.new @spooler = false @lines_sent = 0 @lines_received = 0 + if opts.kind_of?(Hash) and opts.key?(:ssl) + @ssl = opts[:ssl] + else + @ssl = false + end + if sendq_delay @sendq_delay = sendq_delay.to_f else @sendq_delay = 2 end @last_send = Time.new - @sendq_delay + @flood_send = Time.new @last_throttle = Time.new @burst = 0 if sendq_burst @@ -233,18 +330,34 @@ module Irc warning "reconnecting while connected" return end + srv_uri = @server_list[@conn_count % @server_list.size].dup + srv_uri = 'irc://' + srv_uri if !(srv_uri =~ /:\/\//) + @conn_count += 1 + @server_uri = URI.parse(srv_uri) + @server_uri.port = 6667 if !@server_uri.port + debug "connection attempt \##{@conn_count} (#{@server_uri.host}:#{@server_uri.port})" + if(@host) begin - @sock=TCPSocket.new(@server, @port, @host) + @sock=TCPSocket.new(@server_uri.host, @server_uri.port, @host) rescue ArgumentError => e error "Your version of ruby does not support binding to a " error "specific local address, please upgrade if you wish " error "to use HOST = foo" error "(this option has been disabled in order to continue)" - @sock=TCPSocket.new(@server, @port) + @sock=TCPSocket.new(@server_uri.host, @server_uri.port) end else - @sock=TCPSocket.new(@server, @port) + @sock=TCPSocket.new(@server_uri.host, @server_uri.port) + end + if(@ssl) + require 'openssl' + ssl_context = OpenSSL::SSL::SSLContext.new() + ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE + @rawsock = @sock + @sock = OpenSSL::SSL::SSLSocket.new(@rawsock, ssl_context) + @sock.sync_close = true + @sock.connect end @qthread = false @qmutex = Mutex.new @@ -282,6 +395,14 @@ module Irc end end + def handle_socket_error(string, e) + error "#{string} failed: #{e.pretty_inspect}" + # We assume that an error means that there are connection + # problems and that we should reconnect, so we + shutdown + raise SocketError.new(e.inspect) + end + # get the next line from the server (blocks) def gets if @sock.nil? @@ -289,15 +410,13 @@ module Irc return nil end begin - reply = @sock.gets + reply = @filter.in(@sock.gets) @lines_received += 1 reply.strip! if reply debug "RECV: #{reply.inspect}" return reply - rescue => e - warning "socket get failed: #{e.inspect}" - debug e.backtrace.join("\n") - return nil + rescue Exception => e + handle_socket_error(:RECV, e) end end @@ -324,28 +443,26 @@ module Irc end now = Time.new if (now >= (@last_send + @sendq_delay)) - # after @sendq_delay has passed, we allow more @burst - # instead of resetting it to 0, we reduce it by 1 - debug "decreasing @burst" - @burst -= 1 if @burst > 0 - elsif (@burst >= @sendq_burst) + debug "resetting @burst" + @burst = 0 + elsif (@burst > @sendq_burst) # nope. can't send anything, come back to us next tick... debug "can't send yet" @timer.start return end - debug "can send #{@sendq_burst - @burst} lines, there are #{@sendq.length} to send" - while !@sendq.empty? and @burst < @sendq_burst and now > @last_send - MAX_IRC_SEND_PENALTY - mess = @sendq.next - puts_critical(@sendq.shift) - @last_send += mess.irc_send_penalty + @flood_send = now if @flood_send < now + debug "can send #{@sendq_burst - @burst} lines, there are #{@sendq.size} to send" + while !@sendq.empty? and @burst < @sendq_burst and @flood_send - now < MAX_IRC_SEND_PENALTY + debug "sending message (#{@flood_send - now} < #{MAX_IRC_SEND_PENALTY})" + puts_critical(@sendq.shift, true) end if @sendq.empty? @timer.stop end - rescue => e - error "Spooling failed: #{e.inspect}" - error e.backtrace.join("\n") + rescue Exception => e + error "Spooling failed: #{e.pretty_inspect}" + raise e end end end @@ -374,29 +491,36 @@ module Irc # shutdown the connection to the server def shutdown(how=2) - @sock.shutdown(how) unless @sock.nil? + return unless connected? + begin + @sock.close + rescue Exception => e + error "error while shutting down: #{e.pretty_inspect}" + end + @rawsock = nil if @ssl @sock = nil @burst = 0 + @sendq.clear end private # same as puts, but expects to be called with a mutex held on @qmutex - def puts_critical(message) + def puts_critical(message, penalty=false) # debug "in puts_critical" begin debug "SEND: #{message.inspect}" if @sock.nil? error "SEND attempted on closed socket" else - @sock.send(message + "\n",0) + @sock.puts(@filter.out(message)) @last_send = Time.new + @flood_send += message.irc_send_penalty if penalty @lines_sent += 1 @burst += 1 end - rescue => e - error "SEND failed: #{e.inspect}" - raise + rescue Exception => e + handle_socket_error(:SEND, e) end end