Merge remote branch 'jacop/ssl'

Conflicts:
	asyncoperations.lua
This commit is contained in:
Joshua Simmons 2010-07-19 11:18:07 +10:00
commit 01ffa79347
2 changed files with 359 additions and 362 deletions

View File

@ -7,11 +7,7 @@ local meta = _META
function meta:send(fmt, ...) function meta:send(fmt, ...)
local bytes, err = self.socket:send(fmt:format(...) .. "\r\n") local bytes, err = self.socket:send(fmt:format(...) .. "\r\n")
if bytes then if not bytes and err ~= "timeout" and err ~= "wantwrite" then
return
end
if err ~= "timeout" and err ~= "wantwrite" then
self:invoke("OnDisconnect", err, true) self:invoke("OnDisconnect", err, true)
self:shutdown() self:shutdown()
error(err, errlevel) error(err, errlevel)
@ -73,12 +69,12 @@ function meta:setMode(t)
assert(add or rem, "table contains neither 'add' nor 'remove'") assert(add or rem, "table contains neither 'add' nor 'remove'")
if add then if add then
mode = table.concat{"+", add} mode = table.concat{"+", verify(add, 3)}
end end
if rem then if rem then
mode = table.concat{mode, "-", rem} mode = table.concat{mode, "-", verify(rem, 3)}
end end
self:send("MODE %s %s", verify(target, 3), verify(mode, 3)) self:send("MODE %s %s", verify(target, 3), mode)
end end

709
init.lua
View File

