X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Fircsocket.rb;h=e5131c2b6b3edbeb3f542f358a698e90bbd027bd;hb=2e73cfeec6c9f549f216009570a29b12b927a99e;hp=9bdf43a06a4376843a54eb6fdf37612f86bb47b5;hpb=7951841c332f861d84eb8b8b486ff9431a9321c0;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/ircsocket.rb b/lib/rbot/ircsocket.rb index 9bdf43a0..e5131c2b 100644 --- a/lib/rbot/ircsocket.rb +++ b/lib/rbot/ircsocket.rb @@ -1,10 +1,18 @@ +#-- vim:sw=2:et +#++ +# +# :title: IRC Socket +# +# This module implements the IRC socket interface, including IRC message +# penalty computation and the message queue system + require 'monitor' 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 + # According to eggdrop, the initial penalty is penalty = 1 + self.size/100 # on everything but UnderNET where it's # penalty = 2 + self.size/120 @@ -40,8 +48,14 @@ class ::String dests = pars.split($;,2).first penalty += dests.split(',').size when :WHO - # I'm too lazy to implement this one correctly - penalty += 5 + args = pars.split + if args.length > 0 + penalty += args.inject(0){ |sum,x| sum += ((x.length > 4) ? 3 : 5) } + else + penalty += 10 + end + when :PART + penalty += 4 when :AWAY, :JOIN, :VERSION, :TIME, :TRACE, :WHOIS, :DNS penalty += 2 when :INVITE, :NICK @@ -212,7 +226,7 @@ module Irc # wrapped TCPSocket for communication with the server. # emulates a subset of TCPSocket functionality - class IrcSocket + class Socket MAX_IRC_SEND_PENALTY = 10 @@ -231,12 +245,6 @@ module Irc # accumulator for the throttle attr_reader :throttle_bytes - # delay between lines sent - attr_accessor :sendq_delay - - # max lines to burst - attr_accessor :sendq_burst - # an optional filter object. we call @filter.in(data) for # all incoming data and @filter.out(data) for all outgoing data attr_reader :filter @@ -244,6 +252,9 @@ module Irc # normalized uri of the current server attr_reader :server_uri + # penalty multiplier (percent) + attr_accessor :penalty_pct + # default trivial filter class class IdentityFilter def in(x) @@ -262,8 +273,8 @@ module Irc # 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_list, host, sendq_delay=2, sendq_burst=4, opts={}) + # create a new Irc::Socket + def initialize(server_list, host, opts={}) @server_list = server_list.dup @server_uri = nil @conn_count = 0 @@ -273,22 +284,11 @@ module Irc @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 - if sendq_burst - @sendq_burst = sendq_burst.to_i - else - @sendq_burst = 4 - end + @ssl = opts[:ssl] + @ssl_verify = opts[:ssl_verify] + @ssl_ca_file = opts[:ssl_ca_file] + @ssl_ca_path = opts[:ssl_ca_path] + @penalty_pct = opts[:penalty_pct] || 100 end def connected? @@ -306,33 +306,48 @@ module Irc @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 the host is a bracketed (IPv6) address, strip the brackets + # since Ruby doesn't like them in the Socket host parameter + # FIXME it would be safer to have it check for a valid + # IPv6 bracketed address rather than just stripping the brackets + srv_host = @server_uri.host + if srv_host.match(/\A\[(.*)\]\z/) + srv_host = $1 + end + if(@host) begin - @sock=TCPSocket.new(@server_uri.host, @server_uri.port, @host) + sock=TCPSocket.new(srv_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_uri.host, @server_uri.port) + sock=TCPSocket.new(srv_host, @server_uri.port) end else - @sock=TCPSocket.new(@server_uri.host, @server_uri.port) + sock=TCPSocket.new(srv_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 + if @ssl_verify + ssl_context.ca_file = @ssl_ca_file if @ssl_ca_file and not @ssl_ca_file.empty? + ssl_context.ca_path = @ssl_ca_path if @ssl_ca_path and not @ssl_ca_path.empty? + ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER + else + ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context) + sock.sync_close = true + sock.connect end - @last_send = Time.new - @sendq_delay + @sock = sock + @last_send = Time.new @flood_send = Time.new - @last_throttle = Time.new @burst = 0 @sock.extend(MonitorMixin) @sendq = MessageQueue.new @@ -404,9 +419,8 @@ module Irc rescue Exception => e error "error while shutting down: #{e.pretty_inspect}" end - @rawsock = nil if @ssl @sock = nil - @burst = 0 + @server_uri = nil @sendq.clear end @@ -414,30 +428,15 @@ module Irc def writer_loop loop do - # we could wait for the message, then calculate the delay and sleep - # if necessary. however, if high-priority message is enqueued while - # we sleep, it won't be the first to go out when the sleep is over. - # thus, we have to call Time.now() twice, once to calculate the delay - # and once to adjust @burst / @flood_send. begin now = Time.now - if @sendq_delay > 0 - burst_delay = 0 - if @burst > @sendq_burst - burst_delay = @last_send + @sendq_delay - now - end - - flood_delay = @flood_send - MAX_IRC_SEND_PENALTY - now - delay = [burst_delay, flood_delay, 0].max - if delay > 0 - debug "sleep(#{delay}) # (f: #{flood_delay}, b: #{burst_delay})" - sleep(delay) - end + flood_delay = @flood_send - MAX_IRC_SEND_PENALTY - now + delay = [flood_delay, 0].max + if delay > 0 + debug "sleep(#{delay}) # (f: #{flood_delay})" + sleep(delay) end msg = @sendq.shift - now = Time.now - @flood_send = now if @flood_send < now - @burst = 0 if @last_send + @sendq_delay < now debug "got #{msg.inspect} from queue, sending" emergency_puts(msg, true) rescue Exception => e @@ -456,11 +455,16 @@ module Irc if @sock.nil? error "SEND attempted on closed socket" else - @sock.puts(@filter.out(message)) - @last_send = Time.new - @flood_send += message.irc_send_penalty if penalty + # we use Socket#syswrite() instead of Socket#puts() because + # the latter is racy and can cause double message output in + # some circumstances + actual = @filter.out(message) + "\n" + now = Time.new + @sock.syswrite actual + @last_send = now + @flood_send = now if @flood_send < now + @flood_send += message.irc_send_penalty*@penalty_pct/100.0 if penalty @lines_sent += 1 - @burst += 1 end rescue Exception => e handle_socket_error(:SEND, e)