failirc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,100 @@
|
|
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 'failirc/utils'
|
21
|
+
require 'failirc/modes'
|
22
|
+
require 'failirc/mask'
|
23
|
+
|
24
|
+
require 'failirc/server/channels'
|
25
|
+
|
26
|
+
module IRC
|
27
|
+
|
28
|
+
class Server
|
29
|
+
|
30
|
+
class Client
|
31
|
+
attr_reader :server, :socket, :listen, :ip, :port, :channels, :modes, :mask, :nick, :user, :host, :connectedOn
|
32
|
+
attr_accessor :password, :realName
|
33
|
+
|
34
|
+
def initialize (server, socket, listen=nil)
|
35
|
+
@server = server
|
36
|
+
@socket = socket
|
37
|
+
@listen = listen
|
38
|
+
|
39
|
+
@registered = false
|
40
|
+
|
41
|
+
@channels = Channels.new(@server)
|
42
|
+
@modes = Modes.new
|
43
|
+
|
44
|
+
if socket.is_a?(Mask)
|
45
|
+
@mask = socket
|
46
|
+
else
|
47
|
+
@mask = Mask.new
|
48
|
+
self.host = socket.peeraddr[2]
|
49
|
+
@ip = socket.peeraddr[3]
|
50
|
+
@port = socket.addr[1]
|
51
|
+
|
52
|
+
if socket.is_a?(OpenSSL::SSL::SSLSocket)
|
53
|
+
@modes[:ssl] = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@connectedOn = Time.now
|
58
|
+
end
|
59
|
+
|
60
|
+
def nick= (value)
|
61
|
+
@mask.nick = @nick = value
|
62
|
+
end
|
63
|
+
|
64
|
+
def user= (value)
|
65
|
+
@mask.user = @user = value
|
66
|
+
end
|
67
|
+
|
68
|
+
def host= (value)
|
69
|
+
@mask.host = @host = value
|
70
|
+
end
|
71
|
+
|
72
|
+
def send (symbol, *args)
|
73
|
+
begin
|
74
|
+
self.method(symbol).call(*args)
|
75
|
+
rescue Exception => e
|
76
|
+
self.debug e
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def raw (text)
|
81
|
+
@server.dispatcher.dispatch :output, self, text
|
82
|
+
@server.dispatcher.connection.output.push @socket, text
|
83
|
+
end
|
84
|
+
|
85
|
+
def numeric (response, value=nil)
|
86
|
+
raw ":#{server.host} #{'%03d' % response[:code]} #{nick || 'faggot'} #{eval(response[:text])}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
mask.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
return "#<Client: #{mask} #{modes}#{(modes[:registered]) ? ' registered' : ''}>"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,54 @@
|
|
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 'failirc/server/client'
|
21
|
+
|
22
|
+
module IRC
|
23
|
+
|
24
|
+
class Server
|
25
|
+
|
26
|
+
class Clients < Hash
|
27
|
+
attr_reader :server
|
28
|
+
|
29
|
+
def initialize (server, *args)
|
30
|
+
@server = server
|
31
|
+
|
32
|
+
super(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def send (*args)
|
36
|
+
each_value {|user|
|
37
|
+
user.send(*args)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
result = ""
|
43
|
+
|
44
|
+
each_value {|client|
|
45
|
+
result << " #{client.inspect}"
|
46
|
+
}
|
47
|
+
|
48
|
+
return result[1, result.length]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,219 @@
|
|
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 'failirc/server/dispatcher/connectiondispatcher'
|
21
|
+
require 'failirc/server/dispatcher/eventdispatcher'
|
22
|
+
|
23
|
+
module IRC
|
24
|
+
|
25
|
+
class Server
|
26
|
+
|
27
|
+
class Dispatcher
|
28
|
+
attr_reader :server, :connection, :event
|
29
|
+
|
30
|
+
def initialize (server)
|
31
|
+
@server = server
|
32
|
+
|
33
|
+
@connection = ConnectionDispatcher.new(self)
|
34
|
+
@event = EventDispatcher.new(self)
|
35
|
+
|
36
|
+
@intervals = {}
|
37
|
+
@timeouts = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def start
|
41
|
+
@started = true
|
42
|
+
|
43
|
+
@listening = Fiber.new {
|
44
|
+
while true
|
45
|
+
if !@connection.connections.empty?
|
46
|
+
timeout = 0
|
47
|
+
else
|
48
|
+
timeout = 2
|
49
|
+
end
|
50
|
+
|
51
|
+
@connection.accept timeout
|
52
|
+
|
53
|
+
Fiber.yield
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
@reading = Fiber.new {
|
58
|
+
while true
|
59
|
+
@connection.read
|
60
|
+
|
61
|
+
Fiber.yield
|
62
|
+
end
|
63
|
+
}
|
64
|
+
|
65
|
+
@cleaning = Fiber.new {
|
66
|
+
while true
|
67
|
+
@connection.clean
|
68
|
+
|
69
|
+
Fiber.yield
|
70
|
+
end
|
71
|
+
}
|
72
|
+
|
73
|
+
@handling = Fiber.new {
|
74
|
+
while true
|
75
|
+
@connection.handle
|
76
|
+
|
77
|
+
Fiber.yield
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
@writing = Fiber.new {
|
82
|
+
while true
|
83
|
+
@connection.write
|
84
|
+
|
85
|
+
Fiber.yield
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
@defaults = [@listening, @cleaning, @reading, @handling, @writing]
|
90
|
+
|
91
|
+
self.loop
|
92
|
+
end
|
93
|
+
|
94
|
+
def stop
|
95
|
+
if !@started
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
@started = false
|
100
|
+
@stopping = true
|
101
|
+
|
102
|
+
@event.finalize
|
103
|
+
@connection.finalize
|
104
|
+
|
105
|
+
@stopping = false
|
106
|
+
end
|
107
|
+
|
108
|
+
def loop
|
109
|
+
while true
|
110
|
+
@defaults.each {|fiber|
|
111
|
+
begin
|
112
|
+
fiber.resume
|
113
|
+
rescue FiberError
|
114
|
+
self.debug 'Something went deeply wrong in the dispatcher, aborting.'
|
115
|
+
Process::exit 23
|
116
|
+
rescue Exception => e
|
117
|
+
self.debug e
|
118
|
+
end
|
119
|
+
}
|
120
|
+
|
121
|
+
@intervals.each {|fiber, meta|
|
122
|
+
begin
|
123
|
+
if !@intervals[fiber]
|
124
|
+
raise FiberError
|
125
|
+
end
|
126
|
+
|
127
|
+
if meta[:at] <= Time.now
|
128
|
+
fiber.resume
|
129
|
+
|
130
|
+
meta[:at] += meta[:offset]
|
131
|
+
end
|
132
|
+
rescue FiberError
|
133
|
+
clearInterval meta
|
134
|
+
rescue Exception => e
|
135
|
+
self.debug e
|
136
|
+
end
|
137
|
+
}
|
138
|
+
|
139
|
+
@timeouts.each {|fiber, meta|
|
140
|
+
begin
|
141
|
+
if !@timeouts[fiber]
|
142
|
+
raise FiberError
|
143
|
+
end
|
144
|
+
|
145
|
+
if meta[:at] <= Time.now
|
146
|
+
fiber.resume
|
147
|
+
|
148
|
+
clearTimeout meta
|
149
|
+
end
|
150
|
+
rescue FiberError
|
151
|
+
clearTimeout meta
|
152
|
+
rescue Exception => e
|
153
|
+
self.debug e
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def setTimeout (fiber, time)
|
160
|
+
@timeouts[fiber] = {
|
161
|
+
:fiber => fiber,
|
162
|
+
:at => Time.now + time,
|
163
|
+
:on => Time.now,
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
def clearTimeout (timeout)
|
168
|
+
@timeouts.delete(timeout[:fiber])
|
169
|
+
end
|
170
|
+
|
171
|
+
def setInterval (fiber, time)
|
172
|
+
@intervals[fiber] = {
|
173
|
+
:fiber => fiber,
|
174
|
+
:offset => time,
|
175
|
+
:at => Time.now + time,
|
176
|
+
:on => Time.now,
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def clearInterval (interval)
|
181
|
+
@intervals.delete(interval[:fiber])
|
182
|
+
end
|
183
|
+
|
184
|
+
def connections
|
185
|
+
@connection.connections
|
186
|
+
end
|
187
|
+
|
188
|
+
def input
|
189
|
+
@connection.input
|
190
|
+
end
|
191
|
+
|
192
|
+
def output
|
193
|
+
@connection.output
|
194
|
+
end
|
195
|
+
|
196
|
+
def disconnecting
|
197
|
+
@connection.disconnecting
|
198
|
+
end
|
199
|
+
|
200
|
+
def alias (*args)
|
201
|
+
@event.alias(*args)
|
202
|
+
end
|
203
|
+
|
204
|
+
def register (*args)
|
205
|
+
@event.register(*args)
|
206
|
+
end
|
207
|
+
|
208
|
+
def dispatch (*args)
|
209
|
+
@event.dispatch(*args)
|
210
|
+
end
|
211
|
+
|
212
|
+
def execute (*args)
|
213
|
+
@event.execute(*args)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
@@ -0,0 +1,477 @@
|
|
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
|
+
module IRC
|
28
|
+
|
29
|
+
class Server
|
30
|
+
|
31
|
+
class Dispatcher
|
32
|
+
|
33
|
+
class ConnectionDispatcher
|
34
|
+
class Connections
|
35
|
+
attr_reader :server
|
36
|
+
|
37
|
+
def initialize (server)
|
38
|
+
@server = server
|
39
|
+
|
40
|
+
@data = ThreadSafeHash.new
|
41
|
+
|
42
|
+
@data[:listening] = {
|
43
|
+
:sockets => [],
|
44
|
+
:data => {},
|
45
|
+
}
|
46
|
+
|
47
|
+
@data[:sockets] = []
|
48
|
+
@data[:things] = {}
|
49
|
+
@data[:clients] = CaseInsensitiveHash.new
|
50
|
+
@data[:links] = CaseInsensitiveHash.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def listening
|
54
|
+
@data[:listening]
|
55
|
+
end
|
56
|
+
|
57
|
+
def sockets
|
58
|
+
@data[:sockets]
|
59
|
+
end
|
60
|
+
|
61
|
+
def things
|
62
|
+
@data[:things]
|
63
|
+
end
|
64
|
+
|
65
|
+
def clients
|
66
|
+
@data[:clients]
|
67
|
+
end
|
68
|
+
|
69
|
+
def links
|
70
|
+
@data[:links]
|
71
|
+
end
|
72
|
+
|
73
|
+
def empty?
|
74
|
+
sockets.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def exists? (socket)
|
78
|
+
things[socket] ? true : false
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete (socket)
|
82
|
+
if !exists?(socket)
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
thing = @data[:things][socket]
|
87
|
+
|
88
|
+
if thing.is_a?(Client)
|
89
|
+
@data[:clients].delete(thing.nick)
|
90
|
+
@data[:clients].delete(socket)
|
91
|
+
elsif thing.is_a?(Link)
|
92
|
+
@data[:links].delete(thing.host)
|
93
|
+
@data[:links].delete(socket)
|
94
|
+
end
|
95
|
+
|
96
|
+
@data[:sockets].delete(socket)
|
97
|
+
@data[:things].delete(socket)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Data
|
102
|
+
attr_reader :server, :dispatcher
|
103
|
+
|
104
|
+
def initialize (dispatcher)
|
105
|
+
@server = dispatcher.server
|
106
|
+
@dispatcher = dispatcher
|
107
|
+
|
108
|
+
@data = ThreadSafeHash.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def [] (socket)
|
112
|
+
if socket.is_a?(Client) || socket.is_a?(User)
|
113
|
+
socket = socket.socket
|
114
|
+
end
|
115
|
+
|
116
|
+
if !@data[socket].is_a?(Array)
|
117
|
+
@data[socket] = []
|
118
|
+
end
|
119
|
+
|
120
|
+
@data[socket]
|
121
|
+
end
|
122
|
+
|
123
|
+
def push (socket, string)
|
124
|
+
if string.is_a?(String)
|
125
|
+
string.lstrip!
|
126
|
+
end
|
127
|
+
|
128
|
+
if string == :EOC
|
129
|
+
if socket.is_a?(Client) || socket.is_a?(User)
|
130
|
+
socket = socket.socket
|
131
|
+
end
|
132
|
+
|
133
|
+
dispatcher.disconnecting.push({ :thing => dispatcher.connections.things[socket], :output => self[socket] })
|
134
|
+
end
|
135
|
+
|
136
|
+
if (string && !string.empty?) || self[socket].last == :EOC
|
137
|
+
self[socket].push(string)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def pop (socket)
|
142
|
+
self[socket].shift
|
143
|
+
end
|
144
|
+
|
145
|
+
def clear (socket)
|
146
|
+
self[socket].clear
|
147
|
+
end
|
148
|
+
|
149
|
+
def delete (socket)
|
150
|
+
if socket.is_a?(Client) || socket.is_a?(User)
|
151
|
+
socket = socket.socket
|
152
|
+
end
|
153
|
+
|
154
|
+
@data.delete(socket)
|
155
|
+
end
|
156
|
+
|
157
|
+
def first (socket)
|
158
|
+
self[socket].first
|
159
|
+
end
|
160
|
+
|
161
|
+
def last (socket)
|
162
|
+
self[socket].last
|
163
|
+
end
|
164
|
+
|
165
|
+
def empty? (socket=nil)
|
166
|
+
if socket.is_a?(Client) || socket.is_a?(User)
|
167
|
+
socket = socket.socket
|
168
|
+
end
|
169
|
+
|
170
|
+
if socket
|
171
|
+
if @data.has_key?(socket)
|
172
|
+
return @data[socket].empty?
|
173
|
+
else
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
else
|
177
|
+
return @data.empty?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def each (&block)
|
182
|
+
@data.each_key &block
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
attr_reader :server, :dispatcher, :connections, :input, :output, :disconnecting
|
187
|
+
|
188
|
+
def initialize (dispatcher)
|
189
|
+
@server = dispatcher.server
|
190
|
+
@dispatcher = dispatcher
|
191
|
+
|
192
|
+
@connections = Connections.new(server)
|
193
|
+
@input = Data.new(dispatcher)
|
194
|
+
@output = Data.new(dispatcher)
|
195
|
+
@disconnecting = []
|
196
|
+
end
|
197
|
+
|
198
|
+
def sockets
|
199
|
+
@connections.sockets
|
200
|
+
end
|
201
|
+
|
202
|
+
def clients
|
203
|
+
@connections.clients
|
204
|
+
end
|
205
|
+
|
206
|
+
def links
|
207
|
+
@connections.links
|
208
|
+
end
|
209
|
+
|
210
|
+
def listen (options, listen)
|
211
|
+
server = TCPServer.new(options[:bind], options[:port])
|
212
|
+
context = nil
|
213
|
+
|
214
|
+
if options[:ssl] != 'disabled'
|
215
|
+
context = SSLUtils::context(options[:ssl_cert], options[:ssl_key])
|
216
|
+
end
|
217
|
+
|
218
|
+
@connections.listening[:sockets].push(server)
|
219
|
+
@connections.listening[:data][server] = { :listen => listen, :context => context }
|
220
|
+
end
|
221
|
+
|
222
|
+
def accept (timeout=0)
|
223
|
+
begin
|
224
|
+
listening, = IO::select @connections.listening[:sockets], nil, nil, timeout
|
225
|
+
|
226
|
+
if listening
|
227
|
+
listening.each {|server|
|
228
|
+
begin
|
229
|
+
socket, = server.accept_nonblock
|
230
|
+
|
231
|
+
if socket
|
232
|
+
newConnection socket, @connections.listening[:data][server][:listen], @connections.listening[:data][server][:context]
|
233
|
+
end
|
234
|
+
rescue Errno::EAGAIN
|
235
|
+
rescue Exception => e
|
236
|
+
self.debug e
|
237
|
+
end
|
238
|
+
}
|
239
|
+
end
|
240
|
+
rescue IOError
|
241
|
+
@connections.listening[:sockets].each {|socket|
|
242
|
+
if socket.closed?
|
243
|
+
@connections.listening[:sockets].delete(socket)
|
244
|
+
@connections.listening[:data].delete(socket @connections.listening[:data].delete(socket))
|
245
|
+
end
|
246
|
+
}
|
247
|
+
rescue
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Executed with each incoming connection
|
252
|
+
def newConnection (socket, listen, context=nil)
|
253
|
+
# here, somehow we should check if the incoming peer is a linked server or a real client
|
254
|
+
|
255
|
+
host = socket.peeraddr[2]
|
256
|
+
ip = socket.peeraddr[3]
|
257
|
+
port = socket.addr[1]
|
258
|
+
|
259
|
+
self.debug "#{host}[#{ip}/#{port}] connecting."
|
260
|
+
|
261
|
+
Thread.new {
|
262
|
+
begin
|
263
|
+
if listen.attributes['ssl'] != 'disabled'
|
264
|
+
ssl = OpenSSL::SSL::SSLSocket.new socket, context
|
265
|
+
|
266
|
+
ssl.accept
|
267
|
+
socket = ssl
|
268
|
+
end
|
269
|
+
|
270
|
+
@connections.things[socket] = @connections.clients[socket] = Server::Client.new(server, socket, listen)
|
271
|
+
@connections.sockets.push(socket)
|
272
|
+
|
273
|
+
@input[socket]
|
274
|
+
rescue OpenSSL::SSL::SSLError
|
275
|
+
socket.write_nonblock "This is a SSL connection, faggot.\r\n" rescue nil
|
276
|
+
self.debug "#{host}[#{ip}/#{port}] tried to connect to a SSL connection and failed the handshake."
|
277
|
+
socket.close rescue nil
|
278
|
+
rescue Errno::ECONNRESET
|
279
|
+
socket.close rescue nil
|
280
|
+
self.debug "#{host}[#{ip}/#{port}] connection reset."
|
281
|
+
rescue Exception => e
|
282
|
+
socket.close rescue nil
|
283
|
+
self.debug(e)
|
284
|
+
end
|
285
|
+
}
|
286
|
+
end
|
287
|
+
|
288
|
+
def read (timeout=0.1)
|
289
|
+
begin
|
290
|
+
reading, = IO::select @connections.sockets, nil, nil, timeout
|
291
|
+
rescue IOError
|
292
|
+
@connections.sockets.each {|socket|
|
293
|
+
if socket.closed?
|
294
|
+
server.kill socket
|
295
|
+
end
|
296
|
+
}
|
297
|
+
rescue Exception => e
|
298
|
+
self.debug e
|
299
|
+
end
|
300
|
+
|
301
|
+
if !reading
|
302
|
+
return
|
303
|
+
end
|
304
|
+
|
305
|
+
reading.each {|socket|
|
306
|
+
thing = thing socket
|
307
|
+
|
308
|
+
begin
|
309
|
+
input = socket.read_nonblock 2048
|
310
|
+
|
311
|
+
if !input || input.empty?
|
312
|
+
raise Errno::EPIPE
|
313
|
+
end
|
314
|
+
|
315
|
+
input.split(/[\r\n]+/).each {|string|
|
316
|
+
@input.push(socket, string)
|
317
|
+
}
|
318
|
+
rescue IOError
|
319
|
+
server.kill thing, 'Input/output error', true
|
320
|
+
rescue Errno::EBADF, Errno::EPIPE, OpenSSL::SSL::SSLError
|
321
|
+
server.kill thing, 'Client exited', true
|
322
|
+
rescue Errno::ECONNRESET
|
323
|
+
server.kill thing, 'Connection reset by peer', true
|
324
|
+
rescue Errno::ETIMEDOUT
|
325
|
+
server.kill thing, 'Ping timeout', true
|
326
|
+
rescue Errno::EHOSTUNREACH
|
327
|
+
server.kill thing, 'No route to host', true
|
328
|
+
rescue Errno::EAGAIN, IO::WaitReadable
|
329
|
+
rescue Exception => e
|
330
|
+
self.debug e
|
331
|
+
end
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
def clean
|
336
|
+
@disconnecting.each {|data|
|
337
|
+
thing = data[:thing]
|
338
|
+
output = data[:output]
|
339
|
+
|
340
|
+
if output.first == :EOC
|
341
|
+
output.shift
|
342
|
+
handleDisconnection thing, output.shift
|
343
|
+
@disconnecting.delete(data)
|
344
|
+
end
|
345
|
+
}
|
346
|
+
end
|
347
|
+
|
348
|
+
def handle
|
349
|
+
@input.each {|socket|
|
350
|
+
if dispatcher.event.handling[socket] || @input.empty?(socket)
|
351
|
+
next
|
352
|
+
end
|
353
|
+
|
354
|
+
Thread.new {
|
355
|
+
begin
|
356
|
+
if string = @input.pop(socket)
|
357
|
+
dispatcher.dispatch(:input, thing(socket), string)
|
358
|
+
end
|
359
|
+
rescue Exception => e
|
360
|
+
self.debug e
|
361
|
+
end
|
362
|
+
}
|
363
|
+
}
|
364
|
+
end
|
365
|
+
|
366
|
+
def write (timeout=0)
|
367
|
+
begin
|
368
|
+
none, writing, erroring = IO::select nil, @connections.sockets, nil, timeout
|
369
|
+
rescue IOError
|
370
|
+
@connections.sockets.each {|socket|
|
371
|
+
if socket.closed?
|
372
|
+
server.kill thing socket, 'Client exited'
|
373
|
+
end
|
374
|
+
}
|
375
|
+
rescue Exception => e
|
376
|
+
self.debug e
|
377
|
+
end
|
378
|
+
|
379
|
+
if !writing
|
380
|
+
return
|
381
|
+
end
|
382
|
+
|
383
|
+
writing.each {|socket|
|
384
|
+
if @output.empty?(socket)
|
385
|
+
next
|
386
|
+
end
|
387
|
+
|
388
|
+
thing = thing socket
|
389
|
+
|
390
|
+
begin
|
391
|
+
while !@output.empty?(socket)
|
392
|
+
output = @output.first(socket)
|
393
|
+
|
394
|
+
if output == :EOC
|
395
|
+
@output.delete(socket)
|
396
|
+
else
|
397
|
+
output.force_encoding 'ASCII-8BIT'
|
398
|
+
socket.write_nonblock "#{output}\r\n"
|
399
|
+
|
400
|
+
@output.pop(socket)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
rescue IOError
|
404
|
+
server.kill thing, 'Input/output error', true
|
405
|
+
rescue Errno::EBADF, Errno::EPIPE, OpenSSL::SSL::SSLError
|
406
|
+
server.kill thing, 'Client exited', true
|
407
|
+
rescue Errno::ECONNRESET
|
408
|
+
server.kill thing, 'Connection reset by peer', true
|
409
|
+
rescue Errno::ETIMEDOUT
|
410
|
+
server.kill thing, 'Ping timeout', true
|
411
|
+
rescue Errno::EHOSTUNREACH
|
412
|
+
server.kill thing, 'No route to host', true
|
413
|
+
rescue Errno::EAGAIN, IO::WaitWritable
|
414
|
+
rescue Exception => e
|
415
|
+
self.debug e
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
def handleDisconnection (thing, message)
|
421
|
+
@dispatcher.execute(:kill, thing, message) rescue nil
|
422
|
+
|
423
|
+
if thing.is_a?(Client)
|
424
|
+
thing.modes[:quitting] = true
|
425
|
+
|
426
|
+
if thing.modes[:registered]
|
427
|
+
thing.channels.each_value {|channel|
|
428
|
+
channel.users.delete(thing.nick)
|
429
|
+
}
|
430
|
+
end
|
431
|
+
elsif thing.is_a?(Link)
|
432
|
+
# wat
|
433
|
+
end
|
434
|
+
|
435
|
+
@input.delete(thing.socket)
|
436
|
+
@output.delete(thing.socket)
|
437
|
+
connections.delete(thing.socket)
|
438
|
+
|
439
|
+
self.debug "#{thing.mask}[#{thing.ip}/#{thing.port}] disconnected."
|
440
|
+
|
441
|
+
thing.socket.close rescue nil
|
442
|
+
end
|
443
|
+
|
444
|
+
def finalize
|
445
|
+
begin
|
446
|
+
@connections.listening[:sockets].each {|server|
|
447
|
+
server.close
|
448
|
+
}
|
449
|
+
|
450
|
+
@clients.each {|key, client|
|
451
|
+
kill client, 'Good night sweet prince.'
|
452
|
+
}
|
453
|
+
|
454
|
+
@links.each {|key, link|
|
455
|
+
kill client, 'Good night sweet prince.'
|
456
|
+
}
|
457
|
+
rescue Exception => e
|
458
|
+
self.debug e
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def thing (identifier)
|
463
|
+
if identifier.is_a?(Client) || identifier.is_a?(Link)
|
464
|
+
return identifier
|
465
|
+
elsif identifier.is_a?(User)
|
466
|
+
return identifier.client
|
467
|
+
else
|
468
|
+
return @connections.things[identifier]
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
end
|