@ -1,354 +1,355 @@
local socket = require "socket" local socket = require "socket"
local error = error local error = error
local setmetatable = setmetatable local setmetatable = setmetatable
local rawget = rawget local rawget = rawget
local unpack = unpack local unpack = unpack
local pairs = pairs local pairs = pairs
local assert = assert local assert = assert
local require = require local require = require
local tonumber = tonumber local tonumber = tonumber
local type = type local type = type
local pcall = pcall local pcall = pcall
module "irc" module "irc"
local meta = {} local meta = {}
meta.__index = meta meta.__index = meta
_META = meta _META = meta
require "irc.util" require "irc.util"
require "irc.asyncoperations" require "irc.asyncoperations"
local meta_preconnect = {} local meta_preconnect = {}
function meta_preconnect.__index(o, k) function meta_preconnect.__index(o, k)
local v = rawget(meta_preconnect, k) local v = rawget(meta_preconnect, k)
if not v and meta[k] then if not v and meta[k] then
error("field '"..k.."' is not accessible before connecting", 2) error("field '"..k.."' is not accessible before connecting", 2)
end end
return v return v
end end
function new(user) function new(user)
local o = { local o = {
nick = assert(user.nick, "Field 'nick' is required"); nick = assert(user.nick, "Field 'nick' is required");
username = user.username or "lua"; username = user.username or "lua";
realname = user.realname or "Lua owns"; realname = user.realname or "Lua owns";
hooks = {}; hooks = {};
track_users = true; track_users = true;
} }
return setmetatable(o, meta_preconnect) return setmetatable(o, meta_preconnect)
end end
function meta:hook(name, id, f) function meta:hook(name, id, f)
f = f or id f = f or id
self.hooks[name] = self.hooks[name] or {} self.hooks[name] = self.hooks[name] or {}
self.hooks[name][id] = f self.hooks[name][id] = f
return id or f return id or f
end end
meta_preconnect.hook = meta.hook meta_preconnect.hook = meta.hook
function meta:unhook(name, id) function meta:unhook(name, id)
local hooks = self.hooks[name] local hooks = self.hooks[name]
assert(hooks, "no hooks exist for this event") assert(hooks, "no hooks exist for this event")
assert(hooks[id], "hook ID not found") assert(hooks[id], "hook ID not found")
hooks[id] = nil hooks[id] = nil
end end
meta_preconnect.unhook = meta.unhook meta_preconnect.unhook = meta.unhook
function meta:invoke(name, ...) function meta:invoke(name, ...)
local hooks = self.hooks[name] local hooks = self.hooks[name]
if hooks then if hooks then
for id,f in pairs(hooks) do for id,f in pairs(hooks) do
if f(...) then if f(...) then
return true return true
end end
end end
end end
end end
function meta_preconnect:connect(_host, _port) function meta_preconnect:connect(_host, _port)
local host, port, password, secure, timeout local host, port, password, secure, timeout
if type(_host) == "table" then if type(_host) == "table" then
host = _host.host host = _host.host
port = _host.port port = _host.port
timeout = _host.timeout timeout = _host.timeout
password = _host.password password = _host.password
secure = _host.secure secure = _host.secure
else else
host = _host host = _host
port = _port port = _port
end end
host = host or error("host name required to connect", 2) host = host or error("host name required to connect", 2)
port = port or 6667 port = port or 6667
local s = socket.tcp() local s = socket.tcp()
s:settimeout(timeout or 30) s:settimeout(timeout or 30)
assert(s:connect(host, port)) assert(s:connect(host, port))
if secure then if secure then
local work, ssl = pcall(require, "ssl") local work, ssl = pcall(require, "ssl")
if not work then if not work then
error("LuaSec required for secure connections", 2) error("LuaSec required for secure connections", 2)
end end
local params local params
if type(secure) == "table" then if type(secure) == "table" then
params = secure params = secure
else else
params = {mode="client", protocol="tlsv1"} params = {mode = "client", protocol = "tlsv1"}
end end
s = ssl.wrap(s, params) s = ssl.wrap(s, params)
success, errmsg = s:dohandshake() success, errmsg = s:dohandshake()
if not success then if not success then
error(("could not make secure connection %s"):format(errmsg), 2) error(("could not make secure connection: %s"):format(errmsg), 2)
end end
end end
self.socket = s self.socket = s
setmetatable(self, meta) setmetatable(self, meta)
if password then if password then
self:send("PASS %s", password) self:send("PASS %s", password)
end end
self:send("USER %s 0 * :%s", self.username, self.realname) self:send("USER %s 0 * :%s", self.username, self.realname)
self:send("NICK %s", self.nick) self:send("NICK %s", self.nick)
self.channels = {} self.channels = {}
s:settimeout(0) s:settimeout(0)
repeat repeat
self:think() self:think()
until self.authed until self.authed
end end
function meta:disconnect(message) function meta:disconnect(message)
local message = message or "Bye!" message = message or "Bye!"
self:invoke("OnDisconnect", message, false) self:invoke("OnDisconnect", message, false)
self:send("QUIT :%s", message) self:send("QUIT :%s", message)
self:shutdown() self:shutdown()
end end
function meta:shutdown() function meta:shutdown()
self.socket:close() self.socket:close()
setmetatable(self, nil) setmetatable(self, nil)
end end
local function getline(self, errlevel) local function getline(self, errlevel)
local line, err = self.socket:receive("*l") local line, err = self.socket:receive("*l")
if line then if line then
return line return line
end end
if err ~= "timeout" and err ~= "wantread" then if err ~= "timeout" and err ~= "wantread" then
self:invoke("OnDisconnect", err, true) self:invoke("OnDisconnect", err, true)
self:close() self:close()
error(err, errlevel) error(err, errlevel)
end end
end end
function meta:think() function meta:think()
while true do while true do
local line = getline(self, 3) local line = getline(self, 3)
if line then if line then
if not self:invoke("OnRaw", line) then if not self:invoke("OnRaw", line) then
self:handle(parse(line)) self:handle(parse(line))
end end
else else
break break
end end
end end
end end
local handlers = {} local handlers = {}
handlers["PING"] = function(o, prefix, query) handlers["PING"] = function(o, prefix, query)
o:send("PONG :%s", query) o:send("PONG :%s", query)
end end
handlers["001"] = function(o) handlers["001"] = function(o)
o.authed = true o.authed = true
end end
handlers["PRIVMSG"] = function(o, prefix, channel, message) handlers["PRIVMSG"] = function(o, prefix, channel, message)
o:invoke("OnChat", parsePrefix(prefix), channel, message) o:invoke("OnChat", parsePrefix(prefix), channel, message)
end end
handlers["NOTICE"] = function(o, prefix, channel, message) handlers["NOTICE"] = function(o, prefix, channel, message)
o:invoke("OnNotice", parsePrefix(prefix), channel, message) o:invoke("OnNotice", parsePrefix(prefix), channel, message)
end end
handlers["JOIN"] = function(o, prefix, channel) handlers["JOIN"] = function(o, prefix, channel)
local user = parsePrefix(prefix) local user = parsePrefix(prefix)
if o.track_users then if o.track_users then
if user.nick == o.nick then if user.nick == o.nick then
o.channels[channel] = {users = {}} o.channels[channel] = {users = {}}
else else
o.channels[channel].users[user.nick] = user o.channels[channel].users[user.nick] = user
end end
end end
o:invoke("OnJoin", user, channel) o:invoke("OnJoin", user, channel)
end end
handlers["PART"] = function(o, prefix, channel, reason) handlers["PART"] = function(o, prefix, channel, reason)
local user = parsePrefix(prefix) local user = parsePrefix(prefix)
if o.track_users then if o.track_users then
if user.nick == o.nick then if user.nick == o.nick then
o.channels[channel] = nil o.channels[channel] = nil
else else
o.channels[channel].users[user.nick] = nil o.channels[channel].users[user.nick] = nil
end end
end end
o:invoke("OnPart", user, channel, reason) o:invoke("OnPart", user, channel, reason)
end end
handlers["QUIT"] = function(o, prefix, msg) handlers["QUIT"] = function(o, prefix, msg)
local user = parsePrefix(prefix) local user = parsePrefix(prefix)
if o.track_users then if o.track_users then
for channel, v in pairs(o.channels) do for channel, v in pairs(o.channels) do
v.users[user.nick] = nil v.users[user.nick] = nil
end end
end end
o:invoke("OnQuit", user, msg) o:invoke("OnQuit", user, msg)
end end
handlers["NICK"] = function(o, prefix, newnick) handlers["NICK"] = function(o, prefix, newnick)
local user = parsePrefix(prefix) local user = parsePrefix(prefix)
if o.track_users then if o.track_users then
for channel, v in pairs(o.channels) do for channel, v in pairs(o.channels) do
local users = v.users local users = v.users
local oldinfo = users[user.nick] local oldinfo = users[user.nick]
if oldinfo then if oldinfo then
users[newnick] = oldinfo users[newnick] = oldinfo
users[user.nick] = nil users[user.nick] = nil
o:invoke("NickChange", user, newnick, channel) o:invoke("NickChange", user, newnick, channel)
end end
end end
else else
o:invoke("NickChange", user, newnick) o:invoke("NickChange", user, newnick)
end end
end end
--NAMES list --NAMES list
handlers["353"] = function(o, prefix, me, chanType, channel, names) handlers["353"] = function(o, prefix, me, chanType, channel, names)
if o.track_users then if o.track_users then
o.channels[channel] = o.channels[channel] or {users = {}, type = chanType} o.channels[channel] = o.channels[channel] or {users = {}, type = chanType}
local users = o.channels[channel].users local users = o.channels[channel].users
for nick in names:gmatch("(%S+)") do for nick in names:gmatch("(%S+)") do
local access, name = parseNick(nick) local access, name = parseNick(nick)
users[name] = {type = access} users[name] = {type = access}
end end
end end
end end
--end of NAMES --end of NAMES
handlers["366"] = function(o, prefix, me, channel, msg) handlers["366"] = function(o, prefix, me, channel, msg)
if o.track_users then if o.track_users then
o:invoke("NameList", channel, msg) o:invoke("NameList", channel, msg)
end end
end end
--no topic --no topic
handlers["331"] = function(o, prefix, me, channel) handlers["331"] = function(o, prefix, me, channel)
o:invoke("OnTopic", channel, nil) o:invoke("OnTopic", channel, nil)
end end
--new topic --new topic
handlers["TOPIC"] = function(o, prefix, channel, topic) handlers["TOPIC"] = function(o, prefix, channel, topic)
o:invoke("OnTopic", channel, topic) o:invoke("OnTopic", channel, topic)
end end
handlers["332"] = function(o, prefix, me, channel, topic) handlers["332"] = function(o, prefix, me, channel, topic)
o:invoke("OnTopic", channel, topic) o:invoke("OnTopic", channel, topic)
end end
--topic creation info --topic creation info
handlers["333"] = function(o, prefix, me, channel, nick, time) handlers["333"] = function(o, prefix, me, channel, nick, time)
o:invoke("OnTopicInfo", channel, nick, tonumber(time)) o:invoke("OnTopicInfo", channel, nick, tonumber(time))
end end
handlers["KICK"] = function(o, prefix, channel, kicked, reason) handlers["KICK"] = function(o, prefix, channel, kicked, reason)
o:invoke("OnKick", channel, kicked, parsePrefix(prefix), reason) o:invoke("OnKick", channel, kicked, parsePrefix(prefix), reason)
end end
--RPL_UMODEIS --RPL_UMODEIS
--To answer a query about a client's own mode, RPL_UMODEIS is sent back --To answer a query about a client's own mode, RPL_UMODEIS is sent back
handlers["221"] = function(o, prefix, modes) handlers["221"] = function(o, prefix, modes)
o:invoke("OnUserModeIs", modes) o:invoke("OnUserModeIs", modes)
end end
--RPL_CHANNELMODEIS --RPL_CHANNELMODEIS
--The result from common irc servers differs from that defined by the rfc --The result from common irc servers differs from that defined by the rfc
handlers["324"] = function(o, prefix, user, channel, modes) handlers["324"] = function(o, prefix, user, channel, modes)
o:invoke("OnChannelModeIs", user, channel, modes) o:invoke("OnChannelModeIs", user, channel, modes)
end end
handlers["ERROR"] = function(o, prefix, message) handlers["ERROR"] = function(o, prefix, message)
o:invoke("OnDisconnect", message, true) o:invoke("OnDisconnect", message, true)
o:shutdown() o:shutdown()
error(message, 3) error(message, 3)
end end
function meta:handle(prefix, cmd, params) function meta:handle(prefix, cmd, params)
local handler = handlers[cmd] local handler = handlers[cmd]
if handler then if handler then
return handler(self, prefix, unpack(params)) return handler(self, prefix, unpack(params))
end end
end end
local whoisHandlers = { local whoisHandlers = {
["311"] = "userinfo"; ["311"] = "userinfo";
["312"] = "node"; ["312"] = "node";
["319"] = "channels"; ["319"] = "channels";
["330"] = "account"; -- Freenode ["330"] = "account"; -- Freenode
["307"] = "registered"; -- Unreal ["307"] = "registered"; -- Unreal
} }
function meta:whois(nick) function meta:whois(nick)
self:send("WHOIS %s", nick) self:send("WHOIS %s", nick)
local result = {} local result = {}
while true do while true do
local line = getline(self, 3) local line = getline(self, 3)
if line then if line then
local prefix, cmd, args = parse(line) local prefix, cmd, args = parse(line)
local handler = whoisHandlers[cmd] local handler = whoisHandlers[cmd]
if handler then if handler then
result[handler] = args result[handler] = args
elseif cmd == "318" then elseif cmd == "318" then
break break
else else
self:handle(prefix, cmd, args) self:handle(prefix, cmd, args)
end end
end end
end end
if result.account then if result.account then
result.account = result.account[3] result.account = result.account[3]
elseif result.registered then elseif result.registered then
result.account = result.registered[2] result.account = result.registered[2]
end end
return result return result
end end
function meta:topic(channel) function meta:topic(channel)
self:send("TOPIC %s", channel) self:send("TOPIC %s", channel)
end end