syndi 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +12 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +8 -0
- data/INSTALL.md +86 -0
- data/LICENSE +28 -0
- data/README.md +104 -0
- data/Rakefile +26 -0
- data/WINDOWS.md +64 -0
- data/bin/syndi +102 -0
- data/bin/syndi-conf +47 -0
- data/conf/example.yml +101 -0
- data/docs/Events.md +103 -0
- data/docs/Upgrade.md +16 -0
- data/ext/csyndi/events.c +50 -0
- data/ext/csyndi/extconf.rb +20 -0
- data/ext/csyndi/integer.c +53 -0
- data/ext/csyndi/libauto.c +37 -0
- data/ext/csyndi/logger.c +228 -0
- data/include/syndi/csyndi.h +38 -0
- data/include/syndi/events.h +19 -0
- data/include/syndi/integer.h +17 -0
- data/include/syndi/logger.h +57 -0
- data/include/syndi.h +22 -0
- data/lib/syndi/actress.rb +12 -0
- data/lib/syndi/api/events.rb +170 -0
- data/lib/syndi/api/object.rb +29 -0
- data/lib/syndi/api/plugin.rb +155 -0
- data/lib/syndi/api.rb +7 -0
- data/lib/syndi/bot.rb +270 -0
- data/lib/syndi/config.rb +113 -0
- data/lib/syndi/configure/cli.rb +23 -0
- data/lib/syndi/configure/generator.rb +410 -0
- data/lib/syndi/configure.rb +19 -0
- data/lib/syndi/dsl/base.rb +74 -0
- data/lib/syndi/dsl/irc.rb +13 -0
- data/lib/syndi/events.rb +114 -0
- data/lib/syndi/irc/common.rb +63 -0
- data/lib/syndi/irc/library.rb +89 -0
- data/lib/syndi/irc/object/channel.rb +21 -0
- data/lib/syndi/irc/object/entity.rb +90 -0
- data/lib/syndi/irc/object/message.rb +99 -0
- data/lib/syndi/irc/object/user.rb +139 -0
- data/lib/syndi/irc/protocol/numerics.rb +60 -0
- data/lib/syndi/irc/protocol.rb +164 -0
- data/lib/syndi/irc/sasl/diffie_hellman.rb +36 -0
- data/lib/syndi/irc/sasl/mech/dh_blowfish.rb +83 -0
- data/lib/syndi/irc/sasl/mech/plain.rb +39 -0
- data/lib/syndi/irc/sasl/mech.rb +15 -0
- data/lib/syndi/irc/server.rb +301 -0
- data/lib/syndi/irc/state/channel_manager.rb +6 -0
- data/lib/syndi/irc/state/support.rb +142 -0
- data/lib/syndi/irc/state/user_manager.rb +6 -0
- data/lib/syndi/irc/std/commands.rb +99 -0
- data/lib/syndi/irc/std/numerics.rb +216 -0
- data/lib/syndi/irc.rb +8 -0
- data/lib/syndi/jewel/specification.rb +121 -0
- data/lib/syndi/jewel/util.rb +27 -0
- data/lib/syndi/jewel.rb +5 -0
- data/lib/syndi/rubyext/string.rb +10 -0
- data/lib/syndi/verbosity.rb +10 -0
- data/lib/syndi/version.rb +38 -0
- data/lib/syndi.rb +129 -0
- data/spec/helper.rb +32 -0
- data/spec/syndi/events_spec.rb +43 -0
- data/tasks/compile.rake +15 -0
- data/tasks/install.rake +10 -0
- data/tasks/package.rake +13 -0
- data/tasks/spec.rake +12 -0
- metadata +101 -13
@@ -0,0 +1,164 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'syndi/irc/protocol/numerics'
|
5
|
+
|
6
|
+
# namespace Syndi
|
7
|
+
module Syndi
|
8
|
+
|
9
|
+
# namespace IRC
|
10
|
+
module IRC
|
11
|
+
syndiload :SASL, 'syndi/irc/sasl/mech'
|
12
|
+
|
13
|
+
# A class for parsing of data per the specifications of the IRC protocol,
|
14
|
+
# v3.1.
|
15
|
+
#
|
16
|
+
# @see http://tools.ietf.org/html/rfc1459
|
17
|
+
# @see http://ircv3.atheme.org/
|
18
|
+
#
|
19
|
+
# @since 4.0.0
|
20
|
+
class Protocol
|
21
|
+
|
22
|
+
# Construct a new IRC data parser.
|
23
|
+
#
|
24
|
+
# @param [Syndi::IRC::Library] lib The IRC library instance.
|
25
|
+
def initialize lib
|
26
|
+
extend Syndi::IRC::Protocol::Numerics
|
27
|
+
lib.events.on :receive, &method(:parse)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse IRC data.
|
31
|
+
#
|
32
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
33
|
+
# @param [String] raw The data received.
|
34
|
+
def parse irc, raw
|
35
|
+
|
36
|
+
params = raw.split(/\s+/)
|
37
|
+
command = (raw =~ /^:/ ? params[1] : params[0]).dc
|
38
|
+
|
39
|
+
# Check if we process this command.
|
40
|
+
if respond_to? "on_#{command}"
|
41
|
+
send("on_#{command}", irc, raw, params)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# AUTHENTICATE
|
47
|
+
#
|
48
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
49
|
+
# @param [String] raw The data received.
|
50
|
+
# @param [Array<String>] params The data received divided by +\s+ through
|
51
|
+
# regexp.
|
52
|
+
def on_authenticate irc, raw, params
|
53
|
+
username = $m.conf['irc'][irc.s]['SASL']['username']
|
54
|
+
password = $m.conf['irc'][irc.s]['SASL']['password']
|
55
|
+
|
56
|
+
if irc.supp.sasl_method == :dh_blowfish
|
57
|
+
crypt = Syndi::IRC::SASL::Mech::DHBlowfish.encrypt(username, password, params.last)
|
58
|
+
elsif irc.supp.sasl_method == :plain
|
59
|
+
crypt = Syndi::IRC::SASL::Mech::Plain.encrypt(username, password, params.last)
|
60
|
+
end
|
61
|
+
|
62
|
+
while crypt.length >= 400
|
63
|
+
irc.snd "AUTHENTICATE #{crypt.slice!(0, 400)}"
|
64
|
+
end
|
65
|
+
|
66
|
+
if crypt.length > 0
|
67
|
+
irc.snd "AUTHENTICATE #{crypt}"
|
68
|
+
else
|
69
|
+
irc.snd('AUTHENTICATE +')
|
70
|
+
end
|
71
|
+
# And we're done!
|
72
|
+
end
|
73
|
+
|
74
|
+
# CAP
|
75
|
+
#
|
76
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
77
|
+
# @param [String] raw The data received.
|
78
|
+
# @param [Array<String>] params The data received divided by +\s+ through
|
79
|
+
# regexp.
|
80
|
+
def on_cap irc, raw, params
|
81
|
+
case params[3]
|
82
|
+
|
83
|
+
when 'LS'
|
84
|
+
params[4].gsub!(/^:/, '')
|
85
|
+
cap_ls(irc, params[4..-1])
|
86
|
+
when 'ACK'
|
87
|
+
params[4].gsub!(/^:/, '')
|
88
|
+
cap_ack(irc, params[4..-1])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# PING
|
93
|
+
#
|
94
|
+
# Return a PONG.
|
95
|
+
#
|
96
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
97
|
+
# @param [String] raw The data received.
|
98
|
+
# @param [Array<String>] params The data received divided by +\s+ through
|
99
|
+
# regexp.
|
100
|
+
def on_ping irc, raw, params
|
101
|
+
irc.snd("PONG #{params[1]}")
|
102
|
+
end
|
103
|
+
|
104
|
+
#######
|
105
|
+
private
|
106
|
+
#######
|
107
|
+
|
108
|
+
# CAP LS
|
109
|
+
#
|
110
|
+
# Currently, Syndi's capabilities include the multi-prefix, sasl,
|
111
|
+
# account-notify, away-notify, and extended-join extensions.
|
112
|
+
#
|
113
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
114
|
+
# @param [Array<String>] list List of capabilities.
|
115
|
+
def cap_ls irc, list
|
116
|
+
|
117
|
+
req = []
|
118
|
+
|
119
|
+
# Every extension possible will be used except SASL, which requires
|
120
|
+
# special configuration.
|
121
|
+
%w[multi-prefix account-notify away-notify extended-join].each do |ext|
|
122
|
+
req.push ext if list.include? ext
|
123
|
+
end
|
124
|
+
|
125
|
+
if $m.conf['irc'][irc.s]['SASL'] and list.include? 'sasl'
|
126
|
+
req.push 'sasl'
|
127
|
+
end
|
128
|
+
|
129
|
+
# Send CAP REQ.
|
130
|
+
irc.snd("CAP REQ :#{req.join(' ')}") unless req.empty?
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# CAP ACK
|
135
|
+
#
|
136
|
+
# We must save all capabilities into +irc.supp.cap+, and initiate SASL
|
137
|
+
# if possible.
|
138
|
+
#
|
139
|
+
# @param [Syndi::IRC::Server] irc The IRC connection.
|
140
|
+
# @param [Array<String>] list List of capabilities.
|
141
|
+
def cap_ack irc, list
|
142
|
+
|
143
|
+
irc.supp.cap = list
|
144
|
+
|
145
|
+
if list.include? 'sasl'
|
146
|
+
irc.supp.sasl_id << $m.clock.spawn($m.conf['irc'][irc.s]['SASL']['timeout']||10, :once, irc) do |s|
|
147
|
+
$m.error "SASL authentication on #{s} failed: authentication procedure timed out."
|
148
|
+
s.snd('AUTHENTICATE *')
|
149
|
+
s.cap_end
|
150
|
+
end
|
151
|
+
irc.authenticate :dh_blowfish
|
152
|
+
else
|
153
|
+
irc.snd('CAP END') # end capability negotiation and complete registration
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end # class Protocol
|
159
|
+
|
160
|
+
end # module IRC
|
161
|
+
|
162
|
+
end # module Syndi
|
163
|
+
|
164
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This code exists in the public domain.
|
2
|
+
|
3
|
+
class DiffieHellman
|
4
|
+
|
5
|
+
attr_reader :p, :g, :q, :x, :e
|
6
|
+
|
7
|
+
# p is the prime, g the generator and q order of the subgroup
|
8
|
+
def initialize p, g, q
|
9
|
+
@p = p
|
10
|
+
@g = g
|
11
|
+
@q = q
|
12
|
+
end
|
13
|
+
|
14
|
+
# generate the [secret] random value and the public key
|
15
|
+
def generate tries=16
|
16
|
+
tries.times do
|
17
|
+
@x = rand(@q)
|
18
|
+
@e = self.g.mod_exp(@x, self.p)
|
19
|
+
return @e if self.valid?
|
20
|
+
end
|
21
|
+
raise ArgumentError, "can't generate valid e"
|
22
|
+
end
|
23
|
+
|
24
|
+
# validate a public key
|
25
|
+
def valid?(_e = self.e)
|
26
|
+
_e and _e.between?(2, self.p-2) and _e.bits_set > 1
|
27
|
+
end
|
28
|
+
|
29
|
+
# compute the shared secret, given the public key
|
30
|
+
def secret f
|
31
|
+
f.mod_exp(self.x, self.p)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require 'openssl'
|
6
|
+
require 'libsyndi'
|
7
|
+
require 'syndi/irc/sasl/diffie_hellman'
|
8
|
+
|
9
|
+
module Syndi
|
10
|
+
|
11
|
+
module IRC
|
12
|
+
|
13
|
+
module SASL
|
14
|
+
|
15
|
+
module Mech
|
16
|
+
|
17
|
+
# Module which implements the SASL DH-BLOWFISH mechanism.
|
18
|
+
# @author noxgirl
|
19
|
+
module DHBlowfish
|
20
|
+
|
21
|
+
# Create an SASL-encrypted hash.
|
22
|
+
#
|
23
|
+
# @param [String] username The username.
|
24
|
+
# @param [String] password The password associated with the username.
|
25
|
+
# @param [String] provision The key provided by the server.
|
26
|
+
def self.encrypt username, password, provision
|
27
|
+
# This is a fairly complex process. Duplicate +username+ and
|
28
|
+
# +password+ for safety.
|
29
|
+
user = username.dup
|
30
|
+
pass = password.dup
|
31
|
+
|
32
|
+
# Decode the key from base64.
|
33
|
+
key = Base64.decode64(provision).force_encoding('ASCII-8BIT')
|
34
|
+
|
35
|
+
# Unpack it.
|
36
|
+
p, g, y = unpack_key key
|
37
|
+
|
38
|
+
dh = DiffieHellman.new(p, g, 23)
|
39
|
+
pkey = dh.generate
|
40
|
+
secret = OpenSSL::BN.new(dh.secret(y).to_s).to_s(2)
|
41
|
+
pub = OpenSSL::BN.new(pkey.to_s).to_s(2)
|
42
|
+
|
43
|
+
pass.concat "\0"
|
44
|
+
pass.concat('.' * (8 - (password.size % 8)))
|
45
|
+
|
46
|
+
cipher = OpenSSL::Cipher::Cipher.new 'BF-ECB'
|
47
|
+
cipher.key_len = 32
|
48
|
+
cipher.encrypt
|
49
|
+
cipher.key = secret
|
50
|
+
|
51
|
+
enc = cipher.update(pass).to_s
|
52
|
+
answer = [pub.bytesize, pub, user, enc].pack('na*Z*a*')
|
53
|
+
|
54
|
+
Base64.strict_encode64(answer) # finally, return the hash
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array(Numeric, Numeric, Numeric)] +p+, +g+, and +y+ for
|
58
|
+
# Diffie-Hellman key exchange
|
59
|
+
def self.unpack_key key
|
60
|
+
key = key.dup
|
61
|
+
pgy = []
|
62
|
+
|
63
|
+
3.times do
|
64
|
+
size = key.unpack('n').first
|
65
|
+
key.slice! 0, 2
|
66
|
+
pgy << key.unpack("a#{size}").first
|
67
|
+
key.slice!(0, size)
|
68
|
+
end
|
69
|
+
|
70
|
+
pgy.map { |int| OpenSSL::BN.new(int, 2).to_i }
|
71
|
+
end
|
72
|
+
|
73
|
+
end # module DHBlowfish
|
74
|
+
|
75
|
+
end # module Mech
|
76
|
+
|
77
|
+
end # module SASL
|
78
|
+
|
79
|
+
end # module IRC
|
80
|
+
|
81
|
+
end # module Syndi
|
82
|
+
|
83
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Syndi
|
7
|
+
|
8
|
+
module IRC
|
9
|
+
|
10
|
+
module SASL
|
11
|
+
|
12
|
+
module Mech
|
13
|
+
|
14
|
+
# Module which implements the SASL PLAIN mechanism.
|
15
|
+
module Plain
|
16
|
+
|
17
|
+
# Create an SASL-encrypted hash.
|
18
|
+
#
|
19
|
+
# @author noxgirl
|
20
|
+
#
|
21
|
+
# @param [String] username The username.
|
22
|
+
# @param [String] password The password associated with the username.
|
23
|
+
# @param [String] provision The key provided by the server.
|
24
|
+
def self.encrypt username, password, provision
|
25
|
+
# Easy as this:
|
26
|
+
Base64.encode64([username, username, password].join("\0")).gsub(/\n/, '')
|
27
|
+
end
|
28
|
+
|
29
|
+
end # module Plain
|
30
|
+
|
31
|
+
end # module Mech
|
32
|
+
|
33
|
+
end # module SASL
|
34
|
+
|
35
|
+
end # module IRC
|
36
|
+
|
37
|
+
end # module Syndi
|
38
|
+
|
39
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
module Syndi
|
5
|
+
module IRC
|
6
|
+
module SASL
|
7
|
+
module Mech
|
8
|
+
syndiload :DHBlowfish, 'syndi/irc/sasl/mech/dh_blowfish'
|
9
|
+
syndiload :Plain, 'syndi/irc/sasl/mech/plain'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# vim: set ts=4 sts=2 sw=2 et:
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# Copyright (c) 2013, Autumn Perrault, et al. All rights reserved.
|
2
|
+
# This free software is distributed under the FreeBSD license (see LICENSE).
|
3
|
+
|
4
|
+
require 'ostruct'
|
5
|
+
require 'socket'
|
6
|
+
require 'openssl'
|
7
|
+
require 'thread'
|
8
|
+
require 'syndi/dsl/base'
|
9
|
+
require 'syndi/irc/state/support'
|
10
|
+
require 'syndi/irc/std/commands'
|
11
|
+
|
12
|
+
# namespace Syndi
|
13
|
+
module Syndi
|
14
|
+
|
15
|
+
# Entering namespace: IRC
|
16
|
+
module IRC
|
17
|
+
|
18
|
+
# A class which maintains a connection to an IRC server and provides a highly
|
19
|
+
# usable interface for the IRC server.
|
20
|
+
#
|
21
|
+
# @api IRC
|
22
|
+
# @since 4.0.0
|
23
|
+
# @author noxgirl
|
24
|
+
#
|
25
|
+
# @!attribute [r] socket
|
26
|
+
# @return [TCPSocket] The TCP socket being used for the connection.
|
27
|
+
#
|
28
|
+
# @!attribute [r] in
|
29
|
+
# @return [Integer] The number of bytes received from the socket.
|
30
|
+
#
|
31
|
+
# @!attribute [r] out
|
32
|
+
# @return [Integer] The number of bytes sent to the socket.
|
33
|
+
#
|
34
|
+
# @!attribute [r] type
|
35
|
+
# @return [Symbol] +:irc+
|
36
|
+
#
|
37
|
+
# @!attribute [r] supp
|
38
|
+
# @return [Syndi::IRC::State::Support] The IRCd capabilities.
|
39
|
+
#
|
40
|
+
# @!attribute name
|
41
|
+
# @return [String] The name of the server as specified by configuration.
|
42
|
+
#
|
43
|
+
# @!attribute address
|
44
|
+
# @return [String] The address used to connect to the server.
|
45
|
+
#
|
46
|
+
# @!attribute port
|
47
|
+
# @return [Integer] The port used to connect to the server
|
48
|
+
#
|
49
|
+
# @!attribute nick
|
50
|
+
# @return [String] The nickname of the bot on the server.
|
51
|
+
#
|
52
|
+
# @!attribute user
|
53
|
+
# @return [String] The username of the bot on the server.
|
54
|
+
#
|
55
|
+
# @!attribute real
|
56
|
+
# @return [String] The real name or GECOS of the bot on the server.
|
57
|
+
#
|
58
|
+
# @!attribute password
|
59
|
+
# @return [String] If needed, the password used to connect to the server
|
60
|
+
# @return [nil] If not needed.
|
61
|
+
#
|
62
|
+
# @!attribute bind
|
63
|
+
# @return [String] If desired, the address to which to bind for this socket
|
64
|
+
# @return [nil] If not desired.
|
65
|
+
# @note This appears to be unused at the moment.
|
66
|
+
#
|
67
|
+
# @!attribute ssl
|
68
|
+
# @return [true, false] If SSL should [not] be used for the connection.
|
69
|
+
#
|
70
|
+
# @!attribute sasl_id
|
71
|
+
# @return [String] If SASL is desired, the username with which to authenticate.
|
72
|
+
# @return [nil] If not used.
|
73
|
+
# @note This is seemingly deprecated?
|
74
|
+
#
|
75
|
+
# @!attribute connected
|
76
|
+
# @return [true, false] Whether or not we are connected to the server.
|
77
|
+
#
|
78
|
+
# @!attribute mask
|
79
|
+
# @return [String] The bot's own hostname or mask on the IRC server.
|
80
|
+
#
|
81
|
+
# @!attribute recvq
|
82
|
+
# @return [Array<String>] The socket's receive queue, which is comprised of an array
|
83
|
+
# of strings which are pending processing.
|
84
|
+
#
|
85
|
+
#
|
86
|
+
# @!attribute prefixes
|
87
|
+
# @return [Hash{String => String}] The IRC server's supported prefixes, with the key being
|
88
|
+
# the channel mode which represents the prefix, and the value being the prefix.
|
89
|
+
#
|
90
|
+
# @!attribute channel_modes
|
91
|
+
# @return [Hash{Symbol => Array<String>}] The IRC server's supported channel modes, divided as thus:
|
92
|
+
#
|
93
|
+
# - +:list+ = A list of modes which add/remove a nickname or mask from a channel list, such as ops and bans.
|
94
|
+
# - +:always+ = A llst of modes which change a channel setting, and always have a parameter.
|
95
|
+
# - +:set+ = A list of modes which change a channel setting, and which have a parameter only when set.
|
96
|
+
# - +:never+ = A list of modes which change a channel setting, and which never have a parameter.
|
97
|
+
#
|
98
|
+
# @!attribute max_modes
|
99
|
+
# @return [Integer] The maximum number of mode changes which may be specified in a /MODE query.
|
100
|
+
#
|
101
|
+
# @!attribute await_self_who
|
102
|
+
# @return [true, false] Whether or not we are awaiting for a response to a /WHO on ourselves.
|
103
|
+
#
|
104
|
+
# @!attribute channels
|
105
|
+
# @return [Hash{String => IRC::Object::Channel}] A list of channels in which we reside,
|
106
|
+
# with each key being the channel's name in all-lowercase, and the respective values
|
107
|
+
# being of {IRC::Object::Channel IRC::Object::Channel}.
|
108
|
+
#
|
109
|
+
# @!attribute users
|
110
|
+
# @return [Hash{String => IRC::Object::User}] A list of users who are known to us,
|
111
|
+
# with each key being the user's nickname in all-lowercase, and the respective values
|
112
|
+
# being of {IRC::Object::User IRC::Object::User}.
|
113
|
+
class Server
|
114
|
+
include Syndi::DSL::Base
|
115
|
+
|
116
|
+
attr_reader :socket, :in, :out, :type, :supp
|
117
|
+
attr_accessor :name, :address, :port, :nick, :user, :real, :password,
|
118
|
+
:bind, :ssl, :connected, :chans, :users
|
119
|
+
|
120
|
+
# Produce a new instance of {Syndi::IRC::Server}.
|
121
|
+
#
|
122
|
+
# @param [String] name The name of the server to which we should connect.
|
123
|
+
#
|
124
|
+
# @yieldparam [Syndi::IRC::Server] c This instance, intended for configuration of the
|
125
|
+
# attributes.
|
126
|
+
#
|
127
|
+
# Configuration attributes are +address+, +port+, +nick+, +user+, +real+,
|
128
|
+
# +password+, +bind+, and +ssl+.
|
129
|
+
#
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# irc = Syndi::IRC::Server.new('Freenode') do |c|
|
133
|
+
# c.address = 'irc.freenode.net'
|
134
|
+
# c.port = 7000
|
135
|
+
# c.nick = 'cowmoon'
|
136
|
+
# c.user = 'foo1'
|
137
|
+
# c.real = "The night is lovely."
|
138
|
+
# c.bind = 'localhost'
|
139
|
+
# c.ssl = true
|
140
|
+
# end
|
141
|
+
def initialize(name)
|
142
|
+
|
143
|
+
# Prepare attributes.
|
144
|
+
@name = name
|
145
|
+
@address = nil
|
146
|
+
@port = nil
|
147
|
+
@nick = nil
|
148
|
+
@user = nil
|
149
|
+
@real = nil
|
150
|
+
@password = nil
|
151
|
+
@bind = nil
|
152
|
+
@ssl = false
|
153
|
+
|
154
|
+
# Yield for configuration.
|
155
|
+
yield(self) if block_given? or raise ArgumentError, "Server #{name} unable to initialize because it was not configured."
|
156
|
+
|
157
|
+
# Additional instance attributes.
|
158
|
+
@in = 0
|
159
|
+
@out = 0
|
160
|
+
@socket = nil
|
161
|
+
@connected = false
|
162
|
+
@type = :irc
|
163
|
+
|
164
|
+
# Pull in commands.
|
165
|
+
extend Syndi::IRC::Std::Commands
|
166
|
+
# State managers.
|
167
|
+
@supp = Syndi::IRC::State::Support.new
|
168
|
+
@chans = nil
|
169
|
+
@users = nil
|
170
|
+
|
171
|
+
# Our receive queue remainder.
|
172
|
+
@recv_rem = nil
|
173
|
+
|
174
|
+
# Mutual exclusion for thread safety.
|
175
|
+
@lock = Mutex.new
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
# Establish (or attempt to) a connection with the server.
|
180
|
+
def connect
|
181
|
+
|
182
|
+
# Check for missing attributes.
|
183
|
+
begin
|
184
|
+
attribute_check
|
185
|
+
rescue => e
|
186
|
+
$m.error("Cannot connect to server #@name: #{e}", false, e.backtrace)
|
187
|
+
end
|
188
|
+
|
189
|
+
$m.info("Connecting to #@name @ #@address:#@port...")
|
190
|
+
|
191
|
+
# Create a new socket.
|
192
|
+
begin
|
193
|
+
socket = TCPSocket.new(@address, @port, @bind)
|
194
|
+
rescue => e
|
195
|
+
$m.error("Failed to connect to server #@name: #{e}", false, e.backtrace)
|
196
|
+
raise
|
197
|
+
end
|
198
|
+
|
199
|
+
# Wrap it in SSL if told to.
|
200
|
+
if ssl
|
201
|
+
begin
|
202
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket)
|
203
|
+
socket.connect
|
204
|
+
rescue => e
|
205
|
+
$m.error("Failed to connect to server #@name: #{e}", false, e.backtrace)
|
206
|
+
raise
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
@socket = socket
|
211
|
+
|
212
|
+
# Register.
|
213
|
+
emit :irc, :preconnect, self
|
214
|
+
pass @password if @password
|
215
|
+
snd 'CAP LS'
|
216
|
+
self.nickname = @nick
|
217
|
+
user @user, Socket.gethostname, @address, @real
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
# Send data to the socket.
|
222
|
+
#
|
223
|
+
# @param [String] data The string of data, which should not exceed 512 in length.
|
224
|
+
def snd data
|
225
|
+
$m.foreground("{irc-send} #@name << #{data}")
|
226
|
+
@socket.write("#{data}\r\n")
|
227
|
+
@out += "#{data}\r\n".length
|
228
|
+
end
|
229
|
+
|
230
|
+
# Receive data from the socket, and push it into the recvQ.
|
231
|
+
def recv
|
232
|
+
|
233
|
+
# Thread safety.
|
234
|
+
@lock.synchronize do
|
235
|
+
|
236
|
+
if @socket.nil? or @socket.eof?
|
237
|
+
return
|
238
|
+
end
|
239
|
+
|
240
|
+
# Read the data.
|
241
|
+
data = @socket.sysread(1024)
|
242
|
+
# Increase in.
|
243
|
+
@in += data.length
|
244
|
+
|
245
|
+
# Split the data.
|
246
|
+
recv = []
|
247
|
+
until data !~ /\r\n/
|
248
|
+
line, data = data.split(/(?<=\r\n)/, 2)
|
249
|
+
recv.push line.chomp "\r\n"
|
250
|
+
end
|
251
|
+
|
252
|
+
# Check if there's a remainder in the recvQ.
|
253
|
+
if @recv_rem != ''
|
254
|
+
recv[0] = "#@recv_rem#{recv[0]}"
|
255
|
+
@recv_rem = ''
|
256
|
+
end
|
257
|
+
@recv_rem = data if data != ''
|
258
|
+
|
259
|
+
# Lastly, sent the data out
|
260
|
+
recv.each do |dline|
|
261
|
+
$m.foreground("{irc-recv} #@name >> #{dline}")
|
262
|
+
emit :irc, :receive, self, dline # send it out to :receive
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
def to_s; @name; end
|
270
|
+
def inspect; "#<Syndi::IRC::Server: name='#@name'>"; end
|
271
|
+
alias_method :s, :to_s
|
272
|
+
|
273
|
+
#######
|
274
|
+
private
|
275
|
+
#######
|
276
|
+
|
277
|
+
# Check the presence of all attributes.
|
278
|
+
def attribute_check
|
279
|
+
raise(ConfigError, "Missing server address") unless @address
|
280
|
+
raise(ConfigError, "Missing server port") unless @port
|
281
|
+
raise(ConfigError, "Missing nickname to use") unless @nick
|
282
|
+
raise(ConfigError, "Missing username to use") unless @user
|
283
|
+
raise(ConfigError, "Missing realname to use") unless @real
|
284
|
+
end
|
285
|
+
|
286
|
+
# Check if we are connected.
|
287
|
+
#
|
288
|
+
# @return [true, false]
|
289
|
+
def connected?
|
290
|
+
return false unless @socket
|
291
|
+
return false unless @connected
|
292
|
+
true
|
293
|
+
end
|
294
|
+
|
295
|
+
end # class Server
|
296
|
+
|
297
|
+
end # module IRC
|
298
|
+
|
299
|
+
end # module Syndi
|
300
|
+
|
301
|
+
# vim: set ts=4 sts=2 sw=2 et:
|