failirc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/failbot +162 -0
- data/bin/failircd +61 -0
- data/lib/failirc.rb +25 -0
- data/lib/failirc/client.rb +227 -0
- data/lib/failirc/client/channel.rb +98 -0
- data/lib/failirc/client/channels.rb +85 -0
- data/lib/failirc/client/client.rb +59 -0
- data/lib/failirc/client/clients.rb +43 -0
- data/lib/failirc/client/dispatcher.rb +209 -0
- data/lib/failirc/client/dispatcher/connectiondispatcher.rb +410 -0
- data/lib/failirc/client/dispatcher/event.rb +113 -0
- data/lib/failirc/client/dispatcher/eventdispatcher.rb +203 -0
- data/lib/failirc/client/module.rb +103 -0
- data/lib/failirc/client/modules/Base.rb +361 -0
- data/lib/failirc/client/modules/Logger.rb +89 -0
- data/lib/failirc/client/server.rb +89 -0
- data/lib/failirc/client/user.rb +66 -0
- data/lib/failirc/client/users.rb +100 -0
- data/lib/failirc/errors.rb +339 -0
- data/lib/failirc/extensions.rb +124 -0
- data/lib/failirc/mask.rb +117 -0
- data/lib/failirc/modes.rb +54 -0
- data/lib/failirc/responses.rb +360 -0
- data/lib/failirc/server.rb +266 -0
- data/lib/failirc/server/channel.rb +122 -0
- data/lib/failirc/server/channels.rb +84 -0
- data/lib/failirc/server/client.rb +100 -0
- data/lib/failirc/server/clients.rb +54 -0
- data/lib/failirc/server/dispatcher.rb +219 -0
- data/lib/failirc/server/dispatcher/connectiondispatcher.rb +477 -0
- data/lib/failirc/server/dispatcher/event.rb +113 -0
- data/lib/failirc/server/dispatcher/eventdispatcher.rb +196 -0
- data/lib/failirc/server/link.rb +50 -0
- data/lib/failirc/server/links.rb +49 -0
- data/lib/failirc/server/module.rb +103 -0
- data/lib/failirc/server/modules/Base.rb +2545 -0
- data/lib/failirc/server/modules/Cloaking.rb +170 -0
- data/lib/failirc/server/modules/Firewall.rb +104 -0
- data/lib/failirc/server/modules/Netlog.rb +67 -0
- data/lib/failirc/server/modules/Roulette.rb +78 -0
- data/lib/failirc/server/modules/TinyURL.rb +98 -0
- data/lib/failirc/server/modules/Translate.rb +62 -0
- data/lib/failirc/server/modules/WordFilter.rb +144 -0
- data/lib/failirc/server/user.rb +72 -0
- data/lib/failirc/server/users.rb +103 -0
- data/lib/failirc/sslutils.rb +74 -0
- data/lib/failirc/utils.rb +53 -0
- metadata +107 -0
@@ -0,0 +1,410 @@
|
|
1
|
+
# failirc, a fail IRC library.
|
2
|
+
#
|
3
|
+
# Copyleft meh. [http://meh.doesntexist.org | meh.ffff@gmail.com]
|
4
|
+
#
|
5
|
+
# This file is part of failirc.
|
6
|
+
#
|
7
|
+
# failirc is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Affero General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# failirc is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Affero General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
18
|
+
# along with failirc. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require 'thread'
|
21
|
+
require 'socket'
|
22
|
+
require 'openssl/nonblock'
|
23
|
+
|
24
|
+
require 'failirc/utils'
|
25
|
+
require 'failirc/sslutils'
|
26
|
+
|
27
|
+
require 'failirc/client/server'
|
28
|
+
|
29
|
+
module IRC
|
30
|
+
|
31
|
+
class Client
|
32
|
+
|
33
|
+
class Dispatcher
|
34
|
+
|
35
|
+
class ConnectionDispatcher
|
36
|
+
class Connections
|
37
|
+
attr_reader :client
|
38
|
+
|
39
|
+
def initialize (client)
|
40
|
+
@client = client
|
41
|
+
|
42
|
+
@data = ThreadSafeHash.new
|
43
|
+
@data[:sockets] = []
|
44
|
+
@data[:servers] = {
|
45
|
+
:bySocket => {},
|
46
|
+
:byName => {},
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def sockets
|
51
|
+
@data[:sockets]
|
52
|
+
end
|
53
|
+
|
54
|
+
def servers
|
55
|
+
@data[:servers]
|
56
|
+
end
|
57
|
+
|
58
|
+
def empty?
|
59
|
+
sockets.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def exists? (socket)
|
63
|
+
servers[:bySocket][socket] ? true : false
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete (socket)
|
67
|
+
if !exists?(socket)
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
@data[:sockets].delete(socket)
|
72
|
+
|
73
|
+
server = @data[:servers][:bySocket][socket]
|
74
|
+
@data[:servers][:bySocket].delete(socket)
|
75
|
+
@data[:servers][:byName].delete(server.name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Data
|
80
|
+
attr_reader :client, :dispatcher
|
81
|
+
|
82
|
+
def initialize (dispatcher)
|
83
|
+
@client = dispatcher.client
|
84
|
+
@dispatcher = dispatcher
|
85
|
+
|
86
|
+
@data = ThreadSafeHash.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def [] (socket)
|
90
|
+
if socket.is_a?(Server)
|
91
|
+
socket = socket.socket
|
92
|
+
end
|
93
|
+
|
94
|
+
if !@data[socket].is_a?(Array)
|
95
|
+
@data[socket] = []
|
96
|
+
end
|
97
|
+
|
98
|
+
@data[socket]
|
99
|
+
end
|
100
|
+
|
101
|
+
def push (socket, string)
|
102
|
+
if string.is_a?(String)
|
103
|
+
string.lstrip!
|
104
|
+
end
|
105
|
+
|
106
|
+
if string == :EOC
|
107
|
+
if socket.is_a?(Client) || socket.is_a?(User)
|
108
|
+
socket = socket.socket
|
109
|
+
end
|
110
|
+
|
111
|
+
dispatcher.disconnecting.push({ :server => client.server(socket), :output => self[socket] })
|
112
|
+
end
|
113
|
+
|
114
|
+
if (string && !string.empty?) || self[socket].last == :EOC
|
115
|
+
self[socket].push(string)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def pop (socket)
|
120
|
+
self[socket].shift
|
121
|
+
end
|
122
|
+
|
123
|
+
def clear (socket)
|
124
|
+
self[socket].clear
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete (socket)
|
128
|
+
if socket.is_a?(Server)
|
129
|
+
socket = socket.socket
|
130
|
+
end
|
131
|
+
|
132
|
+
@data.delete(socket)
|
133
|
+
end
|
134
|
+
|
135
|
+
def first (socket)
|
136
|
+
self[socket].first
|
137
|
+
end
|
138
|
+
|
139
|
+
def last (socket)
|
140
|
+
self[socket].last
|
141
|
+
end
|
142
|
+
|
143
|
+
def empty? (socket=nil)
|
144
|
+
if socket
|
145
|
+
if socket.is_a?(Server)
|
146
|
+
socket = socket.socket
|
147
|
+
end
|
148
|
+
|
149
|
+
if @data.has_key?(socket)
|
150
|
+
return @data[socket].empty?
|
151
|
+
else
|
152
|
+
return true
|
153
|
+
end
|
154
|
+
else
|
155
|
+
return @data.empty?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def each (&block)
|
160
|
+
@data.each_key &block
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
attr_reader :client, :dispatcher, :connections, :input, :output, :disconnecting
|
165
|
+
|
166
|
+
def initialize (dispatcher)
|
167
|
+
@client = dispatcher.client
|
168
|
+
@dispatcher = dispatcher
|
169
|
+
|
170
|
+
@connections = Connections.new(client)
|
171
|
+
@input = Data.new(dispatcher)
|
172
|
+
@output = Data.new(dispatcher)
|
173
|
+
@disconnecting = []
|
174
|
+
end
|
175
|
+
|
176
|
+
def sockets
|
177
|
+
@connections.sockets
|
178
|
+
end
|
179
|
+
|
180
|
+
def servers
|
181
|
+
@connections.servers
|
182
|
+
end
|
183
|
+
|
184
|
+
def connect (options, config, name=nil)
|
185
|
+
socket = nil
|
186
|
+
context = nil
|
187
|
+
|
188
|
+
begin
|
189
|
+
socket = TCPSocket.new(options[:host], options[:port])
|
190
|
+
rescue Errno::ECONNREFUSED
|
191
|
+
self.debug "Could not connect to #{options[:host]}/#{options[:port]}."
|
192
|
+
return
|
193
|
+
end
|
194
|
+
|
195
|
+
if options[:ssl] != 'disabled'
|
196
|
+
context = SSLUtils::context(options[:ssl_cert], options[:ssl_key])
|
197
|
+
end
|
198
|
+
|
199
|
+
host = socket.peeraddr[2]
|
200
|
+
ip = socket.peeraddr[3]
|
201
|
+
port = socket.peeraddr[1]
|
202
|
+
|
203
|
+
self.debug "Connecting to #{host}[#{ip}/#{port}]"
|
204
|
+
|
205
|
+
begin
|
206
|
+
if config.attributes['ssl'] != 'disabled'
|
207
|
+
ssl = OpenSSL::SSL::SSLSocket.new socket, context
|
208
|
+
|
209
|
+
ssl.connect
|
210
|
+
socket = ssl
|
211
|
+
end
|
212
|
+
|
213
|
+
@connections.servers[:bySocket][socket] = Server.new(client, socket, config, name)
|
214
|
+
@connections.servers[:byName][server(socket).name] = server socket
|
215
|
+
@connections.sockets.push(socket)
|
216
|
+
|
217
|
+
if config.attributes['password']
|
218
|
+
server(socket).password = config.attributes['password']
|
219
|
+
end
|
220
|
+
|
221
|
+
@input[socket]
|
222
|
+
|
223
|
+
dispatcher.execute :connect, @connections.servers[:bySocket][socket]
|
224
|
+
rescue OpenSSL::SSL::SSLError
|
225
|
+
self.debug "Tried to connect to #{host}[#{ip}/#{port}] with SSL but the handshake failed."
|
226
|
+
socket.close rescue nil
|
227
|
+
rescue Errno::ECONNRESET
|
228
|
+
socket.close rescue nil
|
229
|
+
self.debug "#{host}[#{ip}/#{port}] connection reset."
|
230
|
+
rescue Exception => e
|
231
|
+
socket.close rescue nil
|
232
|
+
self.debug e
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def read (timeout=0.1)
|
237
|
+
begin
|
238
|
+
reading, = IO::select @connections.sockets, nil, nil, timeout
|
239
|
+
rescue IOError
|
240
|
+
@connections.sockets.each {|socket|
|
241
|
+
if socket.closed?
|
242
|
+
kill server socket
|
243
|
+
end
|
244
|
+
}
|
245
|
+
rescue Exception => e
|
246
|
+
self.debug e
|
247
|
+
end
|
248
|
+
|
249
|
+
if !reading
|
250
|
+
return
|
251
|
+
end
|
252
|
+
|
253
|
+
reading.each {|socket|
|
254
|
+
server = self.server socket
|
255
|
+
|
256
|
+
begin
|
257
|
+
input = socket.read_nonblock 2048
|
258
|
+
|
259
|
+
if !input || input.empty?
|
260
|
+
raise Errno::EPIPE
|
261
|
+
end
|
262
|
+
|
263
|
+
input.split(/[\r\n]+/).each {|string|
|
264
|
+
@input.push(socket, string)
|
265
|
+
}
|
266
|
+
rescue IOError
|
267
|
+
disconnect server, 'Input/output error'
|
268
|
+
rescue Errno::EBADF, Errno::EPIPE, OpenSSL::SSL::SSLError
|
269
|
+
disconnect server, 'Client exited'
|
270
|
+
rescue Errno::ECONNRESET
|
271
|
+
disconnect server, 'Connection reset by peer'
|
272
|
+
rescue Errno::ETIMEDOUT
|
273
|
+
disconnect server, 'Ping timeout'
|
274
|
+
rescue Errno::EHOSTUNREACH
|
275
|
+
disconnect server, 'No route to host'
|
276
|
+
rescue Errno::EAGAIN, IO::WaitReadable
|
277
|
+
rescue Exception => e
|
278
|
+
self.debug e
|
279
|
+
end
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
def disconnect (server, message)
|
284
|
+
@output.push server, :EOC
|
285
|
+
@output.push server, message
|
286
|
+
end
|
287
|
+
|
288
|
+
def clean
|
289
|
+
@disconnecting.each {|data|
|
290
|
+
server = data[:server]
|
291
|
+
output = data[:output]
|
292
|
+
|
293
|
+
if output.first == :EOC
|
294
|
+
output.shift
|
295
|
+
handleDisconnection server, output.shift
|
296
|
+
@disconnecting.delete(data)
|
297
|
+
end
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
def handle
|
302
|
+
@input.each {|socket|
|
303
|
+
if dispatcher.event.handling[socket] || @input.empty?(socket)
|
304
|
+
next
|
305
|
+
end
|
306
|
+
|
307
|
+
Thread.new {
|
308
|
+
begin
|
309
|
+
if string = @input.pop(socket)
|
310
|
+
dispatcher.dispatch(:input, server(socket), string)
|
311
|
+
end
|
312
|
+
rescue Exception => e
|
313
|
+
self.debug e
|
314
|
+
end
|
315
|
+
}
|
316
|
+
}
|
317
|
+
end
|
318
|
+
|
319
|
+
def write (timeout=0)
|
320
|
+
begin
|
321
|
+
none, writing = IO::select nil, @connections.sockets, nil, timeout
|
322
|
+
rescue IOError
|
323
|
+
@connections.sockets.each {|socket|
|
324
|
+
if socket.closed?
|
325
|
+
kill server socket
|
326
|
+
end
|
327
|
+
}
|
328
|
+
rescue Exception => e
|
329
|
+
self.debug e
|
330
|
+
end
|
331
|
+
|
332
|
+
if !writing
|
333
|
+
return
|
334
|
+
end
|
335
|
+
|
336
|
+
writing.each {|socket|
|
337
|
+
if @output.empty?(socket)
|
338
|
+
next
|
339
|
+
end
|
340
|
+
|
341
|
+
server = self.server socket
|
342
|
+
|
343
|
+
begin
|
344
|
+
while !@output.empty?(socket)
|
345
|
+
output = @output.first(socket)
|
346
|
+
|
347
|
+
if output == :EOC
|
348
|
+
@output.delete(socket)
|
349
|
+
else
|
350
|
+
output.force_encoding 'ASCII-8BIT'
|
351
|
+
socket.write_nonblock "#{output}\r\n"
|
352
|
+
|
353
|
+
@output.pop(socket)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
rescue IOError
|
357
|
+
disconnect server, 'Input/output error'
|
358
|
+
rescue Errno::EBADF, Errno::EPIPE, OpenSSL::SSL::SSLError
|
359
|
+
disconnect server, 'Client exited'
|
360
|
+
rescue Errno::ECONNRESET
|
361
|
+
disconnect server, 'Connection reset by peer'
|
362
|
+
rescue Errno::ETIMEDOUT
|
363
|
+
disconnect server, 'Ping timeout'
|
364
|
+
rescue Errno::EHOSTUNREACH
|
365
|
+
disconnect server, 'No route to host'
|
366
|
+
rescue Errno::EAGAIN, IO::WaitWritable
|
367
|
+
rescue Exception => e
|
368
|
+
self.debug e
|
369
|
+
end
|
370
|
+
}
|
371
|
+
end
|
372
|
+
|
373
|
+
def handleDisconnection (server, message)
|
374
|
+
@dispatcher.execute(:disconnect, server, message) rescue nil
|
375
|
+
|
376
|
+
@input.delete(server.socket)
|
377
|
+
@output.delete(server.socket)
|
378
|
+
connections.delete(server.socket)
|
379
|
+
|
380
|
+
self.debug "Disconnected from #{server}[#{server.ip}/#{server.port}]"
|
381
|
+
|
382
|
+
server.socket.close rescue nil
|
383
|
+
end
|
384
|
+
|
385
|
+
def finalize
|
386
|
+
begin
|
387
|
+
@connections.sockets.each {|socket|
|
388
|
+
disconnect self.server(socket), disconnecting
|
389
|
+
}
|
390
|
+
rescue Exception => e
|
391
|
+
self.debug e
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def server (identifier)
|
396
|
+
if identifier.is_a?(Server)
|
397
|
+
return identifier
|
398
|
+
elsif identifier.is_a?(String)
|
399
|
+
return @connections.servers[:byName][identifier]
|
400
|
+
else
|
401
|
+
return @connections.servers[:bySocket][identifier]
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
409
|
+
|
410
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# failirc, a fail IRC library.
|
2
|
+
#
|
3
|
+
# Copyleft meh. [http://meh.doesntexist.org | meh.ffff@gmail.com]
|
4
|
+
#
|
5
|
+
# This file is part of failirc.
|
6
|
+
#
|
7
|
+
# failirc is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Affero General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# failirc is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Affero General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
18
|
+
# along with failirc. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
module IRC
|
21
|
+
|
22
|
+
class Client
|
23
|
+
|
24
|
+
class Dispatcher
|
25
|
+
|
26
|
+
class Event
|
27
|
+
class Callback
|
28
|
+
attr_reader :method
|
29
|
+
attr_accessor :priority
|
30
|
+
|
31
|
+
def initialize (method, priority=0)
|
32
|
+
@method = method
|
33
|
+
@priority = priority
|
34
|
+
end
|
35
|
+
|
36
|
+
def call (*args)
|
37
|
+
return @method.call(*args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :types, :chain, :aliases, :dispatcher, :server, :string
|
42
|
+
attr_accessor :special
|
43
|
+
|
44
|
+
def initialize (dispatcher, chain, server, string)
|
45
|
+
@dispatcher = dispatcher
|
46
|
+
@chain = chain
|
47
|
+
@server = server
|
48
|
+
@string = string
|
49
|
+
@types = Event.types(dispatcher, chain, string)
|
50
|
+
@aliases = Event.aliases(dispatcher, chain, types)
|
51
|
+
@callbacks = Event.callbacks(dispatcher, chain, types)
|
52
|
+
end
|
53
|
+
|
54
|
+
def callbacks
|
55
|
+
if @callbacks
|
56
|
+
return @callbacks
|
57
|
+
else
|
58
|
+
tmp = Event.callbacks(@dispatcher, @chain, @type)
|
59
|
+
|
60
|
+
if tmp
|
61
|
+
return @callbacks = tmp
|
62
|
+
else
|
63
|
+
return []
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.types (dispatcher, chain, string)
|
69
|
+
types = []
|
70
|
+
|
71
|
+
dispatcher.events[chain].each_key {|key|
|
72
|
+
if key.class == Regexp && key.match(string)
|
73
|
+
types.push key
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
return types
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.aliases (dispatcher, chain, types)
|
81
|
+
aliases = []
|
82
|
+
|
83
|
+
dispatcher.aliases[chain].each {|key, value|
|
84
|
+
if types.include?(value)
|
85
|
+
aliases.push key
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
return aliases
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.callbacks (dispatcher, chain, types)
|
93
|
+
callbacks = []
|
94
|
+
|
95
|
+
if chain == :pre || chain == :post || chain == :default
|
96
|
+
callbacks.insert(-1, *dispatcher.events[chain])
|
97
|
+
else
|
98
|
+
types.each {|type|
|
99
|
+
callbacks.insert(-1, *dispatcher.events[chain][type])
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
return callbacks
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
Event = Dispatcher::Event
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|