Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Better commited than sorry

  • Loading branch information...
commit f03c21c8ad56e480bd1cd0ce3be606feb8fb25d2 1 parent 25e2540
@meh authored
View
17 etc/failbot.yml
@@ -0,0 +1,17 @@
+# failirc client configuration
+---
+
+client:
+ nick: scragarian
+ user: scragarian
+ real_name: scragarian
+
+servers:
+ localhost:
+ port: 6697
+ ssl: enabled
+
+modules:
+ base:
+ messages:
+ quit: FUR DIE LULZ
View
8 etc/failircd.yaml → etc/failircd.yml
@@ -1,3 +1,6 @@
+# failirc server configuration
+---
+
server:
name: Fail IRC
@@ -21,7 +24,7 @@ modules:
motd: >
Welcome to a fail IRC.
- ping timeout: 60
+ ping timeout: 5
allowed nick: "nick.match(/^[0-9A-Za-z_\[\]\{\}\|\^`\-\\=\.]{1,23}$/)"
messages:
@@ -51,5 +54,4 @@ modules:
timeout: 30
service: imageshack
sites:
- - name: 4chan
- match: images\.4chan\.org
+ 4chan: images\.4chan\.org
View
86 lib/failirc/client.rb
@@ -0,0 +1,86 @@
+#--
+# Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
+#
+# This file is part of failirc.
+#
+# failirc is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# failirc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with failirc. If not, see <http://www.gnu.org/licenses/>.
+#++
+
+require 'failirc/version'
+require 'failirc/common/utils'
+require 'failirc/common/events'
+require 'failirc/common/workers'
+require 'failirc/common/modules'
+require 'failirc/common/modes'
+require 'failirc/common/mask'
+
+require 'failirc/client/dispatcher'
+
+module IRC
+
+class Client
+ extend Forwardable
+
+ attr_reader :options, :dispatcher, :created_on
+
+ def_delegators :@dispatcher, :connect, :running?
+ def_delegators :@events, :register, :dispatch, :observe, :fire, :hook
+ def_delegators :@workers, :do
+ def_delegators :@modules, :load
+
+ def initialize (options={})
+ @options = HashWithIndifferentAccess.new(options)
+
+ @dispatcher = Dispatcher.new(self)
+ @events = Events.new(self)
+ @workers = Workers.new(self)
+ @modules = Modules.new(self, '/failirc/client/modules')
+
+ if @options[:modules]
+ @options[:modules].each {|name, data|
+ begin
+ mod = @modules.load(name, data)
+
+ if mod
+ mod.define_singleton_method :client do
+ self
+ end
+
+ hook mod
+
+ IRC.debug "#{name} loaded"
+ else
+ IRC.debug "#{name} had some errors"
+ end
+ rescue LoadError
+ IRC.debug "#{name} not found"
+ end
+ }
+ end
+ end
+
+ def start
+ fire :start, self
+
+ @dispatcher.start
+ end
+
+ def stop
+ fire :stop, self
+
+ @dispatcher.stop
+ end
+end
+
+end
View
97 lib/failirc/client/dispatcher.rb
@@ -0,0 +1,97 @@
+#--
+# Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
+#
+# This file is part of failirc.
+#
+# failirc is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# failirc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with failirc. If not, see <http://www.gnu.org/licenses/>.
+#++
+
+require 'socket'
+require 'timeout'
+require 'openssl'
+
+begin
+ OpenSSL::SSL::SSLSocket.instance_method :read_nonblock
+rescue NameError
+ require 'openssl/nonblock'
+end
+
+require 'failirc/server/dispatcher/server'
+
+module IRC; class Client
+
+class Dispatcher
+ attr_reader :client
+
+ def initialize (client)
+ @client = client
+
+ @servers = []
+ @pipes = IO.pipe
+ end
+
+ def start
+ @running = true
+
+ self.loop
+ end
+
+ def stop
+ return unless running?
+
+ @running = false
+
+ wakeup
+ end
+
+ def running?
+ !!@running
+ end
+
+ def loop
+ self.do while running?
+ end
+
+ def do
+ begin
+ reading, _, erroring = IO.select([@pipes.first] + servers, nil, servers)
+ rescue Errno::EBADF
+ return
+ end
+
+ return unless running?
+
+ erroring.each {|server|
+ server.disconnect 'Input/output error'
+ }
+
+ reading.each {|thing|
+ case thing
+ when Dispatcher::Server then thing.receive
+ when IO then thing.read_nonblock(2048) rescue nil
+ end
+ }
+
+ servers.each {|server|
+ server.handle
+ }
+ end
+
+ def wakeup (options = {})
+ @pipes.last.write '?'
+ end
+
+end
+
+end; end
View
23 lib/failirc/common/modules/module.rb
@@ -25,16 +25,21 @@ class Module
def self.for (what)
scopes = what.scopes
- klass = Class.new(Module)
- klass.define_singleton_method :const_missing do |name|
- scopes.each {|what|
- return what.const_get(name) if what.const_defined?(name)
- }
-
- scopes.first.const_get(name)
- end
+ Class.new(Module).tap {|klass|
+ klass.define_singleton_method :const_missing do |name|
+ scopes.each {|what|
+ return what.const_get(name) if what.const_defined?(name)
+ }
- klass
+ scopes.first.const_get(name)
+ end
+ }.tap {|klass|
+ klass.class_eval {
+ define_method :inspect do
+ "#<Module: for(#{what.class.name})>"
+ end
+ }
+ }
end
include Events::DSL
View
5 lib/failirc/server.rb
@@ -60,11 +60,12 @@ def initialize (options={})
if @options[:modules]
@options[:modules].each {|name, data|
begin
- mod = @modules.load(name, data)
+ mod = @modules.load(name, data || {})
if mod
+ server = self
mod.define_singleton_method :server do
- self
+ server
end
hook mod
View
32 lib/failirc/server/dispatcher.rb
@@ -33,8 +33,6 @@
module IRC; class Server
class Dispatcher
- extend Forwardable
-
attr_reader :server, :servers
def initialize (server)
@@ -51,7 +49,7 @@ def start
end
def stop
- return unless @running
+ return unless running?
@running = false
@@ -59,27 +57,32 @@ def stop
end
def running?
- @running
+ !!@running
+ end
+
+ def reset?
+ !!@reset
end
def loop
- while running?
- self.do
- end
+ self.do while running?
end
def listen (options)
@servers.push(Dispatcher::Server.new(self, options))
+ wakeup
IRC.debug "Starting listening on #{@servers.last.host}:#{@servers.last.port}#{' (SSL)' if @servers.last.ssl?}"
-
- wakeup
end
def do
- reading, _, erroring = IO.select([@pipes.first] + clients + servers, nil, clients)
+ begin
+ reading, _, erroring = IO.select([@pipes.first] + clients + servers, nil, clients)
+ rescue Errno::EBADF
+ return
+ end
- return unless @running or not @clients
+ return unless running? or reset?
erroring.each {|client|
client.disconnect 'Input/output error'
@@ -99,13 +102,18 @@ def do
end
def clients
+ @reset = false
+
@clients ||= @servers.map {|s|
s.clients
}.flatten
end
def wakeup (options = {})
- @clients = nil if options[:reset]
+ if options[:reset]
+ @clients = nil
+ @reset = true
+ end
@pipes.last.write '?'
end
View
4 lib/failirc/server/dispatcher/client.rb
@@ -142,13 +142,13 @@ def disconnect (message, options={})
connected_to.clients.delete(self)
dispatcher.wakeup reset: true
+ @told = true
+
begin
flush
rescue; ensure
@socket.close rescue nil
end
-
- @told = true
end
def disconnected?
View
6 lib/failirc/server/dispatcher/server.rb
@@ -74,15 +74,15 @@ def accept
dispatcher.wakeup reset: true
rescue OpenSSL::SSL::SSLError, Timeout::Error
socket.write_nonblock "This is an SSL connection, faggot.\r\n" rescue nil
- socket.close
+ socket.close rescue nil
IRC.debug "#{host}[#{ip}/#{port}] tried to connect to a SSL connection and failed the handshake."
rescue Errno::ECONNRESET
- socket.close
+ socket.close rescue nil
IRC.debug "#{host}[#{ip}/#{port}] connection reset."
rescue Exception => e
- socket.close
+ socket.close rescue nil
IRC.debug e
end
View
10 lib/failirc/server/modules/base/client.rb
@@ -53,15 +53,15 @@ def self.extended (obj)
@connected_on = Time.now
@registered = false
+ }
+
+ class << obj
extend Forwardable
+ attr_reader :channels, :mask, :connected_on
+ attr_accessor :password, :real_name, :modes
def_delegators :@mask, :nick, :nick=, :user, :user=, :host, :host=
def_delegators :@modes, :can
- }
-
- class << obj
- attr_reader :channels, :mask, :connected_on
- attr_accessor :password, :real_name, :modes
def is_on_channel? (name)
if name.is_a?(Channel)
View
45 lib/failirc/server/modules/firewall.rb
@@ -0,0 +1,45 @@
+# failirc, a fail IRC library.
+#
+# Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
+#
+# This file is part of failirc.
+#
+# failirc is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# failirc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with failirc. If not, see <http://www.gnu.org/licenses/>.
+
+name 'firewall'
+version '0.0.1'
+
+on :start do
+ @log = options[:file] ? File.open(options[:file]) : $stdout
+
+ @mutex = Mutex.new
+end
+
+on :stop do
+ @log.close unless @log == $stdout
+end
+
+on :log do |string|
+ @mutex.synchronize {
+ @log.puts "[#{Time.now}] #{string}"
+ @log.flush
+ }
+end
+
+def dispatch (event, thing, string)
+ server.fire :log, "#{(event.chain == :input) ? '*IN* ' : '*OUT*'} #{thing.to_s} #{string.inspect}"
+end
+
+input { before priority: -10000, &method(:dispatch) }
+output { after priority: 10000, &method(:dispatch) }
View
165 utils/stress.rb
@@ -0,0 +1,165 @@
+#! /usr/bin/env ruby
+require 'socket'
+require 'thread'
+require 'failirc/version'
+require 'failirc/common/utils'
+require 'getoptlong'
+
+args = GetoptLong.new(
+ ['--version', '-v', GetoptLong::NO_ARGUMENT],
+ ['--verbose', '-V', GetoptLong::NO_ARGUMENT],
+ ['--number', '-n', GetoptLong::REQUIRED_ARGUMENT],
+ ['--server', '-s', GetoptLong::REQUIRED_ARGUMENT],
+ ['--prefix', '-p', GetoptLong::REQUIRED_ARGUMENT]
+)
+
+options = {
+ verbose: false,
+ number: 1000,
+ prefix: 'S_'
+}
+
+args.each {|option, value|
+ case option
+ when '--version'
+ puts "Fail IRC stress test #{IRC.version}"
+ exit 0
+
+ when '--verbose'
+ options[:verbose] = true
+
+ when '--number'
+ options[:number] = value.to_i
+
+ when '--server'
+ options[:server] = value
+
+ when '--prefix'
+ options[:prefix] = value
+ end
+}
+
+def wakeup
+ @pipes.last.write '?'
+end
+
+@input = Queue.new
+@sockets = Class.new(Array) {
+ attr_reader :prefix, :server, :port, :number
+
+ def initialize (prefix, server, number)
+ @prefix = prefix
+
+ @server = server.match(/^(.*?):/)[1]
+ @port = server.match(/:(.*?)$/)[1].to_i
+
+ @number = number
+ @unique = 0
+
+ @pipes = IO.pipe
+
+ self << @pipes.first
+ end
+
+ def number= (value)
+ return if value == number
+
+ if value < number
+ tmp, @number = number, value
+
+ slice!(tmp - value, length)
+ else
+ @number = value
+ end
+ end
+
+ def wakeup
+ @pipes.last.write '?'
+ end
+
+ def spawn
+ return if @spawning
+
+ @spawning = true
+
+ self.each {|s|
+ self.delete(s) if (s.closed? rescue true)
+ }
+
+ to_spawn = @number - self.length + 1
+
+ if to_spawn > 0
+ puts "Spawning #{to_spawn} connections to #{@server}:#{@port}"
+
+ 1.upto(to_spawn) {
+ socket = TCPSocket.new(@server, @port)
+
+ name = "#{@prefix}#{@unique += 1}"
+ socket.write_nonblock "NICK #{name}\r\nUSER #{name} #{name} #{name} :#{name}\r\n"
+
+ self << socket
+
+ wakeup
+ }
+ end
+
+ @spawning = false
+ end
+}.new(options[:prefix], options[:server], options[:number])
+
+COMMANDS = {
+ /^PING(.+)$/ => lambda {|match|
+ write("PONG#{match[1]}\r\n")
+ }
+}
+
+Thread.new {
+ loop do
+ begin
+ Thread.new {
+ @sockets.spawn
+ }
+
+ reading, = IO.select(@sockets)
+
+ reading.each {|socket|
+ if socket.class == IO
+ socket.read_nonblock 2048
+ next
+ end
+
+ line = socket.gets
+
+ COMMANDS.each {|regex, block|
+ if matches = regex.match(line)
+ socket.instance_exec matches, &block
+ end
+ }
+ }
+
+ unless @input.empty?
+ line = @input.pop
+
+ @sockets.each {|socket|
+ next if socket.class == IO
+
+ socket.write_nonblock line
+ }
+ end
+ rescue Exception => e
+ IRC.debug e
+ end
+ end
+}
+
+while (line = $stdin.gets) != 'exit'
+ case line
+ when /^send\s+(.*?)$/
+ @input.push eval("%{#{$1}}")
+ @sockets.wakeup
+
+ when /^clients\s+(.*?)/
+ @sockets.number = $1.to_i
+
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.