bin_PROGRAMS = ircd dbtool
ircd_SOURCES = \
src/admin.cpp src/admin.hpp \
+ src/admin_rpl.hpp \
src/common.hpp \
src/crypt_blowfish.cpp src/crypt_blowfish.hpp \
src/db.cpp src/db.hpp \
src/db.cpp src/db.hpp \
src/dbtool.cpp
AM_CXXFLAGS = $(WARNINGCXXFLAGS) $(LIBUV_CFLAGS) $(LIBTLS_CFLAGS) $(LUA_CFLAGS)
+AM_CPPFLAGS = -Isrc -I$(srcdir)/src
LDADD = $(LIBUV_LIBS) $(LIBTLS_LIBS) $(LUA_LIBS) $(SQLITE3_LIBS)
+src/admin.$(OBJEXT): src/admin_rpl.hpp
+src/admin_rpl.hpp: src/ircfmt.hpp rplbindings.lua
+ @if test "x$(LUA)" = xno; then echo "No lua interpreter! Please set LUA="; exit 1; fi
+ $(AM_V_GEN)$(LUA) $(srcdir)/rplbindings.lua <$< >$@
+MAINTAINERCLEANFILES = src/admin_rpl.hpp
+
dist_ircddata_DATA = \
data/help.example.txt
dist_ircdlua_DATA = \
+ lua/autojoin.lua \
lua/channeldump.lua \
lua/ctcp.lua \
lua/init.lua \
LICENSES/GPL-2.0-or-later.txt \
LICENSES/MIT.txt \
LICENSES/Unlicense.txt \
- libtls.supp
+ libtls.supp \
+ rplbindings.lua
.PHONY: valgrind
valgrind: ircd
.PHONY: pch
if ENABLE_PCH
MOSTLYCLEANFILES = common.hpp.$(PCHEXT)
-AM_CPPFLAGS = -Winvalid-pch `test -f common.hpp.$(PCHEXT) && printf $(PCHOPT) "common.hpp"`
+AM_CPPFLAGS += -Winvalid-pch `test -f common.hpp.$(PCHEXT) && printf $(PCHOPT) "common.hpp"`
common.hpp.$(PCHEXT): src/common.hpp
$(AM_V_GEN)$(RM) $@; $(CXXCOMPILE) -x c++-header -o $@ $<
pch: common.hpp.$(PCHEXT)
PKG_CHECK_MODULES(LUA, [lua5.4])
PKG_CHECK_MODULES(SQLITE3, [sqlite3])
+AC_ARG_VAR([LUA], [the Lua 5.4 interpreter])
+AS_IF([test "x$LUA" = x], [AC_PATH_PROGS([LUA], [lua5.4 lua54], [no])])
+
AC_OUTPUT
end
function dispatch_command(client, source, command, ...)
if client.regstate ~= 2 then -- REG_REGISTERED
- -- TODO: err_notregistered(), sendbuf(client)
+ err_notregistered(client)
return
end
if client.umode & 2 == 0 then -- UMODE_OPER
- -- TODO: err_noprivileges(), sendbuf(client)
+ err_noprivileges(client)
return
end
commands[command](client, source, command, table.pack(...))
--- /dev/null
+#!/usr/bin/env lua5.4
+-- Copyright (c) 2026 Egor
+-- SPDX-License-Identifier: GPL-2.0-or-later
+
+-- print line with automatic indentation
+local indent = 0
+local function printlni(format, ...)
+ local s = format:format(...)
+ if s:sub(1, 1) == "}" then
+ indent = indent - 1
+ end
+ io.write(string.rep("\t", indent), format:format(...), "\n")
+ if s:sub(-1) == "{" then
+ indent = indent + 1
+ end
+end
+local check = {
+ ["Client *"] = "ClientRef::check(L, %d)",
+ ["Channel *"] = "ChannelRef::check(L, %d)",
+ ["const char *"] = "luaL_checkstring(L, %d)",
+ ["std::string_view "] = "checkstringview(L, %d)",
+ ["char "] = "luaL_checkstring(L, %d)[0]",
+ ["int "] = "(int)luaL_checkinteger(L, %d)",
+ ["time_t "] = "(time_t)luaL_checkinteger(L, %d)",
+ ["std::span<Client *> "] = "checkclientlist(L, %d)",
+}
+function set(t)
+ local u = {}
+ for i, v in ipairs(t) do u[v] = true end
+ return u
+end
+local blocklist = set{
+ -- unused
+ "rpl_bounce",
+ "rpl_tryagain",
+ "rpl_none",
+ "err_unknownerror",
+ "err_toomanychannels",
+ "err_wasnosuchnick",
+ "err_noorigin",
+ "err_inputtoolong",
+ "err_yourebannedcreep",
+}
+printlni("static void admin_rpl_init(lua_State *L) {")
+printlni("luaL_Reg funcs[] = {")
+for line in io.lines() do
+ local name, fargs = line:match("^void (rpl_[%w_]+)%((.*)%)")
+ if not name then
+ name, fargs = line:match("^void (err_[%w_]+)%((.*)%)")
+ end
+ if name and not blocklist[name] then
+ printlni("{\"%s\", [](lua_State *L) {", name)
+ local aargs = {}
+ local isvoid
+ if fargs == "void" or fargs == "" then
+ isvoid = true
+ fargs = "Client *client"
+ end
+ for typename, argname in fargs:gmatch("([^ ,][^,]* %**)([%w_]+)") do
+ assert(check[typename], name)
+ aargs[#aargs+1] = argname
+ printlni("%s%s = %s;", typename, argname, check[typename]:format(#aargs))
+ end
+ assert(aargs[1] == "client")
+ if isvoid then aargs = {} end
+ printlni("return %s(%s), sendbuf(client), 0;", name, table.concat(aargs, ", "))
+ printlni("}},");
+ end
+end
+printlni("{nullptr, nullptr},")
+printlni("};")
+printlni("lua_pushglobaltable(L);")
+printlni("luaL_setfuncs(L, funcs, 0);")
+printlni("lua_pop(L, 1);")
+printlni("}")
static Client *check(lua_State *L, int idx) {
return static_cast<ClientRef *>(luaL_checkudata(L, idx, mtname))->cl;
}
+ static Client *test(lua_State *L, int idx) {
+ auto ref = static_cast<ClientRef *>(luaL_testudata(L, idx, mtname));
+ return ref ? ref->cl : 0;
+ }
int index(lua_State *L) {
auto key = checkstringview(L, 2);
if (!cl)
client_chghost(cl, newusername, newhostname);
return 0;
}
-static int fn_rpl_whoisspecial(lua_State *L) {
- Client *cl1 = ClientRef::check(L, 1);
- Client *cl2 = ClientRef::check(L, 2);
- const char *text = luaL_checkstring(L, 3);
- rpl_whoisspecial(cl1, cl2, "", text), sendbuf(cl1);
- return 0;
-}
static int fn_getdatafile(lua_State *L) {
const char *fn = luaL_checkstring(L, 1);
const char *res = getdatafile(fn);
static int fn_querymemory(lua_State *L) {
return query(L, db::memory);
}
-
+static std::span<Client *> checkclientlist(lua_State *L, int idx) {
+ lua_Integer len = luaL_len(L, idx);
+ Client **p = static_cast<Client **>(lua_newuserdatauv(L, len*sizeof(Client *), 0));
+ for (lua_Integer i = 0; i < len; i++) {
+ lua_geti(L, idx, i+1);
+ p[i] = ClientRef::test(L, -1);
+ if (!p[i])
+ luaL_error(L, "expected a Set<Client> at index #%d", i+1);
+ lua_pop(L, 1);
+ }
+ return std::span(p, p + len);
+}
+#include "admin_rpl.hpp"
void admin_init(void)
{
lua_State *L = luaL_newstate();
{"channel_mode", fn_channel_mode},
{"channel_topic", fn_channel_topic},
{"client_chghost", fn_client_chghost},
- {"rpl_whoisspecial", fn_rpl_whoisspecial},
{"getdatafile", fn_getdatafile},
{"getluafile", fn_getluafile},
{"querydisk", fn_querydisk},
lua_pushglobaltable(L);
luaL_setfuncs(L, funcs, 0);
lua_pop(L, 1);
+ admin_rpl_init(L);
}
}
void admin_fini(void)
#include <iostream>
#include <iterator>
#include <memory>
+#include <span>
#include <string>
#include <string_view>
#include <utility>
if (cl)
*p++ = cl;
}
- rpl_userhost(client, cls, (int)(p - cls));
+ rpl_userhost(client, std::span(cls, p));
sendbuf(client);
// User-Based Queries
} MATCH_COMMAND("WHO") {
void rpl_away(Client *client, Client *cl) { // 301
num_reply(301, " %s :%s\r\n", cl->nick, cl->away);
}
-void rpl_userhost(Client *client, Client **cls, int count) { // 302
+void rpl_userhost(Client *client, std::span<Client *> cls) { // 302
num_reply(302, "");
- for (int i = 0; i < count; i++) {
+ bool first = true;
+ for (Client *cl : cls) {
append_strf(&databuf, " %s%s%s=%s%s",
- i ? "" : ":",
- cls[i]->nick,
- cls[i]->umode&UMODE_OPER ? "*" : "",
- cls[i]->away ? "-" : "+",
- cls[i]->hostname);
+ first ? ":" : "",
+ cl->nick,
+ cl->umode&UMODE_OPER ? "*" : "",
+ cl->away ? "-" : "+",
+ cl->hostname);
+ first = false;
}
append_str(&databuf, "\r\n");
}
void rpl_whoiscertfp(Client *client, Client *cl, const char *certfp); // 276
void rpl_none(Client *client, const char *line); // 300
void rpl_away(Client *client, Client *cl); // 301
-void rpl_userhost(Client *client, Client **cls, int count); // 302
+void rpl_userhost(Client *client, std::span<Client *> cls); // 302
void rpl_unaway(Client *client); // 305
void rpl_nowaway(Client *client); // 306
void rpl_whoreply(Client *client, Client *cl, Channel *chan); // 352