[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Code for a simple IRC server
- From: Joshua Jensen <jjensen@...>
- Date: Tue, 12 Feb 2008 13:16:58 -0700
As I was digging through old Lua script I wrote some time ago, I came
across a simple IRC server I wrote based on the Tcl IRCd
(http://www.hping.org/tclircd/) by Salvatore Sanfilippo. The code was
written with LuaPlus, although I suspect it will work with the plain Lua
binaries just fine.
Use your IRC client to connect to localhost. Any Lua code entered in
the #lua channel is executed on the server, and the results are fed back
to the channel.
for index = 1, 10 do print(index) end
In case the formatting goes wonky below, you can find the code at
http://luaplus.org/samples/ircserver.lua and
http://luaplus.org/samples/ircclient.lua.
Josh
------------ ircclient.lua
local socket = require("socket")
local c = assert(socket.connect("localhost", 6667))
c:send("NICK MyNick2\n")
c:send("USER MyName 0 * :My Name\n")
while not c:receive("*l"):find("004") do end
c:send("PRIVMSG #test :Hello everyone!\n")
----------- ircserver.lua
-- Based on The Tcl IRCd (http://www.hping.org/tclircd/) by Salvatore
Sanfilippo.
local socket = require("socket")
local hostname = "localhost"
local port = 6667
local version = "LuaIRCD"
local ChannelInfo = {}
local ClientMap = {}
local NickToClientInfo = {}
local ChannelPrivateMessage = { }
function newset()
local reverse = {}
local set = {}
setmetatable(set, { __index = {
insert = function(set, value)
table.insert(set, value)
reverse[value] = table.getn(set)
ClientMap[value] =
{
Client = value,
State = "UNREGISTERED",
Host = type(value) == "tcp{client}" and
value:getpeername() or "localhost",
Port = 0,
Nick = nil,
User = nil,
RealName = nil,
Channels = {},
}
end,
remove = function(set, value)
local clientInfo = ClientMap[value]
if clientInfo and clientInfo.Nick then
NickToClientInfo[clientInfo.Nick] = nil
end
table.remove(set, reverse[value])
reverse[value] = nil
ClientMap[value] = nil
end,
}})
return set
end
local set = newset()
function ircWrite(clientInfo, text)
local nick = ""
if clientInfo then nick = clientInfo.Nick or "" end
io.write('->' .. nick .. ' ' .. text .. "\n")
local bytes, error = clientInfo.Client:send(text .. "\r\n")
if error then
clientInfo.Client:close()
io.write("Removing client from set\n")
set:remove(clientInfo.Client)
end
end
function SendRawMessage(clientInfo, text)
ircWrite(clientInfo, ":" .. hostname .. " " .. text)
end
function SendServerClientMessage(clientInfo, code, text)
local codeStr = tostring(code)
if codeStr:len() == 1 then
codeStr = '00' .. codeStr
elseif codeStr:len() == 2 then
codeStr = '0' .. codeStr
end
ircWrite(clientInfo, ":" .. hostname .. " " .. codeStr .. " " ..
clientInfo.Nick .. " " .. text)
end
function SendUserMessage(clientInfo, target, text, noSelf)
local userStr = ":" .. clientInfo.Nick .. "!" .. clientInfo.User ..
"@" .. clientInfo.Host
if target:sub(1, 1) == "#" then
local channel = ChannelInfo[target]
for _, user in pairs(channel.userList) do
if noSelf == true and user.clientInfo == clientInfo then
else
ircWrite(user.clientInfo, userStr .. " " .. text)
end
end
else
local targetInfo = ClientMap[target]
if targetInfo then
ircWrite(targetInfo.Client, userStr .. " " .. text)
end
end
end
function Command_JOIN(clientInfo, channels)
for channelName in channels:gfind('([^ ,]+),*') do
local channel = ChannelInfo[channelName]
if not channel then
channel =
{
topic = "",
userList = {},
numUsers = 0,
name = channelName,
}
ChannelInfo[channelName] = channel
end
local channelUser = channel.userList[clientInfo]
if not channelUser then
clientInfo.Channels[channelName] = channel
channel.userList[clientInfo] = { clientInfo = clientInfo,
mode = channel.numUsers > 0 and "" or "@" }
channel.numUsers = channel.numUsers + 1
SendUserMessage(clientInfo, channelName, "JOIN " .. channelName)
Command_TOPIC(clientInfo, channelName .. " :" .. channel.topic)
Command_NAMES(clientInfo, channelName)
end
end
end
function Command_LIST(clientInfo, args)
local target
if args then
local _, _, target = args:find("^:%s*(.+)%s*$")
end
for channelName, channel in pairs(clientInfo.Channels) do
SendServerClientMessage(clientInfo, 322, channelName .. " " ..
channel.numUsers .. " :" .. channel.topic)
end
SendServerClientMessage(clientInfo, 323, ":End of LIST")
end
function Command_MODE(clientInfo, args)
if not args then return end
local _, _, target, mode = args:find("^+([^ ]+) *(.*)$")
if not target then return end
local args = {}
for arg in mode:gfind('([^ ]+) *') do
table.insert(args, arg)
end
if target:sub(1, 1) == '#' then
local channel = ChannelInfo[target]
if args[1] == '+o' or args[1] == '-o' then
local modeClient = NickToClientInfo[args[2]]
if not modeClient then return end
local channelUser = channel.userList[clientInfo]
if not channelUser then return end
if channelUser.mode ~= '@' then
SendServerClientMessage(clientInfo, 482, target .. "
:You're not channel operator")
return
end
local modeUser = channel.userList[modeClient]
modeUser.mode = arg[1] == '+o' and '@' or ''
SendUserMessage(clientInfo, target, "MODE " .. target .. " "
.. mode)
else
SendServerClientMessage(clientInfo, 324, target)
end
end
end
function Command_NICK(clientInfo, nick)
if not nick then return end
local oldNick = clientInfo.Nick
if NickToClientInfo[nick] then
SendRawMessage(clientInfo, "433 * " .. nick .. " :Nickname is
already in use")
return
end
for channelName, channel in pairs(clientInfo.Channels) do
SendUserMessage(clientInfo, channelName, "NICK " .. nick)
end
clientInfo.Nick = nick
NickToClientInfo[nick] = NickToClientInfo[oldNick]
NickToClientInfo[oldNick] = nil
end
function Command_NAMES(clientInfo, channelName)
if not channelName then return end
local channel = ChannelInfo[channelName]
if channel then
local users = ""
for userClientInfo, user in pairs(channel.userList) do
users = users .. user.mode .. userClientInfo.Nick .. " "
end
SendServerClientMessage(clientInfo, 353, "= " .. channel.name ..
" :" .. users)
end
SendServerClientMessage(clientInfo, 366, channelName .. " :End of
/NAMES list.")
end
function Command_NOTICE(clientInfo, args)
if not args then return end
local _, _, target, message = args:find("^([^ ]+) +:(.*)$")
if not target then return end
SendUserMessage(clientInfo, target, "NOTICE " .. target .. " :" ..
message, true)
end
function Command_PART(clientInfo, args, command, text)
if not args then return end
local _, _, target, message = args:find("^([^ ]+) +(.*)$")
if not target then return end
for channelName in args:gfind('(#%w+),-') do
local channel = ChannelInfo[channelName]
if channel then
if command == "QUIT" then
SendUserMessage(clientInfo, channelName, "QUIT " ..
text, true)
elseif command then
SendUserMessage(clientInfo, channelName, command .. " "
.. channelName .. " " .. message)
end
clientInfo.Channels[channelName] = nil
channel.userList[clientInfo] = nil
channel.numUsers = channel.numUsers - 1
if channel.numUsers == 0 then
ChannelInfo[channel] = nil
end
end
end
end
function Command_PING(clientInfo, message)
SendRawMessage(clientInfo, "PONG " .. hostname .. " " .. message)
end
function Command_PONG(clientInfo, message)
end
function Command_PRIVMSG(clientInfo, args)
if not args then return end
local _, _, target, message = args:find("^([^ ]+) +:(.*)$")
if not target then return end
SendUserMessage(clientInfo, target, "PRIVMSG " .. target .. " :" ..
message, true)
if not inPrivateMessage then
local func = ChannelPrivateMessage[target]
if func then
inPrivateMessage = true
curClientInfo = clientInfo
curTarget = target
func(clientInfo, target, message)
inPrivateMessage = nil
end
end
end
function Command_QUIT(clientInfo, message)
for channelName, channel in pairs(clientInfo.Channels) do
if channel.userList[clientInfo] then
Command_PART(clientInfo, channelName, "QUIT", message)
end
end
if clientInfo.Nick then
NickToClientInfo[clientInfo.Nick] = nil
end
clientInfo.Client:close()
set:remove(clientInfo.Client)
end
function Command_TOPIC(clientInfo, args)
print("Topic args: " .. args)
local _, _, target, topic = args:find("^([^ ]+) +:%s*(.*)%s*$")
if not target then return end
local channel = ChannelInfo[target]
if not channel then return end
channel.topic = topic
if channel.topic == "" then
SendServerClientMessage(clientInfo, 331, channel.name .. " :No
topic is set")
else
SendServerClientMessage(clientInfo, 332, channel.name .. " :" ..
channel.topic)
end
end
function Command_USERHOST(clientInfo, args)
if not args then return end
local _, _, nicks = args:find(":(.+)")
if not nicks then return end
local text = ""
for nick in nicks:gfind('(.*) -') do
local nickClient = NickToClientInfo[nick]
if nickClient then
local nickInfo = ClientMap[nickClient]
text = text .. nick .. "=+" .. nickClient.User .. "@" ..
nickClient.Host .. " "
end
end
SendServerClientMessage(clientInfo, 302, ":" .. text)
end
function Command_WHO(clientInfo, args)
local _, _, channel = args:find("WHO (.*) (.*)$")
if channel then
handleClientWho(clientInfo, channel)
--[[
foreach {topic userlist usermode} [channelInfoOrReturn $fd
$channel] break
foreach userfd $userlist mode $usermode {
SendServerClientMessage $fd 352 "$channel ~[clientUser $userfd]
[clientHost $userfd] [config hostname] $mode[clientNick $userfd] H :0
[clientRealName $userfd]"
}
SendServerClientMessage $fd 315 "$channel :End of /WHO list."]]
return
end
end
function Command_WHOIS(clientInfo, nick)
if not nick then return end
local targetInfo = ClientMap[nick]
if targetInfo then
SendServerClientMessage(clientInfo, 311, nick .. " ~" ..
targetInfo.User .. " " .. targetInfo.Host .. " * :" .. targetInfo.RealName)
local chans = ""
for channelName, channel in pairs(targetInfo.Channels) do
chans = chans .. channelName .. " "
end
if chans:len() > 1 then
SendServerClientMessage(clientInfo, 319, nick .. " :" .. chans)
end
SendServerClientMessage(clientInfo, 312, nick .. " " .. hostname
.. " :" .. hostname)
end
SendServerClientMessage(clientInfo, 318, nick .. " :End of /WHOIS
list.")
end
local CommandDispatch =
{
PING = Command_PING,
PONG = Command_PONG,
MODE = Command_MODE,
JOIN = Command_JOIN,
PART = Command_PART,
PRIVMSG = Command_PRIVMSG,
NOTICE = Command_NOTICE,
QUIT = Command_QUIT,
NICK = Command_NICK,
TOPIC = Command_TOPIC,
LIST = Command_LIST,
WHOIS = Command_WHOIS,
WHO = Command_WHO,
USERHOST = Command_USERHOST,
}
function ProcessClient(clientInfo)
local line, error = clientInfo.Client:receive()
if error == 'closed' then
Command_QUIT(clientInfo, error .. " from client")
end
if not line or line == "" then
return
end
local _, _, line = line:find("%s*(.+)%s*")
print(clientInfo.State .. ": " .. (clientInfo.Nick or "") .. " -> '"
.. line .. "'")
if clientInfo.State == "UNREGISTERED" then
local _, _, nick = line:find("NICK (.+)")
if nick then
if NickToClientInfo[nick] then
SendRawMessage(clientInfo, "433 * " .. nick .. "
:Nickname is already in use")
return
end
clientInfo.Nick = nick
end
local _, _, user, mode, virtualHost, realName = line:find("USER
(.*) (.*) (.*) :(.+)$")
if user then
clientInfo.User = user
clientInfo.Host = virtualHost
clientInfo.RealName = realName
end
if clientInfo.Nick and clientInfo.User then
clientInfo.State = "REGISTERED"
SendServerClientMessage(clientInfo, 001, "Welcome to the
LuaIRC server " .. clientInfo.Nick .. "!" .. clientInfo.User .. "@" ..
clientInfo.Host)
SendServerClientMessage(clientInfo, 002, "Your host is " ..
hostname .. ", running version " .. version)
SendServerClientMessage(clientInfo, 003, "This server was
created ...")
SendServerClientMessage(clientInfo, 004, hostname .. " " ..
version .. " aAbBcCdDeEfFGhHiIjkKlLmMnNopPQrRsStUvVwWxXyYzZ0123459*@
bcdefFhiIklmnoPqstv")
NickToClientInfo[clientInfo.Nick] = clientInfo
end
---------------------------------------------------------------------------
elseif clientInfo.State == "REGISTERED" then
local _, _, command, args = line:find("^%s*([^ ]+) *(.*)%s*$")
command = command:upper()
local func = CommandDispatch[command]
if type(func) == "function" then
func(clientInfo, args)
else
SendServerClientMessage(clientInfo.Client, 421, line .. "
:Unknown command")
end
end
end
function RunServer()
io.write("Opening server...\n")
local server = assert(socket.bind(hostname, port))
server:settimeout(1) -- make sure we don't block in accept
set:insert(server)
while 1 do
local readable, _, error = socket.select(set, nil)
for _, input in ipairs(readable) do
-- is it a server socket?
if input == server then
io.write("Waiting for clients\n")
local new = input:accept()
if new then
new:settimeout(1)
io.write("Inserting client in set\n")
SendRawMessage({ Client = new }, "NOTICE AUTH :" ..
version .. " initialized.")
set:insert(new)
end
else
ProcessClient(ClientMap[input])
end
end
end
end
local ircPrint = function(...)
SendUserMessage(curClientInfo, curTarget, 'PRIVMSG ' .. curTarget ..
" :" .. arg[1])
end
ChannelPrivateMessage['#lua'] = function(clientInfo, target, message)
local chunk = loadstring(message)
if chunk then
savePrint = print
print = ircPrint
pcall(chunk)
print = savePrint
end
end
RunServer()