Add files via upload

This commit is contained in:
Koragg 2019-01-23 09:35:39 +01:00 committed by GitHub
parent 9a5d7a4e1b
commit 3c0416c420
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 46034 additions and 0 deletions

66
modules/Makefile Normal file
View File

@ -0,0 +1,66 @@
# Master makefile for modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
TOPDIR=../
include $(TOPDIR)/Makefile.inc
###########################################################################
SUBDIRS = $(shell for i in *; do test -f "$$i/Makefile" && echo "$$i"; done)
.PHONY: all-dynamic all-static install clean spotless
###########################################################################
all-dynamic:
@set -e ; for i in $(SUBDIRS) ; do \
$(MAKE) -C $$i $@ DIRNAME="$$i" CFLAGS="$(CFLAGS)" ; \
if $(TEST_NT) ! -f .stamp -o "$$i/.stamp" -nt .stamp ; then \
echo "touch .stamp" ; \
touch .stamp ; \
fi ; \
done
all-static:
@rm -f modext.h modsyms.c modlist.c
@echo '#include "../config.h"' >>modlist.c
@echo '#include "../defs.h"' >>modlist.c
@echo '#include "modext.h"' >>modlist.c
@echo '#include "modsyms.c"' >>modlist.c
@echo 'struct {const char *name; void *symlist;} modlist[] = {' >>modlist.c
@set -e ; for i in $(SUBDIRS) ; do \
$(MAKE) -C $$i $@ DIRNAME="$$i" CFLAGS="$(CFLAGS)" ; \
cat $$i/.modext-*.h >>modext.h ; \
cat $$i/.modsyms-*.c >>modsyms.c ; \
cat $$i/.modlist-*.c >>modlist.c ; \
if $(TEST_NT) "$$i/.stamp" -nt .stamp ; then \
echo "touch .stamp" ; \
touch .stamp ; \
fi ; \
done
@echo '{0}};' >>modlist.c
$(CC) -c modlist.c -o modlist.o
ar -r modules.a modlist.o
$(RANLIB) modules.a
install:
@set -e ; for i in $(SUBDIRS) ; do \
$(MAKE) -C $$i $@ DIRNAME="$$i" ; \
done
clean spotless:
@set -e ; for i in $(SUBDIRS) ; do \
$(MAKE) -C $$i $@ DIRNAME="$$i" ; \
done
rm -f modext.h modsyms.c modlist.c *.[oa] .stamp */.stamp
# Remove modules.a (for recompilation) if the top-level Makefile changed
modules.a: ../Makefile ../Makefile.inc
rm -f $@
$(DATDEST)/modules:
$(MKDIR) $@

190
modules/Makerules Normal file
View File

@ -0,0 +1,190 @@
# Makefile rules for modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
# The following variables must be set by the caller (the Makefile which
# includes this file). See operserv/Makefile for examples of using all the
# variables listed below.
#
# MODULES List of modules, each with the extension ".so". Each
# module is compiled from a source file of the same name
# name ending in ".c" which must contain the required
# module symbol definitions (init_module, etc.), and
# possibly other files as defined by the appropriate
# OBJECTS variable.
# OBJECTS-... List of additional objects for a particular module. The
# "..." is replaced by the name of the module; for
# example, if the module name is "main.so", the OBJECTS
# variable name is "OBJECTS-main.so".
# INCLUDES List of include files every source file in the directory
# depends on.
# INCLUDES-... List of include files a particular source file depends on.
# The "..." is replaced by the _object_ file name of the
# source file; for example, if the filename is "main.c",
# then the INCLUDES variable name is "INCLUDES-main.o".
#
# The following variables are set by this file:
#
# DIRNAME Name of this directory (set by modules/Makefile).
# TOPDIR Top Services directory.
# DEPS Files which every source file should depend on.
# This makefile is complex because there's no way to say something like
# "%.so: %.o $(OBJECTS-%)". Instead, we recursively call ourselves for
# each module, settings OBJECTS to the appropriate target-dependent value.
# We then recursively call ourselves _again_ for each source file, defining
# INCLUDES to the appropriate value.
###########################################################################
TOPDIR=../..
DEPS = Makefile ../Makerules $(TOPDIR)/Makefile.inc $(TOPDIR)/Makefile \
$(TOPDIR)/services.h $(TOPDIR)/modules.h $(TOPDIR)/conffile.h \
$(INCLUDES)
.PHONY: all-dynamic all-static install clean spotless
all-dynamic: $(MODULES)
all-static: $(MODULES:.so=.a)
@set -e ; \
if $(TEST_NT) ! -f $(DIRNAME).o -o .stamp -nt $(DIRNAME).o ; then \
rm -f .$(DIRNAME).lst2 ; \
sort -u .$(DIRNAME).lst >.$(DIRNAME).lst2 ; \
mv -f .$(DIRNAME).lst2 .$(DIRNAME).lst ; \
echo 'ld -r -o $(DIRNAME).o' `cat .$(DIRNAME).lst` ; \
ld -r -o $(DIRNAME).o `cat .$(DIRNAME).lst` ; \
echo 'ar -cr ../modules.a $(DIRNAME).o' ; \
ar -cr ../modules.a $(DIRNAME).o ; \
echo 'touch .stamp' ; \
touch .stamp ; \
fi
ifeq ($(MODULES),)
install:
$(MKDIR) "$(INSTALL_PREFIX)$(DATDEST)/modules/$(DIRNAME)"
clean:
else
install:
$(MKDIR) "$(INSTALL_PREFIX)$(DATDEST)/modules/$(DIRNAME)"
$(INSTALL_DAT) $(MODULES) "$(INSTALL_PREFIX)$(DATDEST)/modules/$(DIRNAME)"
clean:
rm -f $(MODULES) $(MODULES:.so=.a) .$(DIRNAME).lst* *.o .mod* .compiled*
endif
spotless: clean
###########################################################################
.PHONY: FRC
FRC:
ifeq ($(REALLY_COMPILE),)
%_static.so %_static.a:
@echo >&2 '*** Module names must not end in "_static". Compile aborted.'
@false
.%.so .%.a:
@echo >&2 '*** Module names must not begin with ".". Compile aborted.'
@false
# We include FRC as a prerequisite here since we can't check the real
# prerequisites until the sub-make, which won't get executed if the target
# exists and there are no prerequisites listed.
%.so: FRC
@$(MAKE) --no-print-directory $@ TARGET=$(@:.so=) OBJECTS="$(OBJECTS-$@)" REALLY_COMPILE=1
%.a: FRC
@$(MAKE) --no-print-directory $@ TARGET=$(@:.a=) OBJECTS="$(OBJECTS-$(@:.a=.so))" REALLY_COMPILE=1
else
# Compile one or more objects into a dynamic module.
$(TARGET).so: $(TARGET).o $(OBJECTS)
$(CC_SHARED) $^ -o $@
# Compile one or more objects into a static module and generate a symbol
# list. The .a file we create here is just a placeholder to show that
# we've compiled the module; the objects themselves go into ../modules.a
# (via $(DIRNAME).o).
$(TARGET).a: $(TARGET)_static.o $(OBJECTS)
@for i in $^ ; do echo $$i >>.$(DIRNAME).lst ; done
@set -e ; \
FILENAME=$(@:.a=) ; \
MODNAME=`echo $(DIRNAME)_$$FILENAME | sed -e 'y/-/_/' -e 's/_$$//'` ; \
rm -f .modext-$$FILENAME.h .modlist-$$FILENAME.c .modsyms-$$FILENAME.c ; \
echo >>.modext-$$FILENAME.h 'extern void *_this_module_ptr_'$$MODNAME';' ; \
echo >>.modext-$$FILENAME.h 'extern uint32 module_version_'$$MODNAME';' ; \
echo >>.modext-$$FILENAME.h 'extern char module_config_'$$MODNAME'[];' ; \
echo >>.modext-$$FILENAME.h 'extern int init_module_'$$MODNAME'();' ; \
echo >>.modext-$$FILENAME.h 'extern int exit_module_'$$MODNAME'();' ; \
echo >>.modlist-$$FILENAME.c '{ "$(DIRNAME)/'$$FILENAME'", modsyms_'$$MODNAME' },' ; \
echo >>.modsyms-$$FILENAME.c 'struct {const char *name; void *value; } modsyms_'$$MODNAME'[] = {' ; \
echo >>.modsyms-$$FILENAME.c '{"_this_module_ptr",&_this_module_ptr_'$$MODNAME'},' ; \
echo >>.modsyms-$$FILENAME.c '{"module_version",&module_version_'$$MODNAME'},' ; \
echo >>.modsyms-$$FILENAME.c '{"module_config",module_config_'$$MODNAME'},' ; \
echo >>.modsyms-$$FILENAME.c '{"init_module",init_module_'$$MODNAME'},' ; \
echo >>.modsyms-$$FILENAME.c '{"exit_module",exit_module_'$$MODNAME'},' ; \
rm -f .modsyms-$$FILENAME.tmp ; \
for file in $(@:.a=.c) $(OBJECTS-$(@:.a=.so):.o=.c) ; do \
grep '^EXPORT_' $$file \
| sed -e 's/^EXPORT_VAR[ ]*([ ]*\([^,]*\),[ ]*\([A-Za-z0-9_]*\)[ ]*)[ ]*$$/\&\2:\1 \2/' \
-e 's/^EXPORT_ARRAY[ ]*([ ]*\([A-Za-z0-9_]*\)[ ]*)[ ]*$$/ \1:char \1[]/' \
-e 's/^EXPORT_FUNC[ ]*([ ]*\([A-Za-z0-9_]*\)[ ]*)[ ]*$$/ \1:void \1()/' ; \
done >.modsyms-$$FILENAME.tmp ; \
if grep >/dev/null 2>&1 '^EXPORT_' .modsyms-$$FILENAME.tmp ; then \
echo >&2 "$$file: invalid use of EXPORT_xxx" ; \
exit 1 ; \
fi ; \
sed 's/\(.\)\([^:]*\):.*/{"\2",\1\2},/' <.modsyms-$$FILENAME.tmp >>.modsyms-$$FILENAME.c ; \
sed 's/[^:]*:\(.*\)/extern \1;/' <.modsyms-$$FILENAME.tmp >>.modext-$$FILENAME.h;\
rm -f .modsyms-$$FILENAME.tmp ; \
echo '{0}};' >>.modsyms-$$FILENAME.c
@touch $@
ifneq ($(REALLY_COMPILE),2)
MODULE_ID = $(shell echo $(DIRNAME)_$(TARGET) | sed -e 'y/-/_/' -e 's/_$$//')
MODULE_CFLAGS = -DMODULE -DMODULE_ID=$(MODULE_ID)
$(TARGET).o $(TARGET)_static.o: MODULE_CFLAGS += -DMODULE_MAIN_FILE
$(TARGET)_static.o: MODULE_CFLAGS += -D_this_module_ptr=_this_module_ptr_$(MODULE_ID) -Dmodule_version=module_version_$(MODULE_ID) -Dmodule_config=module_config_$(MODULE_ID) -Dinit_module=init_module_$(MODULE_ID) -Dexit_module=exit_module_$(MODULE_ID)
$(TARGET)_static.o: FRC
@$(MAKE) --no-print-directory $@ TARGET=$(@:_static.o=) INCLUDES2="$(INCLUDES-$(@:_static.o=.o))" CFLAGS="$(CFLAGS) $(MODULE_CFLAGS)" REALLY_COMPILE=2
@if $(TEST_NT) ! -f .stamp -o "$@" -nt .stamp ; then \
echo "touch .stamp" ; \
touch .stamp ; \
fi
$(TARGET).o $(OBJECTS): FRC
@$(MAKE) --no-print-directory $@ TARGET=$(@:.o=) INCLUDES2="$(INCLUDES-$@)" CFLAGS="$(CFLAGS) $(MODULE_CFLAGS)" REALLY_COMPILE=2
@if $(TEST_NT) ! -f .stamp -o "$@" -nt .stamp ; then \
echo "touch .stamp" ; \
touch .stamp ; \
fi
else
# Compile a source file into an object file. This construct is used to
# suppress the "target is up to date" message that would otherwise appear.
$(TARGET).o: .compiled-$(TARGET).o FRC
@echo >/dev/null
.compiled-$(TARGET).o: $(TARGET).c $(DEPS) $(INCLUDES2)
cd $(TOPDIR) && $(CC) $(CFLAGS) -I. -c modules/$(DIRNAME)/$< -o modules/$(DIRNAME)/$(TARGET).o
@rm -f $@
@ln -s $(TARGET).o $@
# Compile a source file into an object file suitable for use in a static
# module. This is used with the main object file of a module to generate
# unique names for exported module symbols (init_module and the like).
$(TARGET)_static.o: .compiled-$(TARGET)_static.o FRC
@echo >/dev/null
.compiled-$(TARGET)_static.o: $(TARGET).c $(DEPS) $(INCLUDES2)
cd $(TOPDIR) && $(CC) $(CFLAGS) -I. -c modules/$(DIRNAME)/$< -o modules/$(DIRNAME)/$(TARGET)_static.o
@touch $@
endif # REALLY_COMPILE == 2
endif # REALLY_COMPILE != ""

31
modules/chanserv/Makefile Normal file
View File

@ -0,0 +1,31 @@
# Makefile for ChanServ modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so access-levels.so access-xop.so
OBJECTS-main.so = access.o check.o set.o util.o
INCLUDES = chanserv.h cs-local.h $(TOPDIR)/language.h $(TOPDIR)/encrypt.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/operserv/operserv.h
INCLUDES-access.o = access.h
INCLUDES-check.o = $(TOPDIR)/timeout.h
INCLUDES-main.o = $(TOPDIR)/commands.h $(TOPDIR)/databases.h \
$(TOPDIR)/encrypt.h
INCLUDES-set.o = $(TOPDIR)/encrypt.h
INCLUDES-access-levels.o = $(TOPDIR)/commands.h access.h
INCLUDES-access-xop.o = $(TOPDIR)/commands.h
include ../Makerules
###########################################################################

View File

@ -0,0 +1,584 @@
/* Access list and level modification handling for ChanServ.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "chanserv.h"
#include "access.h"
#include "cs-local.h"
/*************************************************************************/
static Module *module_chanserv;
/*************************************************************************/
/* Local functions. */
static void do_levels(User *u);
static void do_access(User *u);
static int access_list(const User *u, int index, const ChannelInfo *ci,
int *sent_header);
/*************************************************************************/
static Command cmds[] = {
{ "ACCESS", do_access, NULL, -1, -1,-1 },
{ "LEVELS", do_levels, NULL, -1, -1,-1 },
{ NULL }
};
/*************************************************************************/
/***************************** Help display ******************************/
/*************************************************************************/
/* Callback to display help text for ACCESS, ACCESS LEVELS, LEVELS, and
* LEVELS DESC.
*/
static int do_help(User *u, const char *param)
{
int i;
const char *s;
if (stricmp(param, "ACCESS") == 0) {
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS);
if (find_module("chanserv/access-xop")) {
if (protocol_features & PF_HALFOP) {
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS_XOP_HALFOP,
ACCLEV_SOP, ACCLEV_AOP, ACCLEV_HOP, ACCLEV_VOP,
ACCLEV_NOP);
} else {
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS_XOP,
ACCLEV_SOP, ACCLEV_AOP, ACCLEV_VOP, ACCLEV_NOP);
}
}
return 1;
} else if (strnicmp(param, "ACCESS", 6) == 0
&& isspace(param[6])
&& stricmp(param+7+strspn(param+7," \t"), "LEVELS") == 0
) {
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS_LEVELS,
ACCLEV_SOP, ACCLEV_AOP);
if (protocol_features & PF_HALFOP) {
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS_LEVELS_HALFOP,
ACCLEV_HOP);
}
notice_help(s_ChanServ, u, CHAN_HELP_ACCESS_LEVELS_END,
ACCLEV_VOP);
return 1;
} else if (strnicmp(param, "LEVELS", 6) == 0) {
s = (param+6) + strspn(param+6," \t");
if (!*s) {
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS);
if (find_module("chanserv/access-xop")) {
if (protocol_features & PF_HALFOP)
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_XOP_HOP);
else
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_XOP);
}
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_END);
return 1;
} else if (stricmp(s, "DESC") == 0) {
int levelinfo_maxwidth = 0;
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC);
for (i = 0; levelinfo[i].what >= 0; i++) {
int len = strlen(levelinfo[i].name);
if (len > levelinfo_maxwidth)
levelinfo_maxwidth = len;
}
for (i = 0; levelinfo[i].what >= 0; i++) {
if (!*levelinfo[i].name)
continue;
notice(s_ChanServ, u->nick, " %-*s %s",
levelinfo_maxwidth, levelinfo[i].name,
getstring(u->ngi, levelinfo[i].desc));
}
return 1;
}
}
return 0;
}
/*************************************************************************/
/************************** The ACCESS command ***************************/
/*************************************************************************/
static void do_access_add(const User *u, int is_servadmin, ChannelInfo *ci,
const char *nick, const char *levelstr);
static void do_access_del(const User *u, int is_servadmin, ChannelInfo *ci,
const char *nick);
static void do_access_list(const User *u, const ChannelInfo *ci,
const char *startstr, const char *pattern);
static void do_access_listlevel(const User *u, const ChannelInfo *ci,
const char *startstr, const char *levels);
static void do_access_count(const User *u, const ChannelInfo *ci);
static void do_access(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *nick = strtok(NULL, " ");
char *s = strtok(NULL, " ");
ChannelInfo *ci = NULL;
int is_list; /* True when command is a LIST-like command */
int is_servadmin = is_services_admin(u);
is_list = (cmd && (stricmp(cmd,"LIST")==0 || stricmp(cmd,"LISTLEVEL")==0
|| stricmp(cmd,"COUNT")==0));
if (!chan || !cmd) {
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
} else if (!(ci = get_channelinfo(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (ci->flags & CF_VERBOTEN) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!is_servadmin
&& !check_access_cmd(u, ci, "ACCESS", is_list ? "LIST" : cmd)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "ADD") == 0) {
if (!nick || !s)
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_ADD_SYNTAX);
else
do_access_add(u, is_servadmin, ci, nick, s);
} else if (stricmp(cmd, "DEL") == 0) {
if (!nick || s)
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_DEL_SYNTAX);
else
do_access_del(u, is_servadmin, ci, nick);
} else if (stricmp(cmd, "LIST") == 0) {
int have_start = (nick && *nick=='+');
do_access_list(u, ci, have_start ? nick : NULL,
have_start ? s : nick);
} else if (stricmp(cmd, "LISTLEVEL") == 0) {
const char *startstr, *levelstr;
int have_start = (nick && *nick=='+');
if (have_start) {
startstr = nick;
levelstr = s;
} else {
startstr = NULL;
levelstr = nick;
}
if (!levelstr)
syntax_error(s_ChanServ, u, "ACCESS",CHAN_ACCESS_LISTLEVEL_SYNTAX);
else
do_access_listlevel(u, ci, startstr, levelstr);
} else if (stricmp(cmd, "COUNT") == 0) {
if (nick || s)
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_COUNT_SYNTAX);
else
do_access_count(u, ci);
} else { /* Unknown command */
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
}
put_channelinfo(ci);
}
static void do_access_add(const User *u, int is_servadmin, ChannelInfo *ci,
const char *nick, const char *levelstr)
{
int level;
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
return;
}
level = (int)atolsafe(levelstr, ACCLEV_MIN, ACCLEV_MAX);
if (level == ACCLEV_INVALID) {
if (errno == EINVAL) {
syntax_error(s_ChanServ, u, "ADD", CHAN_ACCESS_ADD_SYNTAX);
} else {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE,
ACCLEV_MIN, ACCLEV_MAX);
}
return;
} else if (level == 0) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO);
return;
}
switch (access_add(ci, nick, level,
is_servadmin ? ACCLEV_FOUNDER : get_access(u,ci))) {
case RET_ADDED:
notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nick, ci->name, level);
break;
case RET_CHANGED:
notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED,
nick, ci->name, level);
break;
case RET_UNCHANGED:
notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED,
nick, ci->name, level);
break;
case RET_LISTFULL:
notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT, CSAccessMax);
break;
case RET_NOSUCHNICK:
notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY);
break;
case RET_NICKFORBID:
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
break;
case RET_NOOP:
notice_lang(s_ChanServ, u, CHAN_ACCESS_NOOP, nick);
break;
case RET_PERMISSION:
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
break;
}
}
static void do_access_del(const User *u, int is_servadmin, ChannelInfo *ci,
const char *nick)
{
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
return;
}
switch (access_del(ci, nick,
is_servadmin ? ACCLEV_FOUNDER : get_access(u,ci))) {
case RET_DELETED:
notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED, nick, ci->name);
break;
case RET_NOENTRY:
notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick, ci->name);
break;
case RET_NOSUCHNICK:
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
break;
case RET_NICKFORBID:
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
break;
case RET_PERMISSION:
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
break;
case RET_INTERR:
notice_lang(s_ChanServ, u, INTERNAL_ERROR);
break;
}
}
static void do_access_list(const User *u, const ChannelInfo *ci,
const char *startstr, const char *pattern)
{
NickGroupInfo *ngi;
int count = 0, sent_header = 0, skip = 0, i;
if (startstr) {
skip = (int)atolsafe(startstr+1, 0, INT_MAX);
if (skip < 0) {
syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_LIST_SYNTAX);
return;
}
}
if (ci->access_count == 0) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, ci->name);
return;
}
ARRAY_FOREACH (i, ci->access) {
if (!ci->access[i].nickgroup)
continue;
if (!(ngi = get_ngi_id(ci->access[i].nickgroup)))
continue;
if (pattern && !match_wild_nocase(pattern, ngi_mainnick(ngi))) {
put_nickgroupinfo(ngi);
continue;
}
put_nickgroupinfo(ngi);
count++;
if (count > skip && count <= skip+ListMax)
access_list(u, i, ci, &sent_header);
}
if (count) {
int shown = count - skip;
if (shown < 0)
shown = 0;
else if (count > ListMax)
shown = ListMax;
notice_lang(s_ChanServ, u, LIST_RESULTS, shown, count);
} else {
notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, ci->name);
}
}
static void do_access_listlevel(const User *u, const ChannelInfo *ci,
const char *startstr, const char *levels)
{
int low, high, dir;
int count = 0, sent_header = 0, skip = 0, i;
long v, v2;
char *s;
if (startstr) {
skip = (int)atolsafe(startstr+1, 0, INT_MAX);
if (skip < 0) {
syntax_error(s_ChanServ, u, "ACCESS",CHAN_ACCESS_LISTLEVEL_SYNTAX);
return;
}
}
v = strtol(levels, &s, 10);
if (s > levels && *s == '-')
v2 = strtol(s+1, &s, 10);
else
v2 = v;
if (s == levels || *s != 0) {
syntax_error(s_ChanServ, u, "ACCESS",CHAN_ACCESS_LISTLEVEL_SYNTAX);
return;
} else if (v < ACCLEV_MIN || v > ACCLEV_MAX
|| v2 < ACCLEV_MIN || v2 > ACCLEV_MAX) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE,
ACCLEV_MIN, ACCLEV_MAX);
return;
}
if (v2 < v) {
low = v2;
high = v;
dir = -1;
} else {
low = v;
high = v2;
dir = 1;
}
if (ci->access_count == 0) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, ci->name);
return;
}
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup
&& ci->access[i].level >= low
&& ci->access[i].level <= high
) {
count++;
if (count > skip && count <= skip+ListMax)
access_list(u, i, ci, &sent_header);
}
}
if (count) {
int shown = count - skip;
if (shown < 0)
shown = 0;
else if (count > ListMax)
shown = ListMax;
notice_lang(s_ChanServ, u, LIST_RESULTS, shown, count);
} else {
notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, ci->name);
}
}
static void do_access_count(const User *u, const ChannelInfo *ci)
{
int count = 0, i;
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup)
count++;
}
notice_lang(s_ChanServ, u, CHAN_ACCESS_COUNT, ci->name, count);
}
/*************************************************************************/
static int access_list(const User *u, int index, const ChannelInfo *ci,
int *sent_header)
{
ChanAccess *access = &ci->access[index];
NickGroupInfo *ngi;
if (!access->nickgroup)
return RET_NOENTRY;
if (!(ngi = get_ngi_id(access->nickgroup)))
return RET_INTERR;
if (!*sent_header) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name);
*sent_header = 1;
}
notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FORMAT,
access->level, ngi_mainnick(ngi));
put_nickgroupinfo(ngi);
return RET_LISTED;
}
/*************************************************************************/
/************************** The LEVELS command ***************************/
/*************************************************************************/
static void do_levels(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *what = strtok(NULL, " ");
char *s = strtok(NULL, " ");
ChannelInfo *ci = NULL;
int16 level;
int i;
/* If SET, we want two extra parameters; if DIS[ABLE], we want only
* one; else, we want none.
*/
if (!chan || !cmd || ((stricmp(cmd,"SET")==0) ? !s :
(strnicmp(cmd,"DIS",3)==0) ? (!what || s) : !!what)) {
syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
} else if (!(ci = get_channelinfo(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (ci->flags & CF_VERBOTEN) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!is_founder(u, ci) && !is_services_admin(u)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "SET") == 0) {
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_LEVELS_READONLY);
put_channelinfo(ci);
return;
}
level = atoi(s);
if (level < ACCLEV_MIN || level > ACCLEV_MAX) {
notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE,
ACCLEV_MIN, ACCLEV_MAX);
put_channelinfo(ci);
return;
}
for (i = 0; levelinfo[i].what >= 0; i++) {
if (stricmp(levelinfo[i].name, what) == 0) {
ci->levels[levelinfo[i].what] = level;
notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED,
levelinfo[i].name, chan, level);
put_channelinfo(ci);
return;
}
}
notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
} else if (stricmp(cmd, "DIS") == 0 || stricmp(cmd, "DISABLE") == 0) {
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_LEVELS_READONLY);
put_channelinfo(ci);
return;
}
for (i = 0; levelinfo[i].what >= 0; i++) {
if (stricmp(levelinfo[i].name, what) == 0) {
ci->levels[levelinfo[i].what] = ACCLEV_INVALID;
notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED,
levelinfo[i].name, chan);
put_channelinfo(ci);
return;
}
}
notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
} else if (stricmp(cmd, "LIST") == 0) {
int levelinfo_maxwidth = 0, i;
notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_HEADER, chan);
for (i = 0; levelinfo[i].what >= 0; i++) {
int len = strlen(levelinfo[i].name);
if (len > levelinfo_maxwidth)
levelinfo_maxwidth = len;
}
for (i = 0; levelinfo[i].what >= 0; i++) {
int lev;
if (!*levelinfo[i].name)
continue;
lev = get_ci_level(ci, levelinfo[i].what);
if (lev == ACCLEV_INVALID)
notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
levelinfo_maxwidth, levelinfo[i].name);
else if (lev == ACCLEV_FOUNDER)
notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_FOUNDER,
levelinfo_maxwidth, levelinfo[i].name);
else
notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_NORMAL,
levelinfo_maxwidth, levelinfo[i].name, lev);
}
} else if (stricmp(cmd, "RESET") == 0) {
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_LEVELS_READONLY);
put_channelinfo(ci);
return;
}
reset_levels(ci);
notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan);
} else {
syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
}
put_channelinfo(ci);
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
module_chanserv = find_module("chanserv/main");
if (!module_chanserv) {
module_log("Main ChanServ module not loaded");
return 0;
}
use_module(module_chanserv);
if (!register_commands(module_chanserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(module_chanserv, "HELP", do_help)) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_chanserv) {
remove_callback(module_chanserv, "HELP", do_help);
unregister_commands(module_chanserv, cmds);
unuse_module(module_chanserv);
module_chanserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,409 @@
/* SOP/AOP/VOP handling for ChanServ.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "chanserv.h"
#include "cs-local.h"
/*************************************************************************/
static Module *module_chanserv;
/*************************************************************************/
/* Return the name of the list or the syntax message number corresponding
* to the given level. Does no error checking. */
# define XOP_LISTNAME(level) \
((level)==ACCLEV_SOP ? "SOP" : (level)==ACCLEV_AOP ? "AOP" : \
(level)==ACCLEV_HOP ? "HOP" : (level)==ACCLEV_VOP ? "VOP" : "NOP")
# define XOP_SYNTAX(level) \
((level)==ACCLEV_SOP ? CHAN_SOP_SYNTAX : \
(level)==ACCLEV_AOP ? CHAN_AOP_SYNTAX : \
(level)==ACCLEV_HOP ? CHAN_HOP_SYNTAX : \
(level)==ACCLEV_VOP ? CHAN_VOP_SYNTAX : CHAN_NOP_SYNTAX)
# define XOP_LIST_SYNTAX(level) \
((level)==ACCLEV_SOP ? CHAN_SOP_LIST_SYNTAX : \
(level)==ACCLEV_AOP ? CHAN_AOP_LIST_SYNTAX : \
(level)==ACCLEV_HOP ? CHAN_HOP_LIST_SYNTAX : \
(level)==ACCLEV_VOP ? CHAN_VOP_LIST_SYNTAX : CHAN_NOP_LIST_SYNTAX)
/*************************************************************************/
/* Local functions. */
static void handle_xop(User *u, int level);
static int xop_list(const User *u, int index, const ChannelInfo *ci,
int *sent_header);
/*************************************************************************/
static void do_sop(User *u);
static void do_aop(User *u);
static void do_hop(User *u);
static void do_vop(User *u);
static void do_nop(User *u);
static Command cmds[] = {
{ "SOP", do_sop, NULL, CHAN_HELP_SOP, -1,-1 },
{ "AOP", do_aop, NULL, CHAN_HELP_AOP, -1,-1 },
{ "VOP", do_vop, NULL, CHAN_HELP_VOP, -1,-1 },
{ "NOP", do_nop, NULL, CHAN_HELP_NOP, -1,-1 },
{ NULL }
};
static Command cmds_halfop[] = {
{ "HOP", do_hop, NULL, CHAN_HELP_HOP, -1,-1 },
{ NULL }
};
/*************************************************************************/
/*************************** The *OP commands ****************************/
/*************************************************************************/
/* SOP, VOP, AOP, and HOP wrappers. These just call handle_xop() with the
* appropriate level.
*/
static void do_sop(User *u)
{
handle_xop(u, ACCLEV_SOP);
}
static void do_aop(User *u)
{
handle_xop(u, ACCLEV_AOP);
}
static void do_hop(User *u)
{
handle_xop(u, ACCLEV_HOP);
}
static void do_vop(User *u)
{
handle_xop(u, ACCLEV_VOP);
}
static void do_nop(User *u)
{
handle_xop(u, ACCLEV_NOP);
}
/*************************************************************************/
/* Central handler for all *OP commands. */
static void handle_xop_add(const User *u, int level, int is_servadmin,
ChannelInfo *ci, const char *nick);
static void handle_xop_del(const User *u, int level, int is_servadmin,
ChannelInfo *ci, const char *nick);
static void handle_xop_list(const User *u, int level, ChannelInfo *ci,
const char *startstr, const char *pattern);
static void handle_xop_count(const User *u, int level, ChannelInfo *ci);
static void handle_xop(User *u, int level)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *nick = strtok(NULL, " ");
ChannelInfo *ci = NULL;
int is_list = (cmd && (stricmp(cmd,"LIST")==0 || stricmp(cmd,"COUNT")==0));
int is_servadmin = is_services_admin(u);
if (!chan
|| !cmd
|| (!is_list && !nick)
|| (stricmp(cmd,"COUNT") == 0 && nick)
) {
syntax_error(s_ChanServ, u, XOP_LISTNAME(level), XOP_SYNTAX(level));
} else if (!(ci = get_channelinfo(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (ci->flags & CF_VERBOTEN) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!is_servadmin
&& !check_access_cmd(u, ci, "ACCESS", is_list ? "LIST" : cmd)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "ADD") == 0) {
handle_xop_add(u, level, is_servadmin, ci, nick);
} else if (stricmp(cmd, "DEL") == 0) {
handle_xop_del(u, level, is_servadmin, ci, nick);
} else if (stricmp(cmd, "LIST") == 0) {
int have_start = (nick && *nick=='+');
const char *s = have_start ? strtok(NULL, " ") : nick;
handle_xop_list(u, level, ci, have_start ? nick : NULL, s);
} else if (stricmp(cmd, "COUNT") == 0) {
handle_xop_count(u, level, ci);
} else {
syntax_error(s_ChanServ, u, XOP_LISTNAME(level), XOP_SYNTAX(level));
}
put_channelinfo(ci);
}
static void handle_xop_add(const User *u, int level, int is_servadmin,
ChannelInfo *ci, const char *nick)
{
const char *listname = XOP_LISTNAME(level);
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
return;
}
switch (access_add(ci, nick, level,
is_servadmin ? ACCLEV_FOUNDER : get_access(u,ci))) {
case RET_ADDED:
notice_lang(s_ChanServ, u, CHAN_XOP_ADDED, nick, ci->name, listname);
break;
case RET_CHANGED:
notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_CHANGED,
nick, ci->name, listname);
break;
case RET_UNCHANGED:
notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_UNCHANGED,
nick, ci->name, listname);
break;
case RET_LISTFULL:
notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT, CSAccessMax);
break;
case RET_NOSUCHNICK:
notice_lang(s_ChanServ, u, CHAN_XOP_NICKS_ONLY);
break;
case RET_NICKFORBID:
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
break;
case RET_NOOP:
notice_lang(s_ChanServ, u, CHAN_XOP_NOOP, nick);
break;
case RET_PERMISSION:
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
break;
}
}
static void handle_xop_del(const User *u, int level, int is_servadmin,
ChannelInfo *ci, const char *nick)
{
const char *listname = XOP_LISTNAME(level);
NickInfo *ni;
int i;
if (!is_servadmin && level >= get_access(u, ci)) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
return;
} else if (readonly) {
if (is_servadmin) {
notice_lang(s_ChanServ, u, READ_ONLY_MODE);
} else {
notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
return;
}
}
ni = get_nickinfo(nick);
if (!ni) {
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
return;
}
if (!check_ngi(ni)) {
notice_lang(s_ChanServ, u, INTERNAL_ERROR);
put_nickinfo(ni);
return;
}
ARRAY_SEARCH_SCALAR(ci->access, nickgroup, ni->nickgroup, i);
put_nickinfo(ni);
if (i == ci->access_count || ci->access[i].level != level) {
notice_lang(s_ChanServ, u, CHAN_XOP_NOT_FOUND,
nick, ci->name, listname);
return;
}
ci->access[i].nickgroup = 0;
notice_lang(s_ChanServ, u, CHAN_XOP_DELETED, nick, ci->name, listname);
}
static void handle_xop_list(const User *u, int level, ChannelInfo *ci,
const char *startstr, const char *pattern)
{
const char *listname = XOP_LISTNAME(level);
NickGroupInfo *ngi;
int count = 0, sent_header = 0, skip = 0, i;
if (startstr) {
skip = (int)atolsafe(startstr+1, 0, INT_MAX);
if (skip < 0) {
syntax_error(s_ChanServ, u, listname, XOP_LIST_SYNTAX(level));
return;
}
}
if (ci->access_count == 0) {
notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, ci->name, listname);
return;
}
ARRAY_FOREACH (i, ci->access) {
if (!ci->access[i].nickgroup)
continue;
if (ci->access[i].level != level)
continue;
if (!(ngi = get_ngi_id(ci->access[i].nickgroup)))
continue;
if (pattern && !match_wild_nocase(pattern, ngi_mainnick(ngi))) {
put_nickgroupinfo(ngi);
continue;
}
put_nickgroupinfo(ngi);
count++;
if (count > skip && count <= skip+ListMax)
xop_list(u, i, ci, &sent_header);
}
if (count) {
int shown = count - skip;
if (shown < 0)
shown = 0;
else if (count > ListMax)
shown = ListMax;
notice_lang(s_ChanServ, u, LIST_RESULTS, shown, count);
} else {
notice_lang(s_ChanServ, u, CHAN_XOP_NO_MATCH, ci->name, listname);
}
}
static void handle_xop_count(const User *u, int level, ChannelInfo *ci)
{
const char *listname = XOP_LISTNAME(level);
int count = 0, i;
if (ci->access_count == 0) {
notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, ci->name, listname);
return;
}
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup && ci->access[i].level == level)
count++;
}
if (count)
notice_lang(s_ChanServ, u, CHAN_XOP_COUNT, ci->name, listname, count);
else
notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, ci->name, listname);
}
/*************************************************************************/
static int xop_list(const User *u, int index, const ChannelInfo *ci,
int *sent_header)
{
ChanAccess *access = &ci->access[index];
NickGroupInfo *ngi;
if (!(ngi = get_ngi_id(access->nickgroup)))
return RET_INTERR;
if (!*sent_header) {
notice_lang(s_ChanServ, u, CHAN_XOP_LIST_HEADER,
XOP_LISTNAME(access->level), ci->name);
*sent_header = 1;
}
notice(s_ChanServ, u->nick, " %s", ngi_mainnick(ngi));
put_nickgroupinfo(ngi);
return RET_LISTED;
}
/*************************************************************************/
/*************************************************************************/
/* Callback to display help text for SOP and AOP. */
static int do_help(User *u, const char *param)
{
if (stricmp(param, "SOP") == 0) {
notice_help(s_ChanServ, u, CHAN_HELP_SOP);
notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID1);
notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID2);
notice_help(s_ChanServ, u, CHAN_HELP_SOP_END);
return 1;
} else if (stricmp(param, "AOP") == 0) {
notice_help(s_ChanServ, u, CHAN_HELP_AOP);
notice_help(s_ChanServ, u, CHAN_HELP_AOP_MID);
notice_help(s_ChanServ, u, CHAN_HELP_AOP_END);
return 1;
}
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
module_chanserv = find_module("chanserv/main");
if (!module_chanserv) {
module_log("Main ChanServ module not loaded");
return 0;
}
use_module(module_chanserv);
if (!register_commands(module_chanserv, cmds)
|| ((protocol_features & PF_HALFOP)
&& !register_commands(module_chanserv, cmds_halfop))
) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(module_chanserv, "HELP", do_help)) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_chanserv) {
remove_callback(module_chanserv, "HELP", do_help);
if (protocol_features & PF_HALFOP)
unregister_commands(module_chanserv, cmds_halfop);
unregister_commands(module_chanserv, cmds);
unuse_module(module_chanserv);
module_chanserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

462
modules/chanserv/access.c Normal file
View File

@ -0,0 +1,462 @@
/* Base routines for ChanServ access level handling.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "chanserv.h"
#include "access.h"
#include "cs-local.h"
/*************************************************************************/
/* Array of all access levels: */
EXPORT_ARRAY(levelinfo)
LevelInfo levelinfo[] = {
{ CA_AUTOPROTECT, ACCLEV_SOP, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT,
CL_SET_MODE, { .cumode = {"a",0} } },
{ CA_AUTOOP, ACCLEV_AOP, "AUTOOP", CHAN_LEVEL_AUTOOP,
CL_SET_MODE, { .cumode = {"o",1} } },
{ CA_AUTOHALFOP, ACCLEV_HOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP,
CL_SET_MODE, { .cumode = {"h",1} } },
{ CA_AUTOVOICE, ACCLEV_VOP, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE,
CL_SET_MODE, { .cumode = {"v",0} } },
/* Internal use; not settable by users. These two levels change (in
* effect, not in the actual tables) based on the SECUREOPS and
* RESTRICTED settings. */
{ CA_AUTODEOP, -1, "", -1,
CL_CLEAR_MODE|CL_LESSEQUAL, { cumode: {"oha",0} } },
{ CA_NOJOIN, -100, "", -1,
CL_OTHER|CL_LESSEQUAL },
{ CA_INVITE, ACCLEV_AOP, "INVITE", CHAN_LEVEL_INVITE,
CL_ALLOW_CMD, { .cmd = {"INVITE"} } },
{ CA_AKICK, ACCLEV_SOP, "AKICK", CHAN_LEVEL_AKICK,
CL_ALLOW_CMD, { .cmd = {"AKICK"} } },
{ CA_SET, ACCLEV_FOUNDER, "SET", CHAN_LEVEL_SET,
CL_ALLOW_CMD, { .cmd = {"SET"} } },
{ CA_CLEAR, ACCLEV_SOP, "CLEAR", CHAN_LEVEL_CLEAR,
CL_ALLOW_CMD, { .cmd = {"CLEAR"} } },
{ CA_UNBAN, ACCLEV_AOP, "UNBAN", CHAN_LEVEL_UNBAN,
CL_ALLOW_CMD, { .cmd = {"UNBAN"} } },
{ CA_ACCESS_LIST, 0, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST,
CL_ALLOW_CMD, { .cmd = {"ACCESS","LIST"} } },
{ CA_ACCESS_CHANGE, ACCLEV_HOP, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE,
CL_ALLOW_CMD, { .cmd = {"ACCESS"} } },
{ CA_MEMO, ACCLEV_SOP, "MEMO", CHAN_LEVEL_MEMO,
CL_OTHER },
{ CA_OPDEOP, ACCLEV_AOP, "OP-DEOP", CHAN_LEVEL_OPDEOP,
CL_ALLOW_CMD, { .cmd = {"OP"} } }, /* also includes "DEOP" */
{ CA_VOICE, ACCLEV_VOP, "VOICE", CHAN_LEVEL_VOICE,
CL_ALLOW_CMD, { .cmd = {"VOICE"} } },
{ CA_HALFOP, ACCLEV_HOP, "HALFOP", CHAN_LEVEL_HALFOP,
CL_ALLOW_CMD, { .cmd = {"HALFOP"} } },
{ CA_PROTECT, ACCLEV_SOP, "PROTECT", CHAN_LEVEL_PROTECT,
CL_ALLOW_CMD, { .cmd = {"PROTECT"} } },
{ CA_KICK, ACCLEV_AOP, "KICK", CHAN_LEVEL_KICK,
CL_ALLOW_CMD, { .cmd = {"KICK"} } },
{ CA_TOPIC, ACCLEV_AOP, "TOPIC", CHAN_LEVEL_TOPIC,
CL_ALLOW_CMD, { .cmd = {"TOPIC"} } },
{ CA_STATUS, ACCLEV_SOP, "STATUS", CHAN_LEVEL_STATUS,
CL_ALLOW_CMD, { .cmd = {"STATUS"} } },
{ -1 }
};
/* Default access levels (initialized at runtime): */
int16 def_levels[CA_SIZE];
/* Which levels are "maximums" (CL_LESSEQUAL): */
static int lev_is_max[CA_SIZE];
static int get_access_if_idented(const User *user, const ChannelInfo *ci);
/*************************************************************************/
/************************** Global-use routines **************************/
/*************************************************************************/
/* Return the channel's level for the given category, handling
* ACCLEV_DEFAULT appropriately. Returns ACCLEV_INVALID on invalid
* parameters (`ci' NULL or `what' out of range).
*/
EXPORT_FUNC(get_ci_level)
int get_ci_level(const ChannelInfo *ci, int what)
{
int level;
if (!ci) {
module_log("get_ci_level() called with NULL ChannelInfo!");
return ACCLEV_INVALID;
} else if (what < 0 || what >= CA_SIZE) {
module_log("get_ci_level() called with invalid `what'!");
return ACCLEV_INVALID;
}
level = ci->levels[what];
if (level == ACCLEV_DEFAULT)
level = def_levels[what];
return level;
}
/*************************************************************************/
/* Return 1 if the user's access level on the given channel falls into the
* given category, 0 otherwise. Note that this may seem slightly confusing
* in some cases: for example, check_access(..., CA_NOJOIN) returns true if
* the user does _not_ have access to the channel (i.e. matches the NOJOIN
* criterion).
*/
EXPORT_FUNC(check_access)
int check_access(const User *user, const ChannelInfo *ci, int what)
{
int level = get_access(user, ci);
int limit;
if (level == ACCLEV_FOUNDER)
return lev_is_max[what] ? 0 : 1;
limit = get_ci_level(ci, what);
/* Hacks to make flags work */
if (what == CA_AUTODEOP && (ci->flags & CF_SECUREOPS))
limit = 0;
if (what == CA_NOJOIN && (ci->flags & CF_RESTRICTED))
limit = 0;
if (limit == ACCLEV_INVALID)
return 0;
if (lev_is_max[what])
return level <= limit;
else
return level >= limit;
}
/*************************************************************************/
/* Do like check_access(), but return whether the user would match the
* given category if they had identified for their nick. If the nick is
* not registered, returns the same value as check_access().
*/
EXPORT_FUNC(check_access_if_idented)
int check_access_if_idented(const User *user, const ChannelInfo *ci, int what)
{
int level = get_access_if_idented(user, ci);
int limit;
if (level == ACCLEV_FOUNDER)
return lev_is_max[what] ? 0 : 1;
limit = get_ci_level(ci, what);
/* Hacks to make flags work */
if (what == CA_AUTODEOP && (ci->flags & CF_SECUREOPS))
limit = 0;
if (what == CA_NOJOIN && (ci->flags & CF_RESTRICTED))
limit = 0;
if (limit == ACCLEV_INVALID)
return 0;
if (lev_is_max[what])
return level <= limit;
else
return level >= limit;
}
/*************************************************************************/
/* Return positive if the user is permitted access to the given command for
* the given channel, zero otherwise. If no level is found that
* corresponds to the given command, return -1.
*/
EXPORT_FUNC(check_access_cmd)
int check_access_cmd(const User *user, const ChannelInfo *ci,
const char *command, const char *subcommand)
{
int i;
/* If we have a subcommand, first check for an exact match */
if (subcommand) {
for (i = 0; levelinfo[i].what >= 0; i++) {
if ((levelinfo[i].action & CL_TYPEMASK) == CL_ALLOW_CMD
&& levelinfo[i].target.cmd.sub
&& stricmp(command, levelinfo[i].target.cmd.main) == 0
&& stricmp(subcommand, levelinfo[i].target.cmd.sub) == 0
) {
return check_access(user, ci, levelinfo[i].what);
}
}
}
/* No subcommand or no exact match, so match on command name;
* explicitly skip entries with subcommands (because they didn't
* match before) */
for (i = 0; levelinfo[i].what >= 0; i++) {
if ((levelinfo[i].action & CL_TYPEMASK) == CL_ALLOW_CMD
&& !levelinfo[i].target.cmd.sub
&& stricmp(command, levelinfo[i].target.cmd.main) == 0
) {
return check_access(user, ci, levelinfo[i].what);
}
}
/* No level found */
return -1;
}
/*************************************************************************/
/************************** Local-use routines ***************************/
/*************************************************************************/
/* Return the access level the given user has on the channel. If the
* channel doesn't exist, the user isn't on the access list, or the channel
* is CS_SECURE and the user hasn't IDENTIFY'd with NickServ, return 0.
* The only external use is by the STATUS command.
*/
int get_access(const User *user, const ChannelInfo *ci)
{
if (is_founder(user, ci))
return ACCLEV_FOUNDER;
if (!ci || !valid_ngi(user) || (ci->flags & (CF_VERBOTEN | CF_SUSPENDED)))
return 0;
if (user_identified(user)
|| (user_recognized(user) && !(ci->flags & CF_SECURE))
) {
int32 id = user->ngi->id;
int i;
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup == id)
return ci->access[i].level;
}
}
return 0;
}
/*************************************************************************/
/* Return the access level the given user has on the channel, supposing the
* user was identified for their current nick (if it's registered).
*/
static int get_access_if_idented(const User *user, const ChannelInfo *ci)
{
int i;
int32 id;
if (is_identified(user, ci))
return ACCLEV_FOUNDER;
if (!ci || !valid_ngi(user) || (ci->flags & (CF_VERBOTEN | CF_SUSPENDED)))
return 0;
if (user->ngi->id == ci->founder)
return ACCLEV_FOUNDER;
id = user->ngi->id;
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup == id)
return ci->access[i].level;
}
return 0;
}
/*************************************************************************/
/* Check a channel user mode change, and return the mask of mode flags
* which should be changed (i.e. should be the reverse of the new flags
* given). The `changemask' parameter indicates which flags are changing,
* and `newmodes' indicates the value of the changing flags; a new user on
* a channel, e.g. from JOIN, should have a `changemask' with all bits set
* (because all bits are changing from "undefined" to either on or off).
* The returned value will always be a subset of `changemask'.
*
* Example: autoprotect/autoop user joins channel
* check_access_cumode(..., 0, ~0) -> CUMODE_a | CUMODE_o
* Example: an autodeop user gets +o'd by somebody
* check_access_cumode(..., CUMODE_o, CUMODE_o) -> CUMODE_o
* (i.e. "setting CUMODE_o is incorrect" -> send a MODE -o)
*/
int check_access_cumode(const User *user, const ChannelInfo *ci,
int32 newmodes, int32 changemask)
{
int i;
int32 result = 0;
for (i = 0; levelinfo[i].what >= 0; i++) {
int type = levelinfo[i].action & CL_TYPEMASK;
int32 flags = levelinfo[i].target.cumode.flags;
int clevel = get_ci_level(ci, levelinfo[i].what);
if ((type == CL_SET_MODE || type == CL_CLEAR_MODE)
&& clevel != ACCLEV_INVALID
&& (changemask & flags)
&& check_access(user, ci, levelinfo[i].what)
) {
if (type == CL_SET_MODE && (~newmodes & flags))
result |= ~newmodes & flags;
else if (type == CL_CLEAR_MODE && (newmodes & flags))
result |= newmodes & flags;
while (levelinfo[i].target.cumode.cont)
i++;
}
}
return result;
}
/*************************************************************************/
/* Add an entry `nick' (at `level') to the access list by a user with
* access `uacc'.
*/
int access_add(ChannelInfo *ci, const char *nick, int level, int uacc)
{
int i;
NickInfo *ni;
NickGroupInfo *ngi;
if (level >= uacc)
return RET_PERMISSION;
ni = get_nickinfo(nick);
if (!ni) {
return RET_NOSUCHNICK;
} else if (ni->status & NS_VERBOTEN) {
put_nickinfo(ni);
return RET_NICKFORBID;
} else if (!(ngi = get_ngi(ni))) {
put_nickinfo(ni);
return RET_INTERR;
} else if ((ngi->flags & NF_NOOP) && level > 0) {
put_nickgroupinfo(ngi);
put_nickinfo(ni);
return RET_NOOP;
}
put_nickgroupinfo(ngi);
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup == ni->nickgroup) {
put_nickinfo(ni);
/* Don't allow lowering from a level >= uacc */
if (ci->access[i].level >= uacc)
return RET_PERMISSION;
if (ci->access[i].level == level)
return RET_UNCHANGED;
ci->access[i].level = level;
return RET_CHANGED;
}
}
ARRAY_SEARCH_SCALAR(ci->access, nickgroup, 0, i);
if (i == ci->access_count) {
if (i < CSAccessMax) {
ARRAY_EXTEND(ci->access);
} else {
put_nickinfo(ni);
return RET_LISTFULL;
}
}
ci->access[i].channel = ci;
ci->access[i].nickgroup = ni->nickgroup;
ci->access[i].level = level;
put_nickinfo(ni);
return RET_ADDED;
}
/*************************************************************************/
int access_del(ChannelInfo *ci, const char *nick, int uacc)
{
NickInfo *ni;
int i;
ni = get_nickinfo(nick);
if (!ni) {
return RET_NOSUCHNICK;
} else if (ni->status & NS_VERBOTEN) {
put_nickinfo(ni);
return RET_NICKFORBID;
} else if (!check_ngi(ni)) {
put_nickinfo(ni);
return RET_INTERR;
}
ARRAY_SEARCH_SCALAR(ci->access, nickgroup, ni->nickgroup, i);
put_nickinfo(ni);
if (i == ci->access_count)
return RET_NOENTRY;
if (uacc <= ci->access[i].level)
return RET_PERMISSION;
ci->access[i].nickgroup = 0;
return RET_DELETED;
}
/*************************************************************************/
/*************************************************************************/
int init_access(void)
{
int i;
/* Initialize def_levels[] and lev_is_max[], and convert mode letters
* to flags */
for (i = 0; levelinfo[i].what >= 0; i++) {
int type = levelinfo[i].action & CL_TYPEMASK;
if (type == CL_SET_MODE || type == CL_CLEAR_MODE) {
/* Use MODE_NOERROR to deal with protocols that don't
* support some modes (e.g. +h in AUTODEOP) */
levelinfo[i].target.cumode.flags =
mode_string_to_flags(levelinfo[i].target.cumode.modes,
MODE_CHANUSER | MODE_NOERROR);
}
def_levels[levelinfo[i].what] = levelinfo[i].defval;
lev_is_max[levelinfo[i].what] = levelinfo[i].action & CL_LESSEQUAL;
}
/* Delete any levels for features not supported by protocol */
if (!(protocol_features & PF_HALFOP)) {
int offset = 0;
for (i = 0; i == 0 || levelinfo[i-1].what >= 0; i++) {
if (levelinfo[i].what == CA_AUTOHALFOP
|| levelinfo[i].what == CA_HALFOP
) {
offset++;
} else if (offset) {
memcpy(&levelinfo[i-offset], &levelinfo[i],
sizeof(*levelinfo));
}
}
}
if (!(protocol_features & PF_CHANPROT)) {
int offset = 0;
for (i = 0; i == 0 || levelinfo[i-1].what >= 0; i++) {
if (levelinfo[i].what == CA_AUTOPROTECT
|| levelinfo[i].what == CA_PROTECT
) {
offset++;
} else if (offset) {
memcpy(&levelinfo[i-offset], &levelinfo[i],
sizeof(*levelinfo));
}
}
}
return 1;
}
/*************************************************************************/
void exit_access(void)
{
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

60
modules/chanserv/access.h Normal file
View File

@ -0,0 +1,60 @@
/* Include file for ChanServ access level data.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef CHANSERV_ACCESS_H
#define CHANSERV_ACCESS_H
/*************************************************************************/
/* Access level data structure: */
typedef struct levelinfo_ LevelInfo;
struct levelinfo_ {
int what; /* Level constant (CA_*) */
int defval; /* Default level */
const char *name; /* Level name as a string */
int desc; /* Description message number */
int action; /* What this level does (CL_*) */
union { /* Target of `action', as appropriate */
struct { /* Mode(s) to auto-apply to user */
const char *modes;
int cont; /* If we set this level, do we ignore the next */
/* levelinfo[] entry? */
int32 flags; /* Set at init time */
} cumode;
struct { /* Command (and subcommand, e.g. option for SET) */
const char *main;
const char *sub;
} cmd;
} target;
};
/* What does a level do? */
#define CL_SET_MODE 0 /* Set a user mode */
#define CL_CLEAR_MODE 1 /* Clear a user mode */
#define CL_ALLOW_CMD 2 /* Allow a command to be used */
#define CL_OTHER 0x7F /* Specially handled, or no-op */
#define CL_TYPEMASK 0x7F
#define CL_LESSEQUAL 0x80 /* Apply to users <= level (OR with type) */
/* List of defined levels with descriptions: */
extern LevelInfo levelinfo[];
/*************************************************************************/
#endif /* CHANSERV_ACCESS_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

241
modules/chanserv/chanserv.h Normal file
View File

@ -0,0 +1,241 @@
/* ChanServ-related structures.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef CHANSERV_H
#define CHANSERV_H
#ifndef ENCRYPT_H
# include "../../encrypt.h"
#endif
#ifndef NICKSERV_H
# include "../nickserv/nickserv.h"
#endif
/*************************************************************************/
/* Access levels for users. */
typedef struct {
ChannelInfo *channel;
uint32 nickgroup; /* Zero if entry is not in use */
int16 level;
} ChanAccess;
/* Minimum and maximum valid access levels, inclusive. */
#define ACCLEV_MIN -999
#define ACCLEV_MAX 999
/* Access level indicating founder access. May be assumed to be strictly
* greater than any other level. */
#define ACCLEV_FOUNDER (ACCLEV_MAX+1)
/* Access level value used for disabled channel privileges. */
#define ACCLEV_INVALID (ACCLEV_MIN-1)
/* Access levels used to represent SOPs, AOPs, HOPs and VOPs in channel
* access lists. */
#define ACCLEV_SOP 100
#define ACCLEV_AOP 50
#define ACCLEV_HOP 40
#define ACCLEV_VOP 30
#define ACCLEV_NOP -1
/* Access level value meaning "use the default level". */
#define ACCLEV_DEFAULT -9999
/*************************************************************************/
/* AutoKick data. */
typedef struct {
ChannelInfo *channel;
char *mask; /* NULL if not in use */
char *reason;
char who[NICKMAX];
time_t set;
time_t lastused;
} AutoKick;
/*************************************************************************/
/* Mode lock data. */
typedef struct {
#ifdef CONVERT_DB
char *on, *off; /* Strings of mode characters */
#else
int32 on, off; /* See channel modes below */
#endif
int32 limit; /* 0 if no limit */
char *key; /* NULL if no key */
char *link; /* +L (Unreal, trircd) */
char *flood; /* +f (Unreal, etc.) */
int32 joindelay; /* +J (trircd) */
int32 joinrate1; /* +j (Bahamut/Unreal) */
int32 joinrate2; /* +j (Bahamut/Unreal) */
} ModeLock;
/*************************************************************************/
/* Indices for ci->levels[]: (DO NOT REORDER THESE unless you hack
* the database/version4 module to deal with any changes) */
#define CA_INVITE 0
#define CA_AKICK 1
#define CA_SET 2 /* but not FOUNDER or PASSWORD */
#define CA_UNBAN 3
#define CA_AUTOOP 4
#define CA_AUTODEOP 5 /* Maximum, not minimum; internal use only */
#define CA_AUTOVOICE 6
#define CA_OPDEOP 7 /* ChanServ commands OP and DEOP */
#define CA_ACCESS_LIST 8
#define CA_CLEAR 9
#define CA_NOJOIN 10 /* Maximum; internal use only */
#define CA_ACCESS_CHANGE 11
#define CA_MEMO 12
#define CA_VOICE 13 /* VOICE/DEVOICE commands */
#define CA_AUTOHALFOP 14
#define CA_HALFOP 15 /* HALFOP/DEHALFOP commands */
#define CA_AUTOPROTECT 16
#define CA_PROTECT 17
/* CA_AUTOOWNER 18 */ /* No longer used */
#define CA_KICK 19
#define CA_STATUS 20
#define CA_TOPIC 21
#define CA_SIZE 22
/*************************************************************************/
/* Data for a registered channel. */
struct channelinfo_ {
ChannelInfo *next, *prev;
int usecount;
char name[CHANMAX];
uint32 founder;
uint32 successor; /* Who gets the channel if founder nick
* group is dropped or expires */
Password founderpass;
char *desc;
char *url;
char *email;
char *entry_message; /* Notice sent on entering channel */
time_t time_registered;
time_t last_used;
char *last_topic; /* Last topic on the channel */
char last_topic_setter[NICKMAX]; /* Who set the last topic */
time_t last_topic_time; /* When the last topic was set */
int32 flags; /* See below */
char suspend_who[NICKMAX]; /* Who suspended this channel */
char *suspend_reason; /* Reason for suspension */
time_t suspend_time; /* Time channel was suspended */
time_t suspend_expires; /* Time suspension expires, 0 for no expiry */
int16 levels[CA_SIZE]; /* Access levels for commands */
ChanAccess *access; /* List of authorized users */
int16 access_count;
AutoKick *akick; /* List of users to kickban */
int16 akick_count;
ModeLock mlock; /* Mode lock settings */
/* Online-only data: */
Channel *c; /* Pointer to channel record (if *
* channel is currently in use) */
int bad_passwords; /* # of bad passwords since last good one */
};
/* Retain topic even after last person leaves channel */
#define CF_KEEPTOPIC 0x00000001
/* Don't allow non-authorized users to be opped */
#define CF_SECUREOPS 0x00000002
/* Hide channel from ChanServ LIST command */
#define CF_PRIVATE 0x00000004
/* Topic can only be changed by SET TOPIC */
#define CF_TOPICLOCK 0x00000008
/* Those not allowed ops are kickbanned */
#define CF_RESTRICTED 0x00000010
/* Don't auto-deop anyone */
#define CF_LEAVEOPS 0x00000020
/* Don't allow any privileges unless a user is IDENTIFY'd with NickServ */
#define CF_SECURE 0x00000040
/* Don't allow the channel to be registered or used */
#define CF_VERBOTEN 0x00000080
/* Unused; used to be ENCRYPTEDPW */
/* CF_ENCRYPTEDPW 0x00000100 */
/* Channel does not expire */
#define CF_NOEXPIRE 0x00000200
/* Unused; used to be MEMO_HARDMAX */
/* CF_MEMO_HARDMAX 0x00000400 */
/* Send notice to channel on use of OP/DEOP */
#define CF_OPNOTICE 0x00000800
/* Enforce +o, +v modes (don't allow deopping) */
#define CF_ENFORCE 0x00001000
/* Hide E-mail address from INFO */
#define CF_HIDE_EMAIL 0x00002000
/* Hide last topic from INFO */
#define CF_HIDE_TOPIC 0x00004000
/* Hide mode lock from INFO */
#define CF_HIDE_MLOCK 0x00008000
/* Channel is suspended */
#define CF_SUSPENDED 0x00010000
/* Limit memo sending to users with the MEMO privilege */
#define CF_MEMO_RESTRICTED 0x00020000
/* All channel flags */
#define CF_ALLFLAGS 0x0001FAFF
/*************************************************************************/
/* Prototypes for exported variables and functions. */
E char *s_ChanServ;
E int32 CSMaxReg;
E ChannelInfo *add_channelinfo(ChannelInfo *ci);
E void del_channelinfo(ChannelInfo *ci);
E ChannelInfo *get_channelinfo(const char *chan);
E ChannelInfo *put_channelinfo(ChannelInfo *ci);
E ChannelInfo *first_channelinfo(void);
E ChannelInfo *next_channelinfo(void);
#ifdef STANDALONE_CHANSERV /* see util.c */
# define E2 static
#else
# define E2 extern
#endif
E2 ChannelInfo *new_channelinfo(void);
E2 void free_channelinfo(ChannelInfo *ci);
E2 void reset_levels(ChannelInfo *ci);
#undef E2
E int get_ci_level(const ChannelInfo *ci, int what);
E int get_access(const User *user, const ChannelInfo *ci);
E int check_access(const User *user, const ChannelInfo *ci, int what);
E int check_access_if_idented(const User *user, const ChannelInfo *ci,
int what);
E int check_access_cmd(const User *user, const ChannelInfo *ci,
const char *command, const char *subcommand);
E int check_channel_limit(const NickGroupInfo *ngi, int *max_ret);
/*************************************************************************/
#endif /* CHANSERV_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

532
modules/chanserv/check.c Normal file
View File

@ -0,0 +1,532 @@
/* Routines to check validity of JOINs and mode changes.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "timeout.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "chanserv.h"
#include "cs-local.h"
/*************************************************************************/
static int cb_check_modes = -1;
static int cb_check_chan_user_modes = -1;
static int cb_check_kick = -1;
/*************************************************************************/
/*************************************************************************/
/* Check the current modes on a channel; if they conflict with a mode lock,
* fix them. */
void check_modes(Channel *c)
{
static int in_check_modes = 0;
ChannelInfo *ci;
char newmode[3];
int flag;
if (!c || c->bouncy_modes)
return;
if (!NoBouncyModes) {
/* Check for mode bouncing */
if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) {
wallops(NULL, "Warning: unable to set modes on channel %s. "
"Are your servers configured correctly?", c->name);
module_log("Bouncy modes on channel %s", c->name);
c->bouncy_modes = 1;
return;
}
if (c->chanserv_modetime != time(NULL)) {
c->chanserv_modecount = 0;
c->chanserv_modetime = time(NULL);
}
c->chanserv_modecount++;
}
ci = c->ci;
if (!ci) {
/* Services _always_ knows who should be +r. If a channel tries to be
* +r and is not registered, send mode -r. This will compensate for
* servers that are split when mode -r is initially sent and then try
* to set +r when they rejoin. -TheShadow */
if (c->mode & chanmode_reg) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "-%s",
mode_flags_to_string(chanmode_reg, MODE_CHANNEL));
set_cmode(s_ChanServ, c, buf);
/* Flush it out immediately. Note that this won't cause
* infinite recursion because we're clearing the mode that
* got us here in the first place. */
set_cmode(NULL, c);
}
return;
}
/* Avoid infinite recursion (recursion occurs if set_cmode() flushes
* out mode changes in the middle of setting them here) */
if (in_check_modes)
return;
in_check_modes++;
newmode[2] = 0;
/* Note that MODE_INVALID == 1<<31, so we can just stop there */
for (flag = 1; flag != MODE_INVALID; flag <<= 1) {
int add;
if ((ci->mlock.on | chanmode_reg) & flag)
add = 1;
else if (ci->mlock.off & flag)
add = 0;
else
continue;
if (call_callback_4(cb_check_modes, c, ci, add, flag) > 0) {
continue;
} else if (flag == CMODE_k) {
if (c->key && (!add || (add && c->key && ci->mlock.key
&& strcmp(c->key, ci->mlock.key) != 0))) {
set_cmode(s_ChanServ, c, "-k", c->key);
set_cmode(NULL, c); /* flush it out */
}
if (add && !c->key)
set_cmode(s_ChanServ, c, "+k", ci->mlock.key);
} else if (flag == CMODE_l) {
if (add && ci->mlock.limit != c->limit) {
char limitbuf[16];
snprintf(limitbuf, sizeof(limitbuf), "%d", ci->mlock.limit);
set_cmode(s_ChanServ, c, "+l", limitbuf);
} else if (!add && c->limit != 0) {
set_cmode(s_ChanServ, c, "-l");
}
} else if (add ^ !!(c->mode & flag)) {
newmode[0] = add ? '+' : '-';
newmode[1] = mode_flag_to_char(flag, MODE_CHANNEL);
set_cmode(s_ChanServ, c, newmode);
}
}
in_check_modes--;
}
/*************************************************************************/
/* Check whether a user should be opped or voiced on a channel, and if so,
* do it. Updates the channel's last used time if the user is opped.
* `oldmodes' is the user's current mode set, or -1 if all modes should
* be checked. `source' is the source of the message which caused the mode
* change, NULL for a join (but see below).
*
* Note that this function may be called with an empty `source' (i.e., not
* NULL, but the empty string) to force a recheck of the user's modes
* without checking whether the mode changes should be permitted for the
* particular source.
*/
/* Local helper routine */
static void local_set_cumodes(Channel *c, char plusminus, int32 modes,
struct c_userlist *cu);
void check_chan_user_modes(const char *source, struct c_userlist *u,
Channel *c, int32 oldmodes)
{
User *user = u->user;
ChannelInfo *ci = c->ci;
int32 modes = u->mode;
int is_servermode = (!source || strchr(source, '.') != NULL);
int32 res; /* result from check_access_cumode() */
/* Don't change modes on unregistered or forbidden channels */
if (!ci || (ci->flags & CF_VERBOTEN))
return;
/* Don't reverse mode changes made by Services (because we already
* prevent people from doing improper mode changes via Services, so
* anything that gets here must be okay). */
if (source && (irc_stricmp(source, ServerName) == 0
|| irc_stricmp(source, s_ChanServ) == 0
|| irc_stricmp(source, s_OperServ) == 0))
return;
/* Also don't reverse mode changes by the user themselves, unless the
* user is -o now (this could happen if we've sent out a -o already but
* the user got in a +v or such before the -o reached their server), or
* the user is going to be deopped soon but the -o is held up by
* MergeChannelModes (the CUFLAG_DEOPPED flag).
*
* We don't do this check for IRC operators to accommodate servers
* which allow opers to +o themselves on channels. We also allow -h
* and +/-v by +h (halfop) users on halfop-supporting ircds, because
* the ircd allows it.
*/
if (source && !is_oper(user) && irc_stricmp(source, user->nick) == 0) {
if (!(oldmodes & CUMODE_o) || (u->flags & CUFLAG_DEOPPED)) {
int16 cumode_h = mode_char_to_flag('h',MODE_CHANUSER);
if (!((oldmodes & cumode_h)
&& !((oldmodes^modes) & ~(CUMODE_v|cumode_h)))
) {
local_set_cumodes(c, '-', (modes & ~oldmodes), u);
}
}
return;
}
/* Check early for server auto-ops */
if ((modes & CUMODE_o)
&& !(ci->flags & CF_LEAVEOPS)
&& is_servermode
) {
if (!is_oper(user) /* Here, too, don't subtract modes from opers */
&& (time(NULL)-start_time >= CSRestrictDelay
|| !check_access_if_idented(user, ci, CA_AUTOOP))
&& !check_access(user, ci, CA_AUTOOP)
) {
notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
u->flags |= CUFLAG_DEOPPED;
set_cmode(s_ChanServ, c, "-o", user->nick);
modes &= ~CUMODE_o;
} else if (check_access(user, ci, CA_AUTOOP)) {
/* The user's an autoop user; update the last-used time here,
* because it won't get updated below (they're already opped) */
ci->last_used = time(NULL);
}
}
/* Let the protocol module have a hack at it */
if (call_callback_4(cb_check_chan_user_modes, source, user, c, modes) > 0)
return;
/* Adjust modes based on channel access */
if (oldmodes < 0) {
res = check_access_cumode(user, ci, modes, ~0);
} else {
int32 changed = modes ^ oldmodes;
res = check_access_cumode(user, ci, changed & modes, changed);
}
/* Check for mode additions. Only check if join or server mode change,
* unless ENFORCE is set */
/* Note: modes to add = changed modes & off new-modes = res & ~modes */
if ((res & ~modes)
&& (oldmodes < 0 || is_servermode || (ci->flags & CF_ENFORCE))
) {
local_set_cumodes(c, '+', res & ~modes, u);
if ((res & ~modes) & CUMODE_o)
ci->last_used = time(NULL);
}
/* Don't subtract modes from opers */
if (is_oper(user))
return;
/* Check for mode subtractions */
if (res & modes)
local_set_cumodes(c, '-', res & modes, u);
}
/************************************/
/* Helper routine for check_chan_user_modes(): sets all of the given modes
* on client `cu' in channel `c'.
*/
static void local_set_cumodes(Channel *c, char plusminus, int32 modes,
struct c_userlist *cu)
{
char buf[3], modestr[BUFSIZE], *s;
buf[0] = plusminus;
buf[2] = 0;
strbcpy(modestr, mode_flags_to_string(modes, MODE_CHANUSER));
s = modestr;
while (*s) {
buf[1] = *s++;
set_cmode(s_ChanServ, c, buf, cu->user->nick);
}
/* Set user's modes now, so check_chan_user_modes() can properly
* determine whether subsequent modes should be set or not */
if (plusminus == '+')
cu->mode |= modes;
else if (plusminus == '-')
cu->mode &= ~modes;
}
/*************************************************************************/
/* List of channels currently inhabited */
typedef struct csinhabitdata_ CSInhabitData;
struct csinhabitdata_ {
CSInhabitData *next, *prev;
char chan[CHANMAX];
Timeout *to;
};
static CSInhabitData *inhabit_list = NULL;
/* Tiny helper routine to get ChanServ out of a channel after it went in. */
static void timeout_leave(Timeout *to)
{
CSInhabitData *data = to->data;
send_cmd(s_ChanServ, "PART %s", data->chan);
LIST_REMOVE(data, inhabit_list);
free(data);
}
/* Check whether a user is permitted to be on a channel. If so, return 0;
* else, kickban the user with an appropriate message (could be either
* AKICK or restricted access) and return 1. When `on_join' is nonzero,
* this routine will _not_ call do_kick(), assuming that the user is not
* yet on the internal channel list.
*/
int check_kick(User *user, const char *chan, int on_join)
{
Channel *c = get_channel(chan);
ChannelInfo *ci = get_channelinfo(chan);
int i;
char *mask, *s;
const char *reason;
char reasonbuf[BUFSIZE];
int stay;
if (CSForbidShortChannel && strcmp(chan, "#") == 0) {
mask = sstrdup("*!*@*");
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED);
goto kick;
}
if (is_services_admin(user)) {
put_channelinfo(ci);
return 0;
}
i = call_callback_5(cb_check_kick, user, chan, ci, &mask, &reason);
if (i == 2) {
put_channelinfo(ci);
return 0;
} else if (i == 1) {
goto kick;
}
/* Nothing else here affects IRC operators */
if (is_oper(user)) {
put_channelinfo(ci);
return 0;
}
/* Check join against channel's modes--this is properly the domain of
* the IRC server, but you never know... */
if (c) {
if (c->mode & chanmode_opersonly) {
/* We know from above that they're not an oper */
mask = create_mask(user, 1);
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
}
if (!ci) {
if (CSRegisteredOnly) {
mask = sstrdup("*!*@*");
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED);
goto kick;
} else {
put_channelinfo(ci);
return 0;
}
}
if (ci->flags & (CF_VERBOTEN | CF_SUSPENDED)) {
mask = sstrdup("*!*@*");
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED);
goto kick;
}
if (ci->mlock.on & chanmode_opersonly) {
/* We already know they're not an oper, so kick them off */
mask = create_mask(user, 1);
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
if (!CSSkipModeRCheck && (ci->mlock.on & chanmode_regonly)
&& !user_identified(user)
) {
/* User must have usermode_reg flags, i.e. be using a registered
* nick and have identified, in order to join a chanmode_regonly
* channel */
mask = create_mask(user, 1);
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
ARRAY_FOREACH (i, ci->akick) {
if (!ci->akick[i].mask) {
log_debug(1, "%s autokick %d has NULL mask, deleting", ci->name,i);
ARRAY_REMOVE(ci->akick, i);
i--;
continue;
}
if (match_usermask(ci->akick[i].mask, user)) {
module_log_debug(2, "%s matched akick %s",
user->nick, ci->akick[i].mask);
mask = sstrdup(ci->akick[i].mask);
snprintf(reasonbuf, sizeof(reasonbuf), "AKICK by %s%s%s%s",
ci->akick[i].who,
ci->akick[i].reason ? " (" : "",
ci->akick[i].reason ? ci->akick[i].reason : "",
ci->akick[i].reason ? ")" : "");
reason = reasonbuf;
time(&ci->akick[i].lastused);
goto kick;
}
}
if ((time(NULL)-start_time >= CSRestrictDelay
|| check_access_if_idented(user, ci, CA_NOJOIN))
&& check_access(user, ci, CA_NOJOIN)
) {
mask = create_mask(user, 1);
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
put_channelinfo(ci);
return 0;
kick:
module_log_debug(1, "check_kick() kicking %s!%s@%s from %s",
user->nick, user->username, user->host, chan);
/* When called on join, the user has not been added to our channel user
* list yet, so we check whether the channel does not exist rather than
* whether the channel has only one user in it. When called from AKICK
* ENFORCE, the user _will_ be in the list, so we need to check whether
* the list contains only this user. Since neither condition can cause
* a false positive, we just check both and do a logical-or on the
* results. */
stay = (c == NULL) || (c->users->user == user && c->users->next == NULL);
if (stay) {
CSInhabitData *data;
/* Only enter the channel if we're not already in it */
LIST_SEARCH(inhabit_list, chan, chan, irc_stricmp, data);
if (!data) {
Timeout *to;
send_cmd(s_ChanServ, "JOIN %s", chan);
to = add_timeout(CSInhabit, timeout_leave, 0);
to->data = data = smalloc(sizeof(*data));
LIST_INSERT(data, inhabit_list);
strbcpy(data->chan, chan);
data->to = to;
}
}
/* Make sure the mask has a ! in it */
if (!(s = strchr(mask, '!')) || s > strchr(mask, '@')) {
int len = strlen(mask);
mask = srealloc(mask, len+3);
memmove(mask+2, mask, len+1);
mask[0] = '*';
mask[1] = '!';
}
/* Clear any exceptions matching the user, to ensure that the ban takes
* effect */
if (c)
clear_channel(c, CLEAR_EXCEPTS, user);
/* Apparently invites can get around bans, so check for ban before adding*/
if (!chan_has_ban(chan, mask)) {
send_cmode_cmd(s_ChanServ, chan, "+b %s", mask);
if (c) {
char *av[3];
av[0] = (char *)chan;
av[1] = (char *)"+b";
av[2] = mask;
do_cmode(s_ChanServ, 3, av);
}
}
free(mask);
send_channel_cmd(s_ChanServ, "KICK %s %s :%s", chan, user->nick, reason);
if (!on_join) {
/* The user is already in the channel userlist, so get them out */
char *av[3];
av[0] = (char *)chan;
av[1] = user->nick;
av[2] = (char *)"check_kick"; /* dummy value */
do_kick(s_ChanServ, 3, av);
}
put_channelinfo(ci);
return 1;
}
/*************************************************************************/
/* See if the topic is locked on the given channel, and return 1 (and fix
* the topic) if so, 0 if not. */
int check_topiclock(Channel *c, time_t topic_time)
{
ChannelInfo *ci = c->ci;
if (!ci || !(ci->flags & CF_TOPICLOCK))
return 0;
c->topic_time = topic_time; /* because set_topic() may need it */
set_topic(s_ChanServ, c, ci->last_topic,
*ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ,
ci->last_topic_time);
return 1;
}
/*************************************************************************/
/*************************************************************************/
int init_check(void)
{
cb_check_modes = register_callback("check_modes");
cb_check_chan_user_modes = register_callback("check_chan_user_modes");
cb_check_kick = register_callback("check_kick");
if (cb_check_modes < 0 || cb_check_chan_user_modes < 0
|| cb_check_kick < 0
) {
module_log("check: Unable to register callbacks");
exit_check();
return 0;
}
return 1;
}
/*************************************************************************/
void exit_check()
{
CSInhabitData *inhabit, *tmp;
LIST_FOREACH_SAFE (inhabit, inhabit_list, tmp) {
del_timeout(inhabit->to);
LIST_REMOVE(inhabit, inhabit_list);
free(inhabit);
}
unregister_callback(cb_check_kick);
unregister_callback(cb_check_chan_user_modes);
unregister_callback(cb_check_modes);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

147
modules/chanserv/cs-local.h Normal file
View File

@ -0,0 +1,147 @@
/* Include file for data local to the ChanServ module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef CS_LOCAL_H
#define CS_LOCAL_H
/*************************************************************************/
/*************************************************************************/
/* Maximum number of parameters to allow to a SET MLOCK command. Anything
* over this will be silently discarded. */
#define MAX_MLOCK_PARAMS 256 /* impossible at 512 bytes/line */
/*************************************************************************/
/* Data for a channel option. */
typedef struct {
const char *name;
int32 flag;
int namestr; /* If -1, will be ignored by cs_flags_to_string() */
int onstr, offstr, syntaxstr;
} ChanOpt;
/*************************************************************************/
/* Return values for access list add/delete/list functions. Success > 0,
* failure < 0. Do not use zero as a return value.
*/
#define RET_ADDED 1
#define RET_CHANGED 2
#define RET_UNCHANGED 3
#define RET_DELETED 4
#define RET_LISTED 5
#define RET_PERMISSION -1
#define RET_NOSUCHNICK -2
#define RET_NICKFORBID -3
#define RET_NOOP -4
#define RET_LISTFULL -5
#define RET_NOENTRY -6
#define RET_INTERR -99
/*************************************************************************/
/* External declarations: */
/**** access.c ****/
E int16 def_levels[CA_SIZE];
E int get_access(const User *user, const ChannelInfo *ci);
E int check_access_cumode(const User *user, const ChannelInfo *ci,
int32 newmodes, int32 changemask);
E int access_add(ChannelInfo *ci, const char *nick, int level, int uacc);
E int access_del(ChannelInfo *ci, const char *nick, int uacc);
E int init_access(void);
E void exit_access(void);
/**** check.c ****/
E void check_modes(Channel *c);
E void check_chan_user_modes(const char *source, struct c_userlist *u,
Channel *c, int32 oldmodes);
E int check_kick(User *user, const char *chan, int on_join);
E int check_topiclock(Channel *c, time_t topic_time);
E int init_check(void);
E void exit_check(void);
/**** main.c ****/
E int CSRegisteredOnly;
E int32 CSMaxReg;
E int32 CSDefFlags;
E time_t CSExpire;
E int CSShowPassword;
E int32 CSAccessMax;
E int32 CSAutokickMax;
E time_t CSInhabit;
E time_t CSRestrictDelay;
E int CSListOpersOnly;
E time_t CSSuspendExpire;
E time_t CSSuspendGrace;
E int CSForbidShortChannel;
E int CSSkipModeRCheck;
E ChanOpt chanopts[];
/**** set.c ****/
/* Avoid conflicts with nickserv/set.c */
#define do_set do_set_cs
#define do_unset do_unset_cs
#define init_set init_set_cs
#define exit_set exit_set_cs
E void do_set(User *u);
E void do_unset(User *u);
E int init_set(void);
E void exit_set(void);
/**** util.c ****/
/* Avoid conflicts with nickserv/util.c */
#define init_util init_util_cs
#define exit_util exit_util_cs
E ChannelInfo *makechan(const char *chan);
E int delchan(ChannelInfo *ci);
E void count_chan(const ChannelInfo *ci);
E void uncount_chan(const ChannelInfo *ci);
E int is_founder(const User *user, const ChannelInfo *ci);
E int is_identified(const User *user, const ChannelInfo *ci);
E void restore_topic(Channel *c);
E void record_topic(ChannelInfo *ci, const char *topic,
const char *setter, time_t topic_time);
E void suspend_channel(ChannelInfo *ci, const char *reason,
const char *who, const time_t expires);
E void unsuspend_channel(ChannelInfo *ci, int set_time);
E void chan_bad_password(User *u, ChannelInfo *ci);
E ChanOpt *chanopt_from_name(const char *optname);
E char *chanopts_to_string(const ChannelInfo *ci, const NickGroupInfo *ngi);
/*************************************************************************/
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

2895
modules/chanserv/main.c Normal file

File diff suppressed because it is too large Load Diff

659
modules/chanserv/set.c Normal file
View File

@ -0,0 +1,659 @@
/* SET command handling.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "encrypt.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "chanserv.h"
#include "cs-local.h"
/*************************************************************************/
static int cb_set = -1;
static int cb_set_mlock = -1;
static int cb_unset = -1;
/*************************************************************************/
static void do_set_founder(User *u, ChannelInfo *ci, char *param);
static void do_set_successor(User *u, ChannelInfo *ci, char *param);
static void do_set_password(User *u, ChannelInfo *ci, char *param);
static void do_set_desc(User *u, ChannelInfo *ci, char *param);
static void do_set_url(User *u, ChannelInfo *ci, char *param);
static void do_set_email(User *u, ChannelInfo *ci, char *param);
static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param);
static void do_set_mlock(User *u, ChannelInfo *ci, char *param);
static void do_set_hide(User *u, ChannelInfo *ci, char *param, char *extra);
static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param);
/*************************************************************************/
/*************************************************************************/
/* Main SET routine. Calls other routines as follows:
* do_set_command(User *command_sender, ChannelInfo *ci, char *param);
* The parameter passed is the first space-delimited parameter after the
* option name, except in the case of DESC, TOPIC, and ENTRYMSG, in which
* it is the entire remainder of the line. Additional parameters beyond
* the first passed in the function call can be retrieved using
* strtok(NULL, toks).
*
* do_set_boolean, the default handler, is an exception to this in that it
* also takes the ChanOpt structure for the selected option as a parameter.
*/
void do_set(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *param = NULL, *extra = NULL;
ChannelInfo *ci = NULL;
int is_servadmin = is_services_admin(u);
int used_privs = 0;
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
return;
}
if (chan && cmd) {
if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "TOPIC") == 0
|| stricmp(cmd, "ENTRYMSG") == 0)
param = strtok_remaining();
else
param = strtok(NULL, " ");
if (stricmp(cmd, "HIDE") == 0)
extra = strtok(NULL, " ");
} else {
param = NULL;
}
if (!param || (stricmp(cmd,"MLOCK") != 0 && strtok_remaining())) {
syntax_error(s_ChanServ, u, "SET", CHAN_SET_SYNTAX);
} else if (!(ci = get_channelinfo(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (ci->flags & CF_VERBOTEN) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!check_access_cmd(u, ci, "SET", cmd)
&& (used_privs = 1, !is_servadmin)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (call_callback_4(cb_set, u, ci, cmd, param) > 0) {
return;
} else if (stricmp(cmd, "FOUNDER") == 0) {
used_privs = 0;
if (!is_founder(u, ci) && (used_privs = 1, !is_servadmin)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET FOUNDER as Services"
" admin on \2%s\2", u->nick, ci->name);
}
do_set_founder(u, ci, param);
}
} else if (stricmp(cmd, "SUCCESSOR") == 0) {
used_privs = 0;
if (!is_founder(u, ci) && (used_privs = 1, !is_servadmin)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET SUCCESSOR as Services"
" admin on \2%s\2", u->nick, ci->name);
}
do_set_successor(u, ci, param);
}
} else if (stricmp(cmd, "PASSWORD") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET PASSWORD as Services"
" admin on \2%s\2", u->nick, ci->name);
}
do_set_password(u, ci, param);
}
} else if (stricmp(cmd, "DESC") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET DESC as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_desc(u, ci, param);
} else if (stricmp(cmd, "URL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET URL as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_url(u, ci, param);
} else if (stricmp(cmd, "EMAIL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET EMAIL as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_email(u, ci, param);
} else if (stricmp(cmd, "ENTRYMSG") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET ENTRYMSG as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_entrymsg(u, ci, param);
} else if (stricmp(cmd, "MLOCK") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET MLOCK as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_mlock(u, ci, param);
} else if (stricmp(cmd, "HIDE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET HIDE as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_hide(u, ci, param, extra);
} else {
ChanOpt *co = chanopt_from_name(cmd);
if (co && co->flag == CF_NOEXPIRE && (used_privs = 1, !is_servadmin))
co = NULL;
if (co) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used SET %s as Services admin"
" on \2%s\2", u->nick, co->name, ci->name);
}
do_set_boolean(u, ci, co, param);
} else {
notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, strupper(cmd));
notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET");
}
}
put_channelinfo(ci);
}
/*************************************************************************/
/* Handler for UNSET. */
void do_unset(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
ChannelInfo *ci = NULL;
int is_servadmin = is_services_admin(u);
int used_privs = 0;
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
return;
}
if (!chan || !cmd) {
syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_SYNTAX);
} else if (!(ci = get_channelinfo(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (ci->flags & CF_VERBOTEN) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!check_access_cmd(u, ci, "SET", cmd)
&& (used_privs = 1, !is_servadmin)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (call_callback_3(cb_unset, u, ci, cmd) > 0) {
return;
} else if (stricmp(cmd, "SUCCESSOR") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
if (WallAdminPrivs && !is_founder(u, ci)) {
wallops(s_ChanServ, "\2%s\2 used UNSET SUCCESSOR as"
" Services admin on \2%s\2", u->nick, ci->name);
}
do_set_successor(u, ci, NULL);
}
} else if (stricmp(cmd, "URL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used UNSET URL as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_url(u, ci, NULL);
} else if (stricmp(cmd, "EMAIL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used UNSET EMAIL as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_email(u, ci, NULL);
} else if (stricmp(cmd, "ENTRYMSG") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_ChanServ, "\2%s\2 used UNSET ENTRYMSG as Services admin"
" on \2%s\2", u->nick, ci->name);
}
do_set_entrymsg(u, ci, NULL);
} else {
syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_SYNTAX);
}
put_channelinfo(ci);
}
/*************************************************************************/
/*************************************************************************/
static void do_set_founder(User *u, ChannelInfo *ci, char *param)
{
NickInfo *ni = get_nickinfo(param);
NickGroupInfo *ngi, *oldngi;
if (!ni) {
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
return;
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
put_nickinfo(ni);
return;
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_ChanServ, u, INTERNAL_ERROR);
put_nickinfo(ni);
return;
}
put_nickinfo(ni);
if ((!is_services_admin(u) && check_channel_limit(ngi, NULL) >= 0)
|| ngi->channels_count >= MAX_CHANNELCOUNT
) {
notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param);
return;
}
uncount_chan(ci);
oldngi = get_ngi_id(ci->founder);
module_log("Changing founder of %s from %s to %s by %s!%s@%s", ci->name,
oldngi ? ngi_mainnick(oldngi) : "<unknown>", param, u->nick,
u->username, u->host);
put_nickgroupinfo(oldngi);
ci->founder = ngi->id;
put_nickgroupinfo(ngi);
count_chan(ci);
if (ci->successor == ci->founder) {
module_log("Successor for %s is same as new founder, clearing",
ci->name);
ci->successor = 0;
}
notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param);
}
/*************************************************************************/
static void do_set_successor(User *u, ChannelInfo *ci, char *param)
{
if (param) {
NickInfo *ni;
NickGroupInfo *ngi;
ni = get_nickinfo(param);
if (!ni) {
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
return;
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
put_nickinfo(ni);
return;
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_ChanServ, u, INTERNAL_ERROR);
put_nickinfo(ni);
return;
}
put_nickinfo(ni);
if (ngi->id == ci->founder) {
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER);
put_nickgroupinfo(ngi);
return;
}
if (ci->successor) {
NickGroupInfo *oldngi = get_ngi_id(ci->successor);
module_log("Changing successor of %s from %s to %s by %s!%s@%s",
ci->name,
oldngi ? ngi_mainnick(oldngi) : "<unknown>", param,
u->nick, u->username, u->host);
put_nickgroupinfo(oldngi);
} else {
module_log("Setting successor of %s to %s by %s!%s@%s",
ci->name, param, u->nick, u->username, u->host);
}
ci->successor = ngi->id;
put_nickgroupinfo(ngi);
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name, param);
} else {
module_log("Clearing successor of %s by %s!%s@%s",
ci->name, u->nick, u->username, u->host);
ci->successor = 0;
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_password(User *u, ChannelInfo *ci, char *param)
{
Password passbuf;
User *u2;
if (!(NoAdminPasswordCheck && is_services_admin(u))
&& (stricmp(param, ci->name) == 0
|| stricmp(param, ci->name+1) == 0
|| stricmp(param, u->nick) == 0
|| (StrictPasswords && strlen(param) < 5))
) {
notice_lang(s_ChanServ, u, MORE_OBSCURE_PASSWORD);
return;
}
init_password(&passbuf);
if (encrypt_password(param, strlen(param), &passbuf) != 0) {
clear_password(&passbuf);
memset(param, 0, strlen(param));
module_log("Failed to encrypt password for %s (set)", ci->name);
notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED);
return;
}
copy_password(&ci->founderpass, &passbuf);
clear_password(&passbuf);
if (CSShowPassword)
notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED_TO, ci->name, param);
else
notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED, ci->name);
memset(param, 0, strlen(param));
if (!is_founder(u, ci)) {
module_log("%s!%s@%s set password as Services admin for %s",
u->nick, u->username, u->host, ci->name);
}
/* Clear founder privileges from all other users who might have
* identified earlier. */
for (u2 = first_user(); u2; u2 = next_user()) {
if (u2 != u) {
struct u_chaninfolist *c, *c2;
LIST_FOREACH_SAFE (c, u2->id_chans, c2) {
if (irc_stricmp(c->chan, ci->name) == 0) {
LIST_REMOVE(c, u2->id_chans);
free(c);
}
}
}
}
}
/*************************************************************************/
static void do_set_desc(User *u, ChannelInfo *ci, char *param)
{
free(ci->desc);
ci->desc = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_DESC_CHANGED, ci->name, param);
}
/*************************************************************************/
static void do_set_url(User *u, ChannelInfo *ci, char *param)
{
if (param && !valid_url(param)) {
notice_lang(s_ChanServ, u, BAD_URL);
return;
}
free(ci->url);
if (param) {
ci->url = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_URL_CHANGED, ci->name, param);
} else {
ci->url = NULL;
notice_lang(s_ChanServ, u, CHAN_URL_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_email(User *u, ChannelInfo *ci, char *param)
{
if (param && !valid_email(param)) {
notice_lang(s_ChanServ, u, BAD_EMAIL);
return;
}
if (param && rejected_email(param)) {
notice_lang(s_NickServ, u, REJECTED_EMAIL);
return;
}
free(ci->email);
if (param) {
ci->email = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_EMAIL_CHANGED, ci->name, param);
} else {
ci->email = NULL;
notice_lang(s_ChanServ, u, CHAN_EMAIL_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param)
{
free(ci->entry_message);
if (param) {
ci->entry_message = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_CHANGED, ci->name);
} else {
ci->entry_message = NULL;
notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_mlock(User *u, ChannelInfo *ci, char *param)
{
char *s, modebuf[40], *end, c;
int ac = 0;
char *av[MAX_MLOCK_PARAMS];
char **avptr = &av[0];
int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */
int32 flag;
int params;
ModeLock oldlock;
oldlock = ci->mlock;
memset(&ci->mlock, 0, sizeof(ci->mlock));
while (ac < MAX_MLOCK_PARAMS && (s = strtok(NULL, " ")) != NULL)
av[ac++] = s;
while (*param) {
if (*param == '+') {
add = 1;
param++;
continue;
} else if (*param == '-') {
add = 0;
param++;
continue;
} else if (add < 0) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_PLUS_MINUS);
goto fail;
}
c = *param++;
flag = mode_char_to_flag(c, MODE_CHANNEL);
params = mode_char_to_params(c, MODE_CHANNEL);
if (!flag) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, c);
continue;
} else if (flag == MODE_INVALID) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_CANNOT_LOCK, c);
continue;
}
/* "Off" locks never take parameters (they prevent the mode from
* ever being set in the first place) */
params = add ? (params>>8) & 0xFF : 0;
if (params > ac) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_PARAM, c);
goto fail;
}
if (flag & chanmode_reg)
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_MODE_REG_BAD, c);
else if (add)
ci->mlock.on |= flag, ci->mlock.off &= ~flag;
else
ci->mlock.off |= flag, ci->mlock.on &= ~flag;
switch (c) {
case 'k':
if (add) {
free(ci->mlock.key);
ci->mlock.key = sstrdup(avptr[0]);
} else {
free(ci->mlock.key);
ci->mlock.key = NULL;
}
break;
case 'l':
if (add) {
ci->mlock.limit = atol(avptr[0]);
if (ci->mlock.limit <= 0) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_POSITIVE,
'l');
goto fail;
}
} else {
ci->mlock.limit = 0;
}
break;
} /* switch */
if (call_callback_5(cb_set_mlock, u, ci, c, add, avptr) > 0)
goto fail;
ac -= params;
avptr += params;
} /* while (*param) */
/* Make sure there are no problems. */
if (call_callback_5(cb_set_mlock, u, ci, 0, 0, NULL) > 0)
goto fail;
/* Tell the user about the new mode lock. */
end = modebuf;
*end = 0;
if (ci->mlock.on || ci->mlock.key || ci->mlock.limit)
end += snprintf(end, sizeof(modebuf)-(end-modebuf), "+%s",
mode_flags_to_string(ci->mlock.on, MODE_CHANNEL));
if (ci->mlock.off)
end += snprintf(end, sizeof(modebuf)-(end-modebuf), "-%s",
mode_flags_to_string(ci->mlock.off, MODE_CHANNEL));
if (*modebuf) {
notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name, modebuf);
} else {
notice_lang(s_ChanServ, u, CHAN_MLOCK_REMOVED, ci->name);
}
/* Clean up, implement the new lock and return. */
free(oldlock.key);
free(oldlock.link);
free(oldlock.flood);
check_modes(ci->c);
return;
fail:
/* Failure; restore the old mode lock. */
free(ci->mlock.key);
free(ci->mlock.link);
free(ci->mlock.flood);
ci->mlock = oldlock;
}
/*************************************************************************/
#define HIDE(x) do { \
flag = CF_HIDE_##x; \
onmsg = CHAN_SET_HIDE_##x##_ON; \
offmsg = CHAN_SET_HIDE_##x##_OFF; \
} while (0)
static void do_set_hide(User *u, ChannelInfo *ci, char *param, char *extra)
{
int32 flag;
int onmsg, offmsg;
if (!extra) {
syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX);
return;
}
if (stricmp(param, "EMAIL") == 0) {
HIDE(EMAIL);
} else if (stricmp(param, "TOPIC") == 0) {
HIDE(TOPIC);
} else if (stricmp(param, "MLOCK") == 0) {
HIDE(MLOCK);
} else {
syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX);
return;
}
if (stricmp(extra, "ON") == 0) {
ci->flags |= flag;
notice_lang(s_ChanServ, u, onmsg, ci->name, s_ChanServ);
} else if (stricmp(extra, "OFF") == 0) {
ci->flags &= ~flag;
notice_lang(s_ChanServ, u, offmsg, ci->name, s_ChanServ);
} else {
syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX);
}
}
#undef HIDE
/*************************************************************************/
static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param)
{
if (stricmp(param, "ON") == 0) {
ci->flags |= co->flag;
notice_lang(s_ChanServ, u, co->onstr, ci->name);
} else if (stricmp(param, "OFF") == 0) {
ci->flags &= ~co->flag;
notice_lang(s_ChanServ, u, co->offstr, ci->name);
} else {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "SET %s", co->name);
syntax_error(s_ChanServ, u, buf, co->syntaxstr);
return;
}
}
/*************************************************************************/
/*************************************************************************/
int init_set(void)
{
cb_set = register_callback("SET");
cb_set_mlock = register_callback("SET MLOCK");
cb_unset = register_callback("UNSET");
if (cb_set < 0 || cb_set_mlock < 0 || cb_unset < 0) {
module_log("set: Unable to register callbacks");
exit_set();
return 0;
}
return 1;
}
/*************************************************************************/
void exit_set()
{
unregister_callback(cb_unset);
unregister_callback(cb_set_mlock);
unregister_callback(cb_set);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

421
modules/chanserv/util.c Normal file
View File

@ -0,0 +1,421 @@
/* ChanServ internal utility routines.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
/* If STANDALONE_CHANSERV is defined when compiling this file, the
* following routines will be defined as `static' for use in other
* modules/programs:
* new_channelinfo()
* free_channelinfo()
* reset_levels()
* This file should be #include'd in the file making use of the routines.
*/
#ifdef STANDALONE_CHANSERV
# define STANDALONE_STATIC static
# undef EXPORT_FUNC
# define EXPORT_FUNC(x) /*nothing*/
#else
# define STANDALONE_STATIC /*nothing*/
# include "services.h"
# include "modules.h"
# include "language.h"
# include "modules/nickserv/nickserv.h"
# include "chanserv.h"
# include "cs-local.h"
#endif
/*************************************************************************/
/************************ Global utility routines ************************/
/*************************************************************************/
/* Allocate and initialize a new ChannelInfo structure. */
EXPORT_FUNC(new_channelinfo)
STANDALONE_STATIC ChannelInfo *new_channelinfo(void)
{
ChannelInfo *ci = scalloc(sizeof(ChannelInfo), 1);
reset_levels(ci);
return ci;
}
/*************************************************************************/
/* Free a ChannelInfo structure and all associated data. */
EXPORT_FUNC(free_channelinfo)
STANDALONE_STATIC void free_channelinfo(ChannelInfo *ci)
{
int i;
if (ci) {
clear_password(&ci->founderpass);
free(ci->desc);
free(ci->url);
free(ci->email);
free(ci->last_topic);
free(ci->suspend_reason);
free(ci->access);
ARRAY_FOREACH (i, ci->akick) {
free(ci->akick[i].mask);
free(ci->akick[i].reason);
}
free(ci->akick);
free(ci->mlock.key);
free(ci->mlock.link);
free(ci->mlock.flood);
free(ci->entry_message);
free(ci);
} /* if (ci) */
}
/*************************************************************************/
/* Reset channel access level values to their default state. */
EXPORT_FUNC(reset_levels)
STANDALONE_STATIC void reset_levels(ChannelInfo *ci)
{
int i;
for (i = 0; i < CA_SIZE; i++)
ci->levels[i] = ACCLEV_DEFAULT;
}
/*************************************************************************/
#ifndef STANDALONE_CHANSERV /* to the end of the file */
/*************************************************************************/
/* Check the nick group's number of registered channels against its limit,
* and return -1 if below the limit, 0 if at it exactly, and 1 if over it.
* If `max_ret' is non-NULL, store the nick group's registered channel
* limit there.
*/
EXPORT_FUNC(check_channel_limit)
int check_channel_limit(const NickGroupInfo *ngi, int *max_ret)
{
register int max, count;
max = ngi->channelmax;
if (max == CHANMAX_DEFAULT)
max = CSMaxReg;
else if (max == CHANMAX_UNLIMITED)
max = MAX_CHANNELCOUNT;
count = ngi->channels_count;
if (max_ret)
*max_ret = max;
return count<max ? -1 : count==max ? 0 : 1;
}
/*************************************************************************/
/*********************** Internal utility routines ***********************/
/*************************************************************************/
/* Add a channel to the database. Returns a pointer to the new ChannelInfo
* structure if the channel was successfully registered, NULL otherwise.
* Assumes channel does not already exist. */
ChannelInfo *makechan(const char *chan)
{
ChannelInfo *ci;
ci = scalloc(sizeof(ChannelInfo), 1);
strbcpy(ci->name, chan);
ci->time_registered = time(NULL);
reset_levels(ci);
return add_channelinfo(ci);
}
/*************************************************************************/
/* Remove a channel from the ChanServ database. Return 1 on success, 0
* otherwise. */
int delchan(ChannelInfo *ci)
{
User *u;
Channel *c;
/* Remove channel from founder's owned-channel count */
uncount_chan(ci);
/* Clear link (and "registered" mode) from channel record if it exists */
c = ci->c;
if (c) {
c->ci = NULL;
if (chanmode_reg) {
c->mode &= ~chanmode_reg;
/* Send this out immediately, no set_cmode() delay */
send_cmode_cmd(s_ChanServ, ci->name, "-%s",
mode_flags_to_string(chanmode_reg, MODE_CHANNEL));
}
}
/* Clear channel from users' identified-channel lists */
for (u = first_user(); u; u = next_user()) {
struct u_chaninfolist *uc, *next;
LIST_FOREACH_SAFE (uc, u->id_chans, next) {
if (irc_stricmp(uc->chan, ci->name) == 0) {
LIST_REMOVE(uc, u->id_chans);
free(uc);
}
}
}
/* Now actually delete record */
del_channelinfo(ci);
return 1;
}
/*************************************************************************/
/* Mark the given channel as owned by its founder. This updates the
* founder's list of owned channels (ngi->channels).
*/
void count_chan(const ChannelInfo *ci)
{
NickGroupInfo *ngi = ci->founder ? get_ngi_id(ci->founder) : NULL;
if (!ngi)
return;
/* Be paranoid--this could overflow in extreme cases, though we check
* for that elsewhere as well. */
if (ngi->channels_count >= MAX_CHANNELCOUNT) {
module_log("count BUG: overflow in ngi->channels_count for %u (%s)"
" on %s", ngi->id, ngi_mainnick(ngi), ci->name);
return;
}
ARRAY_EXTEND(ngi->channels);
strbcpy(ngi->channels[ngi->channels_count-1], ci->name);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/* Mark the given channel as no longer owned by its founder. */
void uncount_chan(const ChannelInfo *ci)
{
NickGroupInfo *ngi = ci->founder ? get_ngi_id(ci->founder) : NULL;
int i;
if (!ngi)
return;
ARRAY_SEARCH_PLAIN(ngi->channels, ci->name, irc_stricmp, i);
if (i >= ngi->channels_count) {
module_log("uncount BUG: channel not found in channels[] for %u (%s)"
" on %s", ngi->id,
ngi->nicks_count ? ngi_mainnick(ngi) : "<unknown>",
ci->name);
return;
}
ARRAY_REMOVE(ngi->channels, i);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/* Does the given user have founder access to the channel? */
int is_founder(const User *user, const ChannelInfo *ci)
{
if (!ci || (ci->flags & (CF_VERBOTEN | CF_SUSPENDED)) || !valid_ngi(user))
return 0;
if (user->ngi->id == ci->founder) {
if ((user_identified(user) ||
(user_recognized(user) && !(ci->flags & CF_SECURE))))
return 1;
}
if (is_identified(user, ci))
return 1;
return 0;
}
/*************************************************************************/
/* Has the given user password-identified as founder for the channel? */
int is_identified(const User *user, const ChannelInfo *ci)
{
struct u_chaninfolist *c;
if (!ci)
return 0;
LIST_FOREACH (c, user->id_chans) {
if (irc_stricmp(c->chan, ci->name) == 0)
return 1;
}
return 0;
}
/*************************************************************************/
/* Restore the topic in a channel when it's created, if we should. */
void restore_topic(Channel *c)
{
ChannelInfo *ci = c->ci;
if (ci && (ci->flags & CF_KEEPTOPIC) && ci->last_topic && *ci->last_topic){
set_topic(s_ChanServ, c, ci->last_topic,
*ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ,
ci->last_topic_time);
}
}
/*************************************************************************/
/* Record a channel's topic in its ChannelInfo structure. */
void record_topic(ChannelInfo *ci, const char *topic,
const char *setter, time_t topic_time)
{
if (!ci)
return;
free(ci->last_topic);
if (*topic)
ci->last_topic = sstrdup(topic);
else
ci->last_topic = NULL;
strbcpy(ci->last_topic_setter, setter);
ci->last_topic_time = topic_time;
}
/*************************************************************************/
/* Mark the given channel as suspended. */
void suspend_channel(ChannelInfo *ci, const char *reason,
const char *who, const time_t expires)
{
strbcpy(ci->suspend_who, who);
ci->suspend_reason = sstrdup(reason);
ci->suspend_time = time(NULL);
ci->suspend_expires = expires;
ci->flags |= CF_SUSPENDED;
}
/*************************************************************************/
/* Delete the suspension data for the given channel. We also alter the
* last_seen value to ensure that it does not expire within the next
* CSSuspendGrace seconds, giving its users a chance to reclaim it
* (but only if set_time is non-zero).
*/
void unsuspend_channel(ChannelInfo *ci, int set_time)
{
time_t now = time(NULL);
if (!(ci->flags & CF_SUSPENDED)) {
module_log("unsuspend_channel() called on non-suspended channel %s",
ci->name);
return;
}
ci->flags &= ~CF_SUSPENDED;
free(ci->suspend_reason);
memset(ci->suspend_who, 0, sizeof(ci->suspend_who));
ci->suspend_reason = NULL;
ci->suspend_time = 0;
ci->suspend_expires = 0;
if (set_time && CSExpire && CSSuspendGrace
&& (now - ci->last_used >= CSExpire - CSSuspendGrace)
) {
ci->last_used = now - CSExpire + CSSuspendGrace;
module_log("unsuspend: Altering last_used time for %s to %ld",
ci->name, (long)ci->last_used);
}
}
/*************************************************************************/
/* Register a bad password attempt for a channel. */
void chan_bad_password(User *u, ChannelInfo *ci)
{
bad_password(s_ChanServ, u, ci->name);
ci->bad_passwords++;
if (BadPassWarning && ci->bad_passwords == BadPassWarning) {
wallops(s_ChanServ, "\2Warning:\2 Repeated bad password attempts"
" for channel %s (last attempt by user %s)",
ci->name, u->nick);
}
}
/*************************************************************************/
/* Return the ChanOpt structure for the given option name. If not found,
* return NULL.
*/
ChanOpt *chanopt_from_name(const char *optname)
{
int i;
for (i = 0; chanopts[i].name; i++) {
if (stricmp(chanopts[i].name, optname) == 0)
return &chanopts[i];
}
return NULL;
}
/*************************************************************************/
/* Return a string listing the options (those given in chanopts[]) set on
* the given channel. Uses the given NickGroupInfo for language
* information. The returned string is stored in a static buffer which
* will be overwritten on the next call.
*/
char *chanopts_to_string(const ChannelInfo *ci, const NickGroupInfo *ngi)
{
static char buf[BUFSIZE];
char *end = buf;
const char *commastr = getstring(ngi, COMMA_SPACE);
const char *s;
int need_comma = 0;
int i;
for (i = 0; chanopts[i].name && end-buf < sizeof(buf)-1; i++) {
if (chanopts[i].namestr < 0)
continue;
if (ci->flags & chanopts[i].flag) {
s = getstring(ngi, chanopts[i].namestr);
if (!s)
continue;
if (need_comma)
end += snprintf(end, sizeof(buf)-(end-buf), "%s", commastr);
end += snprintf(end, sizeof(buf)-(end-buf), "%s", s);
need_comma = 1;
}
}
return buf;
}
/*************************************************************************/
#endif /* !STANDALONE_CHANSERV */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

44
modules/database/Makefile Normal file
View File

@ -0,0 +1,44 @@
# Makefile for database modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = standard.so version4.so
OBJECTS-standard.so = fileutil.o
OBJECTS-version4.so = extsyms.o # fileutil.c is #included
SERVDEPS = $(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h \
$(TOPDIR)/modules/memoserv/memoserv.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/akill.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/operserv/news.h \
$(TOPDIR)/modules/operserv/sline.h \
$(TOPDIR)/modules/statserv/statserv.h
INCLUDES-extsyms.o = extsyms.h
INCLUDES-fileutil.o = fileutil.h
INCLUDES-standard.o = fileutil.h $(TOPDIR)/databases.h $(TOPDIR)/encrypt.h
# NOTE: fileutil.c is *not* a typo here!
INCLUDES-version4.o = extsyms.h fileutil.c fileutil.h $(TOPDIR)/databases.h \
$(TOPDIR)/language.h $(TOPDIR)/encrypt.h $(SERVDEPS) \
$(TOPDIR)/modules/nickserv/util.c \
$(TOPDIR)/modules/chanserv/util.c
include ../Makerules
###########################################################################
extsyms.h: $(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h \
$(TOPDIR)/modules/memoserv/memoserv.h \
$(TOPDIR)/modules/statserv/statserv.h
@touch $@

314
modules/database/extsyms.c Normal file
View File

@ -0,0 +1,314 @@
/* Interface to external module (*Serv) symbols for the database/version4
* module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#define IN_EXTSYMS_C
#include "extsyms.h"
/*************************************************************************/
#ifdef __INTEL_COMPILER /* icc hides the symbols from the inline asm */
# define static
#endif
static Module *module_nickserv;
static Module *module_chanserv;
static Module *module_memoserv;
static Module *module_statserv;
#ifdef __INTEL_COMPILER
# undef static
#endif
static const char *this_module_name = "(unknown-module)";
/*************************************************************************/
static void fatal_no_symbol(const char *symbol)
{
fatal("%s: undefined symbol `%s'", this_module_name, symbol);
}
/*************************************************************************/
/*************************************************************************/
#if GCC3_HACK
# if defined(__sparc__)
# define IMPORT_FUNC(modulename, module, func) \
static void __dblocal_##func##_stub0(void) { \
void *ptr = NULL; \
if (!module) \
module = find_module(modulename); \
if (module) \
ptr = get_module_symbol(module, #func); \
if (!ptr) \
fatal_no_symbol(#func); \
__dblocal_##func = ptr; \
} \
static void *__dblocal_##func##_stub1(void) { \
__dblocal_##func##_stub0(); \
__builtin_return(__builtin_apply((void *)__dblocal_##func, \
__builtin_apply_args(), 64)); \
} \
static void __dblocal_##func##_stub(void) { \
(void) __builtin_apply((void *)__dblocal_##func##_stub1, \
__builtin_apply_args(), 64); \
asm("ld [%sp-128],%i0"); \
} \
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;
#elif defined(__POWERPC__)
# define IMPORT_FUNC(modulename, module, func) \
static void __dblocal_##func##_stub0(void) { \
void *ptr = NULL; \
if (!module) \
module = find_module(modulename); \
if (module) \
ptr = get_module_symbol(module, #func); \
if (!ptr) \
fatal_no_symbol(#func); \
__dblocal_##func = ptr; \
} \
static void __dblocal_##func##_stub(void) { \
asm("lwz r0,0(r1)\n \
stwu r0,-104(r1)\n \
stw r3,64(r1)\n \
stw r4,68(r1)\n \
stw r5,72(r1)\n \
stw r6,76(r1)\n \
stw r7,80(r1)\n \
stw r8,84(r1)\n \
stw r9,88(r1)\n \
stw r10,92(r1)\n \
mflr r0\n \
stw r0,100(r1)"); \
asm("mtctr %0\n \
bctrl\n \
lwz r10,92(r1)" : : "r" (__dblocal_##func##_stub0)); \
asm("stw %0,96(r1)" : : "r" (__dblocal_##func)); \
asm("lwz r3,64(r1)\n \
lwz r4,68(r1)\n \
lwz r5,72(r1)\n \
lwz r6,76(r1)\n \
lwz r7,80(r1)\n \
lwz r8,84(r1)\n \
lwz r9,88(r1)\n \
lwz r10,92(r1)\n \
lwz r11,96(r1)\n \
mtctr r11\n \
bctrl\n \
lwz r0,100(r1)\n \
mtlr r0\n \
mr r0,r3"); \
} \
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;
#else /* not SPARC and not PowerPC... must be our good old friend */
# define IMPORT_FUNC(modulename, module, func) \
static void __dblocal_##func##_stub0(void) { \
void *ptr = NULL; \
if (!module) \
module = find_module(modulename); \
if (module) \
ptr = get_module_symbol(module, #func); \
if (!ptr) \
fatal_no_symbol(#func); \
__dblocal_##func = ptr; \
} \
static void *__dblocal_##func##_stub(void) { \
__dblocal_##func##_stub0(); \
__builtin_return(__builtin_apply((void *)__dblocal_##func, \
__builtin_apply_args(), 64)); \
} \
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;
#endif /* to SPARC or not to SPARC */
#elif defined(__GNUC__)
#define IMPORT_FUNC(modulename, module, func) \
static void *__dblocal_##func##_stub(void) { \
void *ptr = NULL; \
if (!module) \
module = find_module(modulename); \
if (module) \
ptr = get_module_symbol(module, #func); \
if (!ptr) \
fatal_no_symbol(#func); \
__dblocal_##func = ptr; \
__builtin_return(__builtin_apply(ptr, __builtin_apply_args(), 64)); \
} \
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;
#elif defined(__INTEL_COMPILER)
volatile void *_fatal_no_symbol = (void *)fatal_no_symbol;
#define IMPORT_FUNC(modulename, module, func) \
static void __dblocal_##func##_stub(void) { \
asm(" movl "#module",%eax; \
testl %eax,%eax; \
jnz 1f; \
leal 8f,%eax; \
pushl %eax; \
call find_module; \
leal 4(%esp),%esp; \
movl %eax,"#module"; \
1: testl %eax,%eax; \
jz 2f; \
leal 9f,%edx; \
pushl %edx; \
pushl %eax; \
call get_module_symbol; \
leal 8(%esp),%esp; \
2: testl %eax,%eax; \
jnz 3f; \
leal 9f,%eax; \
pushl %eax; \
call *_fatal_no_symbol; \
leal 4(%esp),%esp; \
3: movl %eax,__dblocal_"#func"; \
movl %ebp,%esp; \
popl %ebp; \
jmp *%eax; \
8: .ascii \"" modulename "\\0\"; \
9: .ascii \""#func"\\0\" \
"); \
} \
typeof(func) *__dblocal_##func = (typeof(func) *) __dblocal_##func##_stub;
#else
#error IMPORT_FUNC not implemented for this compiler
#endif /* GCC3_HACK etc */
#define IMPORT_VAR(modulename, module, var) \
static typeof(var) *__dblocal_##var##_ptr; \
typeof(var) __dblocal_get_##var(void) { \
if (!__dblocal_##var##_ptr) { \
if (!module) \
module = find_module(modulename); \
if (module) \
__dblocal_##var##_ptr = get_module_symbol(module, #var); \
if (!__dblocal_##var##_ptr) \
fatal_no_symbol(#var); \
} \
return *__dblocal_##var; \
}
#define IMPORT_VAR_MAYBE(modulename, module, var, default) \
static typeof(var) *__dblocal_##var##_ptr; \
typeof(var) __dblocal_get_##var(void) { \
if (!__dblocal_##var##_ptr) { \
if (!module) \
module = find_module(modulename); \
if (module) \
__dblocal_##var##_ptr = get_module_symbol(module, #var); \
} \
return __dblocal_##var##_ptr ? *__dblocal_##var##_ptr : default; \
}
/*************************************************************************/
IMPORT_VAR_MAYBE("chanserv/main", module_chanserv, CSMaxReg, CHANMAX_DEFAULT);
IMPORT_VAR_MAYBE("memoserv/main", module_memoserv, MSMaxMemos,MEMOMAX_DEFAULT);
IMPORT_FUNC("nickserv/main", module_nickserv, del_nickinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, get_nickinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, put_nickinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, del_nickgroupinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, get_nickgroupinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, put_nickgroupinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, first_nickgroupinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, next_nickgroupinfo);
IMPORT_FUNC("nickserv/main", module_nickserv, _get_ngi);
IMPORT_FUNC("nickserv/main", module_nickserv, _get_ngi_id);
IMPORT_FUNC("chanserv/main", module_chanserv, get_channelinfo);
IMPORT_FUNC("chanserv/main", module_chanserv, put_channelinfo);
IMPORT_FUNC("chanserv/main", module_chanserv, reset_levels);
IMPORT_FUNC("statserv/main", module_statserv, get_serverstats);
IMPORT_FUNC("statserv/main", module_statserv, put_serverstats);
/*************************************************************************/
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_nickserv) {
module_nickserv = NULL;
__dblocal_del_nickinfo = (void *) __dblocal_del_nickinfo_stub;
__dblocal_get_nickinfo = (void *) __dblocal_get_nickinfo_stub;
__dblocal_put_nickinfo = (void *) __dblocal_put_nickinfo_stub;
__dblocal_del_nickgroupinfo =
(void *) __dblocal_del_nickgroupinfo_stub;
__dblocal_get_nickgroupinfo =
(void *) __dblocal_get_nickgroupinfo_stub;
__dblocal_put_nickgroupinfo =
(void *) __dblocal_put_nickgroupinfo_stub;
__dblocal_first_nickgroupinfo =
(void *) __dblocal_first_nickgroupinfo_stub;
__dblocal_next_nickgroupinfo =
(void *) __dblocal_next_nickgroupinfo_stub;
__dblocal__get_ngi = (void *) __dblocal__get_ngi_stub;
__dblocal__get_ngi_id = (void *) __dblocal__get_ngi_id_stub;
} else if (mod == module_chanserv) {
module_chanserv = NULL;
__dblocal_CSMaxReg_ptr = NULL;
__dblocal_get_channelinfo = (void *) __dblocal_get_channelinfo_stub;
__dblocal_put_channelinfo = (void *) __dblocal_put_channelinfo_stub;
__dblocal_reset_levels = (void *) __dblocal_reset_levels_stub;
} else if (mod == module_memoserv) {
module_memoserv = NULL;
__dblocal_MSMaxMemos_ptr = NULL;
} else if (mod == module_statserv) {
module_statserv = NULL;
__dblocal_get_serverstats = (void *) __dblocal_get_serverstats_stub;
__dblocal_put_serverstats = (void *) __dblocal_put_serverstats_stub;
}
return 0;
}
/*************************************************************************/
/* `name' is the name of the calling module (used for errors) */
int init_extsyms(const char *name)
{
if (!add_callback(NULL, "unload module", do_unload_module))
return 0;
this_module_name = name;
return 1;
}
/*************************************************************************/
void exit_extsyms()
{
remove_callback(NULL, "unload module", do_unload_module);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,76 @@
/* Header for extsyms.c.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef EXTSYMS_H
#define EXTSYMS_H
/*************************************************************************/
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "modules/memoserv/memoserv.h"
#include "modules/statserv/statserv.h"
#define DEFINE_LOCAL_VAR(sym) extern typeof(sym) __dblocal_get_##sym(void)
#define DEFINE_LOCAL_FUNC(sym) extern typeof(sym) *__dblocal_##sym
DEFINE_LOCAL_VAR(CSMaxReg);
DEFINE_LOCAL_VAR(MSMaxMemos);
DEFINE_LOCAL_FUNC(del_nickinfo);
DEFINE_LOCAL_FUNC(get_nickinfo);
DEFINE_LOCAL_FUNC(put_nickinfo);
DEFINE_LOCAL_FUNC(del_nickgroupinfo);
DEFINE_LOCAL_FUNC(get_nickgroupinfo);
DEFINE_LOCAL_FUNC(put_nickgroupinfo);
DEFINE_LOCAL_FUNC(first_nickgroupinfo);
DEFINE_LOCAL_FUNC(next_nickgroupinfo);
DEFINE_LOCAL_FUNC(_get_ngi);
DEFINE_LOCAL_FUNC(_get_ngi_id);
DEFINE_LOCAL_FUNC(get_channelinfo);
DEFINE_LOCAL_FUNC(put_channelinfo);
DEFINE_LOCAL_FUNC(reset_levels);
DEFINE_LOCAL_FUNC(get_serverstats);
DEFINE_LOCAL_FUNC(put_serverstats);
#ifndef IN_EXTSYMS_C
# define CSMaxReg ((*__dblocal_get_CSMaxReg)())
# define MSMaxMemos ((*__dblocal_get_MSMaxMemos)())
# define del_nickinfo (*__dblocal_del_nickinfo)
# define get_nickinfo (*__dblocal_get_nickinfo)
# define put_nickinfo (*__dblocal_put_nickinfo)
# define del_nickgroupinfo (*__dblocal_del_nickgroupinfo)
# define get_nickgroupinfo (*__dblocal_get_nickgroupinfo)
# define put_nickgroupinfo (*__dblocal_put_nickgroupinfo)
# define first_nickgroupinfo (*__dblocal_first_nickgroupinfo)
# define next_nickgroupinfo (*__dblocal_next_nickgroupinfo)
# define _get_ngi (*__dblocal__get_ngi)
# define _get_ngi_id (*__dblocal__get_ngi_id)
# define get_channelinfo (*__dblocal_get_channelinfo)
# define put_channelinfo (*__dblocal_put_channelinfo)
# define reset_levels (*__dblocal_reset_levels)
# define get_serverstats (*__dblocal_get_serverstats)
# define put_serverstats (*__dblocal_put_serverstats)
#endif
extern int init_extsyms(const char *name);
extern void exit_extsyms(void);
/*************************************************************************/
#endif /* EXTSYMS_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

422
modules/database/fileutil.c Normal file
View File

@ -0,0 +1,422 @@
/* Database file handling routines.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#ifndef CONVERT_DB
# include "modules.h"
#endif
#include <fcntl.h>
#include "fileutil.h"
/*************************************************************************/
/*************************************************************************/
/* Return the version number of the file. Return -1 if there is no version
* number or the number doesn't make sense (i.e. less than 1).
*/
int32 get_file_version(dbFILE *f)
{
FILE *fp = f->fp;
int version = fgetc(fp)<<24 | fgetc(fp)<<16 | fgetc(fp)<<8 | fgetc(fp);
if (ferror(fp)) {
#ifndef CONVERT_DB
module_log_perror("Error reading version number on %s", f->filename);
#endif
return -1;
} else if (feof(fp)) {
#ifndef CONVERT_DB
module_log("Error reading version number on %s: End of file detected",
f->filename);
#endif
return -1;
} else if (version < 1) {
#ifndef CONVERT_DB
module_log("Invalid version number (%d) on %s", version, f->filename);
#endif
return -1;
}
return version;
}
/*************************************************************************/
/* Write the version number to the file. Return 0 on success, -1 on
* failure.
*/
int write_file_version(dbFILE *f, int32 filever)
{
FILE *fp = f->fp;
if (
fputc(filever>>24 & 0xFF, fp) < 0 ||
fputc(filever>>16 & 0xFF, fp) < 0 ||
fputc(filever>> 8 & 0xFF, fp) < 0 ||
fputc(filever & 0xFF, fp) < 0
) {
#ifndef CONVERT_DB
module_log_perror("Error writing version number on %s", f->filename);
#endif
return -1;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Helper functions for open_db(). */
static dbFILE *open_db_read(const char *filename)
{
dbFILE *f;
FILE *fp;
f = smalloc(sizeof(*f));
*f->tempname = 0;
strbcpy(f->filename, filename);
f->mode = 'r';
fp = fopen(f->filename, "rb");
if (!fp) {
int errno_save = errno;
#ifndef CONVERT_DB
if (errno != ENOENT)
module_log_perror("Can't read database file %s", f->filename);
#endif
free(f);
errno = errno_save;
return NULL;
}
f->fp = fp;
return f;
}
/************************************/
static dbFILE *open_db_write(const char *filename, int32 filever)
{
dbFILE *f;
int fd;
f = smalloc(sizeof(*f));
*f->tempname = 0;
strbcpy(f->filename, filename);
filename = f->filename;
f->mode = 'w';
snprintf(f->tempname, sizeof(f->tempname), "%s.new", filename);
if (!*f->tempname || strcmp(f->tempname, filename) == 0) {
#ifndef CONVERT_DB
module_log("Opening database file %s for write: Filename too long",
filename);
#endif
free(f);
errno = ENAMETOOLONG;
return NULL;
}
remove(f->tempname);
/* Use open() to avoid people sneaking a new file in under us */
fd = open(f->tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd >= 0)
f->fp = fdopen(fd, "wb");
if (!f->fp || write_file_version(f, filever) < 0) {
int errno_save = errno;
#ifndef CONVERT_DB
static int walloped = 0;
if (!walloped) {
walloped++;
wallops(NULL, "Can't create temporary database file %s",
f->tempname);
}
errno = errno_save;
module_log_perror("Can't create temporary database file %s",
f->tempname);
#endif
if (f->fp)
fclose(f->fp);
remove(f->tempname);
errno = errno_save;
return NULL;
}
return f;
}
/*************************************************************************/
/* Open a database file for reading (*mode == 'r') or writing (*mode == 'w').
* Return the stream pointer, or NULL on error. When opening for write, the
* file actually opened is a temporary file, which will be renamed to the
* original file on close.
*
* `version' is only used when opening a file for writing, and indicates the
* version number to write to the file.
*/
dbFILE *open_db(const char *filename, const char *mode, int32 version)
{
if (*mode == 'r') {
return open_db_read(filename);
} else if (*mode == 'w') {
return open_db_write(filename, version);
} else {
errno = EINVAL;
return NULL;
}
}
/*************************************************************************/
/* Close a database file. If the file was opened for write, moves the new
* file over the old one, and logs/wallops an error message if the rename()
* fails.
*/
int close_db(dbFILE *f)
{
int res;
if (!f->fp) {
errno = EINVAL;
return -1;
}
res = fclose(f->fp);
f->fp = NULL;
if (res != 0)
return -1;
if (f->mode=='w' && *f->tempname && strcmp(f->tempname,f->filename)!=0) {
if (rename(f->tempname, f->filename) < 0) {
#ifndef CONVERT_DB
int errno_save = errno;
wallops(NULL, "Unable to move new data to database file %s;"
" new data NOT saved.", f->filename);
errno = errno_save;
module_log_perror("Unable to move new data to database file %s;"
" new data NOT saved.", f->filename);
#endif
remove(f->tempname);
}
}
free(f);
return 0;
}
/*************************************************************************/
/* Restore the database file to its condition before open_db(). This is
* identical to close_db() for files open for reading; however, for files
* open for writing, we discard the new temporary file instead of renaming
* it over the old file. The value of errno is preserved.
*/
void restore_db(dbFILE *f)
{
int errno_orig = errno;
if (f->fp)
fclose(f->fp);
if (f->mode == 'w' && *f->tempname)
remove(f->tempname);
free(f);
errno = errno_orig;
}
/*************************************************************************/
/*************************************************************************/
/* Read and write 2- and 4-byte quantities, pointers, and strings. All
* multibyte values are stored in big-endian order (most significant byte
* first). A pointer is stored as a byte, either 0 if NULL or 1 if not,
* and read pointers are returned as either (void *)0 or (void *)1. A
* string is stored with a 2-byte unsigned length (including the trailing
* \0) first; a length of 0 indicates that the string pointer is NULL.
* Written strings are truncated silently at 65534 bytes, and are always
* null-terminated.
*
* All routines return -1 on error, 0 otherwise.
*/
/*************************************************************************/
int read_int8(int8 *ret, dbFILE *f)
{
int c = fgetc(f->fp);
if (c == EOF)
return -1;
*ret = c;
return 0;
}
/* Alternative version of read_int8() to avoid GCC's pointer signedness
* warnings when reading into an unsigned variable: */
int read_uint8(uint8 *ret, dbFILE *f) {
return read_int8((int8 *)ret, f);
}
int write_int8(int8 val, dbFILE *f)
{
if (fputc(val, f->fp) == EOF)
return -1;
return 0;
}
/*************************************************************************/
/* These are inline to help out {read,write}_string. */
inline int read_int16(int16 *ret, dbFILE *f)
{
int c1, c2;
c1 = fgetc(f->fp);
c2 = fgetc(f->fp);
if (c2 == EOF)
return -1;
*ret = c1<<8 | c2;
return 0;
}
inline int read_uint16(uint16 *ret, dbFILE *f) {
return read_int16((int16 *)ret, f);
}
inline int write_int16(int16 val, dbFILE *f)
{
fputc((val>>8), f->fp);
if (fputc(val, f->fp) == EOF)
return -1;
return 0;
}
/*************************************************************************/
int read_int32(int32 *ret, dbFILE *f)
{
int c1, c2, c3, c4;
c1 = fgetc(f->fp);
c2 = fgetc(f->fp);
c3 = fgetc(f->fp);
c4 = fgetc(f->fp);
if (c4 == EOF)
return -1;
*ret = c1<<24 | c2<<16 | c3<<8 | c4;
return 0;
}
int read_uint32(uint32 *ret, dbFILE *f) {
return read_int32((int32 *)ret, f);
}
int write_int32(int32 val, dbFILE *f)
{
fputc((val>>24), f->fp);
fputc((val>>16), f->fp);
fputc((val>> 8), f->fp);
if (fputc((val & 0xFF), f->fp) == EOF)
return -1;
return 0;
}
/*************************************************************************/
int read_time(time_t *ret, dbFILE *f)
{
int32 high, low;
if (read_int32(&high, f) < 0 || read_int32(&low, f) < 0)
return -1;
#if SIZEOF_TIME_T > 4
*ret = (time_t)high << 32 | (time_t)low;
#else
*ret = low;
#endif
return 0;
}
int write_time(time_t val, dbFILE *f)
{
#if SIZEOF_TIME_T > 4
if (write_int32(val>>32, f) < 0
|| write_int32(val & (time_t)0xFFFFFFFF, f) < 0)
#else
if (write_int32(0, f) < 0 || write_int32(val, f) < 0)
#endif
return -1;
return 0;
}
/*************************************************************************/
int read_ptr(void **ret, dbFILE *f)
{
int c;
c = fgetc(f->fp);
if (c == EOF)
return -1;
*ret = (c ? (void *)1 : (void *)0);
return 0;
}
int write_ptr(const void *ptr, dbFILE *f)
{
if (fputc(ptr ? 1 : 0, f->fp) == EOF)
return -1;
return 0;
}
/*************************************************************************/
int read_string(char **ret, dbFILE *f)
{
char *s;
uint16 len;
if (read_uint16(&len, f) < 0)
return -1;
if (len == 0) {
*ret = NULL;
return 0;
}
s = smalloc(len);
if (len != fread(s, 1, len, f->fp)) {
free(s);
return -1;
}
*ret = s;
return 0;
}
int write_string(const char *s, dbFILE *f)
{
uint32 len;
if (!s)
return write_int16(0, f);
len = strlen(s);
if (len > 65534)
len = 65534;
if (write_int16((uint16)(len+1), f) < 0)
return -1;
if (fwrite(s, 1, len, f->fp) != len)
return -1;
if (fputc(0, f->fp) == EOF)
return -1;
return 0;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,72 @@
/* Database file descriptor structure and file handling routine prototypes.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef DATABASE_FILEUTIL_H
#define DATABASE_FILEUTIL_H
/*************************************************************************/
typedef struct dbFILE_ dbFILE;
/* Callers may access fields in this structure directly, but MUST NOT
* modify them. */
struct dbFILE_ {
char mode; /* 'r' for reading, 'w' for writing */
FILE *fp; /* The file pointer itself */
char filename[PATH_MAX+1]; /* Name of the database file */
char tempname[PATH_MAX+1]; /* Name of the temporary file (for writing) */
};
/*************************************************************************/
/* Prototypes and macros: */
E int32 get_file_version(dbFILE *f);
E int write_file_version(dbFILE *f, int32 filever);
E dbFILE *open_db(const char *filename, const char *mode, int32 version);
E void restore_db(dbFILE *f); /* Restore to state before open_db() */
E int close_db(dbFILE *f);
#define read_db(f,buf,len) (fread((buf),1,(len),(f)->fp))
#define write_db(f,buf,len) (fwrite((buf),1,(len),(f)->fp))
#define getc_db(f) (fgetc((f)->fp))
E int read_int8(int8 *ret, dbFILE *f);
E int read_uint8(uint8 *ret, dbFILE *f);
E int write_int8(int8 val, dbFILE *f);
E int read_int16(int16 *ret, dbFILE *f);
E int read_uint16(uint16 *ret, dbFILE *f);
E int write_int16(int16 val, dbFILE *f);
E int read_int32(int32 *ret, dbFILE *f);
E int read_uint32(uint32 *ret, dbFILE *f);
E int write_int32(int32 val, dbFILE *f);
E int read_time(time_t *ret, dbFILE *f);
E int write_time(time_t val, dbFILE *f);
E int read_ptr(void **ret, dbFILE *f);
E int write_ptr(const void *ptr, dbFILE *f);
E int read_string(char **ret, dbFILE *f);
E int write_string(const char *s, dbFILE *f);
#define read_buffer(buf,f) \
(read_db((f),(buf),sizeof(buf)) == sizeof(buf) ? 0 : -1)
#define write_buffer(buf,f) \
(write_db((f),(buf),sizeof(buf)) == sizeof(buf) ? 0 : -1)
/*************************************************************************/
#endif /* DATABASE_FILEUTIL_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

872
modules/database/standard.c Normal file
View File

@ -0,0 +1,872 @@
/* Routines to load/save Services databases in standard format.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "databases.h"
#include "encrypt.h"
#include "fileutil.h"
#define SAFE(x) do { if ((x) < 0) goto fail; } while (0)
/*************************************************************************/
/* Database file format.
* Note: all integer values are big-endian.
*
* File header:
* 4 bytes -- version number (uint32)
* 4 bytes -- size of header (uint32)
* 4 bytes -- absolute offset to field list (uint32)
* 4 bytes -- absolute offset to first record descriptor table (uint32)
*
* Field list:
* 4 bytes -- size of field list (uint32)
* 4 bytes -- number of fields (uint32)
* 4 bytes -- size of a single record == sum of field sizes (uint32)
* N bytes -- field definitions
*
* Field definition:
* 4 bytes -- field data size in bytes
* 2 bytes -- field data type (DBTYPE_*) (uint16)
* 2 bytes -- length of field name, including trailing \0 (uint16)
* N bytes -- field name
*
* Record descriptor table:
* 4 bytes -- absolute offset to next table, or 0 if none (uint32)
* 4 bytes -- size of this table in bytes
* N bytes -- record entries
*
* Entry in record descriptor table:
* 4 bytes -- absolute offset to record data
* 4 bytes -- total length of record, including any strings
*
* Record data:
* N bytes -- main record data
* P bytes -- first string
* Q bytes -- second string
* ...
*
* Field data:
* DBTYPE_INT8: 8-bit value
* DBTYPE_UINT8: 8-bit value
* DBTYPE_INT16: 16-bit value
* DBTYPE_UINT16: 16-bit value
* DBTYPE_INT32: 32-bit value
* DBTYPE_UINT32: 32-bit value
* DBTYPE_TIME: 64-bit value
* DBTYPE_STRING: 32-bit offset to string from start of record
* DBTYPE_BUFFER: N-byte buffer contents
* DBTYPE_PASSWORD: 32-bit cipher string offset, (N-4) byte password buffer
*
* String data:
* 2 bytes -- string length including trailing \0 (uint16), 0 if NULL
* N bytes -- string contents
*/
#define NEWDB_VERSION ('I'<<24 | 'S'<<16 | 'D'<<8 | 1) /* IRC Services DB */
#define RECTABLE_LEN 0x400 /* anything will do, really */
#define RECTABLE_SIZE (RECTABLE_LEN*8)
/* Structure for keeping track of where to put data: */
typedef struct {
const DBTable *table;
int nfields;
struct {
DBField *field;
int32 offset; /* -1 if not found on load */
int rawsize; /* native size on system */
int filesize; /* size when written to file */
} *fields;
} TableInfo;
/*************************************************************************/
/*********************** Internal helper routines ************************/
/*************************************************************************/
/* Helper routine: creates a TableInfo for a table. */
static TableInfo *create_tableinfo(const DBTable *table)
{
TableInfo *ti;
int i;
ti = malloc(sizeof(*ti));
if (!ti) {
module_log("create_tableinfo(): Out of memory for table %s",
table->name);
return NULL;
}
ti->table = table;
for (i = 0; table->fields[i].name; i++)
;
ti->nfields = i;
ti->fields = malloc(sizeof(*ti->fields) * ti->nfields);
for (i = 0; i < ti->nfields; i++) {
uint32 fieldsize;
int rawsize = 0;
ti->fields[i].field = &table->fields[i];
switch (ti->fields[i].field->type) {
case DBTYPE_INT8:
case DBTYPE_UINT8: fieldsize = 1; break;
case DBTYPE_INT16:
case DBTYPE_UINT16: fieldsize = 2; break;
case DBTYPE_INT32:
case DBTYPE_UINT32: fieldsize = 4; break;
case DBTYPE_TIME: fieldsize = 8; rawsize = sizeof(time_t); break;
case DBTYPE_STRING: fieldsize = 4; rawsize = sizeof(char *); break;
case DBTYPE_BUFFER: fieldsize = ti->fields[i].field->length; break;
case DBTYPE_PASSWORD: fieldsize = PASSMAX+4;
rawsize = sizeof(Password); break;
default:
module_log("create_tableinfo(): Invalid type (%d) for field %s"
" in table %s", ti->fields[i].field->type,
ti->fields[i].field->name, ti->table->name);
return NULL;
}
if (!rawsize)
rawsize = fieldsize;
ti->fields[i].rawsize = rawsize;
ti->fields[i].filesize = fieldsize;
ti->fields[i].offset = -1;
}
return ti;
}
/*************************************************************************/
/* Helper routine: frees a TableInfo. */
static void free_tableinfo(TableInfo *ti)
{
if (ti) {
free(ti->fields);
free(ti);
}
}
/*************************************************************************/
/* Helper routine: returns the filename for a table. */
static const char *make_filename(const DBTable *table)
{
static const char okchars[] = /* characters allowed in filenames */
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static char namebuf[1000];
const char *s;
char *d = namebuf;
for (s = table->name; *s && d-namebuf < sizeof(namebuf)-5; s++, d++) {
if (strchr(okchars, *s))
*d = *s;
else
*d = '_';
}
strcpy(d, ".sdb"); /* safe: space left for this above */
return namebuf;
}
/*************************************************************************/
/*********************** Database loading routines ***********************/
/*************************************************************************/
static int read_file_header(dbFILE *f, uint32 *fieldofs_ret,
uint32 *recofs_ret);
static int read_field_list(TableInfo *ti, dbFILE *f, uint32 *recsize_ret);
static int read_records(TableInfo *ti, dbFILE *f, uint32 recsize);
static int standard_load_table(DBTable *table)
{
TableInfo *ti;
const char *filename;
dbFILE *f;
uint32 fieldofs; /* field list offset */
uint32 recofs; /* record descriptor table offset */
uint32 recsize; /* size of a record, less strings */
/* Set up the TableInfo structure */
ti = create_tableinfo(table);
if (!ti)
return 0;
/* Open the file */
filename = make_filename(table);
f = open_db(filename, "r", NEWDB_VERSION);
if (!f) {
module_log_perror("Can't open %s for reading", filename);
free_tableinfo(ti);
return 0;
}
/* Check the file version and header size, and read header fields */
if (!read_file_header(f, &fieldofs, &recofs))
goto fail;
/* Read in the field list and make sure it fits with the DBTable */
if (fseek(f->fp, fieldofs, SEEK_SET) < 0) {
module_log_perror("Can't seek in %s", filename);
goto fail;
}
if (!read_field_list(ti, f, &recsize))
goto fail;
/* Read in the records */
if (fseek(f->fp, recofs, SEEK_SET) < 0) {
module_log_perror("Can't seek in %s", filename);
goto fail;
}
if (!read_records(ti, f, recsize))
goto fail;
/* Call the postload routine, if any */
if (table->postload && !(*table->postload)()) {
module_log_perror("Table %s postload routine failed", table->name);
goto fail;
}
/* Done! */
close_db(f);
free_tableinfo(ti);
return 1;
fail:
close_db(f);
free_tableinfo(ti);
return 0;
}
/*************************************************************************/
/* Read the file header and verify the version and header size, then return
* the field list offset and record descriptor table offset.
*/
static int read_file_header(dbFILE *f, uint32 *fieldofs_ret,
uint32 *recofs_ret)
{
uint32 version; /* version number stored in file */
uint32 hdrsize; /* file header size */
SAFE(read_uint32(&version, f));
if (version != NEWDB_VERSION) {
module_log("Bad version number on %s", f->filename);
return 0;
}
SAFE(read_uint32(&hdrsize, f));
if (hdrsize < 16) {
module_log("Bad header size on %s", f->filename);
return 0;
}
SAFE(read_uint32(fieldofs_ret, f));
SAFE(read_uint32(recofs_ret, f));
return 1;
fail:
module_log("Read error on %s", f->filename);
return 0;
}
/*************************************************************************/
/* Read in the field list, matching it with the field list in the DBTable
* structure and filling in field offsets. The record size (as stored in
* the file) is returned in *recsize_ret.
*/
static int read_field_list(TableInfo *ti, dbFILE *f, uint32 *recsize_ret)
{
uint32 thispos; /* current file position */
uint32 maxpos; /* end of table */
uint32 nfields;
int32 recofs = 0;
int i, j;
SAFE(read_uint32(&maxpos, f)); /* field list size */
maxpos += ftell(f->fp)-4;
SAFE(read_uint32(&nfields, f)); /* number of fields */
SAFE(read_uint32(recsize_ret, f)); /* record size--filled in later */
for (i = 0; i < nfields; i++) {
uint32 length;
uint16 type;
uint16 strsize;
char *fieldname;
SAFE((int32)(thispos = ftell(f->fp)));
SAFE(read_uint32(&length, f));
SAFE(read_uint16(&type, f));
SAFE(read_uint16(&strsize, f));
if (thispos+8+strsize > maxpos) {
module_log("load_table(): premature end of field list");
return 0;
}
SAFE(fseek(f->fp, -2, SEEK_CUR));
SAFE(read_string(&fieldname, f));
if (!fieldname) { /* impossible */
module_log("load_table(): BUG: field name is NULL");
return 0;
}
for (j = 0; j < ti->nfields; j++) {
DBField *field = ti->fields[j].field;
if (type == field->type
&& length == ti->fields[j].filesize
&& strcmp(field->name,fieldname) == 0
) {
ti->fields[j].offset = recofs;
break;
}
}
free(fieldname);
recofs += length;
}
return 1;
fail:
module_log_perror("Error reading from %s", f->filename);
return 0;
}
/*************************************************************************/
/* Read in table records. */
static int read_records(TableInfo *ti, dbFILE *f, uint32 recsize)
{
uint32 *rectable; /* Table of record pointers and lengths */
int recnum; /* Record number within descriptor table */
int reccount; /* Record number in file */
void *record; /* Record pointer from first()/next() */
void *recbuf; /* Buffer for storing record data */
int i;
/* Allocate record buffer */
recbuf = malloc(recsize);
if (!recbuf)
return 0;
/* Variable init */
rectable = NULL;
reccount = 0;
record = NULL;
/* Termination check has to be performed in the middle of the loop,
* since we may need to read the record descriptor table first */
for (recnum = 0; ; recnum = (recnum+1) % (rectable[1]/8), reccount++) {
if (!recnum) { /* Start of a new record descriptor table */
uint32 nextofs, tablesize;
/* Seek to next table, if this isn't the first */
if (rectable) {
if (!rectable[0]) {
module_log("read_records(): %s: next table is 0!",
f->filename);
return 0;
}
SAFE(fseek(f->fp, rectable[0], SEEK_SET));
free(rectable);
rectable = NULL;
}
/* Read in and check the `next' pointer and table size */
SAFE(read_uint32(&nextofs, f));
SAFE(read_uint32(&tablesize, f));
if (!tablesize) {
module_log("read_records(): %s: rectable size is 0!",
f->filename);
return 0;
}
/* Allocate the table memory */
rectable = malloc(tablesize);
if (!rectable) {
module_log("read_records(): %s: no memory for rectable!",
f->filename);
return 0;
}
/* Read in the table */
rectable[0] = nextofs;
rectable[1] = tablesize;
for (i = 2; i < tablesize/4; i++)
SAFE(read_uint32(&rectable[i], f));
/* First record in the table is at index 1 */
recnum++;
}
/* If this is the last record, bail out */
if (!rectable[recnum*2])
break;
/* Make sure the record size is large enough */
if (rectable[recnum*2+1] < recsize) {
module_log("read_records(): %s: record %d is too small,"
" skipping", f->filename, reccount);
continue;
}
/* Allocate a new record */
record = ti->table->newrec();
if (!record) {
module_log("read_records(): %s: newrec() failed for record %d!",
f->filename, reccount);
return 0;
}
/* Seek to the location of this record and read the main record data */
SAFE(fseek(f->fp, rectable[recnum*2], SEEK_SET));
if (fread(recbuf, recsize, 1, f->fp) != 1)
goto fail;
/* Read fields from record buffer and strings from file */
for (i = 0; i < ti->nfields; i++) {
const DBField *field = ti->fields[i].field;
const void *src = (const int8 *)recbuf + ti->fields[i].offset;
if (ti->fields[i].offset < 0) /* field not present in file */
continue;
if (field->type == DBTYPE_STRING) {
uint32 strofs;
char *string;
strofs = ((uint8 *)src)[0] << 24
| ((uint8 *)src)[1] << 16
| ((uint8 *)src)[2] << 8
| ((uint8 *)src)[3];
if (strofs >= rectable[recnum*2+1]) {
module_log("read_records(): %s: string for field `%s' of"
" record %d is out of range, skipping",
f->filename, field->name, reccount);
continue;
}
SAFE(fseek(f->fp, rectable[recnum*2] + strofs, SEEK_SET));
SAFE(read_string(&string, f));
put_dbfield(record, field, &string);
} else if (field->type == DBTYPE_PASSWORD) {
uint32 strofs;
Password pass;
char *cipher;
strofs = ((uint8 *)src)[0] << 24
| ((uint8 *)src)[1] << 16
| ((uint8 *)src)[2] << 8
| ((uint8 *)src)[3];
if (strofs >= rectable[recnum*2+1]) {
module_log("read_records(): %s: string for field `%s' of"
" record %d is out of range, skipping",
f->filename, field->name, reccount);
continue;
}
SAFE(fseek(f->fp, rectable[recnum*2] + strofs, SEEK_SET));
SAFE(read_string((char **)&cipher, f));
init_password(&pass);
set_password(&pass, src+4, cipher);
free(cipher);
put_dbfield(record, field, &pass);
} else if (field->type == DBTYPE_BUFFER) {
put_dbfield(record, field, src);
} else {
char fieldbuf[16]; /* Big enough for any int or time type */
switch (field->type) {
case DBTYPE_INT8:
case DBTYPE_UINT8:
*((uint8 *)fieldbuf) = ((uint8 *)src)[0];
break;
case DBTYPE_INT16:
case DBTYPE_UINT16:
*((uint16 *)fieldbuf) = ((uint8 *)src)[0] << 8
| ((uint8 *)src)[1];
break;
case DBTYPE_INT32:
case DBTYPE_UINT32:
*((uint32 *)fieldbuf) = ((uint8 *)src)[0] << 24
| ((uint8 *)src)[1] << 16
| ((uint8 *)src)[2] << 8
| ((uint8 *)src)[3];
break;
case DBTYPE_TIME:
*((time_t *)fieldbuf) = ((uint8 *)src)[4] << 24
| ((uint8 *)src)[5] << 16
| ((uint8 *)src)[6] << 8
| ((uint8 *)src)[7];
#if SIZEOF_TIME_T >= 8
*((time_t *)fieldbuf) |=(time_t)((uint8 *)src)[0] << 56
| (time_t)((uint8 *)src)[1] << 48
| (time_t)((uint8 *)src)[2] << 40
| (time_t)((uint8 *)src)[3] << 32;
#endif
break;
case DBTYPE_STRING:
case DBTYPE_BUFFER:
case DBTYPE_PASSWORD:
/* Handled above--listed here to avoid warnings */
break;
}
put_dbfield(record, field, fieldbuf);
}
} /* for each field */
/* Add record to table */
ti->table->insert(record);
record = NULL;
} /* for each record */
/* All done */
free(recbuf);
free(rectable);
return 1;
fail:
module_log_perror("read_records(): Read error on %s", f->filename);
if (record)
ti->table->freerec(record);
free(recbuf);
free(rectable);
return 0;
}
/*************************************************************************/
/*********************** Database saving routines ************************/
/*************************************************************************/
static int write_file_header(TableInfo *ti, dbFILE *f);
static int write_field_list(TableInfo *ti, dbFILE *f, uint32 *recsize_ret);
static int write_records(TableInfo *ti, dbFILE *f, uint32 recsize);
static int standard_save_table(DBTable *table)
{
TableInfo *ti;
const char *filename;
dbFILE *f;
uint32 recsize; /* size of a record, less strings */
/* Set up the TableInfo structure */
ti = create_tableinfo(table);
if (!ti)
return 0;
/* Open the file */
filename = make_filename(table);
f = open_db(filename, "w", NEWDB_VERSION);
if (!f) {
module_log_perror("Can't open %s for writing", filename);
free_tableinfo(ti);
return 0;
}
/* Write the file header */
SAFE(write_file_header(ti, f));
/* Write the field list */
SAFE(write_field_list(ti, f, &recsize));
/* Write the actual records */
SAFE(write_records(ti, f, recsize));
/* Done! */
close_db(f);
free_tableinfo(ti);
return 1;
fail:
module_log_perror("Can't write to %s", filename);
restore_db(f);
free_tableinfo(ti);
return 0;
}
/*************************************************************************/
/* Write the file header. */
static int write_file_header(TableInfo *ti, dbFILE *f)
{
/* Note that the version number is already written */
SAFE(write_int32(16, f)); /* header size */
SAFE(write_int32(0, f)); /* field list offset--filled in later */
SAFE(write_int32(0, f)); /* record desc. table offset--filled in later */
return 0;
fail:
return -1;
}
/*************************************************************************/
/* Write the field list, filling in ti->fields[].offset as we go, and send
* back the size of a record; also fills in field list offset in file
* header.
*/
static int write_field_list(TableInfo *ti, dbFILE *f, uint32 *recsize_ret)
{
uint32 listpos; /* file position of start of table */
uint32 endpos; /* file position of end of table */
uint32 recsize = 0;
int i, nfields = 0;
SAFE((int32)(listpos = ftell(f->fp)));
SAFE(fseek(f->fp, 8, SEEK_SET));
SAFE(write_int32(listpos, f));
SAFE(fseek(f->fp, listpos, SEEK_SET));
SAFE(write_int32(0, f)); /* field list size--filled in later*/
SAFE(write_int32(0, f)); /* number of fields--filled in later */
SAFE(write_int32(0, f)); /* record size--filled in later */
for (i = 0; i < ti->nfields; i++) {
if (ti->fields[i].field->load_only)
continue;
SAFE(write_int32(ti->fields[i].filesize, f));
SAFE(write_int16(ti->fields[i].field->type, f));
SAFE(write_string(ti->fields[i].field->name, f));
ti->fields[i].offset = recsize;
nfields++;
recsize += ti->fields[i].filesize;
}
SAFE((int32)(endpos = ftell(f->fp)));
SAFE(fseek(f->fp, listpos, SEEK_SET));
SAFE(write_int32(endpos-listpos, f));
SAFE(write_int32(nfields, f));
SAFE(write_int32(recsize, f));
SAFE(fseek(f->fp, endpos, SEEK_SET));
*recsize_ret = recsize;
return 0;
fail:
return -1;
}
/*************************************************************************/
/* Write out table records. */
static int write_records(TableInfo *ti, dbFILE *f, uint32 recsize)
{
uint32 listpos; /* Offset of current record descriptor table */
int recnum; /* Record number within table */
const void *record; /* Record pointer from first()/next() */
void *recbuf = NULL; /* Buffer for storing record data */
int i;
/* Allocate record buffer */
recbuf = malloc(recsize);
if (!recbuf)
return -1;
/* Variable init */
listpos = 12; /* First record descriptor table pointer is recorded here */
recnum = 0;
/* Note the lack of a termination condition in this loop--we need to
* write a 0 to terminate the record list, which may involve creating a
* new descriptor table, so we let the first part of the loop run and
* break out in the middle when we hit the end of the table. */
for (record = ti->table->first(); ; record = ti->table->next()) {
uint32 thispos; /* Offset this record is written at */
uint32 nextpos; /* Temp variable to hold next record's offset */
if (!recnum) { /* Start of a new rectable */
uint32 oldpos = listpos;
static char dummy_rectable[RECTABLE_SIZE];
/* Seek to a multiple of the table size, just for the hell of it */
SAFE((int32)(listpos = ftell(f->fp)));
listpos = (listpos + (RECTABLE_SIZE-1))
/ RECTABLE_SIZE * RECTABLE_SIZE;
SAFE(fseek(f->fp, listpos, SEEK_SET));
/* Write out an empty descriptor table */
SAFE(write_buffer(dummy_rectable, f));
SAFE(fseek(f->fp, listpos, SEEK_SET));
/* Write the `next' pointer (currently 0) and size */
SAFE(write_int32(0, f));
SAFE(write_int32(RECTABLE_SIZE, f));
/* Write the new table offset into the old `next' pointer */
SAFE(fseek(f->fp, oldpos, SEEK_SET));
SAFE(write_int32(listpos, f));
/* Seek to where the next record should be written */
SAFE(fseek(f->fp, listpos + RECTABLE_SIZE, SEEK_SET));
/* The first record is written at index 1 (don't smash the
* next pointer) */
recnum = 1;
}
/* If this is the last record, record a 0 to terminate the record
* list and bail out */
if (!record) {
SAFE(fseek(f->fp, listpos + recnum*8, SEEK_SET));
SAFE(write_int32(0, f));
SAFE(write_int32(0, f));
break;
}
/* Save the location of this record */
SAFE((int32)(thispos = ftell(f->fp)));
/* Seek to end of record, for writing strings */
SAFE(fseek(f->fp, thispos + recsize, SEEK_SET));
/* Write fields to record buffer and strings to file */
for (i = 0; i < ti->nfields; i++) {
const DBField *field = ti->fields[i].field;
uint8 *dest = (uint8 *)recbuf + ti->fields[i].offset;
if (field->load_only)
continue;
if (field->type == DBTYPE_STRING) {
const char *string;
uint32 strofs;
get_dbfield(record, field, (void *)&string);
SAFE((int32)(strofs = ftell(f->fp)));
strofs -= thispos;
dest[0] = strofs >> 24;
dest[1] = strofs >> 16;
dest[2] = strofs >> 8;
dest[3] = strofs;
SAFE(write_string(string, f));
} else if (field->type == DBTYPE_PASSWORD) {
Password pass;
uint32 strofs;
get_dbfield(record, field, (void *)&pass);
SAFE((int32)(strofs = ftell(f->fp)));
strofs -= thispos;
dest[0] = strofs >> 24;
dest[1] = strofs >> 16;
dest[2] = strofs >> 8;
dest[3] = strofs;
SAFE(write_string(pass.cipher, f));
memcpy(dest+4, pass.password, PASSMAX);
} else if (field->type == DBTYPE_BUFFER) {
get_dbfield(record, field, dest);
} else {
uint8 fieldbuf[16]; /* Big enough for any int or time type */
get_dbfield(record, field, fieldbuf);
switch (field->type) {
case DBTYPE_INT8:
case DBTYPE_UINT8:
dest[0] = *((uint8 *)fieldbuf);
break;
case DBTYPE_INT16:
case DBTYPE_UINT16:
dest[0] = *((uint16 *)fieldbuf) >> 8;
dest[1] = *((uint16 *)fieldbuf);
break;
case DBTYPE_INT32:
case DBTYPE_UINT32:
dest[0] = *((uint32 *)fieldbuf) >> 24;
dest[1] = *((uint32 *)fieldbuf) >> 16;
dest[2] = *((uint32 *)fieldbuf) >> 8;
dest[3] = *((uint32 *)fieldbuf);
break;
case DBTYPE_TIME:
#if SIZEOF_TIME_T >= 8
dest[0] = *((time_t *)fieldbuf) >> 56;
dest[1] = *((time_t *)fieldbuf) >> 48;
dest[2] = *((time_t *)fieldbuf) >> 40;
dest[3] = *((time_t *)fieldbuf) >> 32;
#else
/* Copy the sign bit into the four high bytes */
dest[0] = (int32) *((time_t *)fieldbuf) >> 31;
dest[1] = (int32) *((time_t *)fieldbuf) >> 31;
dest[2] = (int32) *((time_t *)fieldbuf) >> 31;
dest[3] = (int32) *((time_t *)fieldbuf) >> 31;
#endif
dest[4] = *((time_t *)fieldbuf) >> 24;
dest[5] = *((time_t *)fieldbuf) >> 16;
dest[6] = *((time_t *)fieldbuf) >> 8;
dest[7] = *((time_t *)fieldbuf);
break;
case DBTYPE_STRING:
case DBTYPE_BUFFER:
case DBTYPE_PASSWORD:
/* Handled above--listed here to avoid warnings */
break;
}
}
} /* for each field */
/* Save the location of the next record */
SAFE((int32)(nextpos = ftell(f->fp)));
/* Write record pointer and length to table */
SAFE(fseek(f->fp, listpos + recnum*8, SEEK_SET));
SAFE(write_int32(thispos, f));
SAFE(write_int32(nextpos-thispos, f));
/* Write record buffer to file */
SAFE(fseek(f->fp, thispos, SEEK_SET));
if (fwrite(recbuf, recsize, 1, f->fp) != 1)
goto fail;
/* Seek to next record's starting position */
SAFE(fseek(f->fp, nextpos, SEEK_SET));
/* Move to the next record index */
recnum = (recnum+1) % RECTABLE_LEN;
}
/* All done */
free(recbuf);
return 0;
fail:
free(recbuf);
return -1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
#ifndef INCLUDE_IN_VERSION4
ConfigDirective module_config[] = {
{ NULL }
};
static DBModule dbmodule_standard = {
.load_table = standard_load_table,
.save_table = standard_save_table,
};
/*************************************************************************/
int init_module(void)
{
if (!register_dbmodule(&dbmodule_standard)) {
module_log("Unable to register module with database core");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
unregister_dbmodule(&dbmodule_standard);
return 1;
}
/*************************************************************************/
#endif /* !INCLUDE_IN_VERSION4 */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

2526
modules/database/version4.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
# Makefile for encryption modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = md5.so unix-crypt.so
INCLUDES = $(TOPDIR)/encrypt.h
include ../Makerules
###########################################################################

592
modules/encryption/md5.c Normal file
View File

@ -0,0 +1,592 @@
/* Module for encryption using MD5.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "encrypt.h"
/*************************************************************************/
/*************************************************************************/
/* Enable a workaround for Anope/Epona broken MD5 passwords? */
static int EnableAnopeWorkaround = 0;
/* For the Anope hack: */
#define XTOI(c) ((c)>9 ? (c)-'A'+10 : (c)-'0')
/*************************************************************************/
/*======== Beginning of L. Peter Deutsch's md5.h ========*/
/*
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 2.28 2009/06/17 09:18:39 achurch Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef md5_INCLUDED
# define md5_INCLUDED
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif
/* Initialize the algorithm. */
void md5_init(md5_state_t *pms);
/* Append a string to the message. */
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */
#endif
#endif /* md5_INCLUDED */
/*======== End of L. Peter Deutsch's md5.h ========*/
/*======== Beginning of L. Peter Deutsch's md5.c (with the line ========
======== [#include "md5.h"] removed) ========*/
/*
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 2.28 2009/06/17 09:18:39 achurch Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.c is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
either statically or dynamically; added missing #include <string.h>
in library.
2002-03-11 lpd Corrected argument list for main(), and added int return
type, in test program and T value program.
2002-02-21 lpd Added missing #include <stdio.h> in test program.
2000-07-03 lpd Patched to eliminate warnings about "constant is
unsigned in ANSI C, signed in traditional"; made test program
self-checking.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
1999-05-03 lpd Original version.
*/
#include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define BYTE_ORDER 0
#endif
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3 0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6 0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9 0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13 0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16 0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19 0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22 0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25 0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28 0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31 0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35 0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38 0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41 0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44 0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47 0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50 0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53 0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57 0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60 0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63 0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
const md5_word_t *X;
#endif
{
#if BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static const int w = 1;
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {
/* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, T1);
SET(d, a, b, c, 1, 12, T2);
SET(c, d, a, b, 2, 17, T3);
SET(b, c, d, a, 3, 22, T4);
SET(a, b, c, d, 4, 7, T5);
SET(d, a, b, c, 5, 12, T6);
SET(c, d, a, b, 6, 17, T7);
SET(b, c, d, a, 7, 22, T8);
SET(a, b, c, d, 8, 7, T9);
SET(d, a, b, c, 9, 12, T10);
SET(c, d, a, b, 10, 17, T11);
SET(b, c, d, a, 11, 22, T12);
SET(a, b, c, d, 12, 7, T13);
SET(d, a, b, c, 13, 12, T14);
SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, T17);
SET(d, a, b, c, 6, 9, T18);
SET(c, d, a, b, 11, 14, T19);
SET(b, c, d, a, 0, 20, T20);
SET(a, b, c, d, 5, 5, T21);
SET(d, a, b, c, 10, 9, T22);
SET(c, d, a, b, 15, 14, T23);
SET(b, c, d, a, 4, 20, T24);
SET(a, b, c, d, 9, 5, T25);
SET(d, a, b, c, 14, 9, T26);
SET(c, d, a, b, 3, 14, T27);
SET(b, c, d, a, 8, 20, T28);
SET(a, b, c, d, 13, 5, T29);
SET(d, a, b, c, 2, 9, T30);
SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, T33);
SET(d, a, b, c, 8, 11, T34);
SET(c, d, a, b, 11, 16, T35);
SET(b, c, d, a, 14, 23, T36);
SET(a, b, c, d, 1, 4, T37);
SET(d, a, b, c, 4, 11, T38);
SET(c, d, a, b, 7, 16, T39);
SET(b, c, d, a, 10, 23, T40);
SET(a, b, c, d, 13, 4, T41);
SET(d, a, b, c, 0, 11, T42);
SET(c, d, a, b, 3, 16, T43);
SET(b, c, d, a, 6, 23, T44);
SET(a, b, c, d, 9, 4, T45);
SET(d, a, b, c, 12, 11, T46);
SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, T49);
SET(d, a, b, c, 7, 10, T50);
SET(c, d, a, b, 14, 15, T51);
SET(b, c, d, a, 5, 21, T52);
SET(a, b, c, d, 12, 6, T53);
SET(d, a, b, c, 3, 10, T54);
SET(c, d, a, b, 10, 15, T55);
SET(b, c, d, a, 1, 21, T56);
SET(a, b, c, d, 8, 6, T57);
SET(d, a, b, c, 15, 10, T58);
SET(c, d, a, b, 6, 15, T59);
SET(b, c, d, a, 13, 21, T60);
SET(a, b, c, d, 4, 6, T61);
SET(d, a, b, c, 11, 10, T62);
SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
void
md5_init(md5_state_t *pms)
{
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
const md5_byte_t *p = data;
int left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
memcpy(pms->buf, p, left);
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}
/*======== End of L. Peter Deutsch's md5.c ========*/
/*************************************************************************/
/*************************************************************************/
/* Services routines. See encrypt.h for documentation. */
/*************************************************************************/
static int md5_encrypt(const char *src, int len, char *dest, int size)
{
md5_state_t context;
if (size < 16)
return 16;
md5_init(&context);
md5_append(&context, (unsigned char *)src, len);
md5_finish(&context, (unsigned char *)dest);
return 0;
}
/*************************************************************************/
static int md5_decrypt(const char *src, char *dest, int size)
{
return -2; /* decryption impossible (for all practical purposes) */
}
/*************************************************************************/
static int md5_check_password(const char *plaintext, const char *password)
{
char buf[16];
char tmpbuf[8];
int i;
if (md5_encrypt(plaintext, strlen(plaintext), buf, sizeof(buf)) != 0)
return -1;
if (memcmp(buf, password, 16) == 0)
return 1;
if (EnableAnopeWorkaround) {
for (i = 0; i < 16; i += 2)
tmpbuf[i/2] = XTOI(buf[i])<<4 | XTOI(buf[i+1]);
if (memcmp(tmpbuf, password, 8) == 0)
return 1;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Module stuff. */
ConfigDirective module_config[] = {
{ "EnableAnopeWorkaround", { {CD_SET, 0, &EnableAnopeWorkaround} } },
{ NULL }
};
static CipherInfo md5_info = {
.name = "md5",
.encrypt = md5_encrypt,
.decrypt = md5_decrypt,
.check_password = md5_check_password,
};
int init_module(void)
{
if (PASSMAX < 16) {
module_log("PASSMAX too small (must be at least 16)");
return 0;
}
register_cipher(&md5_info);
return 1;
}
int exit_module(int shutdown_unused)
{
unregister_cipher(&md5_info);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,124 @@
/* Module for encryption using the Unix crypt() function.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "encrypt.h"
#if !HAVE_CRYPT
# define crypt(pass,salt) NULL
#endif
/*************************************************************************/
/* Allowable characters in the salt */
const char saltchars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
/*************************************************************************/
/*************************************************************************/
static int unixcrypt_encrypt(const char *src, int len, char *dest, int size)
{
char buf[PASSMAX+1], salt[3];
const char *res;
if (len > sizeof(buf)-1)
len = sizeof(buf)-1;
memcpy(buf, src, len);
buf[len] = 0;
salt[0] = saltchars[random()%64];
salt[1] = saltchars[random()%64];
salt[2] = 0;
res = crypt(buf, salt);
if (!res) {
module_log_perror("encrypt: crypt() failed");
return -1;
}
if (strlen(res) > size-1) {
module_log("encrypt: crypt() returned too long a string! (%d"
" characters)", (int)strlen(res));
return strlen(res) + 1;
}
strscpy(dest, res, size);
return 0;
}
/*************************************************************************/
static int unixcrypt_decrypt(const char *src, char *dest, int size)
{
return -2; /* decryption impossible (in reasonable time) */
}
/*************************************************************************/
static int unixcrypt_check_password(const char *plaintext, const char *password)
{
const char *res;
res = crypt(plaintext, password);
if (!res) {
module_log_perror("check_password: crypt() failed");
return -1;
}
if (strcmp(res, password) == 0)
return 1;
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Module stuff. */
ConfigDirective module_config[] = {
{ NULL }
};
static CipherInfo unixcrypt_info = {
.name = "unix-crypt",
.encrypt = unixcrypt_encrypt,
.decrypt = unixcrypt_decrypt,
.check_password = unixcrypt_check_password,
};
int init_module(void)
{
#if !HAVE_CRYPT
module_log("Your system does not support the crypt() function!");
return 0;
#else
if (PASSMAX < 14) {
module_log("PASSMAX too small (must be at least 14)");
return 0;
}
register_cipher(&unixcrypt_info);
return 1;
#endif
}
int exit_module(int shutdown_unused)
{
unregister_cipher(&unixcrypt_info);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

40
modules/httpd/Makefile Normal file
View File

@ -0,0 +1,40 @@
# Makefile for httpd modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so auth-ip.so auth-password.so dbaccess.so debug.so \
redirect.so top-page.so
OBJECTS-main.so = util.o
INCLUDES = http.h
INCLUDES-dbaccess.o = $(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/operserv/akill.h \
$(TOPDIR)/modules/operserv/news.h \
$(TOPDIR)/modules/operserv/sline.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h \
$(TOPDIR)/modules/chanserv/access.h \
$(TOPDIR)/modules/statserv/statserv.h \
$(TOPDIR)/modules/misc/xml.h
INCLUDES-main.o = $(TOPDIR)/timeout.h
INCLUDES-redirect.o = $(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
include ../Makerules
###########################################################################
# Note that while http.h technically depends on $(TOPDIR)/timeout.h, the
# only thing it uses from that file is "Timeout *", which is unlikely to
# change, so we don't bother depending on it here. Source files which call
# timeout routines should #include "timeout.h" (and include it in their
# dependency lists) directly.

278
modules/httpd/auth-ip.c Normal file
View File

@ -0,0 +1,278 @@
/* IP address authorization module for HTTP server.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "http.h"
#include <netinet/in.h>
#include <netdb.h>
/*************************************************************************/
static Module *module_httpd;
/* List of hosts to allow/deny */
typedef struct {
char *path;
int pathlen; /* for convenience */
uint32 ip, mask; /* network byte order */
int allow; /* 1 = allow, 0 = deny */
} DirInfo;
static DirInfo *protected = NULL;
static int protected_count = 0;
/*************************************************************************/
/************************ Authorization callback *************************/
/*************************************************************************/
static int do_auth(Client *c, int *close_ptr)
{
int i;
ARRAY_FOREACH (i, protected) {
if (strncmp(c->url, protected[i].path, protected[i].pathlen) != 0)
continue;
if ((c->ip & protected[i].mask) != protected[i].ip)
continue;
if (protected[i].allow) {
return HTTP_AUTH_UNDECIDED;
} else {
module_log("Denying request for %s from %s", c->url, c->address);
return HTTP_AUTH_DENY;
}
}
return HTTP_AUTH_UNDECIDED;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_prefix(const char *filename, int linenum, char *param);
static int do_AllowHost(const char *filename, int linenum, char *param);
static int do_DenyHost(const char *filename, int linenum, char *param);
static int do_AllowDenyHost(const char *filename, int linenum, char *param,
int allow);
ConfigDirective module_config[] = {
{ "AllowHost", { { CD_FUNC, 0, do_prefix },
{ CD_FUNC, 0, do_AllowHost } } },
{ "DenyHost", { { CD_FUNC, 0, do_prefix },
{ CD_FUNC, 0, do_DenyHost } } },
{ NULL }
};
static char *prefix = NULL;
/*************************************************************************/
static int do_prefix(const char *filename, int linenum, char *param)
{
if (filename) {
free(prefix);
prefix = strdup(param);
if (!prefix) {
config_error(filename, linenum, "Out of memory");
return 0;
}
}
return 1;
}
static int do_AllowHost(const char *filename, int linenum, char *param)
{
return do_AllowDenyHost(filename, linenum, param, 1);
}
static int do_DenyHost(const char *filename, int linenum, char *param)
{
return do_AllowDenyHost(filename, linenum, param, 0);
}
/*************************************************************************/
static int do_AllowDenyHost(const char *filename, int linenum, char *param,
int allow)
{
char *s;
int mask = 32;
const uint8 *ip;
int recursing = 0, i;
DirInfo di;
static DirInfo *new_protected = NULL;
static int new_protected_count = 0;
if (!filename) {
/* filename == NULL, special actions */
switch (linenum) {
case CDFUNC_INIT: /* prepare for reading */
free(new_protected);
new_protected = NULL;
new_protected_count = 0;
break;
case CDFUNC_SET: /* store new values in config variables */
if (new_protected_count >= 0) {
ARRAY_FOREACH (i, protected)
free(protected[i].path);
free(protected);
protected = new_protected;
protected_count = new_protected_count;
new_protected = NULL;
new_protected_count = -1; /* flag to say "don't copy again" */
}
break;
case CDFUNC_DECONFIG: /* clear out config variables */
ARRAY_FOREACH (i, protected)
free(protected[i].path);
free(protected);
protected = NULL;
protected_count = 0;
break;
} /* switch (linenum) */
return 1;
} /* if (!filename) */
/* filename != NULL, process directive */
if (linenum < 0) {
recursing = 1;
linenum = -linenum;
}
di.path = prefix;
di.pathlen = strlen(prefix);
prefix = NULL;
s = strchr(param, '/');
if (s) {
*s++ = 0;
mask = (int)atolsafe(s, 1, 31);
if (mask < 1) {
config_error(filename, linenum, "Invalid mask length `%s'", s);
free(di.path);
return 0;
}
}
if (strcmp(param, "*") == 0) {
/* All-hosts wildcard -> equivalent to 0.0.0.0/0 */
ip = (const uint8 *)"\0\0\0\0";
mask = 0;
} else if ((ip = pack_ip(param)) != NULL) {
/* IP address -> okay as is */
} else {
/* hostname -> check for double recursion, then look up and
* recursively add addresses */
#ifdef HAVE_GETHOSTBYNAME
struct hostent *hp;
#endif
if (recursing) {
config_error(filename, linenum, "BUG: double recursion (param=%s)",
param);
free(di.path);
return 0;
}
#ifdef HAVE_GETHOSTBYNAME
if ((hp = gethostbyname(param)) != NULL) {
if (hp->h_addrtype == AF_INET) {
for (i = 0; hp->h_addr_list[i]; i++) {
char ipbuf[16];
ip = (const uint8 *)hp->h_addr_list[i];
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u",
ip[0], ip[1], ip[2], ip[3]);
if (strlen(ipbuf) > 15) {
config_error(filename, linenum,
"BUG: strlen(ipbuf) > 15 [%s]", ipbuf);
free(di.path);
return 0;
}
prefix = strdup(di.path);
if (!prefix) {
config_error(filename, linenum, "Out of memory");
free(di.path);
return 0;
}
if (!do_AllowDenyHost(filename, -linenum, ipbuf, allow)) {
free(di.path);
return 0;
}
}
free(di.path);
return 1; /* Success */
} else {
config_error(filename, linenum, "%s: no IPv4 addresses found",
param);
}
} else {
config_error(filename, linenum, "%s: %s", param,
hstrerror(h_errno));
}
#else
config_error(filename, linenum,
"gethostbyname() not available, hostnames may not be"
" used");
#endif
free(di.path);
return 0;
}
di.ip = *((uint32 *)ip);
di.mask = mask ? htonl(0xFFFFFFFFUL << (32-mask)) : 0;
di.ip &= di.mask;
di.allow = allow;
ARRAY_EXTEND(new_protected);
new_protected[new_protected_count-1] = di;
return 1;
}
/*************************************************************************/
/*************************************************************************/
int init_module(void)
{
module_httpd = find_module("httpd/main");
if (!module_httpd) {
module_log("Main httpd module not loaded");
exit_module(0);
return 0;
}
use_module(module_httpd);
if (!add_callback(module_httpd, "auth", do_auth)) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_httpd) {
remove_callback(module_httpd, "auth", do_auth);
unuse_module(module_httpd);
module_httpd = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,265 @@
/* Password authorization module for HTTP server.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "http.h"
/*************************************************************************/
static Module *module_httpd;
static char *AuthName;
typedef struct {
char *path;
int pathlen; /* for convenience */
char *userpass;
} DirInfo;
static DirInfo *protected = NULL;
static int protected_count = 0;
/*************************************************************************/
/************************ Authorization callback *************************/
/*************************************************************************/
static int do_auth(Client *c, int *close_ptr)
{
int i;
char *authinfo, *s;
/* Search for a matching path prefix. */
ARRAY_FOREACH (i, protected) {
if (strncmp(c->url, protected[i].path, protected[i].pathlen) == 0)
break;
}
if (i >= protected_count) {
/* No matching path prefix: return "undecided". */
return HTTP_AUTH_UNDECIDED;
}
/* Check for an Authorization: header. */
authinfo = http_get_header(c, "Authorization");
if (authinfo) {
/* Retrieve the encoded username and password. */
s = strchr(authinfo, ' ');
/* Check and make sure they're actually there. */
if (s) {
/* Skip past any extra whitespace... */
while (*s == ' ' || *s == '\t')
s++;
/* ... then compare against the configured username/password. */
if (strcmp(s, protected[i].userpass) == 0) {
/* Allow the next authorization callback to check this
* request. In general, it is not a good idea to
* explicitly allow requests unless the requesting user
* has (for example) been authorized as the Services root
* or otherwise should clearly have access despite any
* other suthorization checks.
*/
return HTTP_AUTH_UNDECIDED;
}
}
}
/* If the username or password are incorrect (or no Authorization:
* header was supplied, deny the request.
*/
http_send_response(c, HTTP_E_UNAUTHORIZED);
sockprintf(c->socket, "WWW-Authenticate: basic realm=%s\r\n", AuthName);
sockprintf(c->socket, "Content-Type: text/html\r\n");
sockprintf(c->socket, "Content-Length: 14\r\n\r\n");
sockprintf(c->socket, "Access denied.");
return HTTP_AUTH_DENY;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_Protect1(const char *filename, int linenum, char *param);
static int do_Protect2(const char *filename, int linenum, char *param);
ConfigDirective module_config[] = {
{ "AuthName", { { CD_STRING, CF_DIRREQ, &AuthName } } },
{ "Protect", { { CD_FUNC, 0, do_Protect1 },
{ CD_FUNC, 0, do_Protect2 } } },
{ NULL }
};
static char *protect_param1 = NULL;
/*************************************************************************/
static int do_Protect1(const char *filename, int linenum, char *param)
{
if (filename) {
free(protect_param1); /*in case a previous line had only 1 parameter*/
protect_param1 = strdup(param);
if (!protect_param1) {
config_error(filename, linenum, "Out of memory");
return 0;
}
}
return 1;
}
/*************************************************************************/
static int do_Protect2(const char *filename, int linenum, char *param)
{
DirInfo di;
char *s;
int bufsize = 0, i;
static DirInfo *new_protected = NULL;
static int new_protected_count = 0;
if (!filename) {
/* filename == NULL, do special handling */
switch (linenum) {
case CDFUNC_INIT: /* prepare for reading */
ARRAY_FOREACH (i, new_protected) {
free(new_protected[i].path);
free(new_protected[i].userpass);
}
free(new_protected);
new_protected = NULL;
new_protected_count = 0;
break;
case CDFUNC_SET: /* store new values in config variables */
if (new_protected_count >= 0) {
ARRAY_FOREACH (i, protected) {
free(protected[i].path);
free(protected[i].userpass);
}
free(protected);
protected = new_protected;
protected_count = new_protected_count;
new_protected = NULL;
new_protected_count = -1; /* flag to say "don't copy again" */
}
break;
case CDFUNC_DECONFIG: /* clear out config variables */
ARRAY_FOREACH (i, protected) {
free(protected[i].path);
free(protected[i].userpass);
}
free(protected);
protected = NULL;
protected_count = 0;
break;
} /* switch (linenum) */
return 1;
} /* if (!filename) */
/* filename != NULL, process directive */
/* Move path parameter to DirInfo and clear temporary path holder */
if (!protect_param1) {
module_log("config: BUG: missing first parameter for Protect!");
config_error(filename, linenum, "Internal error");
return 0;
}
di.path = protect_param1;
protect_param1 = NULL;
di.pathlen = strlen(di.path);
/* Check for colon in username/password string */
s = strchr(param, ':');
if (!s) {
config_error(filename, linenum,
"Second parameter to Protect must be in the form"
" `username:password'");
return 0;
}
/* base64-encode and store in di.userpass */
bufsize = encode_base64(param, strlen(param), NULL, 0);
if (bufsize <= 0) {
config_error(filename, linenum, "Internal error: base64 encoding"
" failed");
free(di.path);
return 0;
}
di.userpass = malloc(bufsize);
if (!di.userpass) {
config_error(filename, linenum, "Out of memory");
free(di.path);
return 0;
}
if (encode_base64(param, strlen(param), di.userpass, bufsize) != bufsize) {
config_error(filename, linenum, "Internal error: base64 encoding"
" failed");
free(di.userpass);
free(di.path);
return 0;
}
/* Store new record in array and return success */
ARRAY_EXTEND(new_protected);
new_protected[new_protected_count-1] = di;
return 1;
}
/*************************************************************************/
/*************************************************************************/
int init_module(void)
{
module_httpd = find_module("httpd/main");
if (!module_httpd) {
module_log("Main httpd module not loaded");
exit_module(0);
return 0;
}
use_module(module_httpd);
if (!add_callback(module_httpd, "auth", do_auth)) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
int i;
if (module_httpd) {
remove_callback(module_httpd, "auth", do_auth);
unuse_module(module_httpd);
module_httpd = NULL;
}
ARRAY_FOREACH (i, protected) {
free(protected[i].path);
free(protected[i].userpass);
}
free(protected);
protected = NULL;
protected_count = 0;
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

1648
modules/httpd/dbaccess.c Normal file

File diff suppressed because it is too large Load Diff

148
modules/httpd/debug.c Normal file
View File

@ -0,0 +1,148 @@
/* Debug page module for HTTP server.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "http.h"
/*************************************************************************/
static Module *module_httpd;
static char *DebugURL;
/*************************************************************************/
/*************************** Request callback ****************************/
/*************************************************************************/
static int do_request(Client *c, int *close_ptr)
{
/* Check whether this URL belongs to us. If not, pass control on to
* the next callback function.
*/
if (strcmp(c->url, DebugURL) != 0)
return 0;
/* Send initial "200 OK" line and Date: header. */
http_send_response(c, HTTP_S_OK);
/* If not a HEAD request, indicate that we will close the connection
* after this request (because we don't send a Content-Length:
* header). If the request is a HEAD request, the blank line after
* the headers will signal the end of the response, so we can leave
* the connection open for further requests (keepalive).
*/
if (c->method != METHOD_HEAD)
sockprintf(c->socket, "Connection: close\r\n");
/* Send Content-Type: header and end header portion of response */
sockprintf(c->socket, "Content-Type: text/plain\r\n\r\n");
/* Now send the body part of the response for non-HEAD requests.
*
* RFC2616 9.4: Server MUST NOT return a message-body in the response
* to a HEAD request.
*/
if (c->method != METHOD_HEAD) {
int i;
/* Write data to socket. */
sockprintf(c->socket, "address: %s\n", c->address);
sockprintf(c->socket, "request_len: %d\n", c->request_len);
sockprintf(c->socket, "version_major: %d\n", c->version_major);
sockprintf(c->socket, "version_minor: %d\n", c->version_minor);
sockprintf(c->socket, "method: %d\n", c->method);
sockprintf(c->socket, "url: %s\n", c->url);
sockprintf(c->socket, "data_len: %d\n", c->data_len);
sockprintf(c->socket, "headers_count: %d\n", c->headers_count);
ARRAY_FOREACH (i, c->headers)
sockprintf(c->socket, "headers[%d]: %s: %s\n", i, c->headers[i],
c->headers[i] + strlen(c->headers[i]) + 1);
sockprintf(c->socket, "variables_count: %d\n", c->variables_count);
ARRAY_FOREACH (i, c->variables)
sockprintf(c->socket, "variables[%d]: %s: %s\n", i,c->variables[i],
c->variables[i] + strlen(c->variables[i]) + 1);
/* We did not specify a Content-Length: header, so we must close
* the connection to signal end-of-data to the client. However,
* we MUST NOT call disconn() directly as that could lead to
* use of invalid pointers in the main HTTP server module.
* Instead, we set `close_ptr' nonzero, which tells the server to
* close the connection when control returns to it.
*/
*close_ptr = 1;
}
/* Note that we MUST NOT explicitly set `close_ptr' to zero for HEAD
* requests, or any other request for which we can keep the connection
* alive; the client may be an old (HTTP/1.0) client that can't handle
* keepalive, or it may have explicitly requested the connection be
* closed (e.g. with a "Connection: close" header).
*/
/* URL was handled by this module; terminate callback chain. */
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "DebugURL", { { CD_STRING, CF_DIRREQ, &DebugURL } } },
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
module_httpd = find_module("httpd/main");
if (!module_httpd) {
module_log("Main httpd module not loaded");
exit_module(0);
return 0;
}
use_module(module_httpd);
if (!add_callback(module_httpd, "request", do_request)) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_httpd) {
remove_callback(module_httpd, "request", do_request);
unuse_module(module_httpd);
module_httpd = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

142
modules/httpd/http.h Normal file
View File

@ -0,0 +1,142 @@
/* HTTP-related definitions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef HTTP_H
#define HTTP_H
#ifndef TIMEOUT_H
# include "timeout.h"
#endif
/*************************************************************************/
/* Client data structure. */
typedef struct {
Socket *socket;
Timeout *timeout;
char address[22]; /* aaa.bbb.ccc.ddd:ppppp\0 */
uint32 ip;
uint16 port;
int request_count; /* Number of requests so far on this connection */
int in_request; /* Nonzero if request currently being processed */
char *request_buf; /* Pointers below point into this buffer */
int32 request_len; /* Amount of data read so far */
int version_major;
int version_minor;
int method; /* METHOD_* (see below) */
char *url;
char *data; /* For POST */
int32 data_len;
char **headers; /* name + \0 + value */
int32 headers_count;
char **variables; /* name + \0 + value */
int32 variables_count;
} Client;
/*************************************************************************/
/* Maximum length of a single line in an HTTP request (bytes). */
#define HTTP_LINEMAX 4096
/*************************************************************************/
/* Return codes from authorization callbacks. */
#define HTTP_AUTH_UNDECIDED 0
#define HTTP_AUTH_ALLOW 1
#define HTTP_AUTH_DENY 2
/*************************************************************************/
/* HTTP methods. */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_POST 2
/*************************************************************************/
/* HTTP reply codes. Prefix letters are:
* I - 1xx Informational
* S - 2xx Successful
* R - 3xx Redirection
* E - 4xx Client Error
* F - 5xx Server Error (think "Failure")
*/
#define HTTP_I_CONTINUE 100
#define HTTP_I_SWITCHING_PROTOCOLS 101
#define HTTP_S_OK 200
#define HTTP_S_CREATED 201
#define HTTP_S_ACCEPTED 202
#define HTTP_S_NON_AUTHORITATIVE 203
#define HTTP_S_NO_CONTENT 204
#define HTTP_S_RESET_CONTENT 205
#define HTTP_S_PARTIAL_CONTENT 206
#define HTTP_R_MULTIPLE_CHOICES 300
#define HTTP_R_MOVED_PERMANENTLY 301
#define HTTP_R_FOUND 302
#define HTTP_R_SEE_OTHER 303
#define HTTP_R_NOT_MODIFIED 304
#define HTTP_R_USE_PROXY 305
#define HTTP_R_TEMPORARY_REDIRECT 307
#define HTTP_E_BAD_REQUEST 400
#define HTTP_E_UNAUTHORIZED 401
#define HTTP_E_PAYMENT_REQUIRED 402
#define HTTP_E_FORBIDDEN 403
#define HTTP_E_NOT_FOUND 404
#define HTTP_E_METHOD_NOT_ALLOWED 405
#define HTTP_E_NOT_ACCEPTABLE 406
#define HTTP_E_PROXY_AUTH_REQUIRED 407
#define HTTP_E_REQUEST_TIMEOUT 408
#define HTTP_E_CONFLICT 409
#define HTTP_E_GONE 410
#define HTTP_E_LENGTH_REQUIRED 411
#define HTTP_E_PRECONDITION_FAILED 412
#define HTTP_E_REQUEST_ENTITY_TOO_LARGE 413
#define HTTP_E_REQUEST_URI_TOO_LONG 414
#define HTTP_E_UNSUPPORTED_MEDIA_TYPE 415
#define HTTP_E_RANGE_NOT_SATISFIABLE 416
#define HTTP_E_EXPECTATION_FAILED 417
#define HTTP_F_INTERNAL_SERVER_ERROR 500
#define HTTP_F_NOT_IMPLEMENTED 501
#define HTTP_F_BAD_GATEWAY 502
#define HTTP_F_SERVICE_UNAVAILABLE 503
#define HTTP_F_GATEWAY_TIMEOUT 504
#define HTTP_F_HTTP_VER_NOT_SUPPORTED 505
/*************************************************************************/
/*************************************************************************/
/* Utility routines (provided by core module, in util.c): */
extern char *http_get_header(Client *c, const char *header);
extern char *http_get_variable(Client *c, const char *variable);
extern char *http_quote_html(const char *str, char *outbuf, int32 outsize);
extern char *http_quote_url(const char *str, char *outbuf, int32 outsize);
extern char *http_unquote_url(char *buf);
extern void http_send_response(Client *c, int code);
extern void http_error(Client *c, int code, const char *format, ...);
/*************************************************************************/
#endif /* HTTP_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

952
modules/httpd/main.c Normal file
View File

@ -0,0 +1,952 @@
/* Main HTTP server module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "timeout.h"
#include "http.h"
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*************************************************************************/
static int cb_auth = -1;
static int cb_request = -1;
static int32 ListenBacklog;
static int32 RequestBufferSize;
static int32 MaxConnections;
static int32 MaxRequests;
static time_t IdleTimeout;
static int LogConnections;
/* List of ports to listen to (set by ListenTo configuration directive) */
static struct listento_ {
char ip[16]; /* aaa.bbb.ccc.ddd\0 */
uint16 port;
} *ListenTo;
static int ListenTo_count;
#define MAX_LISTENTO 32767
/* Array of listen sockets (corresponding to ListenTo[] entries) */
static Socket **listen_sockets;
/* Array of clients */
static Client *clients;
int clients_count;
/*************************************************************************/
static void do_accept(Socket *listener, void *param);
static void do_disconnect(Socket *socket, void *param_unused);
static void do_readline(Socket *s, void *param_unused);
static void do_readdata(Socket *s, void *param);
static void do_timeout(Timeout *t);
static Client *find_client(Socket *s);
static void set_timeout(Client *c);
static void clear_timeout(Client *c);
static void parse_header(Client *c, char *linestart);
static void handle_request(Client *c);
/*************************************************************************/
/*************************** HTTP server core ****************************/
/*************************************************************************/
/* Accept a connection and perform IP address checks on it; if it doesn't
* pass, disconnect it.
*/
static void do_accept(Socket *listener, void *param)
{
Socket *new = param;
struct sockaddr_in sin;
int sin_len = sizeof(sin);
if (sock_remote(new, (struct sockaddr *)&sin, &sin_len) < 0) {
module_log_perror("sock_remote() failed");
} else if (sin_len > sizeof(sin)) {
module_log("sock_remote() returned oversize address (%d)", sin_len);
} else if (sin.sin_family != AF_INET) {
module_log("sock_remote() returned bad address family (%d)",
sin.sin_family);
} else {
int i = clients_count;
ARRAY_EXTEND(clients);
snprintf(clients[i].address, sizeof(clients[i].address), "%s:%u",
unpack_ip((uint8 *)&sin.sin_addr), ntohs(sin.sin_port));
clients[i].socket = new;
clients[i].ip = sin.sin_addr.s_addr;
clients[i].port = sin.sin_port;
clients[i].timeout = NULL;
clients[i].request_count = 0;
clients[i].in_request = 0;
clients[i].request_buf = smalloc(RequestBufferSize);
clients[i].request_len = 0;
clients[i].version_major = 0;
clients[i].version_minor = 0;
clients[i].method = -1;
clients[i].url = NULL;
clients[i].data = NULL;
clients[i].data_len = 0;
clients[i].headers = NULL;
clients[i].headers_count = 0;
clients[i].variables = NULL;
clients[i].variables_count = 0;
if (clients_count >= MaxConnections) {
module_log("Dropping connection (exceeded MaxConnections: %d)"
" from %s", MaxConnections, clients[i].address);
http_error(&clients[i], HTTP_F_SERVICE_UNAVAILABLE, NULL);
/* http_error() closes socket for us, so don't try to
* disconnect it below */
} else {
set_timeout(&clients[i]);
sock_setcb(new, SCB_READLINE, do_readline);
sock_setcb(new, SCB_DISCONNECT, do_disconnect);
sock_set_blocking(new, 1);
if (LogConnections)
module_log("Accepted connection from %s",
clients[i].address);
}
return;
}
disconn(new);
}
/*************************************************************************/
static void do_disconnect(Socket *socket, void *param_unused)
{
Client *c = find_client(socket);
int index = c - clients;
if (!c) {
module_log("BUG: unexpected disconnect callback for socket %p",socket);
return;
}
clear_timeout(c);
free(c->headers);
free(c->variables);
free(c->request_buf);
ARRAY_REMOVE(clients, index);
}
/*************************************************************************/
/* Read a line from a client socket. If the end of the request data is
* reached, the request is passed to handle_request(); if the request data
* length exceeds the buffer size, an error is sent to the client and the
* connection is closed.
*/
static void do_readline(Socket *socket, void *param_unused)
{
Client *c = find_client(socket);
char line[HTTP_LINEMAX], *linestart, *s;
int32 i;
if (!c) {
module_log("BUG: unexpected readline callback for socket %p", socket);
disconn(socket);
return;
}
if (!sgets(line, sizeof(line), socket) || *line == 0) {
module_log("BUG: sgets() failed in readline callback for socket %p",
socket);
return;
}
i = strlen(line);
if (line[i-1] != '\n') {
module_log("%s: Request/header line too long, closing connection",
c->address);
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
return;
}
line[--i] = 0;
if (i > 0 && line[i-1] == '\r')
line[--i] = 0;
i++; /* include trailing \0 */
if (c->request_len + i > RequestBufferSize) {
module_log("%s: Request too large, closing connection", c->address);
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
return;
}
linestart = c->request_buf + c->request_len;
memcpy(linestart, line, i);
c->request_len += i;
if (!c->url) {
char *method, *url, *version;
if (!*linestart) {
/* RFC2616 4.2: servers SHOULD ignore initial empty lines (OK) */
c->request_len = 0;
set_timeout(c);
return;
}
method = strtok(linestart, " ");
url = strtok(NULL, " ");
version = strtok(NULL, " ");
if (!method || !url || !version) {
/* Note that we don't support REALLY old clients (HTTP 0.9)
* which don't send version strings */
/* RFC2616 10.4: SHOULD ensure client has received error before
* closing connection (NG) -- in this case the client is so
* broken that it doesn't deserve to be worried about */
module_log("%s: Invalid HTTP request", c->address);
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
}
if (strcmp(method, "GET") == 0) {
c->method = METHOD_GET;
} else if (strcmp(method, "HEAD") == 0) {
c->method = METHOD_HEAD;
} else if (strcmp(method, "POST") == 0) {
c->method = METHOD_POST;
} else {
module_log("%s: Unimplemented/unsupported method `%s' requested",
c->address, method);
http_error(c, HTTP_F_NOT_IMPLEMENTED, NULL);
return;
}
if (strncmp(version, "HTTP/", 5) != 0 || !(s = strchr(version+5,'.'))){
module_log("%s: Bad HTTP version string: %s", c->address, version);
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
}
*s++ = 0;
c->version_major = (int)atolsafe(version+5, 0, INT_MAX);
c->version_minor = (int)atolsafe(s, 0, INT_MAX);
if (c->version_major < 0 || c->version_minor < 0) {
module_log("%s: Bad HTTP version string: %s.%s",
c->address, version, s);
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
}
if (c->version_major != 1) {
module_log("%s: Unsupported HTTP version: %d.%d",
c->address, c->version_major, c->version_minor);
http_error(c, HTTP_F_HTTP_VER_NOT_SUPPORTED, NULL);
return;
}
if (strnicmp(url, "http://", 7) == 0) {
/* RFC2616 5.1.2: MUST accept absolute URIs (OK, but we ignore
* the hostname and just pretend it's us) */
s = strchr(url+7, '/');
if (s) {
strmove(url, s);
} else {
url[0] = '/';
url[1] = 0;
}
}
c->url = url;
set_timeout(c);
return;
} /* if (!url) */
/* We already have the URL, so this must be a header or blank line. */
if (*linestart) {
/* Header line: process it */
parse_header(c, linestart);
set_timeout(c);
return;
}
/* End of headers. For GET/HEAD, handle any query string present in
* the URL and process the request immediately. For POST, handle
* Expect: 100-continue, then deal with the body. */
if (c->method == METHOD_GET || c->method == METHOD_HEAD) {
char *s = strchr(c->url, '?');
if (s) {
*s++ = 0;
c->data = s;
c->data_len = strlen(s);
}
handle_request(c);
} else if (c->method == METHOD_POST) {
long length;
s = http_get_header(c, "Content-Length");
if (!s) {
module_log("%s: Missing Content-Length header for POST",
c->address);
http_error(c, HTTP_E_LENGTH_REQUIRED, NULL);
return;
}
errno = 0;
length = atolsafe(s, 0, LONG_MAX);
if (length < 0) {
module_log("%s: Invalid Content-Length header: %s", c->address, s);
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
} else if (c->request_len+length>RequestBufferSize) {
module_log("%s: Request too large, closing connection",c->address);
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
return;
}
c->data = c->request_buf + c->request_len;
c->data_len = (int32)length;
if (length > 0) {
s = http_get_header(c, "Expect");
for (s = strtok(s, ", \t"); s; s = strtok(NULL, ", \t")) {
if (strcmp(s, "100-continue") == 0) {
sockprintf(socket, "HTTP/1.1 100 Continue\r\n\r\n");
break;
}
}
/* Set up to read POST data */
sock_setcb(socket, SCB_READ, do_readdata);
sock_setcb(socket, SCB_READLINE, NULL);
set_timeout(c);
} else {
/* length == 0: do it just like GET */
handle_request(c);
}
} else {
module_log("BUG: do_readline(): unsupported method %d", c->method);
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
}
}
/*************************************************************************/
/* Read data from a client socket. When the end of the data is reached,
* reached, the request is passed to handle_request(). The request is
* assumed to have already been checked for exceeding the buffer size.
*/
static void do_readdata(Socket *socket, void *param)
{
Client *c = find_client(socket);
int32 available = (int32)(long)param, needed, nread;
if (!c) {
module_log("BUG: unexpected readdata callback for socket %p", socket);
disconn(socket);
return;
}
needed = c->data_len - (c->request_len - (c->data - c->request_buf));
if (available > needed)
available = needed;
if (c->request_len + available > RequestBufferSize) {
module_log("BUG: do_readdata(%s[%s]): data size exceeded buffer limit",
c->address, c->url);
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
return;
}
nread = sread(socket, c->request_buf + c->request_len, available);
if (nread != available) {
module_log("BUG: do_readdata(%s[%s]): nread (%d) != available (%d)",
c->address, c->url, nread, available);
}
c->request_len += nread;
needed -= nread;
if (needed <= 0) {
/* Prepare for next request, if any */
sock_setcb(socket, SCB_READ, NULL);
sock_setcb(socket, SCB_READLINE, do_readline);
/* Process this request */
handle_request(c);
}
}
/*************************************************************************/
/* Handle an idle timeout on a client. */
static void do_timeout(Timeout *t)
{
Client *c = find_client(t->data);
if (!c) {
module_log("BUG: do_timeout(): client not found for timeout %p!", t);
return;
}
c->timeout = NULL;
disconn(c->socket);
}
/*************************************************************************/
/*************************************************************************/
/* Return the client structure corresponding to the given socket, or NULL
* if not found.
*/
static Client *find_client(Socket *s)
{
int i;
ARRAY_FOREACH (i, clients) {
if (clients[i].socket == s)
return &clients[i];
}
return NULL;
}
/*************************************************************************/
/* Start an idle timer running on the given client. If a timer was already
* running, clear it and start a new one.
*/
static void set_timeout(Client *c)
{
if (!c->socket) {
module_log("BUG: attempt to set timeout for client %d with no"
" socket!", (int)(c-clients));
return;
}
if (IdleTimeout) {
clear_timeout(c);
c->timeout = add_timeout(IdleTimeout, do_timeout, 0);
c->timeout->data = c->socket;
}
}
/*************************************************************************/
/* Cancel the idle timer for the given client. */
static void clear_timeout(Client *c)
{
if (c->timeout) {
del_timeout(c->timeout);
c->timeout = NULL;
}
}
/*************************************************************************/
/* Do appropriate things with the given header line. */
static void parse_header(Client *c, char *linestart)
{
char *s;
/* Check for whitespace-started line and no headers yet */
if ((*linestart == ' ' || *linestart == '\t') && !c->headers_count) {
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
}
/* Remove all trailing whitespace */
s = linestart + strlen(linestart) - 1;
while (s > linestart && (*s == ' ' || *s == '\t')) {
*s-- = 0;
c->request_len--;
}
if (*linestart == ' ' || *linestart == '\t') {
/* If it starts with whitespace, just tack it onto the end of the
* previous line (convert all leading whitespace to a single space) */
linestart[-1] = ' ';
s = linestart;
while (*s == ' ' || *s == '\t')
s++;
strmove(linestart, s);
c->request_len -= s-linestart;
} else {
/* New header: split into name and value, and create a new
* headers[] entry */
int i = c->headers_count;
ARRAY_EXTEND(c->headers);
c->headers[i] = linestart;
s = strchr(linestart, ':');
if (!s) {
http_error(c, HTTP_E_BAD_REQUEST, NULL);
return;
}
*s++ = 0;
linestart = s;
while (*s == ' ' || *s == '\t')
s++;
strmove(linestart, s);
c->request_len -= s - linestart;
}
}
/*************************************************************************/
/* Parse the data given by `buf' (null-terminated) into variables and
* create an array of them in c->variables. Assume standard
* x-www-form-urlencoding encoding (for multipart data, use
* parse_data_multipart() below).
*/
static void parse_data(Client *c, char *buf)
{
char *start;
int found_equals = 0;
char hexbuf[3];
hexbuf[2] = 0;
free(c->variables);
c->variables = NULL;
c->variables_count = 0;
start = buf;
ARRAY_EXTEND(c->variables);
c->variables[0] = start;
while (*buf) {
switch (*buf) {
case '=':
if (!found_equals) {
*buf = 0;
http_unquote_url(start);
found_equals = 1;
start = buf+1;
}
break;
case '&':
*buf = 0;
http_unquote_url(start);
found_equals = 0;
start = buf+1;
ARRAY_EXTEND(c->variables);
c->variables[c->variables_count-1] = start;
break;
}
buf++;
}
}
/*************************************************************************/
/* Parse the data given by `buf' (null-terminated) into variables and
* create an array of them in c->variables, using the boundary string
* given by `boundary'.
*/
static void parse_data_multipart(Client *c, char *buf, const char *boundary)
{
char *dest = buf;
int boundarylen = strlen(boundary);
char *varname = NULL;
free(c->variables);
c->variables = NULL;
c->variables_count = 0;
buf = strstr(buf, boundary);
if (!buf)
return; /* boundary string not found */
while (*buf && (buf[boundarylen+2] != '-' || buf[boundarylen+3] != '-')) {
char *s;
/* Read in header for this part */
s = buf + strcspn(buf, "\r\n");
if (!*s)
return;
buf = s + strspn(s, "\r") + 1;
while (*buf != '\r' && *buf != '\n') {
s = buf + strcspn(buf, "\r\n");
if (!*s)
return;
if (*s == '\r')
*s++ = 0;
*s++ = 0;
if (strnicmp(buf,"Content-Disposition:",20) == 0) {
buf += 20;
while (*buf && isspace(*buf))
buf++;
if (*buf && strnicmp(buf,"form-data;",10) == 0) {
buf += 10;
while (*buf && isspace(*buf))
buf++;
if (*buf && strnicmp(buf,"name=",5) == 0) {
buf += 5;
if (*buf == '"') {
char *t = strchr(++buf, '"');
if (t)
*t = 0;
} else {
char *t = strchr(buf, ';');
if (t)
*t = 0;
}
varname = dest;
strmove(dest, buf);
dest += strlen(buf)+1;
}
}
}
buf = s;
} /* while (*buf != '\r' && *buf != '\n') */
if (*buf == '\r')
buf++;
buf++;
/* Read in data (variable contents) */
if (varname) {
ARRAY_EXTEND(c->variables);
c->variables[c->variables_count-1] = varname;
varname = NULL;
}
s = buf + strcspn(buf, "\r\n");
if (s > buf) {
memmove(dest, buf, s-buf);
dest += s-buf;
}
if (*s == '\r')
s++;
buf = s;
/* *buf is always pointing to a \n or \0 at the top of this loop */
while (*buf && (buf[1] != '-' || buf[2] != '-'
|| strncmp(buf+3, boundary, boundarylen) != 0)) {
s = buf+1 + strcspn(buf+1, "\r\n");
if (!s)
s = buf + strlen(buf);
memmove(dest, buf, s-buf);
dest += s-buf;
if (*s == '\r')
s++;
buf = s;
}
/* Null-terminate variable contents */
*dest++ = 0;
/* Skip over newline */
if (*buf)
buf++;
} /* while not final boundary line */
}
/*************************************************************************/
static void handle_request(Client *c)
{
int res;
int close = 0;
/* Parse GET query string or POST data into variables */
if (c->data && c->data_len) {
char *s;
if (c->method == METHOD_POST) {
/* There were at least two newlines before the beginning of the
* data, so it's safe to move it back a byte (to add a trailing
* null) */
memmove(c->data-1, c->data, c->data_len);
c->data--;
c->data[c->data_len] = 0;
}
/* Check the content type, and extract the boundary string if it's
* multipart data */
s = http_get_header(c, "Content-Type");
if (s && strnicmp(s, "multipart/form-data;", 20) == 0) {
s += 20;
while (isspace(*s))
s++;
if (strnicmp(s, "boundary=", 9) == 0) {
s += 9;
if (*s == '"') {
char *t = strchr(++s, '"');
if (t)
*t = 0;
}
} else {
s = NULL;
}
} else {
s = NULL;
}
/* Parse data into variables */
if (s)
parse_data_multipart(c, c->data, s);
else
parse_data(c, c->data);
}
c->request_count++;
c->in_request = 1;
if (c->version_major == 1 && c->version_minor == 0) {
close = 1;
} else {
const char *s = http_get_header(c, "Connection");
if (s && strstr(s, "close")) {
/* This might accidentally trigger on something like "abcloseyz",
* but that's okay; all it means is the client has to reconnect */
close = 1;
}
}
res = call_callback_2(cb_auth, c, &close);
if (res < 0) {
module_log("handle_request(): call_callback(cb_request) failed");
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
close = 1;
} else if (res != HTTP_AUTH_DENY) {
res = call_callback_2(cb_request, c, &close);
if (res < 0) {
module_log("handle_request(): call_callback(cb_request) failed");
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
close = 1;
} else if (res == 0) {
http_error(c, HTTP_E_NOT_FOUND, NULL);
}
}
if (close || (MaxRequests && c->request_count >= MaxRequests)
|| c->in_request < 0 /* flag from http_error */
) {
disconn(c->socket);
} else {
free(c->headers);
free(c->variables);
c->in_request = 0;
c->request_len = 0;
c->version_major = 0;
c->version_minor = 0;
c->method = -1;
c->url = NULL;
c->data = NULL;
c->data_len = 0;
c->headers = NULL;
c->headers_count = 0;
c->variables = NULL;
c->variables_count = 0;
set_timeout(c);
}
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_ListenTo(const char *filename, int linenum, char *param);
ConfigDirective module_config[] = {
{ "IdleTimeout", { { CD_TIME, 0, &IdleTimeout } } },
{ "ListenBacklog", { { CD_POSINT, CF_DIRREQ, &ListenBacklog } } },
{ "ListenTo", { { CD_FUNC, CF_DIRREQ, do_ListenTo } } },
{ "LogConnections", { { CD_SET, 0, &LogConnections } } },
{ "MaxConnections", { { CD_POSINT, 0, &MaxConnections } } },
{ "MaxRequests", { { CD_POSINT, 0, &MaxRequests } } },
{ "RequestBufferSize",{ { CD_POSINT, 0, &RequestBufferSize } } },
{ NULL }
};
/*************************************************************************/
static int do_ListenTo(const char *filename, int linenum, char *param)
{
char *s;
int port;
uint8 *ip;
char *ipstr;
char ipbuf[15+1]; /* aaa.bbb.ccc.ddd\0 */
int recursing = 0, i;
static struct listento_ *new_ListenTo;
static int new_ListenTo_count;
if (!filename) {
/* filename == NULL, perform special operations */
switch (linenum) {
case CDFUNC_INIT: /* prepare to read new data */
free(new_ListenTo);
new_ListenTo = NULL;
new_ListenTo_count = 0;
break;
case CDFUNC_SET: /* store new data in config variable */
free(ListenTo);
ListenTo = new_ListenTo;
ListenTo_count = new_ListenTo_count;
new_ListenTo = NULL;
new_ListenTo_count = 0;
break;
case CDFUNC_DECONFIG: /* clear any stored data */
free(ListenTo);
ListenTo = NULL;
ListenTo_count = 0;
break;
} /* switch (linenum) */
return 1;
} /* if (!filename) */
/* filename != NULL, process directive */
if (linenum < 0) {
recursing = 1;
linenum = -linenum;
}
if (ListenTo_count >= MAX_LISTENTO) {
config_error(filename, linenum,
"Too many ListenTo addresses (maximum %d)",
MAX_LISTENTO);
return 0;
}
s = strchr(param, ':');
if (!s) {
config_error(filename, linenum,
"ListenTo address requires both address and port");
return 0;
}
*s++ = 0;
port = atolsafe(s, 1, 65535);
if (port < 1) {
config_error(filename, linenum, "Invalid port number `%s'", s);
return 0;
}
if (strcmp(param, "*") == 0) {
/* "*" -> all addresses (NULL string) */
ipstr = NULL;
} else if ((ip = pack_ip(param)) != NULL) {
/* IP address -> normalize (no leading zeros, etc.) */
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u",
ip[0], ip[1], ip[2], ip[3]);
if (strlen(ipbuf) > 15) {
config_error(filename, linenum, "BUG: strlen(ipbuf) > 15 [%s]",
ipbuf);
return 0;
}
ipstr = ipbuf;
} else {
/* hostname -> check for double recursion, then look up and
* recursively add addresses */
#ifdef HAVE_GETHOSTBYNAME
struct hostent *hp;
#endif
if (recursing) {
config_error(filename, linenum, "BUG: double recursion (param=%s)",
param);
return 0;
}
#ifdef HAVE_GETHOSTBYNAME
if ((hp = gethostbyname(param)) != NULL) {
if (hp->h_addrtype == AF_INET) {
for (i = 0; hp->h_addr_list[i]; i++) {
ip = (uint8 *)hp->h_addr_list[i];
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u",
ip[0], ip[1], ip[2], ip[3]);
if (strlen(ipbuf) > 15) {
config_error(filename, linenum,
"BUG: strlen(ipbuf) > 15 [%s]", ipbuf);
return 0;
}
if (!do_ListenTo(filename, -linenum, ipbuf))
return 0;
}
return 1; /* Success */
} else {
config_error(filename, linenum, "%s: no IPv4 addresses found",
param);
}
} else {
config_error(filename, linenum, "%s: %s", param,
hstrerror(h_errno));
}
#else
config_error(filename, linenum,
"gethostbyname() not available, hostnames may not be"
" used");
#endif
return 0;
}
i = new_ListenTo_count;
ARRAY_EXTEND(new_ListenTo);
if (ipstr)
strcpy(new_ListenTo[i].ip, ipstr);/*safe: strlen(ip)<16 checked above*/
else
memset(new_ListenTo[i].ip, 0, sizeof(new_ListenTo[i].ip));
new_ListenTo[i].port = port;
return 1;
}
/*************************************************************************/
int init_module()
{
int i, opencount;
cb_auth = register_callback("auth");
cb_request = register_callback("request");
if (cb_auth < 0 || cb_request < 0) {
module_log("Unable to register callbacks");
exit_module(0);
return 0;
}
listen_sockets = smalloc(sizeof(*listen_sockets) * ListenTo_count);
opencount = 0;
ARRAY_FOREACH (i, ListenTo) {
listen_sockets[i] = sock_new();
if (listen_sockets[i]) {
if (open_listener(listen_sockets[i],
*ListenTo[i].ip ? ListenTo[i].ip : NULL,
ListenTo[i].port, ListenBacklog) == 0) {
sock_setcb(listen_sockets[i], SCB_ACCEPT, do_accept);
module_log("Listening on %s:%u",
ListenTo[i].ip, ListenTo[i].port);
opencount++;
} else {
module_log_perror("Failed to open listen socket for %s:%u",
ListenTo[i].ip, ListenTo[i].port);
}
} else {
module_log("Failed to create listen socket for %s:%u",
*ListenTo[i].ip ? ListenTo[i].ip : "*",
ListenTo[i].port);
}
}
if (!opencount) {
module_log("No ports could be opened, aborting");
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
int i;
ARRAY_FOREACH (i, ListenTo) {
if (listen_sockets[i]) {
close_listener(listen_sockets[i]);
sock_free(listen_sockets[i]);
}
}
free(ListenTo);
ListenTo = NULL;
ListenTo_count = 0;
free(listen_sockets);
listen_sockets = NULL;
unregister_callback(cb_request);
unregister_callback(cb_auth);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

252
modules/httpd/redirect.c Normal file
View File

@ -0,0 +1,252 @@
/* Nick/channel URL redirect module for HTTP server.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "http.h"
/*************************************************************************/
static Module *module_httpd;
static Module *module_nickserv;
static Module *module_chanserv;
static char *NicknamePrefix;
static char *ChannelPrefix;
/* Imported from NickServ: */
typeof(get_nickinfo) *p_get_nickinfo;
typeof(put_nickinfo) *p_put_nickinfo;
typeof(_get_ngi) *p__get_ngi;
typeof(put_nickgroupinfo) *p_put_nickgroupinfo;
#define get_nickinfo (*p_get_nickinfo)
#define put_nickinfo (*p_put_nickinfo)
#define _get_ngi (*p__get_ngi)
#define put_nickgroupinfo (*p_put_nickgroupinfo)
/* Imported from ChanServ: */
typeof(get_channelinfo) *p_get_channelinfo;
typeof(put_channelinfo) *p_put_channelinfo;
#define get_channelinfo (*p_get_channelinfo)
#define put_channelinfo (*p_put_channelinfo)
/*************************************************************************/
/*************************** Request callback ****************************/
/*************************************************************************/
static int do_request(Client *c, int *close_ptr)
{
if (NicknamePrefix && module_nickserv
&& strncmp(c->url,NicknamePrefix,strlen(NicknamePrefix)) == 0
) {
char *nick;
char newnick[NICKMAX*5]; /* for errors; *5 because of & -> &amp; */
NickInfo *ni = NULL;
NickGroupInfo *ngi = NULL;
nick = c->url + strlen(NicknamePrefix);
ni = get_nickinfo(nick);
ngi = (ni && ni->nickgroup) ? get_ngi(ni) : NULL;
http_quote_html(nick, newnick, sizeof(newnick));
if (ngi && ngi->url) {
/* URL registered, so send a redirect there */
http_send_response(c, HTTP_R_FOUND);
sockprintf(c->socket, "Location: %s\r\n", ngi->url);
sockprintf(c->socket, "Content-Length: 0\r\n\r\n");
} else if (ngi) {
/* Nick not registered (or forbidden) */
http_error(c, HTTP_E_NOT_FOUND,
"<h1 align=center>URL Not Set</h1>"
"The nickname <b>%s</b> does not have a URL set.",
newnick);
} else if (*nick) {
/* Nick not registered (or forbidden) */
http_error(c, HTTP_E_NOT_FOUND,
"<h1 align=center>Nickname Not Registered</h1>"
"The nickname <b>%s</b> is not registered.", newnick);
} else {
/* No nick given; just give a standard 404 */
http_error(c, HTTP_E_NOT_FOUND, NULL);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
return 1;
} else if (ChannelPrefix && module_chanserv
&& strncmp(c->url,ChannelPrefix,strlen(ChannelPrefix)) == 0) {
char *chan;
char newchan[CHANMAX*5];
ChannelInfo *ci;
chan = c->url + strlen(ChannelPrefix);
snprintf(newchan, sizeof(newchan), "#%s", chan);
ci = get_channelinfo(newchan);
/* Leave initial "#" in place */
http_quote_html(chan, newchan+1, sizeof(newchan)-1);
if (ci && ci->url) {
http_send_response(c, HTTP_R_FOUND);
sockprintf(c->socket, "Location: %s\r\n", ci->url);
sockprintf(c->socket, "Content-Length: 0\r\n\r\n");
} else if (ci) {
http_error(c, HTTP_E_NOT_FOUND,
"<h1 align=center>URL Not Set</h1>"
"The channel <b>%s</b> does not have a URL set.",
newchan);
} else if (*chan) {
http_error(c, HTTP_E_NOT_FOUND,
"<h1 align=center>Channel Not Registered</h1>"
"The channel <b>%s</b> is not registered.", newchan);
} else {
http_error(c, HTTP_E_NOT_FOUND, NULL);
}
put_channelinfo(ci);
return 1;
}
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NicknamePrefix", { { CD_STRING, 0, &NicknamePrefix } } },
{ "ChannelPrefix", { { CD_STRING, 0, &ChannelPrefix } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "nickserv/main") == 0) {
p_get_nickinfo = get_module_symbol(mod, "get_nickinfo");
p_put_nickinfo = get_module_symbol(mod, "put_nickinfo");
p__get_ngi = get_module_symbol(mod, "_get_ngi");
p_put_nickgroupinfo = get_module_symbol(mod, "put_nickgroupinfo");
if (p_get_nickinfo && p_put_nickinfo
&& p__get_ngi && p_put_nickgroupinfo
) {
module_nickserv = mod;
} else {
module_log("Required symbols not found, nickname redirects will"
" not be available");
p_get_nickinfo = NULL;
p_put_nickinfo = NULL;
p__get_ngi = NULL;
p_put_nickgroupinfo = NULL;
module_nickserv = NULL;
}
} else if (strcmp(modname, "chanserv/main") == 0) {
p_get_channelinfo = get_module_symbol(mod, "get_channelinfo");
p_put_channelinfo = get_module_symbol(mod, "put_channelinfo");
if (p_get_channelinfo && p_put_channelinfo) {
module_chanserv = mod;
} else {
module_log("Required symbols not found, channel redirects will"
" not be available");
p_get_channelinfo = NULL;
p_put_channelinfo = NULL;
}
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_nickserv) {
p_get_nickinfo = NULL;
p_put_nickinfo = NULL;
p__get_ngi = NULL;
p_put_nickgroupinfo = NULL;
p_get_nickinfo = NULL;
p_put_nickinfo = NULL;
p__get_ngi = NULL;
p_put_nickgroupinfo = NULL;
module_nickserv = NULL;
module_nickserv = NULL;
} else if (mod == module_chanserv) {
p_get_channelinfo = NULL;
p_put_channelinfo = NULL;
module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
Module *tmpmod;
module_httpd = find_module("httpd/main");
if (!module_httpd) {
module_log("Main httpd module not loaded");
exit_module(0);
return 0;
}
use_module(module_httpd);
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(module_httpd, "request", do_request)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
tmpmod = find_module("nickserv/main");
if (tmpmod)
do_load_module(tmpmod, "nickserv/main");
tmpmod = find_module("chanserv/main");
if (tmpmod)
do_load_module(tmpmod, "chanserv/main");
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
if (module_httpd) {
remove_callback(module_httpd, "request", do_request);
unuse_module(module_httpd);
module_httpd = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

116
modules/httpd/top-page.c Normal file
View File

@ -0,0 +1,116 @@
/* Top-page (http://services.example.net/) handler.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "http.h"
/*************************************************************************/
static Module *module_httpd;
static const char *Filename = NULL;
static const char *ContentType = "text/html";
static const char *Redirect = NULL;
/*************************************************************************/
/*************************** Request callback ****************************/
/*************************************************************************/
static int do_request(Client *c, int *close_ptr)
{
if (*c->url && strcmp(c->url, "/") != 0) {
return 0;
}
if (Redirect) {
http_send_response(c, HTTP_R_FOUND);
sockprintf(c->socket, "Location: %s\r\n", Redirect);
sockprintf(c->socket, "Content-Length: 0\r\n\r\n");
} else if (Filename) {
FILE *f = fopen(Filename, "rb");
if (f) {
char buf[4096];
int i;
*close_ptr = 1;
http_send_response(c, HTTP_S_OK);
sockprintf(c->socket,
"Content-Type: %s\r\nConnection: close\r\n\r\n",
ContentType);
while ((i = fread(buf, 1, sizeof(buf), f)) > 0)
swrite(c->socket, buf, i);
fclose(f);
} else if (errno == EACCES) {
http_error(c, HTTP_E_FORBIDDEN, NULL);
} else {
http_error(c, HTTP_E_NOT_FOUND, NULL);
}
} else {
http_error(c, HTTP_E_NOT_FOUND, NULL);
}
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "Filename", { { CD_STRING, 0, &Filename },
{ CD_STRING, CF_OPTIONAL, &ContentType } } },
{ "Redirect", { { CD_STRING, 0, &Redirect } } },
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
module_httpd = find_module("httpd/main");
if (!module_httpd) {
module_log("Main httpd module not loaded");
exit_module(0);
return 0;
}
use_module(module_httpd);
if (!add_callback(module_httpd, "request", do_request)) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_httpd) {
remove_callback(module_httpd, "request", do_request);
unuse_module(module_httpd);
module_httpd = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

424
modules/httpd/util.c Normal file
View File

@ -0,0 +1,424 @@
/* HTTP server common utility routines.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "http.h"
/*************************************************************************/
/*************************************************************************/
/* List of response texts for each response code, and inline functions to
* retrieve them.
*/
static struct {
int code;
const char *text;
const char *desc;
} http_response_text[] = {
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 307, "Temporary Redirect" },
{ 400, "Bad Request",
"Your browser sent a request this server could not understand." },
{ 401, "Unauthorized",
"You are not authorized to access this resource." },
{ 402, "Payment Required" },
{ 403, "Forbidden",
"You are not permitted to access this resource." },
{ 404, "Not Found",
"The requested resource could not be found." },
{ 405, "Method Not Allowed",
"Your browser sent an invalid method for this resource." },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required",
"Your browser sent an invalid request to the server." },
{ 412, "Precondition Failed" },
{ 413, "Request Entity Too Large" },
{ 414, "Request-URI Too Large" },
{ 415, "Unsupported Media Type" },
{ 416, "Requested Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 500, "Internal Server Error",
"An internal server error has occurred." },
{ 501, "Not Implemented",
"The requested method is not implemented by this server." },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable"
"Your request cannot currently be processed. Please try again"
" later." },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ -1 }
};
static inline const char *http_lookup_response(int code)
{
int i;
for (i = 0; http_response_text[i].code > 0; i++) {
if (http_response_text[i].code == code)
return http_response_text[i].text;
}
return NULL;
}
static inline const char *http_lookup_description(int code)
{
int i;
for (i = 0; http_response_text[i].code > 0; i++) {
if (http_response_text[i].code == code)
return http_response_text[i].desc;
}
return NULL;
}
/*************************************************************************/
/*************************************************************************/
/* Return the value of the named header from the client request. If the
* request did not include the named header, return NULL. If `header' is
* NULL, return the next header with the same name as the previous call
* which included a non-NULL `header' or NULL if none, a la strtok().
*/
char *http_get_header(Client *c, const char *header)
{
int i;
static const char *last_header = NULL;
static int last_return;
if (!c) {
module_log("BUG: http_get_header(): client is NULL!");
return NULL;
}
if (!header) {
if (!last_header)
return NULL;
header = last_header;
i = (last_return>=c->headers_count) ? c->headers_count : last_return+1;
} else {
i = 0;
}
last_header = header;
while (i < c->headers_count) {
if (stricmp(c->headers[i], header) == 0) {
last_return = i;
return c->headers[i] + strlen(c->headers[i]) + 1;
}
i++;
}
last_return = i;
return NULL;
}
/*************************************************************************/
/* Return the value of the named variable from the client request. If the
* request did not include the named variable, return NULL. If `variable'
* is NULL, return the next variable with the same name as the previous
* call which included a non-NULL `variable' or NULL if none, a la strtok().
* Variable names are assumed to be case-insensitive.
*/
char *http_get_variable(Client *c, const char *variable)
{
int i;
static const char *last_variable = NULL;
static int last_return;
if (!c) {
module_log("BUG: http_get_variable(): client is NULL!");
return NULL;
}
if (!variable) {
if (!last_variable)
return NULL;
variable = last_variable;
i = (last_return>=c->variables_count) ? c->variables_count
: last_return+1;
} else {
i = 0;
}
last_variable = variable;
while (i < c->variables_count) {
if (stricmp(c->variables[i], variable) == 0) {
last_return = i;
return c->variables[i] + strlen(c->variables[i]) + 1;
}
i++;
}
last_return = i;
return NULL;
}
/*************************************************************************/
/* HTML-quote (&...;) any HTML-special characters (<,>,&) in `str', and
* place the result in `outbuf', truncating to `outsize' bytes (including
* trailing null). &...; entities inserted by this routine will never be
* truncated. Returns `outbuf' on success, NULL on error (invalid
* parameter).
*/
char *http_quote_html(const char *str, char *outbuf, int32 outsize)
{
char *retval = outbuf;
if (!str || !outbuf || outsize <= 0) {
if (outsize <= 0)
module_log("BUG: http_quote_html(): bad outsize (%d)!", outsize);
else
module_log("BUG: http_quote_html(): %s is NULL!",
!str ? "str" : "outbuf");
errno = EINVAL;
return NULL;
}
while (*str && outsize > 1) {
switch (*str) {
case '&':
if (outsize < 6) {
outsize = 0;
break;
}
memcpy(outbuf, "&amp;", 5);
outbuf += 5;
outsize -= 5;
break;
case '<':
if (outsize < 5) {
outsize = 0;
break;
}
memcpy(outbuf, "&lt;", 5);
outbuf += 4;
outsize -= 4;
break;
case '>':
if (outsize < 5) {
outsize = 0;
break;
}
memcpy(outbuf, "&gt;", 5);
outbuf += 4;
outsize -= 4;
break;
default:
*outbuf++ = *str;
outsize--;
break;
}
str++;
}
*outbuf = 0;
return retval;
}
/*************************************************************************/
/* URL-quote (%nn) any special characters (anything except A-Z a-z 0-9 - . _)
* and place the result in `outbuf', truncating to `outsize' bytes (including
* trailing null). %nn tokens inserted by this routine will never be
* truncated. Returns `outbuf' on success, NULL on error (invalid parameter).
*/
char *http_quote_url(const char *str, char *outbuf, int32 outsize)
{
char *retval = outbuf;
if (!str || !outbuf || outsize <= 0) {
if (outsize <= 0)
module_log("BUG: http_quote_url(): bad outsize (%d)!", outsize);
else
module_log("BUG: http_quote_url(): %s is NULL!",
!str ? "str" : "outbuf");
errno = EINVAL;
return NULL;
}
while (*str && outsize > 1) {
if ((*str < 'A' || *str > 'Z')
&& (*str < 'a' || *str > 'z')
&& (*str < '0' || *str > '9')
&& *str != '-'
&& *str != '.'
&& *str != '_'
) {
if (*str == ' ') {
*outbuf++ = '+';
outsize--;
} else {
if (outsize < 4) {
outsize = 0;
break;
}
sprintf(outbuf, "%%%.02X", (unsigned char)*str);
outbuf += 3;
outsize -= 3;
}
} else {
*outbuf++ = *str;
outsize--;
}
str++;
}
*outbuf = 0;
return retval;
}
/*************************************************************************/
/* Remove URL-quoting (%nn) from the string in the given buffer, and return
* the buffer, or NULL on failure (invalid parameter). If the string ends
* with an incomplete %nn token, it is discarded; invalid %nn tokens (i.e.
* "nn" is not a pair of hex digits) are also discarded. Overwrites the
* buffer.
*/
char *http_unquote_url(char *buf)
{
char *retval = buf, *out = buf, *s;
char hexbuf[3] = {0,0,0};
if (!buf) {
module_log("BUG: http_unquote_url(): buf is NULL!");
errno = EINVAL;
return NULL;
}
while (*buf) {
if (*buf == '%') {
if (!buf[1] || !buf[2])
break;
hexbuf[0] = buf[1];
hexbuf[1] = buf[2];
buf += 3;
*out = strtol(hexbuf, &s, 16);
if (!*s) /* i.e. both digits were valid */
out++;
/* else discard character */
} else if (*buf++ == '+') {
*out++ = ' ';
} else {
*out++ = buf[-1];
}
}
*out = 0;
return retval;
}
/*************************************************************************/
/* Send an HTTP response line with the given code, plus the Date header. */
void http_send_response(Client *c, int code)
{
const char *text;
time_t t;
char datebuf[64];
if (!c) {
module_log("BUG: http_send_response(): client is NULL!");
return;
} else if (code < 0 || code > 999) {
module_log("BUG: http_send_response(): code is invalid! (%d)", code);
return;
}
text = http_lookup_response(code);
if (text)
sockprintf(c->socket, "HTTP/1.1 %03d %s\r\n", code, text);
else
sockprintf(c->socket, "HTTP/1.1 %03d Code %03d\r\n", code, code);
time(&t);
if (strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT",
gmtime(&t)) > 0)
sockprintf(c->socket, "Date: %s\r\n", datebuf);
else
module_log("http_send_response(): strftime() failed");
}
/*************************************************************************/
/* Send an error message with the given code and body text, and close the
* client connection. If `format' is NULL, the response text for the given
* code ("Error NNN" if the code is unknown) and descriptive text, if any,
* are used as the body text. Note that the client data structure will be
* unusable after calling this routine.
*/
void http_error(Client *c, int code, const char *format, ...)
{
if (!c) {
module_log("BUG: http_error(): client is NULL!");
return;
} else if (code < 0 || code > 999) {
module_log("BUG: http_error(): code is invalid! (%d)", code);
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
return;
}
http_send_response(c, code);
sockprintf(c->socket,
"Content-Type: text/html\r\nConnection: close\r\n\r\n");
if (c->method != METHOD_HEAD) {
if (format) {
va_list args;
va_start(args, format);
vsockprintf(c->socket, format, args);
va_end(args);
} else {
const char *text, *desc;
text = http_lookup_response(code);
if (text) {
desc = http_lookup_description(code);
sockprintf(c->socket, "<h1 align=center>%s</h1>", text);
if (desc)
sockprintf(c->socket, "%s", desc);
} else {
sockprintf(c->socket, "<h1 align=center>Error %d</h1>", code);
}
}
}
if (c->in_request)
c->in_request = -1; /* signal handle_request() to close later */
else
disconn(c->socket);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

19
modules/mail/Makefile Normal file
View File

@ -0,0 +1,19 @@
# Makefile for protocol modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so sendmail.so smtp.so
INCLUDES = mail.h mail-local.h $(TOPDIR)/timeout.h
include ../Makerules
###########################################################################

83
modules/mail/mail-local.h Normal file
View File

@ -0,0 +1,83 @@
/* Internal mail system declarations.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef MAIL_LOCAL_H
#define MAIL_LOCAL_H
#ifndef TIMEOUT_H
# include "timeout.h"
#endif
/*************************************************************************/
/* Structure describing a message to be sent. */
typedef struct mailmessage_struct MailMessage;
struct mailmessage_struct {
MailMessage *next, *prev;
char *from;
char *fromname;
char *to;
char *subject;
char *body;
char *charset;
MailCallback completion_callback;
void *callback_data;
Timeout *timeout;
};
/*************************************************************************/
/* Pointer to low-level send routine. Low-level modules should set this
* to point to their own send routine. This routine should initiate mail
* sending, then call send_finished() with an appropriate status code when
* the sending completes (successfully or otherwise).
*
* The routine can assume that the `from', `to', `subject', and `body'
* fields of the message will be non-NULL.
*
* The routine can do as it likes with the message data stored in the
* message structure (`from', `fromname', `to', `subject', and `body'),
* but the (pointer) values of the fields themselves should not be changed.
*/
extern void (*low_send)(MailMessage *msg);
/* Pointer to low-level abort routine. Low-level modules should set this
* to point to their own abort routine. This routine will be called when
* the high-level code needs to abort a message (for example, on a send
* timeout); the routine MUST abort sending of the given message.
* send_finished() should not be called, as the high-level code will take
* care of that.
*
* For modules which always complete processing before returning from
* low_send(), this will never be called, so it may be an empty routine;
* however, the `low_abort' pointer must be set or sendmail() will report
* that no low-level sending module is installed.
*/
extern void (*low_abort)(MailMessage *msg);
/* Routine to be called by low_send() when the given message has been sent
* (or sending has failed). The message structure must not be accessed
* after calling this routine!
*/
extern void send_finished(MailMessage *msg, int status);
/*************************************************************************/
#endif /* MAIL_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

48
modules/mail/mail.h Normal file
View File

@ -0,0 +1,48 @@
/* Declarations for public mail routines.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef MAIL_H
#define MAIL_H
/*************************************************************************/
/* Completion callback type: */
typedef void (*MailCallback)(int status, void *data);
/* Constants for mail delivery results, passed to the sendmail() completion
* callback: */
#define MAIL_STATUS_SENT 0 /* Mail sent successfully */
#define MAIL_STATUS_ERROR 1 /* Some unspecified error occurred */
#define MAIL_STATUS_NORSRC 2 /* Insufficient resources available */
#define MAIL_STATUS_REFUSED 3 /* Remote system refused message */
#define MAIL_STATUS_TIMEOUT 4 /* Mail sending timed out */
#define MAIL_STATUS_ABORTED 5 /* Mail sending aborted (e.g. module
* removed while sending) */
/*************************************************************************/
/* Send mail to the given address, calling the given function (if any) on
* completion or failure. */
extern void sendmail(const char *to, const char *subject, const char *body,
const char *charset,
MailCallback completion_callback, void *callback_data);
/*************************************************************************/
#endif /* MAIL_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

319
modules/mail/main.c Normal file
View File

@ -0,0 +1,319 @@
/* Core mail-sending module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "mail.h"
#include "mail-local.h"
/*************************************************************************/
static char *FromAddress;
static char def_FromName[] = "";
static char *FromName = def_FromName;
static int MaxMessages;
static time_t SendTimeout;
/* Low-level send/abort routines (filled in by submodule) */
EXPORT_VAR(void *,low_send)
void (*low_send)(MailMessage *msg) = NULL;
EXPORT_VAR(void *,low_abort)
void (*low_abort)(MailMessage *msg) = NULL;
/* List of mail messages currently in transit */
MailMessage *messages = NULL;
/*************************************************************************/
static void send_timeout(Timeout *t);
/*************************************************************************/
/***************************** Mail sending ******************************/
/*************************************************************************/
/* The function to send mail. Parameters `to', `subject', and `body' must
* be filled in, `to' must be a valid E-mail address, and only the body may
* contain newlines. If `charset' is not NULL, it is used as the MIME
* character set (if NULL, the character set is assumed to be unspecified).
* If `completion_callback' is not NULL, it will be called when delivery of
* the mail has completed or failed (which may be before this function
* returns!) with a delivery code (MAIL_STATUS_*) and the value passed as
* `callback_data'.
*
* The parameters `to', `subject', `body', and `charset' may be modified
* after calling this function, even if the completion callback has not yet
* been called.
*/
EXPORT_FUNC(sendmail)
void sendmail(const char *to, const char *subject, const char *body,
const char *charset,
MailCallback completion_callback, void *callback_data)
{
MailMessage *msg;
int status;
status = MAIL_STATUS_ERROR;
if (!low_send || !low_abort) {
module_log("sendmail(): No low-level mail module installed!");
goto error_return;
}
if (!to || !subject || !body) {
module_log("sendmail(): Got a NULL parameter!");
goto error_return;
}
if (!valid_email(to)) {
module_log("sendmail(): Destination address is invalid: %s", to);
goto error_return;
}
if (strchr(subject, '\n')) {
module_log("sendmail(): Subject contains newlines (invalid)");
goto error_return;
}
status = MAIL_STATUS_NORSRC;
if (MaxMessages) {
/* Count number of messages in transit, and abort if there are
* too many */
int count = 0;
LIST_FOREACH (msg, messages) {
if (++count >= MaxMessages)
break;
}
if (count >= MaxMessages) {
module_log("sendmail(): Too many messages in transit (max %d)",
MaxMessages);
goto error_return;
}
}
msg = malloc(sizeof(*msg));
if (!msg) {
module_log("sendmail(): No memory for message structure");
goto error_return;
}
LIST_INSERT(msg, messages);
msg->completion_callback = completion_callback;
msg->callback_data = callback_data;
msg->from = strdup(FromAddress);
msg->to = strdup(to);
msg->subject = strdup(subject);
msg->body = strdup(body);
if (charset)
msg->charset = strdup(charset);
else
msg->charset = NULL;
if (FromName)
msg->fromname = strdup(FromName);
else
msg->fromname = NULL;
if (!msg->from || !msg->to || !msg->subject || !msg->body
|| (charset && !msg->charset) || (FromName && !msg->fromname)
) {
module_log("sendmail(): No memory for message data");
send_finished(msg, MAIL_STATUS_NORSRC);
return;
}
if (SendTimeout > 0) {
msg->timeout = add_timeout(SendTimeout, send_timeout, 0);
if (!msg->timeout) {
module_log("sendmail(): Unable to create timeout");
send_finished(msg, MAIL_STATUS_NORSRC);
return;
}
msg->timeout->data = msg;
} else {
msg->timeout = NULL;
}
module_log_debug(1, "sendmail: from=%s to=%s subject=[%s]",
msg->from, msg->to, msg->subject);
module_log_debug(2, "sendmail: body=[%s]", msg->body);
(*low_send)(msg);
return;
error_return:
if (completion_callback)
(*completion_callback)(status, callback_data);
}
/*************************************************************************/
/* Handle a timeout on a send operation. Aborts the send and returns
* MAIL_STATUS_TIMEOUT to the caller's completion callback.
*/
static void send_timeout(Timeout *t)
{
MailMessage *msg = t->data;
if (!low_abort) {
module_log("send_timeout(): No low-level mail module installed!");
return;
}
(*low_abort)(msg);
send_finished(msg, MAIL_STATUS_TIMEOUT);
}
/*************************************************************************/
/* Call a message's completion callback function with the given status,
* then unlink and free the message structure. Called by sendmail() and
* low_send().
*/
EXPORT_FUNC(send_finished)
void send_finished(MailMessage *msg, int status)
{
if (!msg) {
module_log("BUG: send_finished() called with msg==NULL");
return;
}
if (msg->completion_callback)
(*msg->completion_callback)(status, msg->callback_data);
free(msg->from);
free(msg->fromname);
free(msg->to);
free(msg->subject);
free(msg->body);
free(msg->charset);
if (msg->timeout)
del_timeout(msg->timeout);
LIST_REMOVE(msg, messages);
free(msg);
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_FromAddress(const char *filename, int linenum, char *param);
static int do_FromName(const char *filename, int linenum, char *param);
ConfigDirective module_config[] = {
{ "FromAddress", { { CD_FUNC, CF_DIRREQ, do_FromAddress } } },
{ "FromName", { { CD_FUNC, 0, do_FromName } } },
{ "MaxMessages", { { CD_POSINT, 0, &MaxMessages } } },
{ "SendTimeout", { { CD_TIME, 0, &SendTimeout } } },
{ NULL }
};
/*************************************************************************/
static int do_FromAddress(const char *filename, int linenum, char *param)
{
static char *new_FromAddress = NULL;
if (filename) {
/* Check parameter for validity and save */
if (!valid_email(param)) {
config_error(filename, linenum,
"FromAddress requires a valid E-mail address");
return 0;
}
free(new_FromAddress);
new_FromAddress = strdup(param);
if (!new_FromAddress) {
config_error(filename, linenum, "Out of memory");
return 0;
}
} else if (linenum == CDFUNC_SET) {
/* Copy new values to config variables and clear */
if (new_FromAddress) { /* paranoia */
free(FromAddress);
FromAddress = new_FromAddress;
} else {
free(new_FromAddress);
}
new_FromAddress = NULL;
} else if (linenum == CDFUNC_DECONFIG) {
/* Reset to defaults */
free(FromAddress);
FromAddress = NULL;
}
return 1;
}
/*************************************************************************/
static int do_FromName(const char *filename, int linenum, char *param)
{
static char *new_FromName = NULL;
if (filename) {
/* Check parameter for validity and save */
if (strchr(param, '\n')) {
config_error(filename, linenum,
"FromName may not contain newlines");
return 0;
}
free(new_FromName);
new_FromName = strdup(param);
if (!new_FromName) {
config_error(filename, linenum, "Out of memory");
return 0;
}
} else if (linenum == CDFUNC_SET) {
/* Copy new values to config variables and clear */
if (new_FromName) { /* paranoia */
if (FromName != def_FromName)
free(FromName);
FromName = new_FromName;
} else {
free(new_FromName);
}
new_FromName = NULL;
} else if (linenum == CDFUNC_DECONFIG) {
/* Reset to defaults */
if (FromName != def_FromName)
free(FromName);
FromName = def_FromName;
}
return 1;
}
/*************************************************************************/
int init_module()
{
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
MailMessage *msg, *msg2;
LIST_FOREACH_SAFE(msg, messages, msg2) {
if (!low_abort) {
if (msg == messages) /* only print message once */
module_log("exit_module(): BUG: No low-level mail module"
" installed but in-transit messages remain"
" (submodule forgot to abort them?)");
} else {
(*low_abort)(msg);
}
send_finished(msg, MAIL_STATUS_ABORTED);
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

219
modules/mail/sendmail.c Normal file
View File

@ -0,0 +1,219 @@
/* Module to send mail using a "sendmail" program.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "mail.h"
#include "mail-local.h"
#include <sys/wait.h> /* for WIFEXITED(), etc. */
/*************************************************************************/
static char *SendmailPath;
static Module *module_mail_main;
static typeof(low_send) *low_send_p;
static typeof(low_abort) *low_abort_p;
/*************************************************************************/
/***************************** Mail sending ******************************/
/*************************************************************************/
static void send_sendmail(MailMessage *msg)
{
FILE *pipe;
char cmd[PATH_MAX+4];
char buf[BUFSIZE], *s;
int res;
time_t t;
snprintf(cmd, sizeof(cmd), "%s -t", SendmailPath);
pipe = popen(cmd, "w");
if (!pipe) {
module_log_perror("Unable to execute %s", SendmailPath);
send_finished(msg, MAIL_STATUS_ERROR);
return;
}
/* Quote all double-quotes in from-name string */
if (*msg->fromname) {
const char *fromname = msg->fromname;
s = buf;
while (s < buf+sizeof(buf)-2 && *fromname) {
if (*fromname == '"')
*s++ = '\\';
*s++ = *fromname++;
}
*s = 0;
fprintf(pipe, "From: \"%s\" <%s>\n", buf, msg->from);
} else {
fprintf(pipe, "From: %s\n", msg->from);
}
time(&t);
if (!strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S", gmtime(&t)))
strbcpy(buf, "Thu, 1 Jan 1970 00:00:00");
fprintf(pipe, "To: %s\nSubject: %s\nDate: %s +0000\n",
msg->to, msg->subject, buf);
if (msg->charset) {
fprintf(pipe,
"MIME-Version: 1.0\nContent-Type: text/plain; charset=%s\n",
msg->charset);
}
fprintf(pipe, "\n%s\n", msg->body);
res = pclose(pipe);
if (res == -1) {
module_log_perror("pclose() failed");
} else if (res != 0) {
module_log_debug(2, "sendmail exit code = %04X\n", res);
module_log("%s exited with %s %d%s", SendmailPath,
WIFEXITED(res) ? "code" : "signal",
WIFEXITED(res) ? WEXITSTATUS(res) : WTERMSIG(res),
WIFEXITED(res) && WEXITSTATUS(res)==127
? " (unable to execute program?)" : "");
send_finished(msg, MAIL_STATUS_ERROR);
return;
}
send_finished(msg, MAIL_STATUS_SENT);
}
/*************************************************************************/
static void abort_sendmail(MailMessage *msg)
{
/* send_sendmail() always completes handling of the message, so this
* routine doesn't need to do anything */
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_SendmailPath(const char *filename, int linenum, char *param);
ConfigDirective module_config[] = {
{ "SendmailPath", { { CD_FUNC, CF_DIRREQ, do_SendmailPath } } },
{ NULL }
};
/*************************************************************************/
static int do_SendmailPath(const char *filename, int linenum, char *param)
{
static char *new_SendmailPath = NULL;
if (filename) {
/* Check parameter for validity and save */
if (*param != '/') {
config_error(filename, linenum,
"SendmailPath value must begin with a slash (`/')");
return 0;
}
free(new_SendmailPath);
new_SendmailPath = strdup(param);
if (!new_SendmailPath) {
config_error(filename, linenum, "Out of memory");
return 0;
}
} else if (linenum == CDFUNC_SET) {
/* Copy new values to config variables and clear */
if (new_SendmailPath) { /* paranoia */
free(SendmailPath);
SendmailPath = new_SendmailPath;
} else {
free(new_SendmailPath);
}
new_SendmailPath = NULL;
} else if (linenum == CDFUNC_DECONFIG) {
/* Reset to defaults */
free(SendmailPath);
SendmailPath = NULL;
}
return 1;
}
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "mail/main") == 0) {
module_mail_main = mod;
low_send_p = get_module_symbol(mod, "low_send");
if (low_send_p)
*low_send_p = send_sendmail;
else
module_log("Unable to find `low_send' symbol, cannot send mail");
low_abort_p = get_module_symbol(mod, "low_abort");
if (low_abort_p)
*low_abort_p = abort_sendmail;
else
module_log("Unable to find `low_abort' symbol, cannot send mail");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_mail_main) {
if (low_send_p)
*low_send_p = NULL;
if (low_abort_p)
*low_abort_p = NULL;
low_send_p = NULL;
low_abort_p = NULL;
module_mail_main = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
Module *tmpmod;
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
tmpmod = find_module("mail/main");
if (tmpmod)
do_load_module(tmpmod, "mail/main");
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_mail_main)
do_unload_module(module_mail_main);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

526
modules/mail/smtp.c Normal file
View File

@ -0,0 +1,526 @@
/* Module to send mail using the SMTP protocol.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "mail.h"
#include "mail-local.h"
/*************************************************************************/
static char **RelayHosts;
int RelayHosts_count;
static char *SMTPName;
static Module *module_mail_main;
static typeof(low_send) *low_send_p;
static typeof(low_abort) *low_abort_p;
/*************************************************************************/
/* Maximum number of garbage lines to accept before disconnecting: */
#define GARBAGE_MAX 10
typedef struct SocketInfo_ {
struct SocketInfo_ *next, *prev;
Socket *sock;
MailMessage *msg;
int msg_status;
int relaynum; /* Index into RelayHosts[], used to try backup servers */
enum { ST_GREETING, ST_HELO, ST_MAIL, ST_RCPT, ST_DATA, ST_FINISH } state;
int replycode; /* nonzero if in the middle of a line (no EOL) */
char replychar; /* 4th character of reply (space or hyphen) */
int garbage; /* number of garbage lines seen so far */
} SocketInfo;
static SocketInfo *connections;
static SocketInfo *get_socketinfo(const Socket *sock, const MailMessage *msg);
static void free_socketinfo(SocketInfo *si);
static void try_next_relay(SocketInfo *si);
static void smtp_readline(Socket *sock, void *param_unused);
static void smtp_writeline(Socket *sock, const char *fmt, ...)
FORMAT(printf,2,3);
static void smtp_disconnect(Socket *sock, void *why);
/*************************************************************************/
/***************************** Mail sending ******************************/
/*************************************************************************/
static void send_smtp(MailMessage *msg)
{
SocketInfo *si;
/* Remove any double quotes in the From: name and log a warning */
if (strchr(msg->fromname, '"')) {
int i;
module_log("warning: double quotes (\") are not allowed in the"
" sender name; will be changed to single quotes (')");
for (i = 0; msg->fromname[i]; i++) {
if (msg->fromname[i] == '"')
msg->fromname[i] = '\'';
}
}
/* Set up a new SocketInfo and start the connection */
si = malloc(sizeof(*si));
if (!si) {
module_log("send_smtp(): no memory for SocketInfo");
send_finished(msg, MAIL_STATUS_NORSRC);
return;
}
si->sock = sock_new();
if (!si->sock) {
module_log("send_smtp(): sock_new() failed");
send_finished(msg, MAIL_STATUS_NORSRC);
free(si);
return;
}
LIST_INSERT(si, connections);
module_log_debug(1, "SMTP(%p) connecting", si->sock);
si->msg = msg;
si->msg_status = MAIL_STATUS_ERROR; /* default--don't depend on this! */
si->state = ST_GREETING;
si->replycode = 0;
si->garbage = 0;
sock_setcb(si->sock, SCB_READLINE, smtp_readline);
sock_setcb(si->sock, SCB_DISCONNECT, smtp_disconnect);
si->relaynum = -1; /* incremented by try_next_relay() */
/* Initiate connection and return */
errno = 0; /* just in case */
try_next_relay(si);
}
/*************************************************************************/
/*************************************************************************/
/* Auxiliary routines: */
/*************************************************************************/
/* Return the SocketInfo corresponding to the given socket or message, or
* NULL if none exists. (If `sock' is non-NULL, the search is done by
* socket, else by message.)
*/
static SocketInfo *get_socketinfo(const Socket *sock, const MailMessage *msg)
{
SocketInfo *si;
LIST_FOREACH (si, connections) {
if (sock ? si->sock == sock : si->msg == msg)
return si;
}
return NULL;
}
/*************************************************************************/
/* Free/clear all data associated with the given SocketInfo. If a message
* is still associated with the SocketInfo, abort it.
*/
static void free_socketinfo(SocketInfo *si)
{
if (si->msg) {
send_finished(si->msg, MAIL_STATUS_ABORTED);
si->msg = NULL;
}
if (si->sock) {
/* The disconnect callback will try to call us again, so avoid
* that confusing situation */
sock_setcb(si->sock, SCB_DISCONNECT, NULL);
module_log_debug(1, "SMTP(%p) closed (free_socketinfo)", si->sock);
sock_free(si->sock);
si->sock = NULL;
}
LIST_REMOVE(si, connections);
free(si);
}
/*************************************************************************/
/*************************************************************************/
/* Try connecting to the next relay in RelayHosts[]. Return 0 if a
* connection was successfully initiated (but possibly not completed), else
* free the SocketInfo and return -1. */
static void try_next_relay(SocketInfo *si)
{
for (;;) {
si->relaynum++;
if (si->relaynum >= RelayHosts_count) {
module_log("send_smtp(): No relay hosts available");
if (errno == ECONNREFUSED)
si->msg_status = MAIL_STATUS_REFUSED;
else
si->msg_status = MAIL_STATUS_ERROR;
send_finished(si->msg, si->msg_status);
si->msg = NULL;
free_socketinfo(si);
return;
}
if (conn(si->sock, RelayHosts[si->relaynum], 25, NULL, 0) == 0)
break;
module_log_perror("send_smtp(): Connection to %s:25 failed",
RelayHosts[si->relaynum]);
}
}
/*************************************************************************/
/* Read a line from an SMTP socket. */
static void smtp_readline(Socket *sock, void *param_unused)
{
SocketInfo *si;
char buf[BUFSIZE], *s;
int have_eol = 0;
int replycode;
if (!(si = get_socketinfo(sock, NULL))) {
module_log("smtp_readline(): no SocketInfo for socket %p!", sock);
sock_setcb(sock, SCB_DISCONNECT, NULL);
disconn(sock);
return;
}
sgets(buf, sizeof(buf), sock);
s = buf + strlen(buf);
if (s[-1] == '\n') {
s--;
have_eol++;
}
if (s[-1] == '\r') {
s--;
}
*s = 0;
module_log_debug(1, "SMTP(%p) received: %s", sock, buf);
if (!si->replycode) {
if (buf[0] < '1' || buf[0] > '5'
|| buf[1] < '0' || buf[1] > '9'
|| buf[2] < '0' || buf[2] > '9'
|| (buf[3] != ' ' && buf[3] != '-')) {
module_log("smtp_readline(%p) got garbage line: %s", sock, buf);
si->garbage++;
if (si->garbage > GARBAGE_MAX) {
int count = 0;
module_log("Too many garbage lines, giving up. Message was:");
module_log(" From: %s%s<%s>",
si->msg->fromname ? si->msg->fromname : "",
si->msg->fromname ? " " : "", si->msg->from);
module_log(" To: %s", si->msg->to);
module_log(" Subject: %s", si->msg->subject);
s = si->msg->body;
while (*s) {
char *t = s + strcspn(s, "\n");
if (*t)
*t++ = 0;
module_log(" %s %s", count ? " " : "Body:", s);
count++;
s = t;
}
si->msg_status = MAIL_STATUS_ERROR;
disconn(si->sock);
return;
}
}
si->replycode = strtol(buf, &s, 10);
if (s != buf+3) {
module_log("BUG: strtol ate %d characters from reply (should be"
" 3)!", (int)(s-buf));
}
if (si->replycode == 0) {
module_log("Got bizarre response code 0 from %s",
RelayHosts[si->relaynum]);
si->replycode = 1;
}
si->replychar = buf[3];
}
if (!have_eol)
return;
replycode = si->replycode;
si->replycode = 0;
if (si->replychar != ' ')
return;
if (replycode >= 400) {
module_log("Received error reply (%d) for socket %p state %d,"
" aborting", replycode, sock, si->state);
si->msg_status = MAIL_STATUS_REFUSED;
disconn(si->sock);
return;
}
switch (si->state++) {
case ST_GREETING:
smtp_writeline(sock, "HELO %s", SMTPName);
break;
case ST_HELO:
smtp_writeline(sock, "MAIL FROM:<%s>", si->msg->from);
break;
case ST_MAIL:
smtp_writeline(sock, "RCPT TO:<%s>", si->msg->to);
break;
case ST_RCPT:
smtp_writeline(sock, "DATA");
break;
case ST_DATA: {
time_t t;
time(&t);
if (!strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S", gmtime(&t)))
strbcpy(buf, "Thu, 1 Jan 1970 00:00:00");
if (si->msg->fromname)
smtp_writeline(sock, "From: \"%s\" <%s>", si->msg->fromname,
si->msg->from);
else
smtp_writeline(sock, "From: <%s>", si->msg->from);
smtp_writeline(sock, "To: <%s>", si->msg->to);
smtp_writeline(sock, "Subject: %s", si->msg->subject);
smtp_writeline(sock, "Date: %s +0000", buf);
if (si->msg->charset) {
smtp_writeline(sock, "MIME-Version: 1.0");
smtp_writeline(sock, "Content-Type: text/plain; charset=%s",
si->msg->charset);
}
#if CLEAN_COMPILE
/* writeline(sock,"") makes GCC warn about an empty format string */
smtp_writeline(sock, "%s", "");
#else
smtp_writeline(sock, "");
#endif
s = si->msg->body;
while (*s) {
char *t = s + strcspn(s, "\r\n");
if (*t == '\r')
*t++ = 0;
if (*t == '\n')
*t++ = 0;
smtp_writeline(sock, "%s%s", *s=='.' ? "." : "", s);
s = t;
}
smtp_writeline(sock, ".");
break;
} /* ST_DATA */
case ST_FINISH:
smtp_writeline(sock, "QUIT");
si->msg_status = MAIL_STATUS_SENT;
disconn(si->sock);
break;
default:
module_log("BUG: bad state %d for socket %p", si->state-1, sock);
si->msg_status = MAIL_STATUS_ERROR;
disconn(si->sock);
break;
} /* switch (si->state++) */
}
/*************************************************************************/
static void smtp_writeline(Socket *sock, const char *fmt, ...)
{
va_list args;
char buf[4096];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
sockprintf(sock, "%s", buf);
swrite(sock, "\r\n", 2);
module_log_debug(1, "SMTP(%p) sent: %s", sock, buf);
}
/*************************************************************************/
/* Handle a socket disconnection. */
static void smtp_disconnect(Socket *sock, void *why)
{
SocketInfo *si;
if (!(si = get_socketinfo(sock, NULL))) {
module_log("smtp_disconnect(): no SocketInfo for socket %p!", sock);
return;
}
module_log_debug(1, "SMTP(%p) closed (%s)", sock,
why==DISCONN_LOCAL ? "local" :
why==DISCONN_CONNFAIL ? "connfail" : "remote");
if (why == DISCONN_CONNFAIL) {
module_log_perror("Connection to server %s failed for socket %p",
RelayHosts[si->relaynum], sock);
try_next_relay(si); /* will call send_finished(), etc. if needed */
return;
} else if (why == DISCONN_REMOTE) {
module_log("Connection to server %s broken for socket %p",
RelayHosts[si->relaynum], sock);
si->msg_status = MAIL_STATUS_ERROR;
}
send_finished(si->msg, si->msg_status);
si->msg = NULL;
free_socketinfo(si);
}
/*************************************************************************/
static void abort_smtp(MailMessage *msg)
{
SocketInfo *si;
if (!(si = get_socketinfo(NULL, msg))) {
module_log("abort_smtp(): no SocketInfo for message %p!", msg);
return;
}
module_log_debug(1, "SMTP(%p) aborted", si);
si->msg = NULL;
free_socketinfo(si);
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
static int do_RelayHost(const char *filename, int linenum, char *param);
ConfigDirective module_config[] = {
{ "RelayHost", { { CD_FUNC, CF_DIRREQ, do_RelayHost } } },
{ "SMTPName", { { CD_STRING, CF_DIRREQ, &SMTPName } } },
{ NULL }
};
/*************************************************************************/
static int do_RelayHost(const char *filename, int linenum, char *param)
{
static char **new_RelayHosts = NULL;
static int new_RelayHosts_count = 0;
int i;
if (filename) {
/* Check parameter for validity and save */
if (!*param) {
/* Empty value */
config_error(filename, linenum, "Empty hostname in RelayHost");
}
ARRAY_EXTEND(new_RelayHosts);
new_RelayHosts[new_RelayHosts_count-1] = sstrdup(param);
} else if (linenum == CDFUNC_INIT) {
/*Prepare for reading config file: clear out "new" array just in case*/
ARRAY_FOREACH (i, new_RelayHosts)
free(new_RelayHosts[i]);
free(new_RelayHosts);
new_RelayHosts = NULL;
new_RelayHosts_count = 0;
} else if (linenum == CDFUNC_SET) {
/* Copy new values to config variables and clear */
ARRAY_FOREACH (i, RelayHosts)
free(RelayHosts[i]);
free(RelayHosts);
RelayHosts = new_RelayHosts;
RelayHosts_count = new_RelayHosts_count;
new_RelayHosts = NULL;
new_RelayHosts_count = 0;
} else if (linenum == CDFUNC_DECONFIG) {
/* Reset to defaults */
ARRAY_FOREACH (i, RelayHosts)
free(RelayHosts[i]);
free(RelayHosts);
RelayHosts = NULL;
RelayHosts_count = 0;
}
return 1;
}
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "mail/main") == 0) {
module_mail_main = mod;
low_send_p = get_module_symbol(mod, "low_send");
if (low_send_p)
*low_send_p = send_smtp;
else
module_log("Unable to find `low_send' symbol, cannot send mail");
low_abort_p = get_module_symbol(mod, "low_abort");
if (low_abort_p)
*low_abort_p = abort_smtp;
else
module_log("Unable to find `low_abort' symbol, cannot send mail");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_mail_main) {
if (low_send_p)
*low_send_p = NULL;
if (low_abort_p)
*low_abort_p = NULL;
low_send_p = NULL;
low_abort_p = NULL;
module_mail_main = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
Module *tmpmod;
connections = NULL;
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
tmpmod = find_module("mail/main");
if (tmpmod)
do_load_module(tmpmod, "mail/main");
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
SocketInfo *si, *si2;
if (module_mail_main)
do_unload_module(module_mail_main);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
LIST_FOREACH_SAFE (si, connections, si2)
free_socketinfo(si);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

29
modules/memoserv/Makefile Normal file
View File

@ -0,0 +1,29 @@
# Makefile for MemoServ modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so forward.so ignore.so
INCLUDES = memoserv.h $(TOPDIR)/commands.h $(TOPDIR)/language.h
INCLUDES-forward.o = $(TOPDIR)/modules/mail/mail.h \
$(TOPDIR)/modules/nickserv/nickserv.h
INCLUDES-ignore.o = $(TOPDIR)/databases.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/nickserv/nickserv.h
INCLUDES-main.o = $(TOPDIR)/databases.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
include ../Makerules
###########################################################################

368
modules/memoserv/forward.c Normal file
View File

@ -0,0 +1,368 @@
/* MemoServ forwarding module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "language.h"
#include "modules/mail/mail.h"
#include "modules/nickserv/nickserv.h"
#include "memoserv.h"
/*************************************************************************/
/*************************** Local variables *****************************/
/*************************************************************************/
static Module *module_memoserv;
static Module *module_nickserv_mail_auth;
static Module *module_mail;
static int MSAllowForward = 0;
static time_t MSForwardDelay = 0;
/*************************************************************************/
static void do_forward(User *u);
/* NOTE: {init,exit}_module() assume that FORWARD is the last item in the list,
* so don't rearrange things here! */
static Command commands[] = {
{ "SET FORWARD", NULL, NULL, MEMO_HELP_SET_FORWARD, -1,-1 },
{ "FORWARD", do_forward, NULL, MEMO_HELP_FORWARD, -1,-1 },
{ NULL }
};
/*************************************************************************/
/*************************** Command handlers ****************************/
/*************************************************************************/
/* Handle the FORWARD command. */
static int fwd_memo(MemoInfo *mi, int num, const User *u, char **p_body,
uint32 *p_bodylen);
static int fwd_memo_callback(int num, va_list args);
static void do_forward(User *u)
{
MemoInfo *mi;
char *numstr = strtok_remaining();
time_t now = time(NULL);
if (!user_identified(u)) {
notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
return;
}
mi = &u->ngi->memos;
if (!numstr || (!isdigit(*numstr) && stricmp(numstr, "ALL") != 0)) {
syntax_error(s_MemoServ, u, "FORWARD", MEMO_FORWARD_SYNTAX);
} else if (mi->memos_count == 0) {
notice_lang(s_MemoServ, u, MEMO_HAVE_NO_MEMOS);
} else if (MSForwardDelay > 0 && u->lastmemofwd+MSForwardDelay > now) {
u->lastmemofwd = now;
notice_lang(s_MemoServ, u, MEMO_FORWARD_PLEASE_WAIT,
maketime(u->ngi,MSForwardDelay,MT_SECONDS));
} else {
int fwdcount, count, last, i;
char *body = NULL;
uint32 bodylen = 0;
if (isdigit(*numstr)) {
/* Forward a specific memo or memos. */
fwdcount = process_numlist(numstr, &count, fwd_memo_callback,
u, mi, &last, &body, &bodylen);
} else {
/* Forward all memos. */
count = 0;
ARRAY_FOREACH (i, mi->memos) {
if (fwd_memo(mi, mi->memos[i].number, u, &body,&bodylen) == 1){
count++;
} else {
module_log("do_forward(): BUG: memo %d not found for"
" ALL (index %d, nick %s, nickgroup %u)",
mi->memos[i].number, i, u->nick, u->ngi->id);
}
}
fwdcount = -1; /* "forwarded all memos" */
}
if (body) {
/* Some/all memos got forwarded. Send the message out and
* report success. */
char subject[BUFSIZE];
const char *s;
if (count == 1)
s = getstring(u->ngi, MEMO_FORWARD_MAIL_SUBJECT);
else
s = getstring(u->ngi, MEMO_FORWARD_MULTIPLE_MAIL_SUBJECT);
snprintf(subject, sizeof(subject), s, u->ni->nick);
sendmail(u->ngi->email, subject, body,
getstring(u->ngi,LANG_CHARSET), NULL, NULL);
free(body);
if (fwdcount < 0)
notice_lang(s_MemoServ, u, MEMO_FORWARDED_ALL);
else if (fwdcount > 1)
notice_lang(s_MemoServ, u, MEMO_FORWARDED_SEVERAL, fwdcount);
else
notice_lang(s_MemoServ, u, MEMO_FORWARDED_ONE, last);
} else {
/* No memos were forwarded. */
if (count == 1 && fwdcount == 0)
notice_lang(s_MemoServ, u, MEMO_DOES_NOT_EXIST, atoi(numstr));
else
notice_lang(s_MemoServ, u, MEMO_FORWARDED_NONE);
}
u->lastmemofwd = now;
}
}
/* Forward a memo by number. Return:
* 1 if the memo was found and delivered
* 0 if the memo was not found
*/
static int fwd_memo(MemoInfo *mi, int num, const User *u, char **p_body,
uint32 *p_bodylen)
{
int i, len;
char thisbody[BUFSIZE*2], timebuf[BUFSIZE];
ARRAY_SEARCH_SCALAR(mi->memos, number, num, i);
if (i < mi->memos_count) {
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_DATE_TIME_FORMAT, mi->memos[i].time);
timebuf[sizeof(timebuf)-1] = 0;
if (mi->memos[i].channel) {
len = snprintf(thisbody, sizeof(thisbody)-1,
getstring(u->ngi, MEMO_FORWARD_CHANMEMO_MAIL_BODY),
mi->memos[i].sender, mi->memos[i].channel, timebuf,
mi->memos[i].text);
} else {
len = snprintf(thisbody, sizeof(thisbody)-1,
getstring(u->ngi, MEMO_FORWARD_MAIL_BODY),
mi->memos[i].sender, timebuf, mi->memos[i].text);
}
thisbody[len++] = '\n';
thisbody[len] = 0;
if (*p_bodylen) {
int old_bodylen = *p_bodylen;
char *dest;
*p_bodylen += strlen(thisbody)+1;
*p_body = srealloc(*p_body, *p_bodylen+1);
dest = *p_body + old_bodylen;
*dest++ = '\n';
strcpy(dest, thisbody); /* safe because allocated above */
} else {
*p_bodylen = strlen(thisbody);
*p_body = sstrdup(thisbody);
}
return 1;
} else {
return 0;
}
}
/* Forward a single memo from a MemoInfo. */
static int fwd_memo_callback(int num, va_list args)
{
User *u = va_arg(args, User *);
MemoInfo *mi = va_arg(args, MemoInfo *);
int *last = va_arg(args, int *);
char **p_body = va_arg(args, char **);
uint32 *p_bodylen = va_arg(args, uint32 *);
int i;
i = fwd_memo(mi, num, u, p_body, p_bodylen);
if (i > 0)
*last = num;
return i;
}
/*************************************************************************/
/* Handle the SET FORWARD command. */
static int do_set_forward(User *u, MemoInfo *mi, const char *option,
const char *param)
{
if (stricmp(option, "FORWARD") != 0)
return 0;
if (!u->ngi->email) {
notice_lang(s_MemoServ, u, MEMO_FORWARD_NEED_EMAIL);
} else if (stricmp(param, "ON") == 0) {
u->ngi->flags |= NF_MEMO_FWD;
u->ngi->flags &= ~NF_MEMO_FWDCOPY;
notice_lang(s_MemoServ, u, MEMO_SET_FORWARD_ON, u->ngi->email);
} else if (stricmp(param, "COPY") == 0) {
u->ngi->flags |= NF_MEMO_FWD | NF_MEMO_FWDCOPY;
notice_lang(s_MemoServ, u, MEMO_SET_FORWARD_COPY, u->ngi->email);
} else if (stricmp(param, "OFF") == 0) {
u->ngi->flags &= ~(NF_MEMO_FWD | NF_MEMO_FWDCOPY);
notice_lang(s_MemoServ, u, MEMO_SET_FORWARD_OFF);
} else {
syntax_error(s_MemoServ, u, "SET FORWARD", MEMO_SET_FORWARD_SYNTAX);
}
return 1;
}
/*************************************************************************/
/****************************** Callbacks ********************************/
/*************************************************************************/
/* Callback to handle an incoming memo. */
static int do_receive_memo(const User *sender, const char *target,
NickGroupInfo *ngi, const char *channel,
const char *text)
{
char subject[BUFSIZE], body[BUFSIZE*2], timebuf[BUFSIZE], tempbuf[BUFSIZE];
time_t now = time(NULL);
int i;
if (!(ngi->flags & NF_MEMO_FWD))
return 0;
strftime_lang(timebuf, sizeof(timebuf), ngi,
STRFTIME_DATE_TIME_FORMAT, now);
timebuf[sizeof(timebuf)-1] = 0;
ARRAY_FOREACH (i, ngi->nicks) {
if (irc_stricmp(ngi->nicks[i],target) == 0) {
target = ngi->nicks[i]; /* use the nick owner's capitalization */
break;
}
}
if (channel) {
snprintf(tempbuf, sizeof(tempbuf), "%s (%s)", target, channel);
target = tempbuf;
}
snprintf(subject, sizeof(subject),
getstring(ngi,MEMO_FORWARD_MAIL_SUBJECT), target);
if (channel) {
snprintf(body, sizeof(body),
getstring(ngi,MEMO_FORWARD_CHANMEMO_MAIL_BODY),
sender->nick, channel, timebuf, text);
} else {
snprintf(body, sizeof(body), getstring(ngi,MEMO_FORWARD_MAIL_BODY),
sender->nick, timebuf, text);
}
sendmail(ngi->email, subject, body, getstring(ngi,LANG_CHARSET), NULL,
NULL);
if (!(ngi->flags & NF_MEMO_FWDCOPY))
return 1;
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "MSAllowForward", { { CD_SET, 0, &MSAllowForward } } },
{ "MSForwardDelay", { { CD_TIME, 0, &MSForwardDelay } } },
{ NULL }
};
/*************************************************************************/
static int do_reconfigure(int after_configure)
{
if (after_configure) {
if (MSAllowForward)
commands[lenof(commands)-2].name = "FORWARD";
else
commands[lenof(commands)-2].name = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
module_memoserv = find_module("memoserv/main");
if (!module_memoserv) {
module_log("Main MemoServ module not loaded");
exit_module(0);
return 0;
}
use_module(module_memoserv);
module_nickserv_mail_auth = find_module("nickserv/mail-auth");
if (!module_nickserv_mail_auth) {
module_log("NickServ AUTH module (mail-auth) required for FORWARD");
exit_module(0);
return 0;
}
use_module(module_nickserv_mail_auth);
module_mail = find_module("mail/main");
if (!module_mail) {
module_log("Mail module not loaded");
exit_module(0);
return 0;
}
use_module(module_mail);
if (!MSAllowForward)
commands[lenof(commands)-2].name = NULL;
if (!register_commands(module_memoserv, commands)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "reconfigure", do_reconfigure)
|| !add_callback_pri(module_memoserv, "receive memo", do_receive_memo,
MS_RECEIVE_PRI_DELIVER)
|| !add_callback(module_memoserv, "SET", do_set_forward)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_mail) {
unuse_module(module_mail);
module_mail = NULL;
}
if (module_nickserv_mail_auth) {
unuse_module(module_nickserv_mail_auth);
module_nickserv_mail_auth = NULL;
}
if (module_memoserv) {
remove_callback(module_memoserv, "SET", do_set_forward);
remove_callback(module_memoserv, "receive memo", do_receive_memo);
unregister_commands(module_memoserv, commands);
unuse_module(module_memoserv);
module_memoserv = NULL;
}
remove_callback(NULL, "reconfigure", do_reconfigure);
commands[lenof(commands)-2].name = "FORWARD";
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

333
modules/memoserv/ignore.c Normal file
View File

@ -0,0 +1,333 @@
/* MemoServ IGNORE module.
* Written by Yusuf Iskenderoglu <uhc0@stud.uni-karlsruhe.de>
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "databases.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"
#include "memoserv.h"
/*************************************************************************/
static Module *module_memoserv;
static int MSIgnoreMax;
/*************************************************************************/
static void do_ignore(User *u);
static Command cmds[] = {
{ "IGNORE", do_ignore, NULL, MEMO_HELP_IGNORE, -1,-1},
{ NULL }
};
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
/* See nickserv/autojoin.c for why we don't create our own table. */
/* Temporary structure for loading/saving access records */
typedef struct {
uint32 nickgroup;
char *mask;
} DBRecord;
static DBRecord dbrec_static;
/* Iterators for first/next routines */
static NickGroupInfo *db_ngi_iterator;
static int db_array_iterator;
/*************************************************************************/
/* Table access routines */
static void *new_ignore(void)
{
return memset(&dbrec_static, 0, sizeof(dbrec_static));
}
static void free_ignore(void *record)
{
free(((DBRecord *)record)->mask);
}
static void insert_ignore(void *record)
{
DBRecord *dbrec = record;
NickGroupInfo *ngi = get_nickgroupinfo(dbrec->nickgroup);
if (!ngi) {
module_log("Discarding ignore record for missing nickgroup %u: %s",
dbrec->nickgroup, dbrec->mask);
free_ignore(record);
} else {
ARRAY_EXTEND(ngi->ignore);
ngi->ignore[ngi->ignore_count-1] = dbrec->mask;
}
}
static void *next_ignore(void)
{
while (db_ngi_iterator
&& db_array_iterator >= db_ngi_iterator->ignore_count
) {
db_ngi_iterator = next_nickgroupinfo();
db_array_iterator = 0;
}
if (db_ngi_iterator) {
dbrec_static.nickgroup = db_ngi_iterator->id;
dbrec_static.mask = db_ngi_iterator->ignore[db_array_iterator++];
return &dbrec_static;
} else {
return NULL;
}
}
static void *first_ignore(void)
{
db_ngi_iterator = first_nickgroupinfo();
db_array_iterator = 0;
return next_ignore();
}
/*************************************************************************/
/* Database table definition */
#define FIELD(name,type,...) \
{ #name, type, offsetof(DBRecord,name) , ##__VA_ARGS__ }
static DBField ignore_dbfields[] = {
FIELD(nickgroup, DBTYPE_UINT32),
FIELD(mask, DBTYPE_STRING),
{ NULL }
};
static DBTable ignore_dbtable = {
.name = "memo-ignore",
.newrec = new_ignore,
.freerec = free_ignore,
.insert = insert_ignore,
.first = first_ignore,
.next = next_ignore,
.fields = ignore_dbfields,
};
#undef FIELD
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Check whether the sender is allowed to send to the recipient (i.e. is
* not being ignored by the recipient).
*/
static int check_if_ignored(User *sender, const char *target,
NickGroupInfo *ngi, const char *channel,
const char *text)
{
int i;
if (!ngi)
return 0;
ARRAY_FOREACH (i, ngi->ignore) {
if (match_wild_nocase(ngi->ignore[i], sender->nick)
|| match_usermask(ngi->ignore[i], sender)
) {
return MEMO_X_GETS_NO_MEMOS;
}
if (sender->ngi) {
/* See if the ignore entry is a nick in the same group as the
* sender, and ignore if so */
NickInfo *ignore_ni;
if ((ignore_ni = get_nickinfo(ngi->ignore[i])) != NULL) {
NickGroupInfo *ignore_ngi =
get_nickgroupinfo(ignore_ni->nickgroup);
uint32 ignore_id = ignore_ngi ? ignore_ngi->id : 0;
if (ignore_ngi)
put_nickgroupinfo(ignore_ngi);
put_nickinfo(ignore_ni);
if (ignore_id == sender->ngi->id) {
return MEMO_X_GETS_NO_MEMOS;
}
}
}
}
return 0;
}
/*************************************************************************/
/*************************** Command functions ***************************/
/*************************************************************************/
/* Handle the MemoServ IGNORE command. */
void do_ignore(User *u)
{
char *cmd = strtok(NULL, " ");
char *mask = strtok(NULL, " ");
NickGroupInfo *ngi = NULL;
NickInfo *ni;
int i;
if (cmd && mask && stricmp(cmd,"LIST") == 0 && is_services_admin(u)) {
if (!(ni = get_nickinfo(mask))) {
notice_lang(s_MemoServ, u, NICK_X_NOT_REGISTERED, mask);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, mask);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_MemoServ, u, INTERNAL_ERROR);
} else if (ngi->ignore_count == 0) {
notice_lang(s_MemoServ, u, MEMO_IGNORE_LIST_X_EMPTY, mask);
} else {
notice_lang(s_MemoServ, u, MEMO_IGNORE_LIST_X, mask);
ARRAY_FOREACH (i, ngi->ignore)
notice(s_MemoServ, u->nick, " %s", ngi->ignore[i]);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
} else if (!cmd || ((stricmp(cmd,"LIST")==0) && mask)) {
syntax_error(s_MemoServ, u, "IGNORE", MEMO_IGNORE_SYNTAX);
} else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_MemoServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (stricmp(cmd, "ADD") == 0) {
if (!mask) {
syntax_error(s_MemoServ, u, "IGNORE", MEMO_IGNORE_ADD_SYNTAX);
return;
}
if (ngi->ignore_count >= MSIgnoreMax) {
notice_lang(s_MemoServ, u, MEMO_IGNORE_LIST_FULL);
return;
}
ARRAY_FOREACH (i, ngi->ignore) {
if (stricmp(ngi->ignore[i], mask) == 0) {
notice_lang(s_MemoServ, u,
MEMO_IGNORE_ALREADY_PRESENT, ngi->ignore[i]);
return;
}
}
ARRAY_EXTEND(ngi->ignore);
ngi->ignore[ngi->ignore_count-1] = sstrdup(mask);
notice_lang(s_MemoServ, u, MEMO_IGNORE_ADDED, mask);
} else if (stricmp(cmd, "DEL") == 0) {
if (!mask) {
syntax_error(s_MemoServ, u, "IGNORE", MEMO_IGNORE_DEL_SYNTAX);
return;
}
ARRAY_SEARCH_PLAIN(ngi->ignore, mask, strcmp, i);
if (i == ngi->ignore_count)
ARRAY_SEARCH_PLAIN(ngi->ignore, mask, stricmp, i);
if (i == ngi->ignore_count) {
notice_lang(s_MemoServ, u, MEMO_IGNORE_NOT_FOUND, mask);
return;
}
notice_lang(s_MemoServ, u, MEMO_IGNORE_DELETED, mask);
free(ngi->ignore[i]);
ARRAY_REMOVE(ngi->ignore, i);
} else if (stricmp(cmd, "LIST") == 0) {
if (ngi->ignore_count == 0) {
notice_lang(s_MemoServ, u, MEMO_IGNORE_LIST_EMPTY);
} else {
notice_lang(s_MemoServ, u, MEMO_IGNORE_LIST);
ARRAY_FOREACH (i, ngi->ignore)
notice(s_MemoServ, u->nick, " %s", ngi->ignore[i]);
}
} else {
syntax_error(s_MemoServ, u, "IGNORE", MEMO_IGNORE_SYNTAX);
}
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "MSIgnoreMax", { { CD_POSINT, CF_DIRREQ, &MSIgnoreMax } } },
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
module_memoserv = find_module("memoserv/main");
if (!module_memoserv) {
module_log("Main MemoServ module not loaded");
return 0;
}
use_module(module_memoserv);
if (!register_commands(module_memoserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback_pri(module_memoserv, "receive memo",
check_if_ignored, MS_RECEIVE_PRI_CHECK)
) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
if (!register_dbtable(&ignore_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&ignore_dbtable);
if (module_memoserv) {
remove_callback(module_memoserv, "receive memo", check_if_ignored);
unregister_commands(module_memoserv, cmds);
unuse_module(module_memoserv);
module_memoserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

1722
modules/memoserv/main.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
/* MemoServ-related structures.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef MEMOSERV_H
#define MEMOSERV_H
/*************************************************************************/
/* Memo info structures. */
typedef struct {
uint32 number; /* Index number -- not necessarily array position! */
int16 flags;
time_t time; /* When memo was sent */
time_t firstread; /* When memo was first read */
char sender[NICKMAX];
char *channel; /* Channel name if this is a channel memo, else NULL */
char *text;
} Memo;
#define MF_UNREAD 0x0001 /* Memo has not yet been read */
#define MF_EXPIREOK 0x0002 /* Memo may be expired */
#define MF_ALLFLAGS 0x0003 /* All memo flags */
struct memoinfo_ {
Memo *memos;
int16 memos_count;
int16 memomax;
};
/*************************************************************************/
/* Definitions of special memo limit values: */
#define MEMOMAX_UNLIMITED -1
#define MEMOMAX_DEFAULT -2
#define MEMOMAX_MAX 32767 /* Maximum memo limit */
/*************************************************************************/
/* Priorities for use with receive memo callback: */
#define MS_RECEIVE_PRI_CHECK 10 /* For checking ability to send */
#define MS_RECEIVE_PRI_DELIVER 0 /* For actually sending the memo */
/*************************************************************************/
/* Exports: */
extern char *s_MemoServ;
extern int32 MSMaxMemos;
/*************************************************************************/
#endif /* MEMOSERV_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

32
modules/misc/Makefile Normal file
View File

@ -0,0 +1,32 @@
# Makefile for HelpServ modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = devnull.so helpserv.so xml-export.so xml-import.so
INCLUDES-helpserv.o = $(TOPDIR)/language.h
INCLUDES-xml-export.o = $(XML_INCLUDES)
INCLUDES-xml-import.o = $(XML_INCLUDES) \
$(TOPDIR)/modules/nickserv/util.c \
$(TOPDIR)/modules/chanserv/util.c
include ../Makerules
###########################################################################
XML_INCLUDES = xml.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h \
$(TOPDIR)/modules/memoserv/memoserv.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/operserv/news.h \
$(TOPDIR)/modules/statserv/statserv.h

184
modules/misc/devnull.c Normal file
View File

@ -0,0 +1,184 @@
/* DevNull module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
/*************************************************************************/
static Module *module_nickserv;
char *s_DevNull;
char *desc_DevNull;
/*************************************************************************/
/**************************** Event callbacks ****************************/
/*************************************************************************/
/* Handler for introducing nicks. */
static int do_introduce(const char *nick)
{
if (!nick || irc_stricmp(nick, s_DevNull) == 0) {
send_pseudo_nick(s_DevNull, desc_DevNull, 0);
if (nick)
return 1;
}
return 0;
}
/*************************************************************************/
/* Handler for PRIVMSGs. */
static int do_privmsg(const char *source, const char *target, char *buf)
{
return irc_stricmp(target, s_DevNull) == 0;
}
/*************************************************************************/
/* Handler for WHOIS's. */
static int do_whois(const char *source, char *who, char *extra)
{
if (irc_stricmp(who, s_DevNull) == 0) {
send_cmd(ServerName, "311 %s %s %s %s * :%s", source, who,
ServiceUser, ServiceHost, desc_DevNull);
send_cmd(ServerName, "312 %s %s %s :%s", source, who,
ServerName, ServerDesc);
send_cmd(ServerName, "318 %s %s End of /WHOIS response.", source, who);
return 1;
} else {
return 0;
}
}
/*************************************************************************/
/* Callback for NickServ REGISTER/LINK check; we disallow
* registration/linking of the DevNull pseudoclient nickname.
*/
static int do_reglink_check(const User *u, const char *nick,
const char *pass, const char *email)
{
return irc_stricmp(nick, s_DevNull) == 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "DevNullName", { { CD_STRING, CF_DIRREQ, &s_DevNull },
{ CD_STRING, 0, &desc_DevNull } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "nickserv/main") == 0) {
module_nickserv = mod;
if (!add_callback(mod, "REGISTER/LINK check", do_reglink_check))
module_log("Unable to register NickServ REGISTER/LINK check"
" callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_nickserv) {
remove_callback(module_nickserv, "REGISTER/LINK check",
do_reglink_check);
module_nickserv = NULL;
}
return 0;
}
/*************************************************************************/
static int do_reconfigure(int after_configure)
{
static char old_s_DevNull[NICKMAX];
static char *old_desc_DevNull = NULL;
if (!after_configure) {
/* Before reconfiguration: save old values. */
strbcpy(old_s_DevNull, s_DevNull);
old_desc_DevNull = strdup(desc_DevNull);
} else {
/* After reconfiguration: handle value changes. */
if (strcmp(old_s_DevNull, s_DevNull) != 0)
send_nickchange(old_s_DevNull, s_DevNull);
if (!old_desc_DevNull || strcmp(old_desc_DevNull, desc_DevNull) != 0)
send_namechange(s_DevNull, desc_DevNull);
free(old_desc_DevNull);
} /* if (!after_configure) */
return 0;
}
/*************************************************************************/
int init_module(void)
{
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "reconfigure", do_reconfigure)
|| !add_callback(NULL, "introduce_user", do_introduce)
|| !add_callback(NULL, "m_privmsg", do_privmsg)
|| !add_callback(NULL, "m_whois", do_whois)
) {
module_log("Unable to register callbacks");
exit_module(0);
return 0;
}
if (linked)
do_introduce(NULL);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (linked)
send_cmd(s_DevNull, "QUIT :");
remove_callback(NULL, "m_whois", do_whois);
remove_callback(NULL, "m_privmsg", do_privmsg);
remove_callback(NULL, "introduce_user", do_introduce);
remove_callback(NULL, "reconfigure", do_reconfigure);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

280
modules/misc/helpserv.c Normal file
View File

@ -0,0 +1,280 @@
/* HelpServ functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include <sys/stat.h>
/*************************************************************************/
static Module *module_nickserv;
static char *s_HelpServ;
static char *desc_HelpServ;
static char *HelpDir;
/*************************************************************************/
/*************************** HelpServ routine ****************************/
/*************************************************************************/
/* Main HelpServ routine. */
static void helpserv(const char *source, char *topic)
{
FILE *f;
struct stat st;
char buf[PATH_MAX+1], *ptr;
const char *s;
char *old_topic; /* an unclobbered (by strtok) copy */
User *u;
if (strnicmp(topic, "\1PING ", 6) == 0) {
s = strtok(topic, " ");
if (!(s = strtok_remaining()))
s = "\1";
notice(s_HelpServ, source, "\1PING %s", s);
return;
}
old_topic = sstrdup(topic);
u = get_user(source);
/* As we copy path parts, (1) lowercase everything and (2) make sure
* we don't let any special characters through -- this includes '.'
* (which could get parent dir) or '/' (which couldn't _really_ do
* anything nasty if we keep '.' out, but better to be on the safe
* side). Special characters turn into '_'.
*/
strbcpy(buf, HelpDir);
ptr = buf + strlen(buf);
for (s = strtok(topic, " "); s && ptr-buf < sizeof(buf)-1;
s = strtok(NULL, " ")) {
*ptr++ = '/';
while (*s && ptr-buf < sizeof(buf)-1) {
if (*s == '.' || *s == '/')
*ptr++ = '_';
else
*ptr++ = tolower(*s);
s++;
}
*ptr = 0;
}
/* If we end up at a directory, go for an "index" file/dir if
* possible.
*/
while (ptr-buf < sizeof(buf)-6
&& stat(buf, &st) == 0 && S_ISDIR(st.st_mode)) {
strcpy(ptr, "/index");
ptr += strlen(ptr);
}
/* Send the file, if it exists.
*/
if (!(f = fopen(buf, "r"))) {
module_log_perror_debug(1, "Cannot open help file %s", buf);
if (u) {
notice_lang(s_HelpServ, u, NO_HELP_AVAILABLE, old_topic);
} else {
notice(s_HelpServ, source,
"Sorry, no help available for \2%s\2.", old_topic);
}
} else {
while (fgets(buf, sizeof(buf), f)) {
s = strtok(buf, "\n");
/* Use this construction to prevent any %'s in the text from
* doing weird stuff to the output. Also replace blank lines by
* spaces (see send.c/notice_list() for an explanation of why).
*/
notice(s_HelpServ, source, "%s", s ? s : " ");
}
fclose(f);
}
free(old_topic);
}
/*************************************************************************/
/**************************** Event callbacks ****************************/
/*************************************************************************/
/* Handler for introducing nicks. */
static int do_introduce(const char *nick)
{
if (!nick || irc_stricmp(nick, s_HelpServ) == 0) {
send_pseudo_nick(s_HelpServ, desc_HelpServ, 0);
if (nick)
return 1;
}
return 0;
}
/*************************************************************************/
/* Handler for PRIVMSGs. */
static int do_privmsg(const char *source, const char *target, char *buf)
{
if (irc_stricmp(target, s_HelpServ) == 0) {
helpserv(source, buf);
return 1;
}
return 0;
}
/*************************************************************************/
/* Handler for WHOIS's. */
static int do_whois(const char *source, char *who, char *extra)
{
if (irc_stricmp(who, s_HelpServ) == 0) {
send_cmd(ServerName, "311 %s %s %s %s * :%s", source, who,
ServiceUser, ServiceHost, desc_HelpServ);
send_cmd(ServerName, "312 %s %s %s :%s", source, who,
ServerName, ServerDesc);
send_cmd(ServerName, "318 %s %s End of /WHOIS response.", source, who);
return 1;
} else {
return 0;
}
}
/*************************************************************************/
/* Callback for NickServ REGISTER/LINK check; we disallow
* registration/linking of the HelpServ pseudoclient nickname.
*/
static int do_reglink_check(const User *u, const char *nick,
const char *pass, const char *email)
{
return irc_stricmp(nick, s_HelpServ) == 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "HelpDir", { { CD_STRING, CF_DIRREQ, &HelpDir } } },
{ "HelpServName", { { CD_STRING, CF_DIRREQ, &s_HelpServ },
{ CD_STRING, 0, &desc_HelpServ } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "nickserv/main") == 0) {
module_nickserv = mod;
if (!add_callback(mod, "REGISTER/LINK check", do_reglink_check))
module_log("Unable to register NickServ REGISTER/LINK check"
" callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_nickserv) {
remove_callback(module_nickserv, "REGISTER/LINK check",
do_reglink_check);
module_nickserv = NULL;
}
return 0;
}
/*************************************************************************/
static int do_reconfigure(int after_configure)
{
static char old_s_HelpServ[NICKMAX];
static char *old_desc_HelpServ = NULL;
if (!after_configure) {
/* Before reconfiguration: save old values. */
free(old_desc_HelpServ);
strbcpy(old_s_HelpServ, s_HelpServ);
old_desc_HelpServ = strdup(desc_HelpServ);
} else {
/* After reconfiguration: handle value changes. */
if (strcmp(old_s_HelpServ, s_HelpServ) != 0)
send_nickchange(old_s_HelpServ, s_HelpServ);
if (!old_desc_HelpServ || strcmp(old_desc_HelpServ,desc_HelpServ) != 0)
send_namechange(s_HelpServ, desc_HelpServ);
free(old_desc_HelpServ);
old_desc_HelpServ = NULL;
} /* if (!after_configure) */
return 0;
}
/*************************************************************************/
int init_module(void)
{
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "reconfigure", do_reconfigure)
|| !add_callback(NULL, "introduce_user", do_introduce)
|| !add_callback(NULL, "m_privmsg", do_privmsg)
|| !add_callback(NULL, "m_whois", do_whois)
) {
module_log("Unable to register callbacks");
exit_module(0);
return 0;
}
Module *tmpmod = find_module("nickserv/main");
if (tmpmod)
do_load_module(tmpmod, "nickserv/main");
if (linked)
do_introduce(NULL);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (linked)
send_cmd(s_HelpServ, "QUIT :");
if (module_nickserv)
do_unload_module(module_nickserv);
remove_callback(NULL, "m_whois", do_whois);
remove_callback(NULL, "m_privmsg", do_privmsg);
remove_callback(NULL, "introduce_user", do_introduce);
remove_callback(NULL, "reconfigure", do_reconfigure);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

505
modules/misc/xml-export.c Normal file
View File

@ -0,0 +1,505 @@
/* Routines to export Services databases in XML format.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "modules/memoserv/memoserv.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/operserv/news.h"
#include "modules/statserv/statserv.h"
#include "xml.h"
/*************************************************************************/
/* Write a field (either string, password, long, or unsigned long) in XML.
* `indent' is the indent string to prefix the output with.
*/
#define XML_PUT_STRING(indent,structure,field) do { \
if ((structure).field) \
writefunc(data, "%s<" #field ">%s</" #field ">\n", indent, \
xml_quotestr((structure).field)); \
} while (0)
#define XML_PUT_PASS(indent,structure,field) \
writefunc(data, "%s<" #field "%s%s%s>%s</" #field ">\n", indent, \
(structure).field.cipher ? " cipher=\"" : "", \
(structure).field.cipher ? (structure).field.cipher : "", \
(structure).field.cipher ? "\"" : "", \
xml_quotebuf((structure).field.password,PASSMAX));
#define XML_PUT_LONG(indent,structure,field) \
writefunc(data, "%s<" #field ">%ld</" #field ">\n", indent, \
(long)(structure).field)
#define XML_PUT_ULONG(indent,structure,field) \
writefunc(data, "%s<" #field ">%lu</" #field ">\n", indent, \
(unsigned long)(structure).field)
/* Write an array of strings (with corresponding count) in XML. */
#define XML_PUT_STRARR(indent,structure,field) do { \
int i; \
writefunc(data, "%s<" #field " count='%lu'>\n", indent, \
(unsigned long)(structure).field##_count); \
ARRAY_FOREACH (i, (structure).field) { \
writefunc(data, "%s\t<array-element>%s</array-element>\n", \
indent, xml_quotestr((structure).field[i])); \
} \
writefunc(data, "%s</" #field ">\n", indent); \
} while (0)
/*************************************************************************/
/*************************** Internal routines ***************************/
/*************************************************************************/
/* Quote any special characters in the given buffer and return the result
* in a static buffer. Trailing null characters are removed.
*/
static char *xml_quotebuf(const char *buf_, int size)
{
const unsigned char *buf = (unsigned char *)buf_;
static char retbuf[BUFSIZE*6+1];
uint32 i;
char *d;
d = retbuf;
while (size > 0 && !buf[size-1])
size--;
for (i = 0; i < size; i++, buf++) {
if (d - retbuf >= sizeof(retbuf)-6) {
#ifdef CONVERT_DB
fprintf(stderr, "warning: xml_quotebuf(%p,%d) result too long,"
" truncated", buf, size);
#else
module_log("warning: xml_quotebuf(%p,%d) result too long,"
" truncated", buf, size);
#endif
break;
}
if (*buf < 32 || *buf > 126) {
sprintf(d, "&#%u;", *buf);
if (*buf < 10)
d += 4;
else if (*buf < 100)
d += 5;
else
d += 6;
} else switch (*buf) {
case '<':
memcpy(d, "&lt;", 4);
d += 4;
break;
case '>':
memcpy(d, "&gt;", 4);
d += 4;
break;
case '&':
memcpy(d, "&amp;", 5);
d += 5;
break;
default:
*d++ = *buf;
break;
}
}
*d = 0;
return retbuf;
}
/* Do the same thing to a \0-terminated string. */
#define xml_quotestr(s) xml_quotebuf((s), strlen((s)))
/*************************************************************************/
/*************************** Database output *****************************/
/*************************************************************************/
/* Write all (major) Services constants to the file. */
static int export_constants(xml_writefunc_t writefunc, void *data)
{
writefunc(data, "\t<constants>\n");
#define XML_PUT_CONST(c) \
writefunc(data, "\t\t<" #c ">%ld</" #c ">\n", (long)c)
XML_PUT_CONST(LANG_DEFAULT);
XML_PUT_CONST(CHANMAX_UNLIMITED);
XML_PUT_CONST(CHANMAX_DEFAULT);
XML_PUT_CONST(TIMEZONE_DEFAULT);
XML_PUT_CONST(ACCLEV_FOUNDER);
XML_PUT_CONST(ACCLEV_INVALID);
XML_PUT_CONST(ACCLEV_SOP);
XML_PUT_CONST(ACCLEV_AOP);
XML_PUT_CONST(ACCLEV_HOP);
XML_PUT_CONST(ACCLEV_VOP);
XML_PUT_CONST(ACCLEV_NOP);
XML_PUT_CONST(MEMOMAX_UNLIMITED);
XML_PUT_CONST(MEMOMAX_DEFAULT);
XML_PUT_CONST(NEWS_LOGON);
XML_PUT_CONST(NEWS_OPER);
XML_PUT_CONST(MD_AKILL);
XML_PUT_CONST(MD_EXCLUDE);
XML_PUT_CONST(MD_EXCEPTION);
XML_PUT_CONST(MD_SGLINE);
XML_PUT_CONST(MD_SQLINE);
XML_PUT_CONST(MD_SZLINE);
#undef XML_PUT_CONST
writefunc(data, "\t</constants>\n");
return 1;
}
/*************************************************************************/
static int export_operserv_data(xml_writefunc_t writefunc, void *data)
{
int32 maxusercnt;
time_t maxusertime;
Password *supass;
if (!get_operserv_data(OSDATA_MAXUSERCNT, &maxusercnt)
|| !get_operserv_data(OSDATA_MAXUSERTIME, &maxusertime)
|| !get_operserv_data(OSDATA_SUPASS, &supass))
return 0;
writefunc(data, "\t<maxusercnt>%d</maxusercnt>\n", maxusercnt);
writefunc(data, "\t<maxusertime>%ld</maxusertime>\n", (long)maxusertime);
if (supass) {
writefunc(data, "\t<supass%s%s%s>%s</supass>\n",
supass->cipher ? " cipher=\"" : "",
supass->cipher ? supass->cipher : "",
supass->cipher ? "\"" : "",
xml_quotebuf(supass->password, PASSMAX));
}
return 1;
}
/*************************************************************************/
static int export_nick_db(xml_writefunc_t writefunc, void *data)
{
NickInfo *ni;
NickGroupInfo *ngi;
int i;
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
writefunc(data, "\t<nickgroupinfo>\n");
XML_PUT_ULONG ("\t\t", *ngi, id);
XML_PUT_STRARR("\t\t", *ngi, nicks);
XML_PUT_ULONG ("\t\t", *ngi, mainnick);
XML_PUT_PASS ("\t\t", *ngi, pass);
XML_PUT_STRING("\t\t", *ngi, url);
XML_PUT_STRING("\t\t", *ngi, email);
XML_PUT_STRING("\t\t", *ngi, last_email);
XML_PUT_STRING("\t\t", *ngi, info);
XML_PUT_LONG ("\t\t", *ngi, flags);
XML_PUT_LONG ("\t\t", *ngi, os_priv);
XML_PUT_LONG ("\t\t", *ngi, authcode);
if (ngi->authcode) {
XML_PUT_LONG ("\t\t", *ngi, authset);
XML_PUT_LONG ("\t\t", *ngi, authreason);
}
if (ngi->flags & NF_SUSPENDED) {
XML_PUT_STRING("\t\t", *ngi, suspend_who);
XML_PUT_STRING("\t\t", *ngi, suspend_reason);
XML_PUT_LONG ("\t\t", *ngi, suspend_time);
XML_PUT_LONG ("\t\t", *ngi, suspend_expires);
}
XML_PUT_LONG ("\t\t", *ngi, language);
XML_PUT_LONG ("\t\t", *ngi, timezone);
XML_PUT_LONG ("\t\t", *ngi, channelmax);
XML_PUT_STRARR("\t\t", *ngi, access);
XML_PUT_STRARR("\t\t", *ngi, ajoin);
writefunc(data, "\t\t<memoinfo>\n\t\t\t<memos count='%d'>\n",
ngi->memos.memos_count);
ARRAY_FOREACH (i, ngi->memos.memos) {
writefunc(data, "\t\t\t\t<memo>\n");
XML_PUT_LONG ("\t\t\t\t\t", ngi->memos.memos[i], number);
XML_PUT_LONG ("\t\t\t\t\t", ngi->memos.memos[i], flags);
XML_PUT_LONG ("\t\t\t\t\t", ngi->memos.memos[i], time);
XML_PUT_STRING("\t\t\t\t\t", ngi->memos.memos[i], sender);
if (ngi->memos.memos[i].channel)
XML_PUT_STRING("\t\t\t\t\t", ngi->memos.memos[i], channel);
XML_PUT_STRING("\t\t\t\t\t", ngi->memos.memos[i], text);
writefunc(data, "\t\t\t\t</memo>\n");
}
writefunc(data, "\t\t\t</memos>\n");
XML_PUT_LONG ("\t\t\t", ngi->memos, memomax);
writefunc(data, "\t\t</memoinfo>\n");
XML_PUT_STRARR("\t\t", *ngi, ignore);
writefunc(data, "\t</nickgroupinfo>\n");
}
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
writefunc(data, "\t<nickinfo>\n");
XML_PUT_STRING("\t\t", *ni, nick);
writefunc(data, "\t\t<status>%d</status>\n",
ni->status & ~NS_TEMPORARY);
XML_PUT_STRING("\t\t", *ni, last_usermask);
XML_PUT_STRING("\t\t", *ni, last_realmask);
XML_PUT_STRING("\t\t", *ni, last_realname);
XML_PUT_STRING("\t\t", *ni, last_quit);
XML_PUT_LONG ("\t\t", *ni, time_registered);
XML_PUT_LONG ("\t\t", *ni, last_seen);
XML_PUT_ULONG ("\t\t", *ni, nickgroup);
writefunc(data, "\t</nickinfo>\n");
}
return 1;
}
/*************************************************************************/
static int export_channel_db(xml_writefunc_t writefunc, void *data)
{
int i;
ChannelInfo *ci;
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
writefunc(data, "\t<channelinfo>\n");
XML_PUT_STRING("\t\t", *ci, name);
XML_PUT_ULONG ("\t\t", *ci, founder);
XML_PUT_ULONG ("\t\t", *ci, successor);
XML_PUT_PASS ("\t\t", *ci, founderpass);
XML_PUT_STRING("\t\t", *ci, desc);
XML_PUT_STRING("\t\t", *ci, url);
XML_PUT_STRING("\t\t", *ci, email);
XML_PUT_LONG ("\t\t", *ci, time_registered);
XML_PUT_LONG ("\t\t", *ci, last_used);
XML_PUT_STRING("\t\t", *ci, last_topic);
XML_PUT_STRING("\t\t", *ci, last_topic_setter);
XML_PUT_LONG ("\t\t", *ci, last_topic_time);
XML_PUT_LONG ("\t\t", *ci, flags);
if (ci->flags & CF_SUSPENDED) {
XML_PUT_STRING("\t\t", *ci, suspend_who);
XML_PUT_STRING("\t\t", *ci, suspend_reason);
XML_PUT_LONG ("\t\t", *ci, suspend_time);
XML_PUT_LONG ("\t\t", *ci, suspend_expires);
}
writefunc(data, "\t\t<levels>\n");
#define XML_PUT_LEVEL(lev) \
if (ci->levels[lev] != ACCLEV_DEFAULT) \
writefunc(data, "\t\t\t<" #lev ">%d</" #lev ">\n", ci->levels[lev])
XML_PUT_LEVEL(CA_INVITE);
XML_PUT_LEVEL(CA_AKICK);
XML_PUT_LEVEL(CA_SET);
XML_PUT_LEVEL(CA_UNBAN);
XML_PUT_LEVEL(CA_AUTOOP);
XML_PUT_LEVEL(CA_AUTODEOP);
XML_PUT_LEVEL(CA_AUTOVOICE);
XML_PUT_LEVEL(CA_OPDEOP);
XML_PUT_LEVEL(CA_ACCESS_LIST);
XML_PUT_LEVEL(CA_CLEAR);
XML_PUT_LEVEL(CA_NOJOIN);
XML_PUT_LEVEL(CA_ACCESS_CHANGE);
XML_PUT_LEVEL(CA_MEMO);
XML_PUT_LEVEL(CA_VOICE);
XML_PUT_LEVEL(CA_AUTOHALFOP);
XML_PUT_LEVEL(CA_HALFOP);
XML_PUT_LEVEL(CA_AUTOPROTECT);
XML_PUT_LEVEL(CA_PROTECT);
#undef XML_PUT_LEVEL
writefunc(data, "\t\t</levels>\n");
writefunc(data, "\t\t<chanaccesslist count='%d'>\n", ci->access_count);
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup) { // Skip empty entries
writefunc(data, "\t\t\t<chanaccess>\n");
XML_PUT_ULONG ("\t\t\t\t", ci->access[i], nickgroup);
XML_PUT_LONG ("\t\t\t\t", ci->access[i], level);
writefunc(data, "\t\t\t</chanaccess>\n");
}
}
writefunc(data, "\t\t</chanaccesslist>\n");
writefunc(data, "\t\t<akicklist count='%d'>\n", ci->akick_count);
ARRAY_FOREACH (i, ci->akick) {
writefunc(data, "\t\t\t<akick>\n");
XML_PUT_STRING("\t\t\t\t", ci->akick[i], mask);
XML_PUT_STRING("\t\t\t\t", ci->akick[i], reason);
XML_PUT_STRING("\t\t\t\t", ci->akick[i], who);
XML_PUT_LONG ("\t\t\t\t", ci->akick[i], set);
XML_PUT_LONG ("\t\t\t\t", ci->akick[i], lastused);
writefunc(data, "\t\t\t</akick>\n");
}
writefunc(data, "\t\t</akicklist>\n");
writefunc(data, "\t\t<mlock>\n");
#ifdef CONVERT_DB
XML_PUT_STRING("\t\t\t", *ci, mlock.on);
XML_PUT_STRING("\t\t\t", *ci, mlock.off);
#else
writefunc(data, "\t\t\t<mlock.on>%s</mlock.on>\n",
mode_flags_to_string(ci->mlock.on, MODE_CHANNEL));
writefunc(data, "\t\t\t<mlock.off>%s</mlock.off>\n",
mode_flags_to_string(ci->mlock.off, MODE_CHANNEL));
#endif
XML_PUT_LONG ("\t\t\t", *ci, mlock.limit);
XML_PUT_STRING("\t\t\t", *ci, mlock.key);
XML_PUT_STRING("\t\t\t", *ci, mlock.link);
XML_PUT_STRING("\t\t\t", *ci, mlock.flood);
XML_PUT_LONG ("\t\t\t", *ci, mlock.joindelay);
XML_PUT_LONG ("\t\t\t", *ci, mlock.joinrate1);
XML_PUT_LONG ("\t\t\t", *ci, mlock.joinrate2);
writefunc(data, "\t\t</mlock>\n");
XML_PUT_STRING("\t\t", *ci, entry_message);
writefunc(data, "\t</channelinfo>\n");
}
return 1;
}
/*************************************************************************/
static int export_news_db(xml_writefunc_t writefunc, void *data)
{
NewsItem *news;
for (news = first_news(); news; news = next_news()) {
writefunc(data, "\t<news>\n");
XML_PUT_LONG ("\t\t", *news, type);
XML_PUT_LONG ("\t\t", *news, num);
XML_PUT_STRING("\t\t", *news, text);
XML_PUT_STRING("\t\t", *news, who);
XML_PUT_LONG ("\t\t", *news, time);
writefunc(data, "\t</news>\n");
}
return 1;
}
/*************************************************************************/
static int export_maskdata(xml_writefunc_t writefunc, void *data)
{
int i;
MaskData *md;
for (i = 0; i < 256; i++) {
for (md = first_maskdata(i); md; md = next_maskdata(i)) {
writefunc(data, "\t<maskdata type='%d'>\n", i);
XML_PUT_LONG ("\t\t", *md, num);
XML_PUT_STRING("\t\t", *md, mask);
if (md->limit)
XML_PUT_LONG("\t\t",*md, limit);
XML_PUT_STRING("\t\t", *md, reason);
XML_PUT_STRING("\t\t", *md, who);
XML_PUT_LONG ("\t\t", *md, time);
XML_PUT_LONG ("\t\t", *md, expires);
XML_PUT_LONG ("\t\t", *md, lastused);
writefunc(data, "\t</maskdata>\n");
}
}
return 1;
}
/*************************************************************************/
static int export_statserv_db(xml_writefunc_t writefunc, void *data)
{
ServerStats *ss;
for (ss = first_serverstats(); ss; ss = next_serverstats()) {
writefunc(data, "\t<serverstats>\n");
XML_PUT_STRING("\t\t", *ss, name);
XML_PUT_LONG ("\t\t", *ss, t_join);
XML_PUT_LONG ("\t\t", *ss, t_quit);
XML_PUT_STRING("\t\t", *ss, quit_message);
writefunc(data, "\t</serverstats>\n");
}
return 1;
}
/*************************************************************************/
/**************************** Global routines ****************************/
/*************************************************************************/
EXPORT_FUNC(xml_export)
int xml_export(xml_writefunc_t writefunc, void *data)
{
writefunc(data, "<?xml version='1.0'?>\n<ircservices-db>\n");
return export_constants(writefunc, data)
&& export_operserv_data(writefunc, data)
&& export_nick_db(writefunc, data)
&& export_channel_db(writefunc, data)
&& export_news_db(writefunc, data)
&& export_maskdata(writefunc, data)
&& export_statserv_db(writefunc, data)
&& (writefunc(data, "</ircservices-db>\n"), 1);
}
/*************************************************************************/
/*************************************************************************/
#ifndef CONVERT_DB
/* Command-line option callback. */
static int do_command_line(const char *option, const char *value)
{
FILE *f;
if (!option || strcmp(option, "export") != 0)
return 0;
if (!value || !*value || strcmp(value,"-") == 0) {
f = stdout;
} else {
f = fopen(value, "w");
if (!f) {
perror(value);
return 2;
}
}
if (!xml_export((xml_writefunc_t)fprintf, f))
return 2;
return 3;
}
#endif /* CONVERT_DB */
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
#ifndef CONVERT_DB
/*************************************************************************/
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
if (!add_callback(NULL, "command line", do_command_line)) {
module_log("Unable to add callback");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
remove_callback(NULL, "command line", do_command_line);
return 1;
}
/*************************************************************************/
#endif /* CONVERT_DB */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

2731
modules/misc/xml-import.c Normal file

File diff suppressed because it is too large Load Diff

33
modules/misc/xml.h Normal file
View File

@ -0,0 +1,33 @@
/* Header for XML import/export modules.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef XML_H
#define XML_H
/*************************************************************************/
/* Type for write function passed to export routine. */
typedef int (*xml_writefunc_t)(void *data, const char *fmt, ...);
/* The export routine itself. */
extern int xml_export(xml_writefunc_t writefunc, void *data);
/*************************************************************************/
#endif /* XML_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

35
modules/nickserv/Makefile Normal file
View File

@ -0,0 +1,35 @@
# Makefile for NickServ modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so access.so autojoin.so link.so mail-auth.so
OBJECTS-main.so = collide.o set.o util.o
INCLUDES = nickserv.h ns-local.h $(TOPDIR)/language.h $(TOPDIR)/encrypt.h \
$(TOPDIR)/modules/memoserv/memoserv.h
INCLUDES-collide.o = $(TOPDIR)/timeout.h
INCLUDES-set.o = $(TOPDIR)/encrypt.h $(TOPDIR)/modules/operserv/operserv.h
INCLUDES-main.o = $(TOPDIR)/commands.h $(TOPDIR)/databases.h \
$(TOPDIR)/encrypt.h $(TOPDIR)/modules/operserv/operserv.h
INCLUDES-access.o = $(TOPDIR)/commands.h $(TOPDIR)/databases.h \
$(TOPDIR)/modules/operserv/operserv.h
INCLUDES-autojoin.o = $(TOPDIR)/commands.h $(TOPDIR)/databases.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-link.o = $(TOPDIR)/commands.h \
$(TOPDIR)/modules/operserv/operserv.h
INCLUDES-mail-auth.o = $(TOPDIR)/commands.h $(TOPDIR)/modules/mail/mail.h \
$(TOPDIR)/modules/operserv/operserv.h
include ../Makerules
###########################################################################

358
modules/nickserv/access.c Normal file
View File

@ -0,0 +1,358 @@
/* Nickname access list module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "databases.h"
#include "modules/operserv/operserv.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
static Module *module_nickserv;
static int32 NSAccessMax;
static int NSFirstAccessEnable;
static int NSFirstAccessWild;
/*************************************************************************/
static void do_access(User *u);
static Command cmds[] = {
{"ACCESS", do_access, NULL, NICK_HELP_ACCESS,
-1, NICK_OPER_HELP_ACCESS},
{ NULL }
};
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
/* See autojoin.c for why we don't create our own table. */
/* Temporary structure for loading/saving access records */
typedef struct {
uint32 nickgroup;
char *mask;
} DBRecord;
static DBRecord dbrec_static;
/* Iterators for first/next routines */
static NickGroupInfo *db_ngi_iterator;
static int db_array_iterator;
/*************************************************************************/
/* Table access routines */
static void *new_access(void)
{
return memset(&dbrec_static, 0, sizeof(dbrec_static));
}
static void free_access(void *record)
{
free(((DBRecord *)record)->mask);
}
static void insert_access(void *record)
{
DBRecord *dbrec = record;
NickGroupInfo *ngi = get_nickgroupinfo(dbrec->nickgroup);
if (!ngi) {
module_log("Discarding access record for missing nickgroup %u: %s",
dbrec->nickgroup, dbrec->mask);
free_access(record);
} else {
ARRAY_EXTEND(ngi->access);
ngi->access[ngi->access_count-1] = dbrec->mask;
}
}
static void *next_access(void)
{
while (db_ngi_iterator
&& db_array_iterator >= db_ngi_iterator->access_count
) {
db_ngi_iterator = next_nickgroupinfo();
db_array_iterator = 0;
}
if (db_ngi_iterator) {
dbrec_static.nickgroup = db_ngi_iterator->id;
dbrec_static.mask = db_ngi_iterator->access[db_array_iterator++];
return &dbrec_static;
} else {
return NULL;
}
}
static void *first_access(void)
{
db_ngi_iterator = first_nickgroupinfo();
db_array_iterator = 0;
return next_access();
}
/*************************************************************************/
/* Database table definition */
#define FIELD(name,type,...) \
{ #name, type, offsetof(DBRecord,name) , ##__VA_ARGS__ }
static DBField access_dbfields[] = {
FIELD(nickgroup, DBTYPE_UINT32),
FIELD(mask, DBTYPE_STRING),
{ NULL }
};
static DBTable access_dbtable = {
.name = "nick-access",
.newrec = new_access,
.freerec = free_access,
.insert = insert_access,
.first = first_access,
.next = next_access,
.fields = access_dbfields,
};
#undef FIELD
/*************************************************************************/
/**************************** Local routines *****************************/
/*************************************************************************/
/* Handle the ACCESS command. */
static void do_access(User *u)
{
char *cmd = strtok(NULL, " ");
char *mask = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
int i;
if (cmd && stricmp(cmd, "LIST") == 0 && mask && is_services_admin(u)) {
ni = get_nickinfo(mask);
ngi = NULL;
if (!ni) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, mask);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, mask);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (ngi->access_count == 0) {
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X_EMPTY, mask);
} else {
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X, mask);
ARRAY_FOREACH (i, ngi->access)
notice(s_NickServ, u->nick, " %s", ngi->access[i]);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
} else if (!cmd || ((stricmp(cmd,"LIST")==0) ? mask!=NULL : mask==NULL)) {
syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX);
} else if (mask && !strchr(mask, '@')) {
notice_lang(s_NickServ, u, BAD_USERHOST_MASK);
notice_lang(s_NickServ, u, MORE_INFO, s_NickServ, "ACCESS");
} else if (ngi = u->ngi, !(ni = u->ni)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (stricmp(cmd, "ADD") == 0) {
if (readonly) {
notice_lang(s_NickServ, u, NICK_ACCESS_DISABLED);
return;
}
if (ngi->access_count >= NSAccessMax) {
notice_lang(s_NickServ, u, NICK_ACCESS_REACHED_LIMIT, NSAccessMax);
return;
}
ARRAY_FOREACH (i, ngi->access) {
if (stricmp(ngi->access[i], mask) == 0) {
notice_lang(s_NickServ, u, NICK_ACCESS_ALREADY_PRESENT, mask);
return;
}
}
if (strchr(mask, '!'))
notice_lang(s_NickServ, u, NICK_ACCESS_NO_NICKS);
ARRAY_EXTEND(ngi->access);
ngi->access[ngi->access_count-1] = sstrdup(mask);
notice_lang(s_NickServ, u, NICK_ACCESS_ADDED, mask);
} else if (stricmp(cmd, "DEL") == 0) {
if (readonly) {
notice_lang(s_NickServ, u, NICK_ACCESS_DISABLED);
return;
}
/* First try for an exact match; then, a case-insensitive one. */
ARRAY_SEARCH_PLAIN(ngi->access, mask, strcmp, i);
if (i == ngi->access_count)
ARRAY_SEARCH_PLAIN(ngi->access, mask, stricmp, i);
if (i == ngi->access_count) {
notice_lang(s_NickServ, u, NICK_ACCESS_NOT_FOUND, mask);
return;
}
notice_lang(s_NickServ, u, NICK_ACCESS_DELETED, ngi->access[i]);
free(ngi->access[i]);
ARRAY_REMOVE(ngi->access, i);
} else if (stricmp(cmd, "LIST") == 0) {
if (ngi->access_count == 0) {
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_EMPTY);
} else {
notice_lang(s_NickServ, u, NICK_ACCESS_LIST);
ARRAY_FOREACH (i, ngi->access)
notice(s_NickServ, u->nick, " %s", ngi->access[i]);
}
} else {
syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX);
}
}
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Nick-registration callback (initializes access list). */
static int do_registered(User *u, NickInfo *ni, NickGroupInfo *ngi,
int *replied)
{
if (NSFirstAccessEnable) {
ngi->access_count = 1;
ngi->access = smalloc(sizeof(char *));
if (NSFirstAccessWild) {
ngi->access[0] = create_mask(u, 0);
} else {
ngi->access[0] = smalloc(strlen(u->username)+strlen(u->host)+2);
sprintf(ngi->access[0], "%s@%s", u->username, u->host);
}
}
return 0;
}
/*************************************************************************/
/* Check whether a user is on the access list of the nick they're using.
* Return 1 if on the access list, 0 if not.
*/
static int check_on_access(User *u)
{
int i;
char buf[BUFSIZE];
if (!u->ni || !u->ngi) {
module_log("check_on_access() BUG: ni or ngi is NULL!");
return 0;
}
if (u->ngi->access_count == 0)
return 0;
i = strlen(u->username);
snprintf(buf, sizeof(buf), "%s@%s", u->username, u->host);
ARRAY_FOREACH (i, u->ngi->access) {
if (match_wild_nocase(u->ngi->access[i], buf))
return 1;
}
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NSAccessMax", { { CD_POSINT, CF_DIRREQ, &NSAccessMax } } },
{ "NSFirstAccessEnable",{{CD_SET, 0, &NSFirstAccessEnable } } },
{ "NSFirstAccessWild",{ { CD_SET, 0, &NSFirstAccessWild } } },
{ NULL }
};
/*************************************************************************/
int init_module()
{
if (NSAccessMax > MAX_NICK_ACCESS) {
module_log("NSAccessMax upper-bounded at MAX_NICK_ACCESS (%d)",
MAX_NICK_ACCESS);
NSAccessMax = MAX_NICK_ACCESS;
}
module_nickserv = find_module("nickserv/main");
if (!module_nickserv) {
module_log("Main NickServ module not loaded");
return 0;
}
use_module(module_nickserv);
if (!register_commands(module_nickserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(module_nickserv, "check recognized", check_on_access)
|| !add_callback(module_nickserv, "registered", do_registered)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&access_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&access_dbtable);
if (module_nickserv) {
remove_callback(module_nickserv, "registered", do_registered);
remove_callback(module_nickserv, "check recognized", check_on_access);
unregister_commands(module_nickserv, cmds);
unuse_module(module_nickserv);
module_nickserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

459
modules/nickserv/autojoin.c Normal file
View File

@ -0,0 +1,459 @@
/* NickServ auto-join module.
* Written by Yusuf Iskenderoglu <uhc0@stud.uni-karlsruhe.de>
* Idea taken from PTlink Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "databases.h"
#include "nickserv.h"
#include "modules/operserv/operserv.h"
#include "modules/chanserv/chanserv.h"
/*************************************************************************/
static Module *module_nickserv;
static Module *module_chanserv;
static typeof(check_access_cmd) *check_access_cmd_p;
static int cb_send_svsjoin = -1;
static int NSAutojoinMax;
/*************************************************************************/
static void do_ajoin(User *u);
static Command cmds[] = {
{ "AJOIN", do_ajoin, NULL, NICK_HELP_AJOIN,
-1, NICK_OPER_HELP_AJOIN },
{ NULL }
};
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
/*
* Implementation note:
*
* Ideally, we would implement the autojoin database as e.g. a hash table
* completely separate from the nickgroup table, with each record
* consisting of a nickgroup ID and array of channels; in fact, if we had
* a working SELECT-like function, we could just have one channel per
* record and iterate through all matches for a particular ID. Aside from
* having to hook into the nickgroup delete function to ensure thaat
* autojoin records are deleted along with nickgroup data, this would make
* the autojoin database completely independent.
*
* However, saving autojoin data using the database/version4 module would
* no longer work correctly, because the database code would have no easy
* way to know whether the table even existed, and would also require the
* table's get() function to be exported from this module (or else have to
* use first()/next() for _every_ nickgroup to find the appropriate
* autojoin data, if any).
*
* For this reason, the ajoin[] array has been left in the NickGroupInfo
* structure in this version. A proper database implementation is left as
* an exercise for the reader.
*/
/*************************************************************************/
/* Temporary structure for loading/saving autojoin records */
typedef struct {
uint32 nickgroup;
char *channel;
} DBRecord;
static DBRecord dbrec_static;
/* Iterators for first/next routines */
static NickGroupInfo *db_ngi_iterator;
static int db_array_iterator;
/*************************************************************************/
/* Table access routines */
static void *new_autojoin(void)
{
return memset(&dbrec_static, 0, sizeof(dbrec_static));
}
static void free_autojoin(void *record)
{
free(((DBRecord *)record)->channel);
}
static void insert_autojoin(void *record)
{
DBRecord *dbrec = record;
NickGroupInfo *ngi = get_nickgroupinfo(dbrec->nickgroup);
if (!ngi) {
module_log("Discarding autojoin record for missing nickgroup %u: %s",
dbrec->nickgroup, dbrec->channel);
free_autojoin(record);
} else {
ARRAY_EXTEND(ngi->ajoin);
ngi->ajoin[ngi->ajoin_count-1] = dbrec->channel;
}
}
static void *next_autojoin(void)
{
while (db_ngi_iterator
&& db_array_iterator >= db_ngi_iterator->ajoin_count
) {
db_ngi_iterator = next_nickgroupinfo();
db_array_iterator = 0;
}
if (db_ngi_iterator) {
dbrec_static.nickgroup = db_ngi_iterator->id;
dbrec_static.channel = db_ngi_iterator->ajoin[db_array_iterator++];
return &dbrec_static;
} else {
return NULL;
}
}
static void *first_autojoin(void)
{
db_ngi_iterator = first_nickgroupinfo();
db_array_iterator = 0;
return next_autojoin();
}
/*************************************************************************/
/* Database table definition */
#define FIELD(name,type,...) \
{ #name, type, offsetof(DBRecord,name) , ##__VA_ARGS__ }
static DBField autojoin_dbfields[] = {
FIELD(nickgroup, DBTYPE_UINT32),
FIELD(channel, DBTYPE_STRING),
{ NULL }
};
static DBTable autojoin_dbtable = {
.name = "nick-autojoin",
.newrec = new_autojoin,
.freerec = free_autojoin,
.insert = insert_autojoin,
.first = first_autojoin,
.next = next_autojoin,
.fields = autojoin_dbfields,
};
#undef FIELD
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
/* Send autojoin commands for an identified user. */
static int do_identified(User *u, int unused)
{
NickGroupInfo *ngi = u->ngi;
int i;
ARRAY_FOREACH (i, ngi->ajoin) {
struct u_chanlist *uc;
if (!valid_chan(ngi->ajoin[i])) {
notice_lang(s_NickServ, u, NICK_AJOIN_AUTO_REMOVE, ngi->ajoin[i]);
free(ngi->ajoin[i]);
ARRAY_REMOVE(ngi->ajoin, i);
i--;
continue;
}
LIST_SEARCH(u->chans, chan->name, ngi->ajoin[i], irc_stricmp, uc);
if (!uc) {
Channel *c = get_channel(ngi->ajoin[i]);
if (c && (c->mode & CMODE_i)
&& c->ci && check_access_cmd_p
&& (*check_access_cmd_p)(u, c->ci, "INVITE", NULL) > 0
) {
send_cmd(s_NickServ, "INVITE %s %s", u->nick, ngi->ajoin[i]);
}
call_callback_2(cb_send_svsjoin, u->nick, ngi->ajoin[i]);
}
}
return 0;
}
/*************************************************************************/
/* Handle autojoin help. */
static int do_help(User *u, const char *param)
{
if (stricmp(param, "AJOIN") == 0) {
Module *mod;
notice_help(s_NickServ, u, NICK_HELP_AJOIN);
if ((mod = find_module("chanserv/main")) != NULL) {
const char *my_s_ChanServ;
const char **ptr = get_module_symbol(mod, "s_ChanServ");
if (ptr) {
my_s_ChanServ = *ptr;
} else {
static int warned = 0;
if (!warned) {
module_log("HELP AJOIN: cannot retrieve symbol"
" `s_ChanServ' from module `chanserv/main'");
warned = 1;
}
my_s_ChanServ = "ChanServ";
}
notice_help(s_NickServ, u, NICK_HELP_AJOIN_END_CHANSERV,
my_s_ChanServ);
} else {
notice_help(s_NickServ, u, NICK_HELP_AJOIN_END);
}
return 1;
}
return 0;
}
/*************************************************************************/
/*************************** Command functions ***************************/
/*************************************************************************/
void do_ajoin(User *u)
{
char *cmd = strtok(NULL, " ");
char *chan = strtok(NULL, " ");
NickGroupInfo *ngi = u->ngi;
int i;
if (cmd && chan && stricmp(cmd,"LIST") == 0 && is_services_admin(u)) {
NickInfo *ni = get_nickinfo(chan);
ngi = NULL;
if (!ni) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, chan);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, chan);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->ajoin_count) {
notice_lang(s_NickServ, u, NICK_AJOIN_LIST_X_EMPTY, chan);
} else {
notice_lang(s_NickServ, u, NICK_AJOIN_LIST_X, chan);
ARRAY_FOREACH (i, ngi->ajoin)
notice(s_NickServ, u->nick, " %s", ngi->ajoin[i]);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
} else if (!cmd || ((stricmp(cmd,"LIST")==0) && chan)) {
syntax_error(s_NickServ, u, "AJOIN", NICK_AJOIN_SYNTAX);
} else if (!valid_ngi(u)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (stricmp(cmd, "ADD") == 0) {
if (readonly) {
notice_lang(s_NickServ, u, NICK_AJOIN_DISABLED);
return;
}
if (!chan || *chan != '#') {
syntax_error(s_NickServ, u, "AJOIN", NICK_AJOIN_ADD_SYNTAX);
return;
}
if (!valid_chan(chan)) {
notice_lang(s_NickServ, u, CHAN_INVALID, chan);
return;
}
if (ngi->ajoin_count + 1 > NSAutojoinMax) {
notice_lang(s_NickServ, u, NICK_AJOIN_LIST_FULL, NSAutojoinMax);
return;
}
ARRAY_FOREACH (i, ngi->ajoin) {
if (stricmp(ngi->ajoin[i], chan) == 0) {
notice_lang(s_NickServ, u,
NICK_AJOIN_ALREADY_PRESENT, ngi->ajoin[i]);
return;
}
}
ARRAY_EXTEND(ngi->ajoin);
ngi->ajoin[ngi->ajoin_count-1] = sstrdup(chan);
notice_lang(s_NickServ, u, NICK_AJOIN_ADDED, chan);
} else if (stricmp(cmd, "DEL") == 0) {
if (readonly) {
notice_lang(s_NickServ, u, NICK_AJOIN_DISABLED);
return;
}
if (!chan || *chan != '#') {
syntax_error(s_NickServ, u, "AJOIN", NICK_AJOIN_DEL_SYNTAX);
return;
}
ARRAY_SEARCH_PLAIN(ngi->ajoin, chan, strcmp, i);
if (i == ngi->ajoin_count)
ARRAY_SEARCH_PLAIN(ngi->ajoin, chan, irc_stricmp, i);
if (i == ngi->ajoin_count) {
notice_lang(s_NickServ, u, NICK_AJOIN_NOT_FOUND, chan);
return;
}
free(ngi->ajoin[i]);
ARRAY_REMOVE(ngi->ajoin, i);
notice_lang(s_NickServ, u, NICK_AJOIN_DELETED, chan);
} else if (stricmp(cmd, "LIST") == 0) {
if (!ngi->ajoin_count) {
notice_lang(s_NickServ, u, NICK_AJOIN_LIST_EMPTY);
} else {
notice_lang(s_NickServ, u, NICK_AJOIN_LIST);
ARRAY_FOREACH (i, ngi->ajoin)
notice(s_NickServ, u->nick, " %s", ngi->ajoin[i]);
}
} else {
syntax_error(s_NickServ, u, "AJOIN", NICK_AJOIN_SYNTAX);
}
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NSAutojoinMax", { { CD_POSINT, CF_DIRREQ, &NSAutojoinMax } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *name)
{
if (strcmp(name,"chanserv/main") == 0) {
module_chanserv = mod;
if (!(check_access_cmd_p = get_module_symbol(mod,"check_access_cmd"))){
module_log("Symbol `check_access_cmd' not found, auto-inviting"
" disabled");
}
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_chanserv) {
check_access_cmd_p = NULL;
module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_module()
{
Module *mod;
if (!(protocol_features & PF_SVSJOIN)) {
if (protocol_features & PF_UNSET) {
module_log("No protocol module loaded--you must load a"
" protocol module before loading this module");
} else {
module_log("SVSJOIN not supported by this IRC server (%s)",
protocol_name);
}
return 0;
}
module_nickserv = find_module("nickserv/main");
if (!module_nickserv) {
module_log("Main NickServ module not loaded");
return 0;
}
use_module(module_nickserv);
if (!register_commands(module_nickserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
cb_send_svsjoin = register_callback("send_svsjoin");
if (cb_send_svsjoin < 0) {
module_log("Unable to register callback");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(module_nickserv, "identified", do_identified)
|| !add_callback(module_nickserv, "HELP", do_help)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&autojoin_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
mod = find_module("chanserv/main");
if (mod)
do_load_module(mod, "chanserv/main");
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (module_chanserv)
do_unload_module(module_chanserv);
unregister_dbtable(&autojoin_dbtable);
if (module_nickserv) {
remove_callback(module_nickserv, "HELP", do_help);
remove_callback(module_nickserv, "identified", do_identified);
unregister_commands(module_nickserv, cmds);
unuse_module(module_nickserv);
module_nickserv = NULL;
}
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_callback(cb_send_svsjoin);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

258
modules/nickserv/collide.c Normal file
View File

@ -0,0 +1,258 @@
/* Routines related to nickname colliding.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "timeout.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
static int cb_collide = -1;
/* Structure used for collide/release/433 timeouts */
static struct my_timeout {
struct my_timeout *next, *prev;
NickInfo *ni;
Timeout *to;
int type;
} *my_timeouts;
static void timeout_collide(Timeout *t);
static void timeout_release(Timeout *t);
static void timeout_send_433(Timeout *t);
/*************************************************************************/
/*************************************************************************/
/* Collide a nick. */
void collide_nick(NickInfo *ni, int from_timeout)
{
if (!ni->user)
return;
if (!from_timeout) {
rem_ns_timeout(ni, TO_COLLIDE, 1);
rem_ns_timeout(ni, TO_SEND_433, 1);
}
if (call_callback_1(cb_collide, ni->user) > 0)
return;
if (NSForceNickChange) {
char *guestnick = make_guest_nick();
notice_lang(s_NickServ, ni->user, FORCENICKCHANGE_NOW, guestnick);
send_nickchange_remote(ni->nick, guestnick);
ni->status |= NS_GUESTED;
return;
} else {
notice_lang(s_NickServ, ni->user, DISCONNECT_NOW);
kill_user(s_NickServ, ni->nick, "Nick kill enforced");
introduce_enforcer(ni);
}
}
/*************************************************************************/
/* Introduce an enforcer for a given nick. */
void introduce_enforcer(NickInfo *ni)
{
char realname[NICKMAX+16]; /*Long enough for s_NickServ + " Enforcement"*/
snprintf(realname, sizeof(realname), "%s Enforcement", s_NickServ);
send_nick(ni->nick, NSEnforcerUser, NSEnforcerHost, ServerName,
realname, enforcer_modes);
ni->status |= NS_KILL_HELD;
add_ns_timeout(ni, TO_RELEASE, NSReleaseTimeout);
}
/*************************************************************************/
/* Release hold on a nick. */
void release_nick(NickInfo *ni, int from_timeout)
{
if (!from_timeout)
rem_ns_timeout(ni, TO_RELEASE, 1);
send_cmd(ni->nick, "QUIT");
ni->status &= ~NS_KILL_HELD;
}
/*************************************************************************/
/* Add a collide/release/433 timeout. */
void add_ns_timeout(NickInfo *ni, int type, time_t delay)
{
Timeout *to;
struct my_timeout *t;
void (*timeout_routine)(Timeout *);
if (!ni) {
log("BUG: NULL NickInfo in add_ns_timeout (type=%d delay=%ld)",
type, (long)delay);
return;
}
if (type == TO_COLLIDE)
timeout_routine = timeout_collide;
else if (type == TO_RELEASE)
timeout_routine = timeout_release;
else if (type == TO_SEND_433)
timeout_routine = timeout_send_433;
else {
module_log("BUG: unknown timeout type %d! ni=%p (%s), delay=%ld",
type, ni, ni->nick, (long)delay);
return;
}
to = add_timeout(delay, timeout_routine, 0);
to->data = ni;
t = smalloc(sizeof(*t));
LIST_INSERT(t, my_timeouts);
t->ni = ni;
t->to = to;
t->type = type;
ni->usecount++; /* make sure it isn't deleted when we're not looking */
}
/*************************************************************************/
/* Remove a collide/release timeout from our private list. If del_to is
* nonzero, also delete the associated timeout. If type == -1, delete
* timeouts of all types. If ni == NULL, delete all timeouts of the given
* type(s).
*/
void rem_ns_timeout(NickInfo *ni, int type, int del_to)
{
struct my_timeout *t, *t2;
LIST_FOREACH_SAFE (t, my_timeouts, t2) {
if ((!ni || t->ni == ni) && (type < 0 || t->type == type)) {
LIST_REMOVE(t, my_timeouts);
if (del_to)
del_timeout(t->to);
put_nickinfo(t->ni); /* cancel usecount++ above */
free(t);
}
}
}
/*************************************************************************/
/*************************************************************************/
/* Collide a nick on timeout. */
static void timeout_collide(Timeout *t)
{
NickInfo *ni = t->data;
int idented = 0;
if (ni) {
if (ni->nickgroup != 0) {
NickGroupInfo *ngi = get_ngi(ni);
idented = (ngi && (nick_identified(ni) || nick_ident_nomail(ni)));
put_nickgroupinfo(ngi);
}
} else {
log("BUG: NULL NickInfo in timeout_collide");
return;
}
/* If they identified or don't exist anymore, don't kill them. */
if (!(idented || !ni->user || ni->user->my_signon > t->settime)) {
/* The RELEASE timeout will always add to the beginning of the
* list, so we won't see it. Which is fine because it can't be
* triggered yet anyway. */
collide_nick(ni, 1);
}
rem_ns_timeout(ni, TO_COLLIDE, 0);
}
/*************************************************************************/
/* Release a nick on timeout. */
static void timeout_release(Timeout *t)
{
NickInfo *ni = t->data;
if (!ni) {
log("BUG: NULL NickInfo in timeout_release");
return;
}
release_nick(ni, 1);
rem_ns_timeout(ni, TO_RELEASE, 0);
}
/*************************************************************************/
/* Send a 433 (nick in use) numeric to the given user. */
static void timeout_send_433(Timeout *t)
{
NickInfo *ni = t->data;
User *u;
if (!ni) {
log("BUG: NULL NickInfo in timeout_send_433");
return;
}
/* If they identified or don't exist anymore, don't send the 433. */
if (!((nick_identified(ni) || nick_ident_nomail(ni))
|| !(u = get_user(ni->nick))
|| u->my_signon > t->settime)
) {
if (ni->status & NS_VERBOTEN) {
send_cmd(ServerName, "433 %s %s :Nickname may not be used",
ni->nick, ni->nick);
} else {
send_cmd(ServerName, "433 %s %s :Nickname is registered to"
" someone else", ni->nick, ni->nick);
}
}
rem_ns_timeout(ni, TO_SEND_433, 0);
}
/*************************************************************************/
/*************************************************************************/
int init_collide(void)
{
cb_collide = register_callback("collide");
if (cb_collide < 0) {
module_log("collide: Unable to register callbacks");
exit_collide();
return 0;
}
return 1;
}
/*************************************************************************/
void exit_collide()
{
rem_ns_timeout(NULL, -1, 1);
unregister_callback(cb_collide);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

319
modules/nickserv/link.c Normal file
View File

@ -0,0 +1,319 @@
/* Nickname linking module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "commands.h"
#include "modules/operserv/operserv.h"
#include "modules/chanserv/chanserv.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
static Module *module_nickserv;
static int32 NSLinkMax;
/*************************************************************************/
static void do_link(User *u);
static void do_unlink(User *u);
static void do_listlinks(User *u);
static Command cmds[] = {
{ "LINK", do_link, NULL, NICK_HELP_LINK, -1,-1 },
{ "UNLINK", do_unlink, NULL, -1,
NICK_HELP_UNLINK, NICK_OPER_HELP_UNLINK, },
{ "LISTLINKS",do_listlinks,NULL, -1,
NICK_HELP_LISTLINKS, NICK_OPER_HELP_LISTLINKS },
{ "SET MAINNICK", NULL, NULL, NICK_HELP_SET_MAINNICK, -1,-1 },
{ NULL }
};
/*************************************************************************/
/*************************** Command functions ***************************/
/*************************************************************************/
static void do_link(User *u)
{
char *nick = strtok(NULL, " ");
NickInfo *ni = u->ni, *ni2;
NickGroupInfo *ngi = u->ngi;
int n;
if (readonly && !is_services_admin(u)) {
notice_lang(s_NickServ, u, NICK_LINK_DISABLED);
} else if (!nick) {
syntax_error(s_NickServ, u, "LINK", NICK_LINK_SYNTAX);
} else if (strlen(nick) > protocol_nickmax) {
notice_lang(s_NickServ, u, NICK_TOO_LONG, protocol_nickmax);
} else if (!valid_nick(nick)) {
notice_lang(s_NickServ, u, NICK_INVALID, nick);
} else if (!reglink_check(u, nick, NULL, NULL)) {
notice_lang(s_NickServ, u, NICK_CANNOT_BE_LINKED, nick);
return;
} else if (!ni || !ngi || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (irc_stricmp(u->nick, nick) == 0) {
notice_lang(s_NickServ, u, NICK_LINK_SAME);
} else if ((ni2 = get_nickinfo(nick)) != NULL) {
int i;
ARRAY_SEARCH_PLAIN(ngi->nicks, nick, irc_stricmp, i);
if (i < ngi->nicks_count)
notice_lang(s_NickServ, u, NICK_LINK_ALREADY_LINKED, nick);
else if (ni2->status & NS_VERBOTEN)
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
else
notice_lang(s_NickServ, u, NICK_X_ALREADY_REGISTERED, nick);
put_nickinfo(ni2);
} else if (get_user(nick)) {
notice_lang(s_NickServ, u, NICK_LINK_IN_USE, nick);
} else if (ngi->nicks_count >= NSLinkMax) {
notice_lang(s_NickServ, u, NICK_LINK_TOO_MANY, NSLinkMax);
} else if (NSRegEmailMax && ngi->email && !is_services_admin(u)
&& (n=abs(count_nicks_with_email(ngi->email))) >= NSRegEmailMax) {
notice_lang(s_NickServ, u, NICK_LINK_TOO_MANY_NICKS, n,
NSRegEmailMax);
} else {
ni2 = makenick(nick, NULL);
if (ni->last_usermask)
ni2->last_usermask = sstrdup(ni->last_usermask);
if (ni->last_realmask)
ni2->last_realmask = sstrdup(ni->last_realmask);
if (ni->last_realname)
ni2->last_realname = sstrdup(ni->last_realname);
if (ni->last_quit)
ni2->last_quit = sstrdup(ni->last_quit);
ni2->time_registered = ni2->last_seen = time(NULL);
ni2->nickgroup = ni->nickgroup;
put_nickinfo(ni2);
ARRAY_EXTEND(ngi->nicks);
strbcpy(ngi->nicks[ngi->nicks_count-1], nick);
module_log("%s!%s@%s linked nick %s to %s",
u->nick, u->username, u->host, nick, u->nick);
notice_lang(s_NickServ, u, NICK_LINKED, nick);
if (readonly)
notice_lang(s_NickServ, u, READ_ONLY_MODE);
}
} /* do_link() */
/*************************************************************************/
static void do_unlink(User *u)
{
NickInfo *ni = u->ni, *ni2;
NickGroupInfo *ngi = u->ngi, *ngi2 = NULL;
char *nick = strtok(NULL, " ");
char *extra = strtok(NULL, " ");
int is_servadmin = is_services_admin(u);
int force = (extra != NULL && stricmp(extra,"FORCE") == 0);
if (readonly && !is_servadmin) {
notice_lang(s_NickServ, u, NICK_LINK_DISABLED);
} else if (!nick || (extra && (!is_oper(u) || !force))) {
syntax_error(s_NickServ, u, "UNLINK",
is_oper(u) ? NICK_UNLINK_OPER_SYNTAX
: NICK_UNLINK_SYNTAX);
} else if (force && !is_servadmin) {
notice_lang(s_NickServ, u, PERMISSION_DENIED);
} else if (!ni || !ngi || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (irc_stricmp(nick, u->nick) == 0) {
notice_lang(s_NickServ, u, NICK_UNLINK_SAME);
} else if (!(ni2 = get_nickinfo(nick)) || !ni2->nickgroup
|| !(ngi2 = get_ngi(ni2)) || ngi2->nicks_count == 1) {
notice_lang(s_NickServ, u, force ? NICK_UNLINK_NOT_LINKED
: NICK_UNLINK_NOT_LINKED_YOURS, nick);
} else if (!force && ni2->nickgroup != ni->nickgroup) {
notice_lang(s_NickServ, u, NICK_UNLINK_NOT_LINKED_YOURS, nick);
} else {
int msg;
char *param1;
if (ni2->nickgroup != ni->nickgroup) {
delnick(ni2);
msg = NICK_X_UNLINKED;
param1 = ngi_mainnick(ngi2);
} else {
delnick(ni2);
msg = NICK_UNLINKED;
param1 = ngi_mainnick(ngi); /* Not used, but for completeness */
}
if (force && WallAdminPrivs) {
wallops(s_NickServ, "\2%s\2 used UNLINK FORCE on \2%s\2",
u->nick, nick);
}
notice_lang(s_NickServ, u, msg, nick, param1);
module_log("%s!%s@%s unlinked nick %s from %s", u->nick,
u->username, u->host, nick, param1);
if (readonly)
notice_lang(s_NickServ, u, READ_ONLY_MODE);
}
put_nickgroupinfo(ngi2);
}
/*************************************************************************/
static void do_listlinks(User *u)
{
char *nick = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
int i;
if (nick) {
if (!is_services_oper(u)) {
syntax_error(s_NickServ, u, "LISTLINKS", NICK_LISTLINKS_SYNTAX);
return;
} else if (!(ni = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
return;
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, ni->nick);
put_nickinfo(ni);
return;
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
put_nickinfo(ni);
return;
}
} else {
if (!(ni = u->ni) || !(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
return;
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
return;
}
ni->usecount++;
ngi->usecount++;
}
notice_lang(s_NickServ, u, NICK_LISTLINKS_HEADER, ni->nick);
ARRAY_FOREACH (i, ngi->nicks) {
notice(s_NickServ, u->nick, " %c%s",
i==ngi->mainnick ? '*' : ' ', ngi->nicks[i]);
}
notice_lang(s_NickServ, u, NICK_LISTLINKS_FOOTER, ngi->nicks_count);
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/* SET MAINNICK handler */
static int do_set_mainnick(User *u, NickInfo *ni, NickGroupInfo *ngi,
const char *cmd, const char *param)
{
int i;
if (stricmp(cmd, "MAINNICK") != 0)
return 0;
ARRAY_SEARCH_PLAIN(ngi->nicks, param, irc_stricmp, i);
if (i >= ngi->nicks_count) {
notice_lang(s_NickServ, u, NICK_SET_MAINNICK_NOT_FOUND, param);
} else {
module_log("%s!%s@%s set main nick of %s (group %u) to %s",
u->nick, u->username, u->host, ngi_mainnick(ngi),
ngi->id, ngi->nicks[i]);
ngi->mainnick = i;
notice_lang(s_NickServ, u, NICK_SET_MAINNICK_CHANGED, param);
}
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NSLinkMax", { { CD_POSINT, CF_DIRREQ, &NSLinkMax } } },
{ NULL }
};
static int old_NICK_DROPPED = -1;
static int old_NICK_X_DROPPED = -1;
/*************************************************************************/
int init_module(void)
{
if (NSLinkMax > MAX_NICKCOUNT) {
module_log("NSLinkMax upper-bounded at MAX_NICKCOUNT (%d)",
MAX_NICKCOUNT);
NSLinkMax = MAX_NICKCOUNT;
}
module_nickserv = find_module("nickserv/main");
if (!module_nickserv) {
module_log("Main NickServ module not loaded");
return 0;
}
use_module(module_nickserv);
if (!register_commands(module_nickserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(module_nickserv, "SET", do_set_mainnick)) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
old_NICK_DROPPED = mapstring(NICK_DROPPED, NICK_DROPPED_LINKS);
old_NICK_X_DROPPED = mapstring(NICK_X_DROPPED, NICK_X_DROPPED_LINKS);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (old_NICK_DROPPED >= 0) {
mapstring(NICK_DROPPED, old_NICK_DROPPED);
old_NICK_DROPPED = -1;
}
if (old_NICK_X_DROPPED >= 0) {
mapstring(NICK_X_DROPPED, old_NICK_X_DROPPED);
old_NICK_X_DROPPED = -1;
}
if (module_nickserv) {
remove_callback(module_nickserv, "SET", do_set_mainnick);
unregister_commands(module_nickserv, cmds);
unuse_module(module_nickserv);
module_nickserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,772 @@
/* Nickname mail address authentication module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "language.h"
#include "modules/mail/mail.h"
#include "modules/operserv/operserv.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
/*
* This module provides the ability to verify the accuracy of E-mail
* addresses associated with nicknames. Upon registering a new nickname
* or changing the E-mail address of a nickname, a random authentication
* code is generated and mailed to the user, with instructions to use the
* AUTH command (provided by this module) to verify the nick registration
* or address change. Until this is done, the nick may not be identified
* for, essentially preventing use of the nick.
*
* The authentication code generated is a random 9-digit number,
* lower-bounded to 100,000,000 to avoid potential difficulties with
* leading zeroes.
*/
/*************************************************************************/
/*************************** Local variables *****************************/
/*************************************************************************/
static Module *module_nickserv;
static Module *module_mail;
static time_t NSNoAuthExpire = 0;
static time_t NSSendauthDelay = 0;
static int cb_authed = -1;
static void do_auth(User *u);
static void do_sendauth(User *u);
static void do_reauth(User *u);
static void do_restoremail(User *u);
static void do_setauth(User *u);
static void do_getauth(User *u);
static void do_clearauth(User *u);
static Command commands[] = {
{ "AUTH", do_auth, NULL, NICK_HELP_AUTH, -1,-1 },
{ "SENDAUTH", do_sendauth, NULL, NICK_HELP_SENDAUTH, -1,-1 },
{ "REAUTH", do_reauth, NULL, NICK_HELP_REAUTH, -1,-1 },
{ "RESTOREMAIL", do_restoremail, NULL, NICK_HELP_RESTOREMAIL,-1,-1},
{ "SETAUTH", do_setauth, is_services_admin,
-1,-1,NICK_OPER_HELP_SETAUTH },
{ "GETAUTH", do_getauth, is_services_admin,
-1,-1,NICK_OPER_HELP_GETAUTH },
{ "CLEARAUTH",do_clearauth,is_services_admin,
-1,-1,NICK_OPER_HELP_CLEARAUTH },
{ NULL }
};
/*************************************************************************/
/**************************** Local routines *****************************/
/*************************************************************************/
/* Create an authentication code for the given nickname group and store it
* in the NickGroupInfo structure. `reason' is the NICKAUTH_* value to
* store in the `authreason' field.
*/
static void make_auth(NickGroupInfo *ngi, int16 reason)
{
ngi->authcode = rand()%900000000 + 100000000;
ngi->authset = time(NULL);
ngi->authreason = reason;
}
/*************************************************************************/
/* Clear any authentication code from the given nickname group, along with
* all associated data.
*/
static void clear_auth(NickGroupInfo *ngi)
{
ngi->authcode = 0;
ngi->authset = 0;
ngi->authreason = 0;
ngi->bad_auths = 0;
free(ngi->last_email);
ngi->last_email = NULL;
}
/*************************************************************************/
/*************************************************************************/
/* Send mail to a nick's E-mail address containing the authentication code.
* `u' is the user causing the mail to be sent; `ngi' is the nickgroup and
* `nick' is the nickname of the user whose authcode should be sent; `what'
* is one of the IS_* constants defined below. Returns 1 on success, 0 on
* error.
*/
#define send_auth(u,ngi,nick,what) send_auth_(u,ngi,nick,what,__LINE__)
#define IS_REGISTRATION NICK_AUTH_MAIL_TEXT_REG
#define IS_EMAIL_CHANGE NICK_AUTH_MAIL_TEXT_EMAIL
#define IS_SENDAUTH NICK_AUTH_MAIL_TEXT_SENDAUTH
#define IS_REAUTH NICK_AUTH_MAIL_TEXT_REAUTH
#define IS_SETAUTH -1
/*************************************************************************/
/* Data list for mail completion callbacks. */
struct sendauth_data {
struct sendauth_data *next, *prev;
User *u;
char nick[NICKMAX];
char email[BUFSIZE];
int what;
};
static struct sendauth_data *sendauth_list;
static void send_auth_callback(int status, void *data);
/*************************************************************************/
/* Actual routine to send mail. */
static int send_auth_(User *u, NickGroupInfo *ngi, const char *nick,
int what, int caller_line)
{
char subject[BUFSIZE], body[BUFSIZE];
const char *text;
struct sendauth_data *sad;
if (!u || !ngi || !ngi->email) {
module_log("send_auth() with %s! (called from line %d)",
!u ? "null User"
: !ngi ? "null NickGroupInfo"
: "NickGroupInfo with no E-mail",
caller_line);
return 0;
}
text = what<0 ? "" : getstring(ngi, what);
snprintf(subject, sizeof(subject), getstring(ngi,NICK_AUTH_MAIL_SUBJECT),
nick);
if (what == IS_SETAUTH) {
snprintf(body, sizeof(body),
getstring(ngi,NICK_AUTH_MAIL_BODY_SETAUTH),
nick, ngi->authcode, s_NickServ, s_NickServ, ngi->authcode);
} else {
snprintf(body, sizeof(body), getstring(ngi,NICK_AUTH_MAIL_BODY),
nick, ngi->authcode, s_NickServ, s_NickServ, ngi->authcode,
s_NickServ, text, u->username, u->host);
}
sad = smalloc(sizeof(*sad));
LIST_INSERT(sad, sendauth_list);
sad->u = u;
strbcpy(sad->nick, nick);
strbcpy(sad->email, ngi->email);
sad->what = what;
sendmail(ngi->email, subject, body, getstring(ngi,LANG_CHARSET),
send_auth_callback, sad);
return 1;
}
/*************************************************************************/
/* Completion callback for sendmail(). Clears the SENDAUTH timer on
* SENDAUTH message failure, and sends a notice to the user (if still
* online) that the mail was or was not sent.
*/
static void send_auth_callback(int status, void *data)
{
struct sendauth_data *sad = data;
if (sad->what == IS_SENDAUTH && status != MAIL_STATUS_SENT) {
NickInfo *ni = get_nickinfo(sad->nick);
if (ni) {
NickGroupInfo *ngi = get_ngi(ni);
if (ngi)
ngi->last_sendauth = 0;
put_nickgroupinfo(ngi);
}
put_nickinfo(ni);
}
if (sad->u) { /* If the user has logged off this will be NULL */
switch (sad->what) {
case IS_REGISTRATION:
case IS_EMAIL_CHANGE:
case IS_SENDAUTH:
case IS_REAUTH:
if (status != MAIL_STATUS_SENT) {
if (status == MAIL_STATUS_NORSRC)
notice_lang(s_NickServ, sad->u, NICK_SENDAUTH_NORESOURCES);
else
notice_lang(s_NickServ, sad->u, NICK_SENDAUTH_ERROR);
}
break;
case IS_SETAUTH:
switch (status) {
case MAIL_STATUS_SENT:
/* No message */
break;
case MAIL_STATUS_REFUSED:
notice_lang(s_NickServ, sad->u, NICK_SETAUTH_SEND_REFUSED,
sad->email);
break;
case MAIL_STATUS_TIMEOUT:
notice_lang(s_NickServ, sad->u, NICK_SETAUTH_SEND_REFUSED,
sad->email);
break;
case MAIL_STATUS_NORSRC:
notice_lang(s_NickServ, sad->u, NICK_SETAUTH_SEND_NORESOURCES,
sad->email);
break;
default:
notice_lang(s_NickServ, sad->u, NICK_SETAUTH_SEND_ERROR,
sad->email);
break;
}
break;
} /* switch (sad->what) */
} /* if (sad->u) */
LIST_REMOVE(sad, sendauth_list);
free(sad);
}
/*************************************************************************/
/* Routine to clear `u' field from callback data entries for a quitting
* user.
*/
static int sendauth_userdel(User *user, const char *reason, int is_kill)
{
struct sendauth_data *sad;
LIST_FOREACH (sad, sendauth_list) {
if (sad->u == user)
sad->u = NULL;
}
return 0;
}
/*************************************************************************/
/*************************** Command handlers ****************************/
/*************************************************************************/
/* Handle the AUTH command. */
static void do_auth(User *u)
{
char *s = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
if (!s || !*s) {
syntax_error(s_NickServ, u, "AUTH", NICK_AUTH_SYNTAX);
} else if (readonly) {
notice_lang(s_NickServ, u, NICK_AUTH_DISABLED);
} else if (!(ni = u->ni)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
} else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->authcode) {
notice_lang(s_NickServ, u, NICK_AUTH_NOT_NEEDED);
} else if (!ngi->email) {
module_log("BUG: do_auth() for %s[%u]: authcode set but no email!",
ni->nick, ngi->id);
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else {
int32 code;
const char *what = "(unknown)";
int16 authreason = ngi->authreason;
code = (int32)atolsafe(s, 0, 0x7FFFFFFF);
if (code != ngi->authcode) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "AUTH for %s", ni->nick);
notice_lang(s_NickServ, u, NICK_AUTH_FAILED);
if (bad_password(NULL, u, buf) == 1)
notice_lang(s_NickServ, u, PASSWORD_WARNING_FOR_AUTH);
ngi->bad_auths++;
if (BadPassWarning && ngi->bad_auths >= BadPassWarning) {
wallops(s_NickServ, "\2Warning:\2 Repeated bad AUTH attempts"
" for nick %s", ni->nick);
}
return;
}
clear_auth(ngi);
set_identified(u);
switch (authreason) {
case NICKAUTH_REGISTER:
notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_REGISTER);
what = "REGISTER";
break;
case NICKAUTH_SET_EMAIL:
notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_SET_EMAIL);
what = "SET EMAIL";
break;
case NICKAUTH_REAUTH:
notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_REAUTH);
what = "REAUTH";
break;
case NICKAUTH_SETAUTH:
what = "SETAUTH";
/* fall through */
default:
/* "you may now continue using your nick", good for a default */
notice_lang(s_NickServ, u, NICK_AUTH_SUCCEEDED_SETAUTH);
break;
}
module_log("%s@%s authenticated %s for %s", u->username, u->host,
what, ni->nick);
call_callback_4(cb_authed, u, ni, ngi, authreason);
}
}
/*************************************************************************/
/* Handle the SENDAUTH command. To prevent abuse the command is limited to
* one use per nick per day (reset when Services starts up).
*/
static void do_sendauth(User *u)
{
char *s = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
time_t now = time(NULL);
if (s) {
syntax_error(s_NickServ, u, "SENDAUTH", NICK_SENDAUTH_SYNTAX);
} else if (!(ni = u->ni)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
} else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->authcode) {
notice_lang(s_NickServ, u, NICK_AUTH_NOT_NEEDED);
} else if (ngi->last_sendauth
&& now - ngi->last_sendauth < NSSendauthDelay) {
notice_lang(s_NickServ, u, NICK_SENDAUTH_TOO_SOON,
maketime(ngi,NSSendauthDelay-(now-ngi->last_sendauth),0));
} else if (!ngi->email) {
module_log("BUG: do_sendauth() for %s[%u]: authcode set but no email!",
ni->nick, ngi->id);
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else {
notice_lang(s_NickServ, u, NICK_AUTH_SENDING, ngi->email);
if (!send_auth(u, ngi, ni->nick, IS_SENDAUTH)) {
module_log("Valid SENDAUTH by %s!%s@%s failed",
u->nick, u->username, u->host);
notice_lang(s_NickServ, u, NICK_SENDAUTH_ERROR);
} else {
ngi->last_sendauth = time(NULL);
}
}
}
/*************************************************************************/
/* Handle the REAUTH command. */
static void do_reauth(User *u)
{
NickInfo *ni;
NickGroupInfo *ngi;
if (strtok_remaining()) {
syntax_error(s_NickServ, u, "REAUTH", NICK_REAUTH_SYNTAX);
} else if (!(ni = u->ni)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
} else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (ngi->authcode) {
notice_lang(s_NickServ, u, NICK_REAUTH_HAVE_AUTHCODE);
} else if (!ngi->email) {
notice_lang(s_NickServ, u, NICK_REAUTH_NO_EMAIL);
} else {
make_auth(ngi, NICKAUTH_REAUTH);
notice_lang(s_NickServ, u, NICK_REAUTH_AUTHCODE_SET);
if (!send_auth(u, ngi, ni->nick, IS_REAUTH)) {
module_log("send_auth() failed for REAUTH by %s", u->nick);
notice_lang(s_NickServ, u, NICK_SENDAUTH_ERROR);
}
ngi->last_sendauth = 0;
}
}
/*************************************************************************/
/* Handle the RESTOREMAIL command. */
static void do_restoremail(User *u)
{
char *password = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
if (!password) {
syntax_error(s_NickServ, u, "RESTOREMAIL", NICK_RESTOREMAIL_SYNTAX);
} else if (!(ni = u->ni)) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
} else if (!(ngi = u->ngi) || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->last_email || !ngi->authcode
|| ngi->authreason != NICKAUTH_SET_EMAIL) {
notice_lang(s_NickServ, u, NICK_RESTOREMAIL_NOT_NOW);
} else if (!nick_check_password(u, u->ni, password, "IDENTIFY",
INTERNAL_ERROR)) {
/* Nothing, error message has already been sent */
} else {
/* Restore previous E-mail address _before_ calling clear_auth() */
free(ngi->email);
ngi->email = ngi->last_email;
ngi->last_email = NULL;
clear_auth(ngi);
set_identified(u);
notice_lang(s_NickServ, u, NICK_RESTOREMAIL_DONE, ngi->email);
}
}
/*************************************************************************/
/* Handle the SETAUTH command (Services admins only). */
static void do_setauth(User *u)
{
char *nick = strtok(NULL, " ");
NickInfo *ni = NULL;
NickGroupInfo *ngi = NULL;
if (!nick) {
syntax_error(s_NickServ, u, "SETAUTH", NICK_SETAUTH_SYNTAX);
} else if (!(ni = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (ngi->authcode && ngi->authreason != NICKAUTH_REAUTH) {
notice_lang(s_NickServ, u, NICK_AUTH_HAS_AUTHCODE, ni->nick);
} else if (!ngi->email) {
notice_lang(s_NickServ, u, NICK_SETAUTH_NO_EMAIL, ni->nick);
} else {
int i;
if (ngi->authcode) {
/* Convert reason to SETAUTH */
ngi->authreason = NICKAUTH_SETAUTH;
} else {
make_auth(ngi, NICKAUTH_SETAUTH);
}
notice_lang(s_NickServ, u, NICK_SETAUTH_AUTHCODE_SET,
ngi->authcode, ni->nick);
if (!send_auth(u, ngi, ni->nick, IS_SETAUTH)) {
module_log("send_auth() failed for SETAUTH on %s by %s",
nick, u->nick);
notice_lang(s_NickServ, u, NICK_SETAUTH_SEND_ERROR, ngi->email);
}
ngi->last_sendauth = 0;
ARRAY_FOREACH (i, ngi->nicks) {
NickInfo *ni2 = get_nickinfo(ngi->nicks[i]);
if (!ni2)
continue;
ni2->authstat &= ~NA_IDENTIFIED;
if (ni2->user) {
notice_lang(s_NickServ, ni2->user, NICK_SETAUTH_USER_NOTICE,
ngi->email, s_NickServ);
}
put_nickinfo(ni2);
}
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/* Handle the GETAUTH command (Services admins only). */
static void do_getauth(User *u)
{
char *nick = strtok(NULL, " ");
NickInfo *ni = NULL;
NickGroupInfo *ngi = NULL;
if (!nick) {
syntax_error(s_NickServ, u, "GETAUTH", NICK_GETAUTH_SYNTAX);
} else if (!(ni = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->authcode) {
notice_lang(s_NickServ, u, NICK_AUTH_NO_AUTHCODE, ni->nick);
} else {
if (WallAdminPrivs) {
wallops(s_NickServ, "\2%s\2 used GETAUTH on \2%s\2",
u->nick, nick);
}
notice_lang(s_NickServ, u, NICK_GETAUTH_AUTHCODE_IS,
ni->nick, ngi->authcode);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/* Handle the CLEARAUTH command (Services admins only). */
static void do_clearauth(User *u)
{
char *nick = strtok(NULL, " ");
NickInfo *ni = NULL;
NickGroupInfo *ngi = NULL;
if (!nick) {
syntax_error(s_NickServ, u, "CLEARAUTH", NICK_CLEARAUTH_SYNTAX);
} else if (!(ni = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!ngi->authcode) {
notice_lang(s_NickServ, u, NICK_AUTH_NO_AUTHCODE, ni->nick);
} else {
if (WallAdminPrivs) {
wallops(s_NickServ, "\2%s\2 used CLEARAUTH on \2%s\2",
u->nick, nick);
}
clear_auth(ngi);
notice_lang(s_NickServ, u, NICK_CLEARAUTH_CLEARED, ni->nick);
if (readonly)
notice_lang(s_NickServ, u, READ_ONLY_MODE);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Nick-registration callback: clear IDENTIFIED flag, set nick flags
* appropriately (no kill, secure), send authcode mail.
*/
static int do_registered(User *u, NickInfo *ni, NickGroupInfo *ngi,
int *replied)
{
ni->authstat &= ~NA_IDENTIFIED;
ngi->last_sendauth = 0;
make_auth(ngi, NICKAUTH_REGISTER);
if (!*replied) {
notice_lang(s_NickServ, u, NICK_REGISTERED, u->nick);
*replied = 1;
}
notice_lang(s_NickServ, u, NICK_AUTH_SENDING, ngi->email);
notice_lang(s_NickServ, u, NICK_AUTH_FOR_REGISTER, s_NickServ);
if (!send_auth(u, ngi, ni->nick, IS_REGISTRATION)) {
module_log("send_auth() failed for registration (%s)", u->nick);
notice_lang(s_NickServ, u, NICK_SENDAUTH_ERROR);
}
return 0;
}
/*************************************************************************/
/* SET EMAIL callback: clear IDENTIFIED flag, send authcode mail. */
static int do_set_email(User *u, NickGroupInfo *ngi, const char *last_email)
{
/* Note: we assume here that if it's not a servadmin doing the change,
* it must be the user him/herself (and thus use u->nick and u->ni). */
if (ngi->email && !is_services_admin(u)) {
u->ni->authstat &= ~NA_IDENTIFIED;
if (last_email)
ngi->last_email = sstrdup(last_email);
ngi->last_sendauth = 0;
make_auth(ngi, NICKAUTH_SET_EMAIL);
notice_lang(s_NickServ, u, NICK_AUTH_SENDING, ngi->email);
notice_lang(s_NickServ, u, NICK_AUTH_FOR_SET_EMAIL, s_NickServ);
if (!send_auth(u, ngi, u->nick, IS_EMAIL_CHANGE)) {
module_log("send_auth() failed for E-mail change (%s)", u->nick);
notice_lang(s_NickServ, u, NICK_SENDAUTH_ERROR);
}
}
return 0;
}
/*************************************************************************/
/* IDENTIFY check: do not allow identification if nick not authenticated
* (except for REAUTH). */
static int do_identify_check(const User *u, const char *pass)
{
if (u->ngi && u->ngi != NICKGROUPINFO_INVALID && ngi_unauthed(u->ngi)) {
notice_lang(s_NickServ, u, NICK_PLEASE_AUTH, u->ngi->email);
notice_lang(s_NickServ, u, MORE_INFO, s_NickServ, "AUTH");
return 1;
}
return 0;
}
/*************************************************************************/
/* Expiration check callback. */
static int do_check_expire(NickInfo *ni, NickGroupInfo *ngi)
{
time_t now = time(NULL);
if (!NSNoAuthExpire)
return 0;
if (ngi && ngi->authcode && now - ngi->authset >= NSNoAuthExpire) {
if (ngi->authreason == NICKAUTH_REAUTH) {
clear_auth(ngi);
} else if (ngi->authreason == NICKAUTH_REGISTER) {
module_log("Expiring unauthenticated nick %s", ni->nick);
return 1;
}
}
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NSNoAuthExpire", { { CD_TIME, 0, &NSNoAuthExpire } } },
{ "NSSendauthDelay", { { CD_TIME, 0, &NSSendauthDelay } } },
{ NULL }
};
/* Old message number */
static int old_LIST_OPER_SYNTAX = -1;
static int old_HELP_REGISTER_EMAIL = -1;
static int old_OPER_HELP_LIST = -1;
/*************************************************************************/
int init_module(void)
{
module_nickserv = find_module("nickserv/main");
if (!module_nickserv) {
module_log("Main NickServ module not loaded");
return 0;
}
use_module(module_nickserv);
module_mail = find_module("mail/main");
if (!module_mail) {
module_log("Mail module not loaded");
return 0;
}
use_module(module_mail);
if (!NSRequireEmail) {
module_log("NSRequireEmail must be set to use nickname"
" authentication");
return 0;
}
if (!register_commands(module_nickserv, commands)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
cb_authed = register_callback("authed");
if (cb_authed < 0) {
module_log("Unable to register callback");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "user delete", sendauth_userdel)
|| !add_callback(module_nickserv, "registered", do_registered)
|| !add_callback(module_nickserv, "SET EMAIL", do_set_email)
|| !add_callback(module_nickserv, "IDENTIFY check", do_identify_check)
|| !add_callback(module_nickserv, "check_expire", do_check_expire)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
old_LIST_OPER_SYNTAX =
mapstring(NICK_LIST_OPER_SYNTAX, NICK_LIST_OPER_SYNTAX_AUTH);
old_HELP_REGISTER_EMAIL =
mapstring(NICK_HELP_REGISTER_EMAIL, NICK_HELP_REGISTER_EMAIL_AUTH);
old_OPER_HELP_LIST =
mapstring(NICK_OPER_HELP_LIST, NICK_OPER_HELP_LIST_AUTH);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
if (old_OPER_HELP_LIST >= 0) {
mapstring(NICK_OPER_HELP_LIST, old_OPER_HELP_LIST);
old_OPER_HELP_LIST = -1;
}
if (old_HELP_REGISTER_EMAIL >= 0) {
mapstring(NICK_HELP_REGISTER_EMAIL, old_HELP_REGISTER_EMAIL);
old_HELP_REGISTER_EMAIL = -1;
}
if (old_LIST_OPER_SYNTAX >= 0) {
mapstring(NICK_LIST_OPER_SYNTAX, old_LIST_OPER_SYNTAX);
old_LIST_OPER_SYNTAX = -1;
}
if (module_mail) {
unuse_module(module_mail);
module_mail = NULL;
}
if (module_nickserv) {
remove_callback(module_nickserv, "check_expire", do_check_expire);
remove_callback(module_nickserv, "IDENTIFY check", do_identify_check);
remove_callback(module_nickserv, "SET EMAIL", do_set_email);
remove_callback(module_nickserv, "registered", do_registered);
unregister_commands(module_nickserv, commands);
unuse_module(module_nickserv);
module_nickserv = NULL;
}
remove_callback(NULL, "user delete", sendauth_userdel);
unregister_callback(cb_authed);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

2717
modules/nickserv/main.c Normal file

File diff suppressed because it is too large Load Diff

272
modules/nickserv/nickserv.h Normal file
View File

@ -0,0 +1,272 @@
/* NickServ-related structures.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef NICKSERV_H
#define NICKSERV_H
#ifndef ENCRYPT_H
# include "../../encrypt.h"
#endif
#ifndef MEMOSERV_H
# include "../memoserv/memoserv.h"
#endif
/*************************************************************************/
/*************************************************************************/
/* Data structure for a single nickname. */
struct nickinfo_ {
NickInfo *next, *prev;
int usecount;
char nick[NICKMAX];
int16 status; /* See NS_* below */
char *last_usermask; /* Last user@host mask (uses fakehost if avail.) */
char *last_realmask; /* Last user@realhost (not fakehost) mask seen */
char *last_realname;
char *last_quit;
time_t time_registered;
time_t last_seen;
uint32 nickgroup; /* ID of nick group for this nick */
uint32 id_stamp; /* Services stamp of user who last ID'd for nick */
/* Online-only information: */
User *user; /* User using this nick, NULL if not online */
int16 authstat; /* Authorization status; see NA_* below */
int bad_passwords; /* # of bad passwords for nick since last good one */
};
/*************************************************************************/
/* Data for a group of nicks. */
struct nickgroupinfo_ {
NickGroupInfo *next, *prev;
int usecount;
uint32 id;
nickname_t *nicks; /* Array of nicks */
uint16 nicks_count; /* Number of nicks in this nick group */
uint16 mainnick; /* Index of "main" nick (for chan access lists etc) */
Password pass;
char *url;
char *email;
char *last_email; /* Previous (authenticated) E-mail address */
char *info;
int32 flags; /* See NF_* below */
int16 os_priv; /* OperServ privilege level; see NP_* below */
int32 authcode; /* Authentication code (used by mail-auth module) */
time_t authset; /* Time authentication code was set */
int16 authreason; /* Reason auth. code was set; see NICKAUTH_* below */
char suspend_who[NICKMAX]; /* Who suspended this nickname */
char *suspend_reason; /* Reason for suspension */
time_t suspend_time; /* Time nick was suspended */
time_t suspend_expires; /* Time suspension expires, 0 for no expiry */
int16 language; /* Language selected by nickname owner (LANG_*) */
int16 timezone; /* Offset from UTC, in minutes */
int16 channelmax; /* Maximum number of registered channels allowed */
char **access; /* Array of access masks */
int16 access_count; /* # of entries */
char **ajoin; /* Array of autojoin channel */
int16 ajoin_count; /* # of entries */
char **ignore; /* Array of memo ignore entries */
int16 ignore_count; /* # of entries */
MemoInfo memos;
/* Online-only info: */
channame_t *channels; /* Array of names of channels currently registered */
int16 channels_count; /* Number of channels currently registered */
User **id_users; /* Users who have identified for this group (array) */
int id_users_count;
time_t last_sendauth; /* Time of last SENDAUTH (mail-auth module) */
int bad_auths; /* Number of bad AUTH commands for this group */
};
/*************************************************************************/
/* Nickname status flags: */
#define NS_VERBOTEN 0x0002 /* Nick may not be registered or used */
#define NS_NOEXPIRE 0x0004 /* Nick never expires */
#define NS_PERMANENT 0x0006 /* All permanent flags */
/* The following flags are temporary: */
#define NS_KILL_HELD 0x8000 /* Nick is being held after a kill */
#define NS_GUESTED 0x4000 /* SVSNICK has been sent but nick has not
* yet changed. An enforcer will be
* introduced when it does change. */
#define NS_TEMPORARY 0xC000 /* All temporary flags */
/* Nickname authorization flags: */
#define NA_IDENTIFIED 0x0001 /* User has IDENTIFY'd */
#define NA_IDENT_NOMAIL 0x0002 /* User has identified, but hasn't set an
* E-mail address and one is required
* (in this case we don't allow privs
* except for SET EMAIL on the nick) */
#define NA_RECOGNIZED 0x0004 /* User is recognized as nick owner */
/* Nickgroup setting flags: */
#define NF_KILLPROTECT 0x00000001 /* Kill others who take this nick */
#define NF_SECURE 0x00000002 /* Don't recognize unless IDENTIFY'd */
#define NF_MEMO_HARDMAX 0x00000008 /* Don't allow user to change memo limit */
#define NF_MEMO_SIGNON 0x00000010 /* Notify of memos at signon and un-away */
#define NF_MEMO_RECEIVE 0x00000020 /* Notify of new memos when sent */
#define NF_PRIVATE 0x00000040 /* Don't show in LIST to non-servadmins */
#define NF_HIDE_EMAIL 0x00000080 /* Don't show E-mail in INFO */
#define NF_HIDE_MASK 0x00000100 /* Don't show last seen address in INFO */
#define NF_HIDE_QUIT 0x00000200 /* Don't show last quit message in INFO */
#define NF_KILL_QUICK 0x00000400 /* Kill in 20 seconds instead of 60 */
#define NF_KILL_IMMED 0x00000800 /* Kill immediately instead of in 60 sec */
#define NF_MEMO_FWD 0x00001000 /* Forward memos to E-mail address */
#define NF_MEMO_FWDCOPY 0x00002000 /* Save copy of forwarded memos */
#define NF_SUSPENDED 0x00004000 /* Nickgroup is suspended */
#define NF_NOOP 0x00008000 /* Do not add to channel access lists */
#define NF_ALLFLAGS 0x0000FFFB /* All flags */
#define NF_NOGROUP 0x00000004 /* Used by database/version4 during load */
/* Nickgroup OperServ privilege levels: */
#define NP_SERVOPER 0x1000
#define NP_SERVADMIN 0x2000
/* Maximum number of nicks we can record for a nick group: */
#define MAX_NICKCOUNT 32767
/* Maximum number of access entries: */
#define MAX_NICK_ACCESS 32767
/* Maximum number of channels we can record: */
#define MAX_CHANNELCOUNT 32767
/* Value indicating "no limit" */
#define CHANMAX_UNLIMITED -2
/* Value indicating "default limit" (as set in configuration file) */
#define CHANMAX_DEFAULT -1
/* Value indicating "no time zone set, use local default" */
#define TIMEZONE_DEFAULT 0x7FFF
/* Used when we can't retrieve the NickGroupInfo record for a nick, to
* prevent the nick from being registered. */
#define NICKGROUPINFO_INVALID ((NickGroupInfo *)PTR_INVALID)
/* Authentication code reasons: */
#define NICKAUTH_MIN NICKAUTH_REGISTER
#define NICKAUTH_REGISTER 1 /* Newly registered */
#define NICKAUTH_SET_EMAIL 2 /* E-mail address changed */
#define NICKAUTH_SETAUTH 3 /* SETAUTH command used */
#define NICKAUTH_REAUTH 4 /* REAUTH command used */
#define NICKAUTH_MAX NICKAUTH_REAUTH
/*************************************************************************/
/* Macros for checking whether a user is recognized or has identified for
* their nickname, by either NickInfo or User structure. */
#define nick_recognized(ni) ((ni) && ((ni)->authstat & NA_RECOGNIZED))
#define user_recognized(u) nick_recognized((u)->ni)
#define nick_identified(ni) ((ni) && ((ni)->authstat & NA_IDENTIFIED))
#define user_identified(u) nick_identified((u)->ni)
#define nick_id_or_rec(ni) \
((ni) && ((ni)->authstat & (NA_IDENTIFIED|NA_RECOGNIZED)))
#define user_id_or_rec(u) nick_id_or_rec((u)->ni)
#define nick_ident_nomail(ni) ((ni) && ((ni)->authstat & NA_IDENT_NOMAIL))
#define user_ident_nomail(u) nick_ident_nomail((u)->ni)
/* Is the given nickgroup unauthenticated? (Currently, this means that the
* nickgroup has an authcode set from something other than REAUTH.) */
#define ngi_unauthed(ngi) \
((ngi) && (ngi)->authcode && ((ngi)->authreason != NICKAUTH_REAUTH))
/* Does the given user have a valid NickGroupInfo? */
#define valid_ngi(u) ((u) && ((u)->ngi) && ((u)->ngi != NICKGROUPINFO_INVALID))
/* Macro to retrieve the main nick for a given NickGroupInfo. */
#define ngi_mainnick(ngi) ((ngi)->nicks[(ngi)->mainnick])
/*************************************************************************/
/*************************************************************************/
/* Prototypes for exported functions and variables. */
E char *s_NickServ;
E NickInfo *add_nickinfo(NickInfo *ni);
E void del_nickinfo(NickInfo *ni);
E NickInfo *get_nickinfo(const char *nick);
E NickInfo *put_nickinfo(NickInfo *ni);
E NickInfo *first_nickinfo(void);
E NickInfo *next_nickinfo(void);
E NickGroupInfo *add_nickgroupinfo(NickGroupInfo *ngi);
E void del_nickgroupinfo(NickGroupInfo *ngi);
E NickGroupInfo *get_nickgroupinfo(uint32 id);
E NickGroupInfo *put_nickgroupinfo(NickGroupInfo *ngi);
E NickGroupInfo *first_nickgroupinfo(void);
E NickGroupInfo *next_nickgroupinfo(void);
#ifdef STANDALONE_NICKSERV /* see util.c */
# define E2 static
#else
# define E2 extern
#endif
E2 NickInfo *new_nickinfo(void);
E2 void free_nickinfo(NickInfo *ni);
E2 NickGroupInfo *new_nickgroupinfo(const char *seed);
E2 void free_nickgroupinfo(NickGroupInfo *ngi);
#undef E2
E NickInfo *get_nickinfo_noexpire(const char *nick);
#define get_ngi(ni) _get_ngi((ni), __FILE__, __LINE__)
#define check_ngi(ni) (put_nickgroupinfo(get_ngi((ni))) != NULL)
#define get_ngi_id(id) _get_ngi_id((id), __FILE__, __LINE__)
#define check_ngi_id(id) (put_nickgroupinfo(get_ngi_id((id))) != NULL)
E NickGroupInfo *_get_ngi(const NickInfo *ni, const char *file, int line);
E NickGroupInfo *_get_ngi_id(uint32 id, const char *file, int line);
E int has_identified_nick(const User *u, uint32 group);
/*************************************************************************/
#endif /* NICKSERV_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

134
modules/nickserv/ns-local.h Normal file
View File

@ -0,0 +1,134 @@
/* Include file for data local to the NickServ module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef NS_LOCAL_H
#define NS_LOCAL_H
/*************************************************************************/
/*************************************************************************/
/* User-configurable settings: */
/* Maximum number of tries to randomly select a new NickGroupInfo ID before
* giving up. Assuming a sufficiently random random-number generator and a
* database of one million nicknames, the default setting of 1000 gives
* odds of approximately 10^3600 to 1 against an accidental failure. */
#define NEWNICKGROUP_TRIES 1000
/* Number of DROPEMAIL commands to buffer for DROPEMAIL-CONFIRM. */
#define DROPEMAIL_BUFSIZE 4
/*************************************************************************/
/*************************************************************************/
#define TO_COLLIDE 0 /* Collide the user with this nick */
#define TO_RELEASE 1 /* Release a collided nick */
#define TO_SEND_433 2 /* Send a 433 numeric */
/*************************************************************************/
/* External declarations: */
/**** collide.c ****/
E void introduce_enforcer(NickInfo *ni);
E void collide_nick(NickInfo *ni, int from_timeout);
E void release_nick(NickInfo *ni, int from_timeout);
E void add_ns_timeout(NickInfo *ni, int type, time_t delay);
E void rem_ns_timeout(NickInfo *ni, int type, int del_to);
E int init_collide(void);
E void exit_collide(void);
/**** main.c ****/
E int cb_reglink_check;
E char * s_NickServ;
E int32 NSRegEmailMax;
E int NSRequireEmail;
E int NSRegDenyIfSuspended;
E time_t NSRegDelay;
E time_t NSInitialRegDelay;
E time_t NSSetEmailDelay;
E int32 NSDefFlags;
E time_t NSExpire;
E time_t NSExpireWarning;
E int NSShowPassword;
E char * NSEnforcerUser;
E char * NSEnforcerHost;
E int NSForceNickChange;
E time_t NSReleaseTimeout;
E int NSAllowKillImmed;
E int NSListOpersOnly;
E int NSSecureAdmins;
E time_t NSSuspendExpire;
E time_t NSSuspendGrace;
/**** set.c ****/
/* Avoid conflicts with chanserv/set.c */
#define do_set do_set_ns
#define do_unset do_unset_ns
#define init_set init_set_ns
#define exit_set exit_set_ns
E void do_set(User *u);
E void do_unset(User *u);
E int init_set(void);
E void exit_set(void);
/**** util.c ****/
/* Avoid conflicts with chanserv/util.c */
#define init_util init_util_ns
#define exit_util exit_util_ns
E int reglink_check(User *u, const char *nick, char *password, char *email);
E void update_userinfo(const User *u);
E int validate_user(User *u);
E void cancel_user(User *u);
E void set_identified(User *u);
E NickInfo *makenick(const char *nick, NickGroupInfo **nickgroup_ret);
E int delnick(NickInfo *ni);
E int delgroup(NickGroupInfo *ngi);
E int drop_nickgroup(NickGroupInfo *ngi, const User *u, const char *dropemail);
E void suspend_nick(NickGroupInfo *ngi, const char *reason,
const char *who, const time_t expires);
E void unsuspend_nick(NickGroupInfo *ngi, int set_time);
E int nick_check_password(User *u, NickInfo *ni, const char *password,
const char *command, int failure_msg);
E int count_nicks_with_email(const char *email);
E int init_util(void);
E void exit_util(void);
/*************************************************************************/
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

707
modules/nickserv/set.c Normal file
View File

@ -0,0 +1,707 @@
/* Routines to handle the NickServ SET command.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "encrypt.h"
#include "modules/operserv/operserv.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
static int cb_set = -1;
static int cb_set_email = -1;
static int cb_unset = -1;
/*************************************************************************/
static void do_set_password(User *u, NickGroupInfo *ngi, NickInfo *ni,
char *param);
static void do_set_language(User *u, NickGroupInfo *ngi, char *param);
static void do_set_url(User *u, NickGroupInfo *ngi, char *param);
static void do_set_email(User *u, NickGroupInfo *ngi, char *param);
static void do_set_info(User *u, NickGroupInfo *ngi, char *param);
static void do_set_kill(User *u, NickGroupInfo *ngi, char *param);
static void do_set_secure(User *u, NickGroupInfo *ngi, char *param);
static void do_set_private(User *u, NickGroupInfo *ngi, char *param);
static void do_set_noop(User *u, NickGroupInfo *ngi, char *param);
static void do_set_hide(User *u, NickGroupInfo *ngi, char *param,
char *setting);
static void do_set_timezone(User *u, NickGroupInfo *ngi, char *param);
static void do_set_noexpire(User *u, NickInfo *ni, char *param);
/*************************************************************************/
/*************************************************************************/
void do_set(User *u)
{
char *cmd = strtok(NULL, " ");
char *param = strtok_remaining();
char *extra = NULL;
NickInfo *ni;
NickGroupInfo *ngi = NULL;
int is_servadmin = is_services_admin(u);
int used_privs = 0;
if (readonly) {
notice_lang(s_NickServ, u, NICK_SET_DISABLED);
return;
}
if (is_servadmin && cmd && *cmd == '!') {
ni = get_nickinfo(cmd+1);
if (!ni) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, cmd+1);
return;
}
cmd = strtok(param, " ");
param = strtok_remaining();
used_privs = !(valid_ngi(u) && ni->nickgroup == u->ngi->id);
} else {
ni = u->ni;
if (ni)
ni->usecount++;
}
if (cmd && stricmp(cmd, "INFO") != 0) {
param = strtok(param, " ");
extra = strtok(NULL, " ");
}
if (!cmd || !param
|| (stricmp(cmd,"HIDE")==0 ? extra==NULL : extra!=NULL)
) {
if (is_oper(u))
syntax_error(s_NickServ, u, "SET", NICK_SET_OPER_SYNTAX);
else
syntax_error(s_NickServ, u, "SET", NICK_SET_SYNTAX);
} else if (!ni) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, ni->nick);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!is_servadmin && !user_identified(u)
&& !(stricmp(cmd,"EMAIL")==0 && user_ident_nomail(u))) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (call_callback_5(cb_set, u, ni, ngi, cmd, param) > 0) {
/* nothing */
} else if (stricmp(cmd, "PASSWORD") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET PASSWORD as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_password(u, ngi, ni, param);
} else if (stricmp(cmd, "LANGUAGE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET LANGUAGE as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_language(u, ngi, param);
} else if (stricmp(cmd, "URL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET URL as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_url(u, ngi, param);
} else if (stricmp(cmd, "EMAIL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET EMAIL as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_email(u, ngi, param);
} else if (stricmp(cmd, "INFO") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET INFO as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_info(u, ngi, param);
} else if (stricmp(cmd, "KILL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET KILL as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_kill(u, ngi, param);
} else if (stricmp(cmd, "SECURE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET SECURE as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_secure(u, ngi, param);
} else if (stricmp(cmd, "PRIVATE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET PRIVATE as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_private(u, ngi, param);
} else if (stricmp(cmd, "NOOP") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET NOOP as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_noop(u, ngi, param);
} else if (stricmp(cmd, "HIDE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET HIDE as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_hide(u, ngi, param, extra);
} else if (stricmp(cmd, "TIMEZONE") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used SET TIMEZONE as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_timezone(u, ngi, param);
} else if (stricmp(cmd, "NOEXPIRE") == 0) {
if (WallAdminPrivs && is_servadmin) {
wallops(s_NickServ, "\2%s\2 used SET NOEXPIRE on \2%s\2",
u->nick, ni->nick);
}
do_set_noexpire(u, ni, param);
} else {
notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION, strupper(cmd));
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
void do_unset(User *u)
{
char *cmd = strtok(NULL, " ");
char *extra = strtok_remaining();
NickInfo *ni;
NickGroupInfo *ngi = NULL;
int is_servadmin = is_services_admin(u);
int used_privs = 0;
int syntax_msg;
if (readonly) {
notice_lang(s_NickServ, u, NICK_SET_DISABLED);
return;
}
if (is_oper(u))
syntax_msg = NSRequireEmail ? NICK_UNSET_OPER_SYNTAX_REQ_EMAIL
: NICK_UNSET_OPER_SYNTAX;
else
syntax_msg = NSRequireEmail ? NICK_UNSET_SYNTAX_REQ_EMAIL
: NICK_UNSET_SYNTAX;
if (is_servadmin && cmd && *cmd == '!') {
ni = get_nickinfo(cmd+1);
if (!ni) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, cmd+1);
return;
}
cmd = strtok(extra, " ");
extra = strtok_remaining();
used_privs = !(valid_ngi(u) && ni->nickgroup == u->ngi->id);
} else {
ni = u->ni;
if (ni)
ni->usecount++;
}
if (!cmd || extra) {
syntax_error(s_NickServ, u, "UNSET", syntax_msg);
} else if (!ni) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, ni->nick);
} else if (!(ngi = get_ngi(ni))) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
} else if (!is_servadmin && !user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (call_callback_4(cb_unset, u, ni, ngi, cmd) > 0) {
/* nothing */
} else if (stricmp(cmd, "URL") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used UNSET URL as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_url(u, ngi, NULL);
} else if (stricmp(cmd, "EMAIL") == 0) {
if (NSRequireEmail) {
if (ni != u->ni)
notice_lang(s_NickServ, u, NICK_UNSET_EMAIL_OTHER_BAD);
else
notice_lang(s_NickServ, u, NICK_UNSET_EMAIL_BAD);
} else {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used UNSET EMAIL as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_email(u, ngi, NULL);
}
} else if (stricmp(cmd, "INFO") == 0) {
if (WallAdminPrivs && used_privs) {
wallops(s_NickServ, "\2%s\2 used UNSET INFO as Services admin"
" on \2%s\2", u->nick, ni->nick);
}
do_set_info(u, ngi, NULL);
} else {
syntax_error(s_NickServ, u, "UNSET", syntax_msg);
}
put_nickinfo(ni);
put_nickgroupinfo(ngi);
}
/*************************************************************************/
static void do_set_password(User *u, NickGroupInfo *ngi, NickInfo *ni,
char *param)
{
Password passbuf;
if (NSSecureAdmins && u->ni != ni && nick_is_services_admin(ni)
&& !is_services_root(u)
) {
notice_lang(s_NickServ, u, PERMISSION_DENIED);
return;
} else if (!(NoAdminPasswordCheck && is_services_admin(u))
&& (stricmp(param, ni->nick) == 0
|| (StrictPasswords && strlen(param) < 5))) {
notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
return;
}
init_password(&passbuf);
if (encrypt_password(param, strlen(param), &passbuf) != 0) {
clear_password(&passbuf);
memset(param, 0, strlen(param));
module_log("Failed to encrypt password for %s (set)", ni->nick);
notice_lang(s_NickServ, u, NICK_SET_PASSWORD_FAILED);
return;
}
copy_password(&ngi->pass, &passbuf);
clear_password(&passbuf);
if (NSShowPassword)
notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED_TO, param);
else
notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED);
memset(param, 0, strlen(param));
}
/*************************************************************************/
static void do_set_language(User *u, NickGroupInfo *ngi, char *param)
{
int langnum;
if (param[strspn(param, "0123456789")] != 0) { /* i.e. not a number */
syntax_error(s_NickServ, u, "SET LANGUAGE", NICK_SET_LANGUAGE_SYNTAX);
return;
}
langnum = atoi(param)-1;
if (langnum < 0 || langnum >= NUM_LANGS || langlist[langnum] < 0) {
notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_UNKNOWN,
langnum+1, s_NickServ);
return;
}
ngi->language = langlist[langnum];
notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_CHANGED,
getstring(ngi,LANG_NAME));
}
/*************************************************************************/
static void do_set_url(User *u, NickGroupInfo *ngi, char *param)
{
const char *nick = ngi_mainnick(ngi);
if (param && !valid_url(param)) {
notice_lang(s_NickServ, u, BAD_URL);
return;
}
free(ngi->url);
if (param) {
ngi->url = sstrdup(param);
notice_lang(s_NickServ, u, NICK_SET_URL_CHANGED, nick, param);
} else {
ngi->url = NULL;
notice_lang(s_NickServ, u, NICK_UNSET_URL, nick);
}
}
/*************************************************************************/
static void do_set_email(User *u, NickGroupInfo *ngi, char *param)
{
const char *nick = ngi_mainnick(ngi);
char oldemail[BUFSIZE];
if (param && !valid_email(param)) {
notice_lang(s_NickServ, u, BAD_EMAIL);
return;
}
if (param && rejected_email(param)) {
notice_lang(s_NickServ, u, REJECTED_EMAIL);
return;
}
if (param && !is_services_admin(u)) {
int n = count_nicks_with_email(param);
time_t now = time(NULL);
if (n < 0) {
notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNAUTHED);
return;
} else if (NSRegEmailMax && n >= NSRegEmailMax) {
notice_lang(s_NickServ, u, NICK_SET_EMAIL_TOO_MANY_NICKS,
param, n, NSRegEmailMax);
return;
} else if (now < u->last_nick_set_email + NSSetEmailDelay) {
time_t left = (u->last_nick_set_email + NSSetEmailDelay) - now;
notice_lang(s_NickServ, u, NICK_SET_EMAIL_PLEASE_WAIT,
maketime(u->ngi, left, MT_SECONDS));
return;
}
u->last_nick_set_email = now;
}
if (ngi->email) {
strbcpy(oldemail, ngi->email);
free(ngi->email);
} else {
*oldemail = 0;
}
if (param) {
int i;
ngi->email = sstrdup(param);
if (*oldemail) {
module_log("%s E-mail address changed from %s to %s by %s!%s@%s",
nick, oldemail, param, u->nick, u->username, u->host);
} else {
module_log("%s E-mail address set to %s by %s!%s@%s",
nick, param, u->nick, u->username, u->host);
}
notice_lang(s_NickServ, u, NICK_SET_EMAIL_CHANGED, nick, param);
/* If any nicknames belonging to this group were IDENT_NOMAIL,
* set them IDENTIFIED */
ARRAY_FOREACH (i, ngi->nicks) {
NickInfo *ni = get_nickinfo(ngi->nicks[i]);
if (ni && nick_ident_nomail(ni)) {
ni->authstat &= ~NA_IDENT_NOMAIL;
ni->authstat |= NA_IDENTIFIED;
}
put_nickinfo(ni);
}
} else {
ngi->email = NULL;
if (*oldemail) {
module_log("%s E-mail address cleared by %s!%s@%s (was %s)",
nick, u->nick, u->username, u->host, oldemail);
}
notice_lang(s_NickServ, u, NICK_UNSET_EMAIL, nick);
}
call_callback_3(cb_set_email, u, ngi, *oldemail ? oldemail : NULL);
}
/*************************************************************************/
static void do_set_info(User *u, NickGroupInfo *ngi, char *param)
{
const char *nick = ngi_mainnick(ngi);
free(ngi->info);
if (param) {
ngi->info = sstrdup(param);
notice_lang(s_NickServ, u, NICK_SET_INFO_CHANGED, nick);
} else {
ngi->info = NULL;
notice_lang(s_NickServ, u, NICK_UNSET_INFO, nick);
}
}
/*************************************************************************/
static void do_set_kill(User *u, NickGroupInfo *ngi, char *param)
{
if (stricmp(param, "ON") == 0) {
ngi->flags |= NF_KILLPROTECT;
ngi->flags &= ~(NF_KILL_QUICK | NF_KILL_IMMED);
notice_lang(s_NickServ, u, NICK_SET_KILL_ON);
} else if (stricmp(param, "QUICK") == 0) {
ngi->flags |= NF_KILLPROTECT | NF_KILL_QUICK;
ngi->flags &= ~NF_KILL_IMMED;
notice_lang(s_NickServ, u, NICK_SET_KILL_QUICK);
} else if (stricmp(param, "IMMED") == 0) {
if (NSAllowKillImmed) {
ngi->flags |= NF_KILLPROTECT | NF_KILL_QUICK | NF_KILL_IMMED;
notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED);
} else {
notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED_DISABLED);
return;
}
} else if (stricmp(param, "OFF") == 0) {
ngi->flags &= ~(NF_KILLPROTECT | NF_KILL_QUICK | NF_KILL_IMMED);
notice_lang(s_NickServ, u, NICK_SET_KILL_OFF);
} else {
syntax_error(s_NickServ, u, "SET KILL",
NSAllowKillImmed ? NICK_SET_KILL_IMMED_SYNTAX
: NICK_SET_KILL_SYNTAX);
return;
}
}
/*************************************************************************/
static void do_set_secure(User *u, NickGroupInfo *ngi, char *param)
{
if (stricmp(param, "ON") == 0) {
ngi->flags |= NF_SECURE;
notice_lang(s_NickServ, u, NICK_SET_SECURE_ON);
} else if (stricmp(param, "OFF") == 0) {
ngi->flags &= ~NF_SECURE;
notice_lang(s_NickServ, u, NICK_SET_SECURE_OFF);
} else {
syntax_error(s_NickServ, u, "SET SECURE", NICK_SET_SECURE_SYNTAX);
return;
}
}
/*************************************************************************/
static void do_set_private(User *u, NickGroupInfo *ngi, char *param)
{
if (stricmp(param, "ON") == 0) {
ngi->flags |= NF_PRIVATE;
notice_lang(s_NickServ, u, NICK_SET_PRIVATE_ON);
} else if (stricmp(param, "OFF") == 0) {
ngi->flags &= ~NF_PRIVATE;
notice_lang(s_NickServ, u, NICK_SET_PRIVATE_OFF);
} else {
syntax_error(s_NickServ, u, "SET PRIVATE", NICK_SET_PRIVATE_SYNTAX);
return;
}
}
/*************************************************************************/
static void do_set_noop(User *u, NickGroupInfo *ngi, char *param)
{
if (stricmp(param, "ON") == 0) {
ngi->flags |= NF_NOOP;
notice_lang(s_NickServ, u, NICK_SET_NOOP_ON);
} else if (stricmp(param, "OFF") == 0) {
ngi->flags &= ~NF_NOOP;
notice_lang(s_NickServ, u, NICK_SET_NOOP_OFF);
} else {
syntax_error(s_NickServ, u, "SET NOOP", NICK_SET_NOOP_SYNTAX);
return;
}
}
/*************************************************************************/
static void do_set_hide(User *u, NickGroupInfo *ngi, char *param,
char *setting)
{
int flag, onmsg, offmsg;
if (stricmp(param, "EMAIL") == 0) {
flag = NF_HIDE_EMAIL;
onmsg = NICK_SET_HIDE_EMAIL_ON;
offmsg = NICK_SET_HIDE_EMAIL_OFF;
} else if (stricmp(param, "USERMASK") == 0) {
flag = NF_HIDE_MASK;
onmsg = NICK_SET_HIDE_MASK_ON;
offmsg = NICK_SET_HIDE_MASK_OFF;
} else if (stricmp(param, "QUIT") == 0) {
flag = NF_HIDE_QUIT;
onmsg = NICK_SET_HIDE_QUIT_ON;
offmsg = NICK_SET_HIDE_QUIT_OFF;
} else {
syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
return;
}
if (stricmp(setting, "ON") == 0) {
ngi->flags |= flag;
notice_lang(s_NickServ, u, onmsg, s_NickServ);
} else if (stricmp(setting, "OFF") == 0) {
ngi->flags &= ~flag;
notice_lang(s_NickServ, u, offmsg, s_NickServ);
} else {
syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
return;
}
}
/*************************************************************************/
/* Timezone definitions. */
static struct {
const char *name;
int16 offset;
} timezones[] = {
/* GMT-12 */
/* GMT-11 */
/* GMT-10 */ {"HST",-600},
/* GMT-9 */
/* GMT-8 */ {"PST",-480},
/* GMT-7 */ {"MST",-420}, {"PDT",-420},
/* GMT-6 */ {"CST",-360}, {"MDT",-360},
/* GMT-5 */ {"EST",-300}, {"CDT",-300},
/* GMT-4 */ {"AST",-240}, {"EDT",-240},
/* GMT-3 */ {"BRT",-180}, {"ADT",-180},
/* GMT-2 */ {"BRST",-120},
/* GMT-1 */
/* GMT+0 */ {"GMT", 0}, {"UTC", 0}, {"WET", 0},
/* GMT+1 */ {"MET", 60}, {"BST", 60}, {"IST", 60},
/* GMT+2 */ {"EET", 120},
/* GMT+3 */ {"MSK", 180},
/* GMT+4 */ {"MSD", 240},
/* GMT+5 */
/* GMT+6 */
/* GMT+7 */
/* GMT+8 */
/* GMT+9 */ {"JST", 540}, {"KST", 540},
/* GMT+10 */
/* GMT+11 */
/* GMT+12 */ {"NZST",720},
/* GMT+13 */ {"NZDT",780},
{ NULL }
};
static void do_set_timezone(User *u, NickGroupInfo *ngi, char *param)
{
char *s;
int i, j;
char timebuf[BUFSIZE];
if (stricmp(param, "DEFAULT") == 0) {
ngi->timezone = TIMEZONE_DEFAULT;
notice_lang(s_NickServ, u, NICK_SET_TIMEZONE_DEFAULT);
return;
}
if (strnicmp(param, "GMT+", 4) == 0 || strnicmp(param, "GMT-", 4) == 0
|| strnicmp(param, "UTC+", 4) == 0 || strnicmp(param, "UTC-", 4) == 0
) {
/* Treat GMT+n, UTC-n, etc. as just +n or -n */
param += 3;
}
if (*param == '+' || *param == '-') {
i = strtol(param+1, &s, 10);
if (*s == ':') {
if (s[1]>='0' && s[1]<='5' && s[2]>='0' && s[2]<='9')
j = strtol(s+1, &s, 10);
else
j = -1;
} else {
j = 0;
}
if (i < 0 || i > 23 || j < 0 || j > 59 || *s) {
syntax_error(s_NickServ, u, "SET TIMEZONE",
NICK_SET_TIMEZONE_SYNTAX);
return;
}
ngi->timezone = i*60 + j;
if (*param == '-')
ngi->timezone = -ngi->timezone;
} else {
for (i = 0; timezones[i].name; i++) {
if (stricmp(param, timezones[i].name) == 0)
break;
}
if (!timezones[i].name) {
syntax_error(s_NickServ, u, "SET TIMEZONE",
NICK_SET_TIMEZONE_SYNTAX);
return;
}
ngi->timezone = timezones[i].offset;
}
/* This is tricky, because we want the calling user's language but the
* target user's timezone. */
if (ngi->timezone != TIMEZONE_DEFAULT) {
j = ngi->timezone * 60;
} else {
time_t tmp = 0;
struct tm *tm = localtime(&tmp);
j = tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec;
if (tm->tm_year < 70)
j -= 86400;
}
if (valid_ngi(u) && u->ngi->timezone != TIMEZONE_DEFAULT) {
j -= u->ngi->timezone * 60;
} else {
time_t tmp = 0;
struct tm *tm = localtime(&tmp);
tmp = tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec;
if (tm->tm_year < 70)
tmp -= 86400;
j -= tmp;
}
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_DATE_TIME_FORMAT, time(NULL) + j);
if (ngi->timezone < 0)
i = -ngi->timezone;
else
i = ngi->timezone;
notice_lang(s_NickServ, u, NICK_SET_TIMEZONE_TO,
ngi->timezone<0 ? '-' : '+', i/60, i%60, timebuf);
}
/*************************************************************************/
static void do_set_noexpire(User *u, NickInfo *ni, char *param)
{
if (!is_services_admin(u)) {
notice_lang(s_NickServ, u, PERMISSION_DENIED);
return;
}
if (stricmp(param, "ON") == 0) {
ni->status |= NS_NOEXPIRE;
notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_ON, ni->nick);
} else if (stricmp(param, "OFF") == 0) {
ni->status &= ~NS_NOEXPIRE;
notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_OFF, ni->nick);
} else {
syntax_error(s_NickServ, u, "SET NOEXPIRE", NICK_SET_NOEXPIRE_SYNTAX);
return;
}
}
/*************************************************************************/
/*************************************************************************/
int init_set(void)
{
cb_set = register_callback("SET");
cb_set_email = register_callback("SET EMAIL");
cb_unset = register_callback("UNSET");
if (cb_set < 0 || cb_set_email < 0 || cb_unset < 0) {
module_log("set: Unable to register callbacks");
exit_set();
return 0;
}
return 1;
}
/*************************************************************************/
void exit_set()
{
unregister_callback(cb_unset);
unregister_callback(cb_set_email);
unregister_callback(cb_set);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

854
modules/nickserv/util.c Normal file
View File

@ -0,0 +1,854 @@
/* NickServ utility routines.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
/* If STANDALONE_NICKSERV is defined when compiling this file, the
* following four routines will be defined as `static' for use in other
* modules/programs:
* new_nickinfo()
* free_nickinfo()
* new_nickgroupinfo()
* free_nickgroupinfo()
* This file should be #include'd in the file making use of the routines.
* Note that new_nickgroupinfo() will not check for the presence of any
* nickgroup ID it assigns, since get_nickgroupinfo() is not assumed to be
* available.
*/
#ifdef STANDALONE_NICKSERV
# define STANDALONE_STATIC static
# undef EXPORT_FUNC
# define EXPORT_FUNC(x) /*nothing*/
#else
# define STANDALONE_STATIC /*nothing*/
# include "services.h"
# include "modules.h"
# include "language.h"
# include "encrypt.h"
# include "nickserv.h"
# include "ns-local.h"
#endif
/*************************************************************************/
#ifndef STANDALONE_NICKSERV
static int cb_set_identified = -1;
static int cb_cancel_user = -1;
static int cb_check_recognized = -1;
static int cb_delete = -1;
static int cb_groupdelete = -1;
#endif
/*************************************************************************/
/************************ Global utility routines ************************/
/*************************************************************************/
/* Allocate and initialize a new NickInfo structure. */
EXPORT_FUNC(new_nickinfo)
STANDALONE_STATIC NickInfo *new_nickinfo(void)
{
NickInfo *ni = scalloc(sizeof(*ni), 1);
return ni;
}
/*************************************************************************/
/* Free a NickInfo structure and all associated data. */
EXPORT_FUNC(free_nickinfo)
STANDALONE_STATIC void free_nickinfo(NickInfo *ni)
{
if (ni) {
free(ni->last_usermask);
free(ni->last_realmask);
free(ni->last_realname);
free(ni->last_quit);
free(ni);
}
}
/*************************************************************************/
/* Allocate and initialize a new NickGroupInfo structure. If `seed' is
* non-NULL, the group ID will be set to a value unique from any other
* currently stored in the database (in this case `seed' is used in the
* initial attempt to allocate the ID). If `seed' is NULL, the ID value
* is left at zero.
*/
EXPORT_FUNC(new_nickgroupinfo)
STANDALONE_STATIC NickGroupInfo *new_nickgroupinfo(const char *seed)
{
NickGroupInfo *ngi = scalloc(sizeof(*ngi), 1);
if (seed) {
uint32 id;
int count;
id = 0;
for (count = 0; seed[count] != 0; count++)
id ^= seed[count] << ((count % 6) * 5);
if (id == 0)
id = 1;
#ifndef STANDALONE_NICKSERV
count = 0;
while (put_nickgroupinfo(get_nickgroupinfo(id)) != NULL
&& ++count < NEWNICKGROUP_TRIES
) {
id = rand() + rand(); /* 32 bits of randomness, hopefully */
if (id == 0)
id = 1;
}
if (count >= NEWNICKGROUP_TRIES) {
module_log("new_nickgroupinfo() unable to allocate unused ID"
" after %d tries! Giving up.", NEWNICKGROUP_TRIES);
free(ngi);
return NULL;
}
#endif
ngi->id = id;
}
ngi->memos.memomax = MEMOMAX_DEFAULT;
ngi->channelmax = CHANMAX_DEFAULT;
ngi->language = LANG_DEFAULT;
ngi->timezone = TIMEZONE_DEFAULT;
return ngi;
}
/*************************************************************************/
/* Free a NickGroupInfo structure and all associated data. */
EXPORT_FUNC(free_nickgroupinfo)
STANDALONE_STATIC void free_nickgroupinfo(NickGroupInfo *ngi)
{
int i;
if (!ngi)
return;
free(ngi->nicks);
clear_password(&ngi->pass);
free(ngi->url);
free(ngi->email);
free(ngi->info);
free(ngi->suspend_reason);
ARRAY_FOREACH (i, ngi->access)
free(ngi->access[i]);
free(ngi->access);
ARRAY_FOREACH (i, ngi->ajoin)
free(ngi->ajoin[i]);
free(ngi->ajoin);
free(ngi->channels);
ARRAY_FOREACH (i, ngi->memos.memos) {
free(ngi->memos.memos[i].channel);
free(ngi->memos.memos[i].text);
}
free(ngi->memos.memos);
ARRAY_FOREACH (i, ngi->ignore)
free(ngi->ignore[i]);
free(ngi->ignore);
ARRAY_FOREACH (i, ngi->id_users) {
User *u = ngi->id_users[i];
int j;
ARRAY_SEARCH_PLAIN_SCALAR(u->id_nicks, ngi->id, j);
if (j < u->id_nicks_count) {
ARRAY_REMOVE(u->id_nicks, j);
#ifndef STANDALONE_NICKSERV
} else {
module_log("BUG: free_nickgroupinfo(): user %p (%s) listed in"
" id_users for nickgroup %u, but nickgroup not in"
" id_nicks!", u, u->nick, ngi->id);
#endif
}
}
free(ngi->id_users);
free(ngi);
}
/*************************************************************************/
#ifndef STANDALONE_NICKSERV /* to the end of the file */
/*************************************************************************/
/* Retrieve the NickGroupInfo structure associated with the given NickInfo.
* If it cannot be retrieved, log an error message and return NULL.
* Note: Callers should use get_ngi[_id]() or check_ngi[_id](), which embed
* the current source file and line number in the log message.
*/
EXPORT_FUNC(_get_ngi)
NickGroupInfo *_get_ngi(const NickInfo *ni, const char *file, int line)
{
NickGroupInfo *ngi;
if (!ni) {
module_log("BUG: ni==NULL in get_ngi() (called from %s:%d)",
file, line);
return NULL;
}
ngi = get_nickgroupinfo(ni->nickgroup);
if (!ngi)
module_log("Unable to get NickGroupInfo (id %u) for %s at %s:%d",
ni->nickgroup, ni->nick, file, line);
return ngi;
}
EXPORT_FUNC(_get_ngi_id)
NickGroupInfo *_get_ngi_id(uint32 id, const char *file, int line)
{
NickGroupInfo *ngi = get_nickgroupinfo(id);
if (!ngi)
module_log("Unable to get NickGroupInfo (id %u) at %s:%d",
id, file, line);
return ngi;
}
/*************************************************************************/
/* Return whether the user has previously identified for the given nick
* group (1=yes, 0=no). If the nick group does not exist or is in a
* not-authenticated state, this function will always return 0.
*/
EXPORT_FUNC(has_identified_nick)
int has_identified_nick(const User *u, uint32 group)
{
int i, unauthed;
NickGroupInfo *ngi;
ngi = get_ngi_id(group);
unauthed = ngi_unauthed(ngi);
put_nickgroupinfo(ngi);
if (!ngi || unauthed)
return 0;
ARRAY_SEARCH_PLAIN_SCALAR(u->id_nicks, group, i);
return i < u->id_nicks_count;
}
/*************************************************************************/
/*********************** Internal utility routines ***********************/
/*************************************************************************/
/* Check whether the given nickname is allowed to be registered (with the
* given password and E-mail address) or linked (password/email NULL).
* Returns nonzero if allowed, 0 if not. A wrapper for the "REGISTER/LINK
* check" callback.
*/
int reglink_check(User *u, const char *nick, char *password, char *email)
{
int res = call_callback_4(cb_reglink_check, u, nick, password, email);
if (res < 0)
module_log("REGISTER/LINK callback returned an error");
return res == 0;
}
/*************************************************************************/
/* Update the last_usermask, last_realmask, and last_realname nick fields
* from the given user's data, and sets last_seen to the current time.
* Assumes u->ni is non-NULL.
*/
void update_userinfo(const User *u)
{
const char *host;
NickInfo *ni = u->ni;
ni->last_seen = time(NULL);
free(ni->last_usermask);
if (u->fakehost)
host = u->fakehost;
else
host = u->host;
ni->last_usermask = smalloc(strlen(u->username)+strlen(host)+2);
sprintf(ni->last_usermask, "%s@%s", u->username, host);
free(ni->last_realmask);
ni->last_realmask = smalloc(strlen(u->username)+strlen(u->host)+2);
sprintf(ni->last_realmask, "%s@%s", u->username, u->host);
free(ni->last_realname);
ni->last_realname = sstrdup(u->realname);
}
/*************************************************************************/
/* Check whether a user is recognized for the nick they're using, or if
* they're the same user who last identified for the nick. If not, send
* warnings as appropriate. If so (and not NI_SECURE), update last seen
* info. Return 1 if the user is valid and recognized, 0 otherwise (note
* that this means an NI_SECURE nick will return 0 from here unless the
* user has identified for the nick before). If the user's nick is not
* registered, 0 is returned.
*
* The `nick' parameter is the nick the user is using. It is passed
* separately to handle cases where the User structure has a different
* nick (e.g. when changing nicks).
*/
int validate_user(User *u)
{
NickInfo *ni;
NickGroupInfo *ngi;
int is_recognized;
if (u->ni)
put_nickinfo(u->ni);
if (u->ngi && u->ngi != NICKGROUPINFO_INVALID)
put_nickgroupinfo(u->ngi);
ni = get_nickinfo(u->nick);
if (ni && ni->nickgroup) {
ngi = get_ngi(ni);
if (!ngi) {
put_nickinfo(ni);
ni = NULL;
ngi = NICKGROUPINFO_INVALID;
}
} else {
ngi = NULL;
}
u->ni = ni;
u->ngi = ngi;
if (!ni)
return 0;
ni->authstat = 0;
ni->user = u;
if ((ni->status & NS_VERBOTEN) || (ngi->flags & NF_SUSPENDED)) {
if (usermode_reg) {
send_cmd(s_NickServ, "SVSMODE %s :-%s", u->nick,
mode_flags_to_string(usermode_reg, MODE_USER));
}
notice_lang(s_NickServ, u, NICK_MAY_NOT_BE_USED);
notice_lang(s_NickServ, u, DISCONNECT_IN_1_MINUTE);
add_ns_timeout(ni, TO_SEND_433, 40);
add_ns_timeout(ni, TO_COLLIDE, 60);
return 0;
}
if (!ngi_unauthed(ngi)) {
/* Neither of these checks should be done if the nick is awaiting
* authentication */
if (has_identified_nick(u, ngi->id)) {
set_identified(u);
return 1;
}
if (!NoSplitRecovery) {
/*
* This can be exploited to gain improper privilege if an
* attacker has the same Services stamp, username and hostname
* as the victim.
*
* Under ircd.dal 4.4.15+ (Dreamforge) and other servers
* supporting a Services stamp, Services guarantees that the
* first condition cannot occur unless the stamp counter rolls
* over, which happens every 2^31-1 client connections. This
* is practically infeasible given present technology. As an
* example, on a network of 30 servers, an attack introducing
* 50 new clients every second on every server (requiring at
* least 10-15 megabits of bandwidth) would need to be
* sustained for over 16 days to cause the stamp to roll over.
* Note, however, that since the Services stamp counter is
* initialized randomly at startup, a restart of Services will
* change this situation unpredictably; in the worst case, the
* stamp could be initialized to a value already in use on the
* network.
*
* Under other servers, an attack is theoretically possible,
* but would require access to either the computer the victim
* is using for IRC or the DNS servers for the victim's domain
* and IP address range in order to have the same hostname, and
* would require that the attacker connect so that he has the
* same server timestamp as the victim. Practically, the
* former can be accomplished either by finding a victim who
* uses a shell account on a multiuser system and obtaining an
* account on the same system, or through the scripting
* capabilities of many IRC clients combined with social
* engineering; the latter could be accomplished by finding a
* server with a clock slower than that of the victim's server
* and timing the connection attempt properly.
*
* If someone gets a hacked server into your network, all bets
* are off.
*/
if (ni->id_stamp != 0 && u->servicestamp == ni->id_stamp) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "%s@%s", u->username, u->host);
if (ni->last_realmask && strcmp(buf, ni->last_realmask) == 0) {
set_identified(u);
return 1;
}
}
}
} /* if (!ngi_unauthed(ngi)) */
/* From here on down, the user is known to be not identified. Clear
* any "registered nick" mode from them. */
if (usermode_reg) {
send_cmd(s_NickServ, "SVSMODE %s :-%s", u->nick,
mode_flags_to_string(usermode_reg, MODE_USER));
}
is_recognized = (call_callback_1(cb_check_recognized, u) == 1);
if (!(ngi->flags & NF_SECURE) && !ngi_unauthed(ngi) && is_recognized) {
ni->authstat |= NA_RECOGNIZED;
update_userinfo(u);
return 1;
}
if (is_recognized || ngi_unauthed(ngi) || !NSAllowKillImmed
|| !(ngi->flags & NF_KILL_IMMED)
) {
if (ngi->flags & NF_SECURE)
notice_lang(s_NickServ, u, NICK_IS_SECURE, s_NickServ);
else
notice_lang(s_NickServ, u, NICK_IS_REGISTERED, s_NickServ);
}
if ((ngi->flags & NF_KILLPROTECT) && !is_recognized) {
if (!ngi_unauthed(ngi)
&& NSAllowKillImmed
&& (ngi->flags & NF_KILL_IMMED)
) {
collide_nick(ni, 0);
} else if (!ngi_unauthed(ngi) && (ngi->flags & NF_KILL_QUICK)) {
notice_lang(s_NickServ, u, DISCONNECT_IN_20_SECONDS);
add_ns_timeout(ni, TO_COLLIDE, 20);
add_ns_timeout(ni, TO_SEND_433, 10);
} else {
notice_lang(s_NickServ, u, DISCONNECT_IN_1_MINUTE);
add_ns_timeout(ni, TO_COLLIDE, 60);
add_ns_timeout(ni, TO_SEND_433, 40);
}
}
if (!noexpire && NSExpire && NSExpireWarning
&& !(ni->status & NS_NOEXPIRE)
) {
int time_left = NSExpire - (time(NULL) - ni->last_seen);
if (time_left <= NSExpireWarning) {
notice_lang(s_NickServ, u, NICK_EXPIRES_SOON,
maketime(ngi,time_left,0), s_NickServ, s_NickServ);
}
}
return 0;
}
/*************************************************************************/
/* Cancel validation flags for a nick (i.e. when the user with that nick
* signs off or changes nicks). Also cancels any impending collide, sets
* the nick's last seen time if the user is recognized for the nick and
* unlocks the nick's info records.
*/
void cancel_user(User *u)
{
if (u->ni) {
NickInfo *ni = u->ni;
int old_status = ni->status;
int old_authstat = ni->authstat;
if (nick_id_or_rec(ni))
ni->last_seen = time(NULL);
ni->user = NULL;
ni->status &= ~NS_TEMPORARY;
ni->authstat = 0;
if (old_status & NS_GUESTED)
introduce_enforcer(ni);
call_callback_3(cb_cancel_user, u, old_status, old_authstat);
rem_ns_timeout(ni, TO_COLLIDE, 1);
put_nickinfo(u->ni);
put_nickgroupinfo(u->ngi);
}
u->ni = NULL;
u->ngi = NULL;
}
/*************************************************************************/
/* Marks a user as having identified for the given nick. */
void set_identified(User *u)
{
NickInfo *ni;
NickGroupInfo *ngi;
if (!u || !(ni = u->ni) || !(ngi = u->ngi) || ngi==NICKGROUPINFO_INVALID) {
module_log("BUG: set_identified() for unregistered nick! user%s%s",
u ? "=" : " is NULL", u ? u->nick : "");
return;
}
int old_authstat = ni->authstat;
ni->authstat &= ~NA_IDENT_NOMAIL;
ni->authstat |= NA_IDENTIFIED;
ni->id_stamp = u->servicestamp;
if (!nick_recognized(ni)) {
update_userinfo(u);
ni->authstat |= NA_RECOGNIZED;
}
if (!has_identified_nick(u, ngi->id)) {
ARRAY_EXTEND(u->id_nicks);
u->id_nicks[u->id_nicks_count-1] = ngi->id;
ARRAY_EXTEND(ngi->id_users);
ngi->id_users[ngi->id_users_count-1] = u;
}
if (usermode_reg) {
send_cmd(s_NickServ, "SVSMODE %s :+%s", u->nick,
mode_flags_to_string(usermode_reg, MODE_USER));
}
call_callback_2(cb_set_identified, u, old_authstat);
}
/*************************************************************************/
/*************************************************************************/
/* Add a nick to the database. Returns a pointer to the new NickInfo
* structure if the nick was successfully registered, NULL otherwise.
* Assumes nick does not already exist. If `nickgroup_ret' is non-NULL,
* a new NickGroupInfo structure is created, ni->nickgroup/ngi->*nick*
* are set appropriately, and a pointer to the NickGroupInfo is returned
* in that variable. Note that makenick() may return NULL if a nick group
* is requested and new_nickgroupinfo() returns NULL.
*
* If `nick' is longer than NICKMAX-1 characters, it is silently truncated
* in the new NickInfo and NickGroupInfo.
*/
NickInfo *makenick(const char *nick, NickGroupInfo **nickgroup_ret)
{
NickInfo *ni;
NickGroupInfo *ngi;
#if CLEAN_COMPILE
ngi = NULL;
#endif
if (nickgroup_ret) {
ngi = new_nickgroupinfo(nick);
if (!ngi)
return NULL;
}
ni = new_nickinfo();
strbcpy(ni->nick, nick);
if (nickgroup_ret) {
ni->nickgroup = ngi->id;
ARRAY_EXTEND(ngi->nicks);
strbcpy(ngi->nicks[0], nick);
*nickgroup_ret = add_nickgroupinfo(ngi);
}
return add_nickinfo(ni);
}
/*************************************************************************/
/* Remove a nick from the NickServ database. Return 1 on success, 0
* otherwise.
*/
int delnick(NickInfo *ni)
{
int i;
NickGroupInfo *ngi;
rem_ns_timeout(ni, -1, 1);
if (ni->status & NS_KILL_HELD)
release_nick(ni, 0);
if (ni->user) {
if (usermode_reg)
send_cmd(s_NickServ, "SVSMODE %s :-%s", ni->nick,
mode_flags_to_string(usermode_reg, MODE_USER));
ni->user->ni = NULL;
ni->user->ngi = NULL;
}
ngi = ni->nickgroup ? get_nickgroupinfo(ni->nickgroup) : NULL;
if (ngi) {
ARRAY_SEARCH_PLAIN(ngi->nicks, ni->nick, irc_stricmp, i);
if (i >= ngi->nicks_count) {
module_log("BUG: delete nick: no entry in ngi->nicks[] for"
" nick %s", ni->nick);
} else {
ARRAY_REMOVE(ngi->nicks, i);
/* Adjust ngi->mainnick as needed; if the main nick is being
* deleted, switch it to the next one in the array, or the
* previous if it's currently the last one. */
if (ngi->mainnick > i || ngi->mainnick >= ngi->nicks_count)
ngi->mainnick--;
}
}
call_callback_1(cb_delete, ni);
if (ngi) {
if (ngi->nicks_count == 0) {
call_callback_2(cb_groupdelete, ngi, ni->nick);
del_nickgroupinfo(ngi);
} else {
put_nickgroupinfo(ngi);
}
}
del_nickinfo(ni);
return 1;
}
/*************************************************************************/
/* Remove a nick group and all associated nicks from the NickServ database.
* Return 1 on success, 0 otherwise.
*/
int delgroup(NickGroupInfo *ngi)
{
int i;
ARRAY_FOREACH (i, ngi->nicks) {
NickInfo *ni = get_nickinfo(ngi->nicks[i]);
if (!ni) {
/* We might have just caused it to expire, so don't log any
* error messages */
continue;
}
rem_ns_timeout(ni, -1, 1);
if (ni->status & NS_KILL_HELD) {
release_nick(ni, 0);
}
if (ni->user) {
if (usermode_reg)
send_cmd(s_NickServ, "SVSMODE %s :-%s", ni->nick,
mode_flags_to_string(usermode_reg, MODE_USER));
ni->user->ni = NULL;
ni->user->ngi = NULL;
}
call_callback_1(cb_delete, ni);
del_nickinfo(ni);
}
call_callback_2(cb_groupdelete, ngi, ngi_mainnick(ngi));
del_nickgroupinfo(ngi);
return 1;
}
/*************************************************************************/
/* Drop a nickgroup, logging appropriate information about it first. `u'
* is the user dropping the nickgroup; `dropemail' should have one of the
* following values:
* - NULL for a user dropping their own nick
* - PTR_INVALID for a DROPNICK from a Services admin
* - The pattern used for a DROPEMAIL from a Services admin
* Returns the result of delgroup(ngi).
*/
int drop_nickgroup(NickGroupInfo *ngi, const User *u, const char *dropemail)
{
int i;
module_log("%s!%s@%s dropped nickgroup %u%s%s%s%s%s%s%s:",
u->nick, u->username, u->host, ngi->id,
ngi->email ? " (E-mail " : "",
ngi->email ? ngi->email : "",
ngi->email ? ")" : "",
dropemail ? " as Services admin" : "",
(dropemail && dropemail!=PTR_INVALID) ? " (DROPEMAIL on " : "",
(dropemail && dropemail!=PTR_INVALID) ? dropemail : "",
(dropemail && dropemail!=PTR_INVALID) ? ")" : "");
ARRAY_FOREACH (i, ngi->nicks) {
module_log(" -- %s!%s@%s dropped nick %s",
u->nick, u->username, u->host, ngi->nicks[i]);
}
return delgroup(ngi);
}
/*************************************************************************/
/* Suspend the given nick group. */
void suspend_nick(NickGroupInfo *ngi, const char *reason,
const char *who, const time_t expires)
{
strbcpy(ngi->suspend_who, who);
ngi->suspend_reason = sstrdup(reason);
ngi->suspend_time = time(NULL);
ngi->suspend_expires = expires;
ngi->flags |= NF_SUSPENDED;
}
/*************************************************************************/
/* Delete the suspension data for the given nick group. We also alter the
* last_seen value for all nicks in this group to ensure that they do not
* expire within the next NSSuspendGrace seconds, giving the owner a chance
* to reclaim them (but only if set_time is nonzero).
*/
void unsuspend_nick(NickGroupInfo *ngi, int set_time)
{
time_t now = time(NULL);
if (!(ngi->flags & NF_SUSPENDED)) {
module_log("unsuspend: called on non-suspended nick group %u [%s]",
ngi->id, ngi->nicks[0]);
return;
}
ngi->flags &= ~NF_SUSPENDED;
free(ngi->suspend_reason);
memset(ngi->suspend_who, 0, sizeof(ngi->suspend_who));
ngi->suspend_reason = NULL;
ngi->suspend_time = 0;
ngi->suspend_expires = 0;
if (set_time && NSExpire && NSSuspendGrace) {
int i;
if (ngi->authcode) {
ngi->authset = now;
module_log("unsuspend: altering authset time for %s (nickgroup"
" %u) to %ld", ngi_mainnick(ngi), ngi->id,
(long)ngi->authset);
}
ARRAY_FOREACH (i, ngi->nicks) {
NickInfo *ni = get_nickinfo(ngi->nicks[i]);
if (!ni)
continue;
if (now - ni->last_seen >= NSExpire - NSSuspendGrace) {
ni->last_seen = now - NSExpire + NSSuspendGrace;
module_log("unsuspend: altering last_seen time for %s to %ld",
ni->nick, (long)ni->last_seen);
}
put_nickinfo(ni);
}
}
}
/*************************************************************************/
/*************************************************************************/
/* Check the password given by the user and handle errors if it's not
* correct (or the encryption routine fails). `command' is the name of
* the command calling this function, and `failure_msg' is the message to
* send to the user if the encryption routine fails. Returns 1 if the
* password matches, 0 otherwise.
*/
int nick_check_password(User *u, NickInfo *ni, const char *password,
const char *command, int failure_msg)
{
int res;
NickGroupInfo *ngi = get_ngi(ni);
if (!ngi) {
module_log("%s: no nickgroup for %s, aborting password check",
command, ni->nick);
notice_lang(s_NickServ, u, failure_msg);
return 0;
}
res = check_password(password, &ngi->pass);
put_nickgroupinfo(ngi);
if (res == 0) {
module_log("%s: bad password for %s from %s!%s@%s",
command, ni->nick, u->nick, u->username, u->host);
bad_password(s_NickServ, u, ni->nick);
ni->bad_passwords++;
if (BadPassWarning && ni->bad_passwords == BadPassWarning) {
wallops(s_NickServ, "\2Warning:\2 Repeated bad password attempts"
" for nick %s", ni->nick);
}
return 0;
} else if (res == -1) {
module_log("%s: check_password failed for %s",
command, ni->nick);
notice_lang(s_NickServ, u, failure_msg);
return 0;
} else {
ni->bad_passwords = 0;
return 1;
}
}
/*************************************************************************/
/* Return the number of registered nicknames with the given E-mail address.
* If a registered nickname matches the given E-mail address but the
* address has not been authenticated, return the negative of the number of
* registered nicknames: for example, if there are 5 nicks with the given
* address, -5 would be returned.
*
* Note that this function is O(n) in the total number of registered nick
* groups, so do not use it lightly!
*/
int count_nicks_with_email(const char *email)
{
int count = 0, unauthed = 0;
NickGroupInfo *ngi;
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
if (ngi->email && stricmp(ngi->email, email) == 0) {
if (ngi_unauthed(ngi))
unauthed = 1;
count += ngi->nicks_count;
}
}
return unauthed ? -count : count;
}
/*************************************************************************/
/*************************************************************************/
int init_util(void)
{
cb_set_identified = register_callback("set identified");
cb_cancel_user = register_callback("cancel user");
cb_check_recognized = register_callback("check recognized");
cb_delete = register_callback("nick delete");
cb_groupdelete = register_callback("nickgroup delete");
if (cb_cancel_user < 0 || cb_check_recognized < 0 || cb_delete < 0
|| cb_groupdelete < 0
) {
module_log("Unable to register callbacks (util.c)");
return 0;
}
return 1;
}
/*************************************************************************/
void exit_util()
{
unregister_callback(cb_groupdelete);
unregister_callback(cb_delete);
unregister_callback(cb_check_recognized);
unregister_callback(cb_cancel_user);
unregister_callback(cb_set_identified);
}
/*************************************************************************/
#endif /* !STANDALONE_NICKSERV */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

31
modules/operserv/Makefile Normal file
View File

@ -0,0 +1,31 @@
# Makefile for OperServ modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = main.so akill.so news.so sessions.so sline.so
OBJECTS-main.so = maskdata.o
INCLUDES = operserv.h $(TOPDIR)/commands.h $(TOPDIR)/databases.h \
$(TOPDIR)/language.h
INCLUDES-main.o = maskdata.h akill.h news.h \
$(TOPDIR)/timeout.h $(TOPDIR)/encrypt.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-maskdata.o = maskdata.h
INCLUDES-akill.o = maskdata.h akill.h
INCLUDES-news.o = news.h
INCLUDES-sessions.o = maskdata.h akill.h
INCLUDES-sline.o = maskdata.h sline.h
include ../Makerules
###########################################################################

813
modules/operserv/akill.c Normal file
View File

@ -0,0 +1,813 @@
/* Autokill list module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "databases.h"
#include "language.h"
#include "timeout.h"
#include "operserv.h"
#define NEED_MAKE_REASON
#include "maskdata.h"
#include "akill.h"
/*************************************************************************/
static Module *module_operserv;
static int cb_send_akill = -1;
static int cb_send_exclude = -1;
static int cb_cancel_akill = -1;
static int cb_cancel_exclude = -1;
static char * AutokillReason;
static int ImmediatelySendAutokill;
static time_t AutokillExpiry;
static time_t AkillChanExpiry;
static time_t OperMaxExpiry;
static int WallOSAkill;
static int WallAutokillExpire;
int EnableExclude; /* not static because main.c/do_help() needs it */
static time_t ExcludeExpiry;
static char * ExcludeReason;
EXPORT_VAR(int,EnableExclude)
static void do_akill(User *u);
static void do_akillchan(User *u);
static void do_exclude(User *u);
static Command cmds[] = {
{"AKILL", do_akill, is_services_oper, OPER_HELP_AKILL, -1,-1},
{"AKILLCHAN", do_akillchan, is_services_oper, OPER_HELP_AKILLCHAN, -1,-1},
{"EXCLUDE", do_exclude, is_services_oper, OPER_HELP_EXCLUDE, -1,-1},
{ NULL }
};
/*************************************************************************/
/************************** Internal functions ***************************/
/*************************************************************************/
/* Send an autokill to the uplink server. */
static void send_akill(const MaskData *akill)
{
char *username, *host;
static int warned_exclude = 0;
/* Don't send autokills if EnableExclude but no ircd support */
if (EnableExclude && !(protocol_features & PF_AKILL_EXCL)) {
if (!warned_exclude) {
wallops(s_OperServ, "Warning: Autokill exclusions are enabled,"
" but this IRC server does not support autokill"
" exclusions; autokills will not be sent to servers.");
module_log("EnableExclude on server type without exclusions--"
"autokill sending disabled");
warned_exclude = 1;
}
return;
} else {
warned_exclude = 0;
}
username = sstrdup(akill->mask);
host = strchr(username, '@');
if (!host) {
/* Glurp... this oughtn't happen, but if it does, let's not
* play with null pointers. Yell and bail out. */
wallops(NULL, "Missing @ in autokill: %s", akill->mask);
module_log("BUG: (send_akill) Missing @ in mask: %s", akill->mask);
free(username);
return;
}
*host++ = 0;
call_callback_5(cb_send_akill, username, host, akill->expires, akill->who,
make_reason(AutokillReason, akill));
free(username);
}
/*************************************************************************/
/* Remove an autokill from the uplink server. */
static void cancel_akill(char *mask)
{
char *s = strchr(mask, '@');
if (s) {
*s++ = 0;
call_callback_2(cb_cancel_akill, mask, s);
} else {
module_log("BUG: (cancel_akill) Missing @ in mask: %s", mask);
}
}
/*************************************************************************/
/* Send an autokill exclusion to the uplink server. */
static void send_exclude(const MaskData *exclude)
{
char *username, *host;
username = sstrdup(exclude->mask);
host = strchr(username, '@');
if (!host) {
wallops(NULL, "Missing @ in autokill exclusion: %s", exclude->mask);
module_log("BUG: (send_exclude) Missing @ in mask: %s", exclude->mask);
return;
}
*host++ = 0;
call_callback_5(cb_send_exclude, username, host, exclude->expires,
exclude->who, make_reason(ExcludeReason, exclude));
free(username);
}
/*************************************************************************/
/* Remove an autokill exclusion from the uplink server. */
static void cancel_exclude(char *mask)
{
char *s = strchr(mask, '@');
if (s) {
*s++ = 0;
call_callback_2(cb_cancel_exclude, mask, s);
} else {
module_log("BUG: (cancel_exclude) Missing @ in mask: %s", mask);
}
}
/*************************************************************************/
/************************** External functions ***************************/
/*************************************************************************/
/* Does the user match any autokills? Return 1 (and kill the user) if so,
* else 0.
*/
static int do_user_check(int ac, char **av)
{
const char *nick = av[0], *username = av[3], *host = av[4];
char buf[BUFSIZE];
MaskData *akill;
if (noakill)
return 0;
snprintf(buf, sizeof(buf), "%s@%s", username, host);
if ((akill = get_matching_maskdata(MD_AKILL, buf)) != NULL) {
if (EnableExclude
&& put_maskdata(get_matching_maskdata(MD_EXCLUDE, buf))
) {
put_maskdata(akill);
return 0;
}
/* Don't use kill_user(); that's for people who have already
* signed on. This is called before the User structure is
* created. */
send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ,
make_reason(AutokillReason, akill));
send_akill(akill);
time(&akill->lastused);
put_maskdata(akill);
return 1;
}
return 0;
}
/*************************************************************************/
/************************** AKILL list editing ***************************/
/*************************************************************************/
/* Note that all string parameters are assumed to be non-NULL; expiry must
* be set to the time when the autokill should expire (0 for none). Mask
* is converted to lowercase on return.
*/
EXPORT_FUNC(create_akill)
void create_akill(char *mask, const char *reason, const char *who,
time_t expiry)
{
MaskData *akill;
strlower(mask);
if (maskdata_count(MD_AKILL) >= MAX_MASKDATA) {
module_log("Attempt to add autokill to full list!");
return;
}
akill = scalloc(1, sizeof(*akill));
akill->mask = sstrdup(mask);
akill->reason = sstrdup(reason);
akill->time = time(NULL);
akill->expires = expiry;
strbcpy(akill->who, who);
akill = add_maskdata(MD_AKILL, akill);
if (ImmediatelySendAutokill)
send_akill(akill);
}
/*************************************************************************/
/*************************************************************************/
/* Handle an OperServ AKILL command. */
/*************************************************************************/
static int check_add_akill(const User *u, uint8 type, char *mask,
time_t *expiry_ptr);
static void do_add_akill(const User *u, uint8 type, MaskData *md);
static void do_del_akill(const User *u, uint8 type, MaskData *md);
static MaskDataCmdInfo akill_cmd_info = {
.name = "AKILL",
.md_type = MD_AKILL,
.def_expiry_ptr = &AutokillExpiry,
.msg_add_too_many = OPER_TOO_MANY_AKILLS,
.msg_add_exists = OPER_AKILL_EXISTS,
.msg_added = OPER_AKILL_ADDED,
.msg_del_not_found = OPER_AKILL_NOT_FOUND,
.msg_deleted = OPER_AKILL_REMOVED,
.msg_cleared = OPER_AKILL_CLEARED,
.msg_list_header = OPER_AKILL_LIST_HEADER,
.msg_list_empty = OPER_AKILL_LIST_EMPTY,
.msg_list_no_match = OPER_AKILL_LIST_NO_MATCH,
.msg_check_no_match = OPER_AKILL_CHECK_NO_MATCH,
.msg_check_header = OPER_AKILL_CHECK_HEADER,
.msg_check_count = OPER_AKILL_CHECK_TRAILER,
.msg_count = OPER_AKILL_COUNT,
.mangle_mask = (void (*))strlower,
.check_add_mask = check_add_akill,
.do_add_mask = do_add_akill,
.do_del_mask = do_del_akill,
.do_unknown_cmd = NULL,
};
/*************************************************************************/
static void do_akill(User *u)
{
if (is_services_admin(u) || !OperMaxExpiry)
akill_cmd_info.def_expiry_ptr = &AutokillExpiry;
else
akill_cmd_info.def_expiry_ptr = &OperMaxExpiry;
do_maskdata_cmd(&akill_cmd_info, u);
}
/*************************************************************************/
static int check_add_akill(const User *u, uint8 type, char *mask,
time_t *expiry_ptr)
{
char *s, *t;
time_t len;
if (strchr(mask, '!')) {
notice_lang(s_OperServ, u, OPER_AKILL_NO_NICK);
notice_lang(s_OperServ, u, BAD_USERHOST_MASK);
return 0;
}
s = strchr(mask, '@');
if (!s || s == mask || s[1] == 0) {
notice_lang(s_OperServ, u, BAD_USERHOST_MASK);
return 0;
}
/* Make sure mask is not too general. */
*s++ = 0;
if (strchr(mask,'*') != NULL && mask[strspn(mask,"*?")] == 0
&& ((t = strchr(mask,'?')) == NULL || strchr(t+1,'?') == NULL)
) {
/* Username part matches anything; check host part */
if (strchr(s,'*') != NULL && s[strspn(s,"*?.")] == 0
&& ((t = strchr(s,'.')) == NULL || strchr(t+1,'.') == NULL)
) {
/* Hostname mask matches anything or nearly anything, so
* disallow mask. */
notice_lang(s_OperServ, u, OPER_AKILL_MASK_TOO_GENERAL);
return 0;
}
}
s[-1] = '@'; /* Replace "@" that we killed above */
/* Check expiration limit for non-servadmins. */
len = *expiry_ptr - time(NULL);
if (OperMaxExpiry && !is_services_admin(u)
&& (!*expiry_ptr || len > OperMaxExpiry)
) {
notice_lang(s_OperServ, u, OPER_AKILL_EXPIRY_LIMITED,
maketime(u->ngi, OperMaxExpiry, MT_DUALUNIT));
return 0;
}
return 1;
}
/*************************************************************************/
static void do_add_akill(const User *u, uint8 type, MaskData *md)
{
if (WallOSAkill) {
char buf[BUFSIZE];
expires_in_lang(buf, sizeof(buf), NULL, md->expires);
wallops(s_OperServ, "%s added an autokill for \2%s\2 (%s)",
u->nick, md->mask, buf);
}
if (ImmediatelySendAutokill)
send_akill(md);
}
/*************************************************************************/
static void do_del_akill(const User *u, uint8 type, MaskData *md)
{
cancel_akill(md->mask);
}
/*************************************************************************/
/*************************************************************************/
/* Handle an OperServ EXCLUDE command. */
/*************************************************************************/
static int check_add_exclude(const User *u, uint8 type, char *mask,
time_t *expiry_ptr);
static void do_add_exclude(const User *u, uint8 type, MaskData *md);
static void do_del_exclude(const User *u, uint8 type, MaskData *md);
static MaskDataCmdInfo exclude_cmd_info = {
.name = "EXCLUDE",
.md_type = MD_EXCLUDE,
.def_expiry_ptr = &ExcludeExpiry,
.msg_add_too_many = OPER_TOO_MANY_EXCLUDES,
.msg_add_exists = OPER_EXCLUDE_EXISTS,
.msg_added = OPER_EXCLUDE_ADDED,
.msg_del_not_found = OPER_EXCLUDE_NOT_FOUND,
.msg_deleted = OPER_EXCLUDE_REMOVED,
.msg_cleared = OPER_EXCLUDE_CLEARED,
.msg_list_header = OPER_EXCLUDE_LIST_HEADER,
.msg_list_empty = OPER_EXCLUDE_LIST_EMPTY,
.msg_list_no_match = OPER_EXCLUDE_LIST_NO_MATCH,
.msg_check_no_match = OPER_EXCLUDE_CHECK_NO_MATCH,
.msg_check_header = OPER_EXCLUDE_CHECK_HEADER,
.msg_check_count = OPER_EXCLUDE_CHECK_TRAILER,
.msg_count = OPER_EXCLUDE_COUNT,
.mangle_mask = (void (*))strlower,
.check_add_mask = check_add_exclude,
.do_add_mask = do_add_exclude,
.do_del_mask = do_del_exclude,
.do_unknown_cmd = NULL,
};
/*************************************************************************/
static void do_exclude(User *u)
{
if (is_services_admin(u) || !OperMaxExpiry)
exclude_cmd_info.def_expiry_ptr = &AutokillExpiry;
else
exclude_cmd_info.def_expiry_ptr = &OperMaxExpiry;
do_maskdata_cmd(&exclude_cmd_info, u);
}
/*************************************************************************/
static int check_add_exclude(const User *u, uint8 type, char *mask,
time_t *expiry_ptr)
{
char *s;
s = strchr(mask, '@');
if (!s || s == mask || s[1] == 0) {
notice_lang(s_OperServ, u, BAD_USERHOST_MASK);
return 0;
}
return 1;
}
/*************************************************************************/
static void do_add_exclude(const User *u, uint8 type, MaskData *md)
{
if (WallOSAkill) {
char buf[BUFSIZE];
expires_in_lang(buf, sizeof(buf), NULL, md->expires);
wallops(s_OperServ, "%s added an EXCLUDE for \2%s\2 (%s)",
u->nick, md->mask, buf);
}
send_exclude(md);
}
/*************************************************************************/
static void do_del_exclude(const User *u, uint8 type, MaskData *md)
{
cancel_exclude(md->mask);
}
/*************************************************************************/
/*************************************************************************/
/* Handle an OperServ AKILLCHAN command. */
/*************************************************************************/
static void do_akillchan(User *u)
{
char *channel, *expiry_str, *reason, *s;
int kill; /* kill users in the channel? */
int32 expiry;
Channel *c;
struct c_userlist *cu, *cu2;
int count;
int old_immed; /* saved value of ImmediatelySendAutokill */
kill = 0;
expiry_str = NULL;
s = strtok(NULL, " ");
if (s && stricmp(s,"KILL") == 0) {
kill = 1;
s = strtok(NULL, " ");
}
if (s && *s == '+') {
expiry_str = s+1;
s = strtok(NULL, " ");
}
if (!s || *s != '#') {
syntax_error(s_OperServ, u, "AKILLCHAN", OPER_AKILLCHAN_SYNTAX);
return;
}
channel = s;
reason = strtok_remaining();
if (!reason) {
syntax_error(s_OperServ, u, "AKILLCHAN", OPER_AKILLCHAN_SYNTAX);
return;
}
if (!(c = get_channel(channel))) {
notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, channel);
return;
}
if (expiry_str) {
expiry = dotime(expiry_str);
} else {
if (!is_services_admin(u) && OperMaxExpiry
&& (!AkillChanExpiry || OperMaxExpiry < AkillChanExpiry)
) {
expiry = OperMaxExpiry;
} else {
expiry = AkillChanExpiry;
}
}
if (expiry)
expiry += time(NULL);
if (WallOSAkill)
wallops(s_OperServ, "%s used AKILLCHAN for \2%s\2", u->nick, c->name);
count = 0;
old_immed = ImmediatelySendAutokill;
ImmediatelySendAutokill = 1;
LIST_FOREACH_SAFE (cu, c->users, cu2) {
char buf[BUFSIZE];
if (is_oper(cu->user))
continue;
/* Killing the user before adding the autokill opens a small hole
* in that the user may be able to reconnect before the new
* autokill reaches the server, but under normal conditions the
* actual chance of this is vanishingly small. On the other hand,
* the chance of people complaining about "user not found" errors
* from ircds that kill users on autokill if we do the autokill
* first is significantly greater... */
/* But as long as you kill the user first, make sure you save the
* hostname _before_ it gets freed, idiot. */
snprintf(buf, sizeof(buf), "*@%s", cu->user->host);
if (kill)
kill_user(s_OperServ, cu->user->nick, reason);
if (!put_maskdata(get_maskdata(MD_AKILL, buf)))
create_akill(buf, reason, u->nick, expiry);
count++;
}
ImmediatelySendAutokill = old_immed;
if (count == 1) {
notice_lang(s_OperServ, u,
kill ? OPER_AKILLCHAN_KILLED_ONE
: OPER_AKILLCHAN_AKILLED_ONE);
} else {
notice_lang(s_OperServ, u,
kill ? OPER_AKILLCHAN_KILLED : OPER_AKILLCHAN_AKILLED,
count);
}
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
/* Callback on connection to uplink server. */
static int do_connect(void)
{
if (ImmediatelySendAutokill) {
MaskData *akill;
for (akill = first_maskdata(MD_AKILL); akill;
akill = next_maskdata(MD_AKILL)
) {
send_akill(akill);
}
}
return 0;
}
/*************************************************************************/
/* Callback for autokill expiration. */
static int expire_omitted_count = 0;
static time_t expire_last_time = 0;
static Timeout *expire_to = NULL;
static void expire_ratelimit_timeout(Timeout *to);
static int do_expire_maskdata(uint32 type, MaskData *md)
{
if (type == MD_AKILL) {
if (WallAutokillExpire) {
if (time(NULL) == expire_last_time) {
if (expire_to)
del_timeout(expire_to);
expire_to = add_timeout_ms(1500, expire_ratelimit_timeout, 0);
expire_omitted_count++;
} else {
wallops(s_OperServ, "Autokill on %s has expired", md->mask);
}
expire_last_time = time(NULL);
}
cancel_akill(md->mask);
return 1;
}
return 0;
}
static void expire_ratelimit_timeout(Timeout *to)
{
wallops(s_OperServ, "%d more autokill%s ha%s expired",
expire_omitted_count, expire_omitted_count==1 ? "" : "s",
expire_omitted_count==1 ? "s" : "ve");
expire_omitted_count = 0;
expire_last_time = 0;
expire_to = NULL;
}
/*************************************************************************/
/* Callback to display help text for HELP AKILL. */
static int do_help(User *u, const char *param)
{
if (stricmp(param, "AKILL") == 0) {
notice_help(s_OperServ, u, OPER_HELP_AKILL);
if (OperMaxExpiry)
notice_help(s_OperServ, u, OPER_HELP_AKILL_OPERMAXEXPIRY,
maketime(u->ngi, OperMaxExpiry, MT_DUALUNIT));
notice_help(s_OperServ, u, OPER_HELP_AKILL_END);
return 1;
} else if (stricmp(param, "AKILLCHAN") == 0) {
notice_help(s_OperServ, u, OPER_HELP_AKILLCHAN,
maketime(u->ngi, AkillChanExpiry, 0));
return 1;
}
return 0;
}
/*************************************************************************/
static int do_stats_all(User *user, const char *s_OperServ)
{
int32 count, mem;
MaskData *md;
count = mem = 0;
for (md = first_maskdata(MD_AKILL); md; md = next_maskdata(MD_AKILL)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
for (md = first_maskdata(MD_EXCLUDE); md;
md = next_maskdata(MD_EXCLUDE)
) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_AKILL_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
static void insert_akill(void *md) { add_maskdata(MD_AKILL, md); }
static void *first_akill(void) { return first_maskdata(MD_AKILL); }
static void *next_akill(void) { return next_maskdata(MD_AKILL); }
static void insert_exclude(void *md) { add_maskdata(MD_EXCLUDE, md); }
static void *first_exclude(void) { return first_maskdata(MD_EXCLUDE); }
static void *next_exclude(void) { return next_maskdata(MD_EXCLUDE); }
static DBField akill_exclude_dbfields[] = {
{ "mask", DBTYPE_STRING, offsetof(MaskData,mask) },
{ "reason", DBTYPE_STRING, offsetof(MaskData,reason) },
{ "who", DBTYPE_BUFFER, offsetof(MaskData,who), NICKMAX },
{ "time", DBTYPE_TIME, offsetof(MaskData,time) },
{ "expires", DBTYPE_TIME, offsetof(MaskData,expires) },
{ "lastused", DBTYPE_TIME, offsetof(MaskData,lastused) },
{ NULL }
};
static DBTable akill_dbtable = {
.name = "akill",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_akill,
.first = first_akill,
.next = next_akill,
.fields = akill_exclude_dbfields,
};
static DBTable exclude_dbtable = {
.name = "exclude",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_exclude,
.first = first_exclude,
.next = next_exclude,
.fields = akill_exclude_dbfields,
};
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "AkillChanExpiry", { { CD_TIME, 0, &AkillChanExpiry } } },
{ "AutokillExpiry", { { CD_TIME, 0, &AutokillExpiry } } },
{ "AutokillReason", { { CD_STRING, CF_DIRREQ, &AutokillReason } } },
{ "EnableExclude", { { CD_SET, 0, &EnableExclude } } },
{ "ExcludeExpiry", { { CD_TIME, 0, &ExcludeExpiry } } },
{ "ExcludeReason", { { CD_STRING, 0, &ExcludeReason } } },
{ "ImmediatelySendAutokill",{{CD_SET, 0, &ImmediatelySendAutokill } } },
{ "OperMaxExpiry", { { CD_TIME, 0, &OperMaxExpiry } } },
{ "WallAutokillExpire",{{ CD_SET, 0, &WallAutokillExpire } } },
{ "WallOSAkill", { { CD_SET, 0, &WallOSAkill } } },
{ NULL }
};
/* Pointer to EXCLUDE command record (for EnableExclude) */
static Command *cmd_EXCLUDE;
/*************************************************************************/
static int do_reconfigure(int after_configure)
{
if (after_configure) {
/* After reconfiguration: handle value changes. */
if (EnableExclude && !ExcludeReason) {
module_log("EXCLUDE enabled but ExcludeReason not set; disabling"
" EXCLUDE");
EnableExclude = 0;
}
if (EnableExclude)
cmd_EXCLUDE->name = "EXCLUDE";
else
cmd_EXCLUDE->name = "";
} /* if (!after_configure) */
return 0;
}
/*************************************************************************/
int init_module(void)
{
if (EnableExclude && !ExcludeReason) {
module_log("EXCLUDE enabled but ExcludeReason not set");
return 0;
}
module_operserv = find_module("operserv/main");
if (!module_operserv) {
module_log("Main OperServ module not loaded");
return 0;
}
use_module(module_operserv);
if (!register_commands(module_operserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
cmd_EXCLUDE = lookup_cmd(module_operserv, "EXCLUDE");
if (!cmd_EXCLUDE) {
module_log("BUG: unable to find EXCLUDE command entry");
exit_module(0);
return 0;
}
if (!EnableExclude)
cmd_EXCLUDE->name = "";
cb_send_akill = register_callback("send_akill");
cb_send_exclude = register_callback("send_exclude");
cb_cancel_akill = register_callback("cancel_akill");
cb_cancel_exclude = register_callback("cancel_exclude");
if (cb_send_akill < 0 || cb_send_exclude < 0 || cb_cancel_akill < 0
|| cb_cancel_exclude < 0
) {
module_log("Unable to register callbacks");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "reconfigure", do_reconfigure)
|| !add_callback(NULL, "connect", do_connect)
|| !add_callback(NULL, "user check", do_user_check)
|| !add_callback(module_operserv, "expire maskdata", do_expire_maskdata)
|| !add_callback(module_operserv, "HELP", do_help)
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&akill_dbtable)
|| !register_dbtable(&exclude_dbtable)
) {
module_log("Unable to register database tables");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&exclude_dbtable);
unregister_dbtable(&akill_dbtable);
remove_callback(NULL, "user check", do_user_check);
remove_callback(NULL, "connect", do_connect);
remove_callback(NULL, "reconfigure", do_reconfigure);
unregister_callback(cb_cancel_exclude);
unregister_callback(cb_cancel_akill);
unregister_callback(cb_send_exclude);
unregister_callback(cb_send_akill);
if (module_operserv) {
remove_callback(module_operserv, "STATS ALL", do_stats_all);
remove_callback(module_operserv, "HELP", do_help);
remove_callback(module_operserv, "expire maskdata",do_expire_maskdata);
unregister_commands(module_operserv, cmds);
unuse_module(module_operserv);
module_operserv = NULL;
}
cmd_EXCLUDE->name = "EXCLUDE";
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

26
modules/operserv/akill.h Normal file
View File

@ -0,0 +1,26 @@
/* Header for autokill stuff.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef AKILL_H
#define AKILL_H
E void create_akill(char *mask, const char *reason, const char *who,
time_t expiry);
#endif /* AKILL_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

2241
modules/operserv/main.c Normal file

File diff suppressed because it is too large Load Diff

674
modules/operserv/maskdata.c Normal file
View File

@ -0,0 +1,674 @@
/* Miscellaneous MaskData-related functionality.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "commands.h"
#include "operserv.h"
#include "maskdata.h"
static void do_maskdata_add(const MaskDataCmdInfo *info, const User *u,
char *mask, const char *expiry);
static void do_maskdata_del(const MaskDataCmdInfo *info, const User *u,
const char *mask);
static void do_maskdata_clear(const MaskDataCmdInfo *info, const User *u,
const char *mask);
static void do_maskdata_list(const MaskDataCmdInfo *info, const User *u,
int is_view, const char *mask,
const char *skipstr);
static void maskdata_list_entry(const MaskDataCmdInfo *info, const User *u,
int is_view, const MaskData *md);
static void do_maskdata_check(const MaskDataCmdInfo *info, const User *u,
const char *mask);
/*************************************************************************/
/*************************** Database routines ***************************/
/*************************************************************************/
static MaskData *masklist[256];
static int32 masklist_count[256];
static int masklist_iterator[256];
static int cb_expire_md = -1;
/*************************************************************************/
/* Check for expiration of a MaskData; delete it and return 1 if expired,
* return 0 if not expired.
*/
static int check_expire_maskdata(uint8 type, MaskData *md)
{
if (md->expires && md->expires <= time(NULL)) {
call_callback_2(cb_expire_md, type, md);
del_maskdata(type, md);
return 1;
}
return 0;
}
/*************************************************************************/
EXPORT_FUNC(add_maskdata)
MaskData *add_maskdata(uint8 type, MaskData *data)
{
int num = masklist_count[type];
if (num >= MAX_MASKDATA)
fatal("add_maskdata(): too many items for type %u", type);
ARRAY2_EXTEND(masklist[type], masklist_count[type]);
memcpy(&masklist[type][num], data, sizeof(*data));
masklist[type][num].next = (MaskData *)(long)num; /* use as index */
free(data);
masklist[type][num].type = type;
masklist[type][num].usecount = 1;
return &masklist[type][num];
}
/*************************************************************************/
EXPORT_FUNC(del_maskdata)
void del_maskdata(uint8 type, MaskData *data)
{
int num = (int)(long)(data->next);
if (num < 0 || num >= masklist_count[type]) {
module_log("del_maskdata(): invalid index %d for type %u at %p",
num, type, data);
return;
}
free(data->mask);
free(data->reason);
ARRAY2_REMOVE(masklist[type], masklist_count[type], num);
if (num < masklist_iterator[type])
masklist_iterator[type]--;
while (num < masklist_count[type]) {
masklist[type][num].next = (MaskData *)(long)num;
num++;
}
}
/*************************************************************************/
EXPORT_FUNC(get_maskdata)
MaskData *get_maskdata(uint8 type, const char *mask)
{
int i;
MaskData *result;
ARRAY2_SEARCH(masklist[type],masklist_count[type],mask,mask,stricmp,i);
if (i >= masklist_count[type])
return NULL;
result = &masklist[type][i];
if (!noexpire && !result->usecount && check_expire_maskdata(type,result))
result = NULL;
else
result->usecount++;
return result;
}
/*************************************************************************/
EXPORT_FUNC(get_matching_maskdata)
MaskData *get_matching_maskdata(uint8 type, const char *str)
{
int i;
ARRAY2_FOREACH (i, masklist[type], masklist_count[type]) {
if (match_wild_nocase(masklist[type][i].mask, str)) {
MaskData *result = &masklist[type][i];
if (noexpire || result->usecount
|| !check_expire_maskdata(type,result)
) {
result->usecount++;
return result;
} else {
i--;
}
}
}
return NULL;
}
/*************************************************************************/
EXPORT_FUNC(put_maskdata)
MaskData *put_maskdata(MaskData *data)
{
if (data) {
if (data->usecount > 0)
data->usecount--;
else
module_log_debug(1, "BUG: put_maskdata(%u,%s) with usecount==0",
data->type, data->mask);
}
return data;
}
/*************************************************************************/
EXPORT_FUNC(first_maskdata)
MaskData *first_maskdata(uint8 type)
{
masklist_iterator[type] = 0;
return next_maskdata(type);
}
EXPORT_FUNC(next_maskdata)
MaskData *next_maskdata(uint8 type)
{
MaskData *result;
do {
if (masklist_iterator[type] >= masklist_count[type])
return NULL;
result = &masklist[type][masklist_iterator[type]++];
} while (!noexpire && check_expire_maskdata(type, result));
return result;
}
/*************************************************************************/
EXPORT_FUNC(maskdata_count)
int maskdata_count(uint8 type)
{
return masklist_count[type];
}
/*************************************************************************/
EXPORT_FUNC(get_exception_by_num)
MaskData *get_exception_by_num(int num)
{
int i;
MaskData *result;
ARRAY2_SEARCH_SCALAR(masklist[MD_EXCEPTION], masklist_count[MD_EXCEPTION],
num, num, i);
if (i >= masklist_count[MD_EXCEPTION])
return NULL;
result = &masklist[MD_EXCEPTION][i];
return !noexpire && check_expire_maskdata(MD_EXCEPTION, result)
? NULL : result;
}
/*************************************************************************/
/* Move an exception so it has the given number; we can assume that no
* other exception already has that number.
*/
EXPORT_FUNC(move_exception)
MaskData *move_exception(MaskData *except, int newnum)
{
int count; /* shortcut for "masklist_count[MD_EXCEPTION]" */
int index; /* index of `except' */
int newindex; /* where `except' should be moved to */
MaskData tmp; /* to save the data while we move it around */
count = masklist_count[MD_EXCEPTION];
index = except - masklist[MD_EXCEPTION];
if ((index == 0 || except[-1].num < newnum)
&& (index == count-1 || except[1].num >= newnum)
) {
/* New number is already between previous and next entries; no need
* to move it around, just renumber and exit */
except->num = newnum;
while (++index < count && except[1].num == except[0].num) {
except[1].num++;
except++;
}
return except;
}
/* Save the old exception data and remove it from the array */
tmp = *except;
if (index < count-1)
memmove(except, except+1, sizeof(*except) * ((count-1)-index));
/* Find where the exception should go */
for (newindex = 0; newindex < count-1; newindex++) {
if (masklist[MD_EXCEPTION][newindex].num >= newnum)
break;
}
/* Sanity check--this case should have been caught above */
if (index == newindex) {
module_log("BUG: move_exception didn't catch index == newindex for"
" exception %d!", newnum);
}
/* Actually put it where it belongs */
except = &masklist[MD_EXCEPTION][newindex];
if (newindex < count-1)
memmove(except+1, except, sizeof(*except) * ((count-1)-newindex));
*except = tmp;
except->num = newnum;
/* Increment following exception number as needed */
for (index = newindex+1; index < count; index++) {
if (except[1].num == except[0].num)
except[1].num++;
else
break;
except++;
}
/* Update all index pointers (we only need to touch the ones that
* moved, but do it for all of them for simplicity/safety) */
for (index = 0; index < count; index++) {
masklist[MD_EXCEPTION][index].next = (MaskData *)(long)index;
}
return &masklist[MD_EXCEPTION][newindex];
}
/*************************************************************************/
/* Free all memory used by database tables. */
static void clean_dbtables(void)
{
int i, j;
for (i = 0; i < 256; i++) {
for (j = 0; j < masklist_count[i]; j++) {
free(masklist[i][j].mask);
free(masklist[i][j].reason);
}
free(masklist[i]);
masklist[i] = NULL;
masklist_count[i] = 0;
}
}
/*************************************************************************/
/**************** MaskData-related command helper routine ****************/
/*************************************************************************/
/* This routines is designed to be called from a command handler for a
* command that deals with MaskData records (AKILL, EXCEPTION, etc.), to
* help avoid code repetition in those command handlers. The `info'
* structure passed as the first parameter to each function contains
* information about the command, such as command name and message numbers;
* see maskdata.h for details.
*/
void do_maskdata_cmd(const MaskDataCmdInfo *info, const User *u)
{
const char *cmd;
char *mask, *expiry, *skipstr, *s;
cmd = strtok(NULL, " ");
if (!cmd)
cmd = "";
expiry = NULL;
skipstr = NULL;
s = strtok_remaining();
if (stricmp(cmd,"ADD") == 0 && s && *s == '+') {
expiry = strtok(s+1, " ");
s = strtok_remaining();
} else if ((stricmp(cmd,"LIST") == 0 || stricmp(cmd,"VIEW") == 0)
&& s && *s == '+') {
skipstr = strtok(s+1, " ");
s = strtok_remaining();
}
if (s && *s == '"') {
mask = s+1;
s = strchr(mask, '"');
if (!s) {
notice_lang(s_OperServ, u, MISSING_QUOTE);
return;
}
strtok(s, " "); /* prime strtok() for later */
*s = 0; /* null-terminate quoted string */
} else {
mask = strtok(s, " "); /* this will still work if s is NULL: mask
* will end up NULL, which is what we want */
}
if (mask && info->mangle_mask)
info->mangle_mask(mask);
if (stricmp(cmd, "ADD") == 0) {
do_maskdata_add(info, u, mask, expiry);
} else if (stricmp(cmd, "DEL") == 0) {
do_maskdata_del(info, u, mask);
} else if (stricmp(cmd, "CLEAR") == 0) {
do_maskdata_clear(info, u, mask);
} else if (stricmp(cmd, "LIST") == 0 || stricmp(cmd, "VIEW") == 0) {
do_maskdata_list(info, u, stricmp(cmd,"VIEW")==0, mask, skipstr);
} else if (stricmp(cmd, "CHECK") == 0) {
do_maskdata_check(info, u, mask);
} else if (stricmp(cmd, "COUNT") == 0) {
notice_lang(s_OperServ, u, info->msg_count,
maskdata_count(info->md_type), info->name);
} else if (!info->do_unknown_cmd || !info->do_unknown_cmd(u, cmd, mask)) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_SYNTAX);
}
}
/*************************************************************************/
/* Handle the ADD subcommand. */
static void do_maskdata_add(const MaskDataCmdInfo *info, const User *u,
char *mask, const char *expiry)
{
MaskData *md;
time_t now = time(NULL);
time_t expires;
const char *reason;
if (maskdata_count(info->md_type) >= MAX_MASKDATA) {
notice_lang(s_OperServ, u, info->msg_add_too_many, info->name);
return;
}
reason = strtok_remaining();
if (!reason) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_ADD_SYNTAX);
return;
}
expires = expiry ? dotime(expiry) : *info->def_expiry_ptr;
if (expires < 0) {
notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
return;
} else {
if (expires > 0)
expires += now; /* Make it into an absolute time */
}
/* Run command-specific checks. */
if (info->check_add_mask
&& !info->check_add_mask(u, info->md_type, mask, &expires)
) {
return;
}
/* Make sure mask does not already exist on list. */
if (put_maskdata(get_maskdata(info->md_type, mask))) {
notice_lang(s_OperServ, u, info->msg_add_exists, mask, info->name);
return;
}
md = scalloc(1, sizeof(*md));
md->mask = sstrdup(mask);
md->reason = sstrdup(reason);
md->time = time(NULL);
md->expires = expires;
strbcpy(md->who, u->nick);
md = add_maskdata(info->md_type, md);
if (info->do_add_mask)
info->do_add_mask(u, info->md_type, md);
notice_lang(s_OperServ, u, info->msg_added, mask, info->name);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
/*************************************************************************/
/* Handle the DEL subcommand. */
static void do_maskdata_del(const MaskDataCmdInfo *info, const User *u,
const char *mask)
{
MaskData *md;
if (!mask) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_DEL_SYNTAX);
return;
}
md = get_maskdata(info->md_type, mask);
if (md) {
if (info->do_del_mask)
info->do_del_mask(u, info->md_type, md);
del_maskdata(info->md_type, md);
notice_lang(s_OperServ, u, info->msg_deleted, mask, info->name);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
} else {
notice_lang(s_OperServ, u, info->msg_del_not_found, mask, info->name);
}
}
/*************************************************************************/
/* Handle the CLEAR subcommand. */
static void do_maskdata_clear(const MaskDataCmdInfo *info, const User *u,
const char *mask)
{
MaskData *md;
if (!mask || stricmp(mask,"ALL") != 0) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_CLEAR_SYNTAX);
return;
}
for (md = first_maskdata(info->md_type); md;
md = next_maskdata(info->md_type)
) {
if (info->do_del_mask)
info->do_del_mask(u, info->md_type, md);
del_maskdata(info->md_type, md);
}
notice_lang(s_OperServ, u, info->msg_cleared, info->name);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
/*************************************************************************/
/* Handle the LIST and VIEW subcommands. */
static void do_maskdata_list(const MaskDataCmdInfo *info, const User *u,
int is_view, const char *mask,
const char *skipstr)
{
const MaskData *md;
int count = 0, skip = 0, noexpire = 0;
char *s;
if (skipstr) {
skip = (int)atolsafe(skipstr, 0, INT_MAX);
if (skip < 0) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_LIST_SYNTAX);
return;
}
}
if ((s = strtok(NULL," ")) != NULL && stricmp(s,"NOEXPIRE") == 0)
noexpire = 1;
if (maskdata_count(info->md_type) == 0) {
notice_lang(s_OperServ, u, info->msg_list_empty, info->name);
return;
}
for (md = first_maskdata(info->md_type); md;
md = next_maskdata(info->md_type)
) {
if ((!mask || (match_wild_nocase(mask, md->mask)
&& (!noexpire || !md->expires)))
) {
if (!count)
notice_lang(s_OperServ, u, info->msg_list_header, info->name);
count++;
if (count > skip && count <= skip+ListMax) {
maskdata_list_entry(info, u, is_view, md);
}
}
}
if (count) {
int shown = count - skip;
if (shown < 0)
shown = 0;
else if (shown > ListMax)
shown = ListMax;
notice_lang(s_OperServ, u, LIST_RESULTS, shown, count);
} else {
notice_lang(s_OperServ, u, info->msg_list_no_match, info->name);
}
}
/*************************************************************************/
/* Display a single MaskData entry. Called from do_maskdata_list(). */
static void maskdata_list_entry(const MaskDataCmdInfo *info, const User *u,
int is_view, const MaskData *md)
{
if (is_view) {
char timebuf[BUFSIZE], usedbuf[BUFSIZE];
char expirebuf[BUFSIZE];
const char *who;
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_SHORT_DATE_FORMAT, md->time);
strftime_lang(usedbuf, sizeof(usedbuf), u->ngi,
STRFTIME_SHORT_DATE_FORMAT, md->lastused);
expires_in_lang(expirebuf, sizeof(expirebuf), u->ngi, md->expires);
who = *md->who ? md->who : "<unknown>";
if (md->lastused) {
notice_lang(s_OperServ, u, OPER_MASKDATA_VIEW_FORMAT, md->mask,
who, timebuf, usedbuf, expirebuf, md->reason);
} else {
notice_lang(s_OperServ, u, OPER_MASKDATA_VIEW_UNUSED_FORMAT,
md->mask, who, timebuf, expirebuf, md->reason);
}
} else { /* !is_view */
notice_lang(s_OperServ, u, OPER_MASKDATA_LIST_FORMAT, md->mask,
md->reason);
}
}
/*************************************************************************/
/* Handle the CHECK subcommand. */
static void do_maskdata_check(const MaskDataCmdInfo *info, const User *u,
const char *mask)
{
const MaskData *md;
int count = 0;
int did_header = 0;
if (!mask) {
syntax_error(s_OperServ, u, info->name, OPER_MASKDATA_CHECK_SYNTAX);
return;
}
for (md = first_maskdata(info->md_type); md;
md = next_maskdata(info->md_type)
) {
if (count < ListMax && match_wild_nocase(md->mask, mask)) {
count++;
if (!did_header) {
notice_lang(s_OperServ, u, info->msg_check_header,
mask, info->name);
did_header = 1;
}
notice(s_OperServ, u->nick, " %s", md->mask);
}
}
if (did_header)
notice_lang(s_OperServ, u, info->msg_check_count, count);
else
notice_lang(s_OperServ, u, info->msg_check_no_match, mask, info->name);
}
/*************************************************************************/
/************************ Miscellaneous functions ************************/
/*************************************************************************/
/* Allocate or free a MaskData structure. Used for database new/free
* functions.
*/
void *new_maskdata(void)
{
return scalloc(1, sizeof(MaskData));
}
void free_maskdata(void *record)
{
MaskData *md = record;
if (md) {
free(md->mask);
free(md->reason);
free(md);
}
}
/*************************************************************************/
/* Create and return the reason string for the given MaskData and format
* string. The string will be truncated at BUFSIZE-1 bytes. The string is
* returned in a static buffer, and will be overwritten by subsequent calls.
*/
char *make_reason(const char *format, const MaskData *data)
{
static char reason[BUFSIZE];
char *s;
int data_reason_len = -1;
s = reason;
while (*format && s-reason < sizeof(reason)-1) {
if (*format == '%' && format[1] == 's') {
int left = (sizeof(reason)-1) - (s-reason);
if (data_reason_len < 0)
data_reason_len = strlen(data->reason);
if (left > data_reason_len)
left = data_reason_len;
memcpy(s, data->reason, left);
s += left;
format += 2;
} else {
*s++ = *format++;
}
}
*s = 0;
return reason;
}
/*************************************************************************/
/* MaskData initialization: set up expiration callback. */
int init_maskdata(void)
{
cb_expire_md = register_callback("expire maskdata");
if (cb_expire_md < 0) {
module_log("Unable to register MaskData expiration callback");
return 0;
}
return 1;
}
/* MaskData cleanup: remove expiration callback. */
void exit_maskdata(void)
{
clean_dbtables();
unregister_callback(cb_expire_md);
cb_expire_md = -1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

155
modules/operserv/maskdata.h Normal file
View File

@ -0,0 +1,155 @@
/* Header for common mask data structure.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef MASKDATA_H
#define MASKDATA_H
/*************************************************************************/
/* This structure and the corresponding functions are used by the autokill,
* exception, and S-line modules.
*/
typedef struct maskdata_ MaskData;
struct maskdata_ {
MaskData *next, *prev;
int usecount;
uint8 type;
int num; /* Index number */
char *mask;
int16 limit; /* For exceptions only */
char *reason;
char who[NICKMAX];
time_t time;
time_t expires; /* Or 0 for no expiry */
time_t lastused; /* Last time used, 0 if never used */
};
/* Types of mask data: */
#define MD_AKILL 0
#define MD_EXCLUDE 1
#define MD_EXCEPTION 2
#define MD_SGLINE 'G' /* DO NOT CHANGE: some code relies on */
#define MD_SQLINE 'Q' /* these values being the same as the */
#define MD_SZLINE 'Z' /* corresponding S-line letter */
/* Maximum number of mask data items for a single type: */
#define MAX_MASKDATA 32767
/* Maximum value for `limit' field of an entry: */
#define MAX_MASKDATA_LIMIT 32767
/*************************************************************************/
/* Database functions: */
E MaskData *add_maskdata(uint8 type, MaskData *data);
E void del_maskdata(uint8 type, MaskData *data);
E MaskData *get_maskdata(uint8 type, const char *mask);
E MaskData *get_matching_maskdata(uint8 type, const char *str);
E MaskData *put_maskdata(MaskData *data);
E MaskData *first_maskdata(uint8 type);
E MaskData *next_maskdata(uint8 type);
E int maskdata_count(uint8 type);
E MaskData *get_exception_by_num(int num);
E MaskData *move_exception(MaskData *except, int newnum);
/*************************************************************************/
/*************************************************************************/
/* OperServ internal stuff. */
/*************************************************************************/
/* Data structure for MaskData-related commands: */
typedef struct {
const char *name; /* Command name */
uint8 md_type; /* MaskData type */
time_t *def_expiry_ptr; /* Pointer to default expiry time */
/* Various messages. sprintf() parameters are given in order in
* parentheses. */
/* Syntax message (%s: command name) */
/* Message for adding to a full list (%s: command name) */
int msg_add_too_many;
/* Message for adding a duplicate record (%s: mask, %s: command name) */
int msg_add_exists;
/* Message for successful add (%s: mask, %s: command name) */
int msg_added;
/* Message for no record found on delete (%s: mask, %s: command name) */
int msg_del_not_found;
/* Message for successful delete (%s: mask, %s: command name) */
int msg_deleted;
/* Message for successful clear (%s: command name) */
int msg_cleared;
/* Message for list header (%s: command name) */
int msg_list_header;
/* Message for LIST on empty list (%s: command name) */
int msg_list_empty;
/* Message for LIST with no matching records (%s: command name) */
int msg_list_no_match;
/* Message for no match on CHECK (%s: mask, %s: command name) */
int msg_check_no_match;
/* Message for CHECK response header (%s: mask, %s: command name) */
int msg_check_header;
/* Message for CHECK response trailer (%d: count of matches) */
int msg_check_count;
/* Message for record count (%d: count, %s: command name) */
int msg_count;
/* Helper functions called by do_maskdata_cmd(). */
/* Make any alterations to the mask necessary for addition or deletion;
* if not NULL, called before any other checks (other than for NULL)
* are performed. */
void (*mangle_mask)(char *mask);
/* Check whether the mask is appropriate; return 1 if OK, else 0.
* The mask and expiry time may be modified. If NULL, any mask and
* expiry time are accepted. */
int (*check_add_mask)(const User *u, uint8 type, char *mask,
time_t *expiry_ptr);
/* Operations to perform on mask addition. If NULL, nothing is done. */
void (*do_add_mask)(const User *u, uint8 type, MaskData *md);
/* Operations to perform on mask deletion. If NULL, nothing is done. */
void (*do_del_mask)(const User *u, uint8 type, MaskData *md);
/* Handler for unknown commands; should return 1 if the command was
* handled, 0 otherwise. */
int (*do_unknown_cmd)(const User *u, const char *cmd, char *mask);
} MaskDataCmdInfo;
/*************************************************************************/
/* MaskData command related functions: */
E void do_maskdata_cmd(const MaskDataCmdInfo *info, const User *u);
E char *make_reason(const char *format, const MaskData *data);
/* Other functions: */
E void *new_maskdata(void);
E void free_maskdata(void *record);
E int init_maskdata(void);
E void exit_maskdata(void);
/*************************************************************************/
#endif /* MASKDATA_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

598
modules/operserv/news.c Normal file
View File

@ -0,0 +1,598 @@
/* News module.
* Based on code by Andrew Kempe (TheShadow).
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "databases.h"
#include "language.h"
#include "operserv.h"
#include "news.h"
/*************************************************************************/
static Module *module_operserv;
static void do_logonnews(User *u);
static void do_opernews(User *u);
static Command cmds[] = {
/* Anyone can use *NEWS LIST, but *NEWS {ADD,DEL} are reserved for
* Services operators. (The command routines check permissions.) */
{"LOGONNEWS", do_logonnews, NULL, NEWS_HELP_LOGON, -1,-1},
{"OPERNEWS", do_opernews, NULL, NEWS_HELP_OPER, -1,-1},
{NULL}
};
/*************************************************************************/
/* List of messages for each news type. This simplifies message sending. */
#define MSG_SYNTAX 0
#define MSG_LIST_HEADER 1
#define MSG_LIST_ENTRY 2
#define MSG_LIST_NONE 3
#define MSG_ADD_SYNTAX 4
#define MSG_ADD_FULL 5
#define MSG_ADDED 6
#define MSG_DEL_SYNTAX 7
#define MSG_DEL_NOT_FOUND 8
#define MSG_DELETED 9
#define MSG_DEL_NONE 10
#define MSG_DELETED_ALL 11
#define MSG_MAX 11
struct newsmsgs {
int16 type;
const char *name;
int msgs[MSG_MAX+1];
};
static struct newsmsgs msgarray[] = {
{ NEWS_LOGON, "LOGON",
{ NEWS_LOGON_SYNTAX,
NEWS_LOGON_LIST_HEADER,
NEWS_LOGON_LIST_ENTRY,
NEWS_LOGON_LIST_NONE,
NEWS_LOGON_ADD_SYNTAX,
NEWS_LOGON_ADD_FULL,
NEWS_LOGON_ADDED,
NEWS_LOGON_DEL_SYNTAX,
NEWS_LOGON_DEL_NOT_FOUND,
NEWS_LOGON_DELETED,
NEWS_LOGON_DEL_NONE,
NEWS_LOGON_DELETED_ALL
}
},
{ NEWS_OPER, "OPER",
{ NEWS_OPER_SYNTAX,
NEWS_OPER_LIST_HEADER,
NEWS_OPER_LIST_ENTRY,
NEWS_OPER_LIST_NONE,
NEWS_OPER_ADD_SYNTAX,
NEWS_OPER_ADD_FULL,
NEWS_OPER_ADDED,
NEWS_OPER_DEL_SYNTAX,
NEWS_OPER_DEL_NOT_FOUND,
NEWS_OPER_DELETED,
NEWS_OPER_DEL_NONE,
NEWS_OPER_DELETED_ALL
}
}
};
static int *findmsgs(int16 type, char **typename) {
int i;
for (i = 0; i < lenof(msgarray); i++) {
if (msgarray[i].type == type) {
if (typename)
*typename = (char *)msgarray[i].name;
return msgarray[i].msgs;
}
}
return NULL;
}
/*************************************************************************/
/* Main handler for NEWS commands. */
static void do_news(User *u, int16 type);
/* Lists all a certain type of news. */
static void do_news_list(User *u, int16 type, int *msgs);
/* Add news items. */
static void do_news_add(User *u, int16 type, int *msgs, const char *typename);
static int add_newsitem(User *u, const char *text, int16 type);
/* Delete news items. */
static void do_news_del(User *u, int16 type, int *msgs, const char *typename);
static int del_newsitem(int num, int16 type);
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
static NewsItem *newslist = NULL;
static int32 newslist_count = 0;
static int newslist_iterator;
/*************************************************************************/
static void *new_news(void)
{
return scalloc(1, sizeof(NewsItem));
}
/*************************************************************************/
static void free_news(void *record)
{
NewsItem *news = record;
if (news) {
free(news->text);
free(news);
}
}
/*************************************************************************/
NewsItem *add_news(NewsItem *newsitem)
{
if (newslist_count >= MAX_NEWS)
fatal("add_news(): too many news items!");
ARRAY_EXTEND(newslist);
memcpy(&newslist[newslist_count-1], newsitem, sizeof(NewsItem));
newslist[newslist_count-1].next =
(NewsItem *)(long)(newslist_count-1); /*index*/
free(newsitem);
return &newslist[newslist_count-1];
}
/*************************************************************************/
void del_news(NewsItem *newsitem)
{
int num = (int)(long)(newsitem->next);
if (num < 0 || num >= newslist_count) {
module_log("del_news(): invalid index %d in news item at %p",
num, newsitem);
return;
}
free(newsitem->text);
ARRAY_REMOVE(newslist, num);
if (num < newslist_iterator)
newslist_iterator--;
while (num < newslist_count) {
newslist[num].next = (NewsItem *)(long)num;
num++;
}
}
/*************************************************************************/
NewsItem *get_news(int16 type, int32 num)
{
int i;
ARRAY_FOREACH (i, newslist) {
if (newslist[i].type == type && newslist[i].num == num)
break;
}
return i<newslist_count ? &newslist[i] : NULL;
}
/*************************************************************************/
NewsItem *put_news(NewsItem *news)
{
return news;
}
/*************************************************************************/
NewsItem *first_news(void)
{
newslist_iterator = 0;
return next_news();
}
NewsItem *next_news(void)
{
if (newslist_iterator >= newslist_count)
return NULL;
return &newslist[newslist_iterator++];
}
/*************************************************************************/
int news_count(void)
{
return newslist_count;
}
/*************************************************************************/
/* Free all memory used by database tables. */
static void clean_dbtables(void)
{
int i;
ARRAY_FOREACH (i, newslist)
free(newslist[i].text);
free(newslist);
newslist = NULL;
newslist_count = 0;
}
/*************************************************************************/
/* News database table info */
static DBField news_dbfields[] = {
{ "type", DBTYPE_INT16, offsetof(NewsItem,type) },
{ "num", DBTYPE_INT32, offsetof(NewsItem,num) },
{ "text", DBTYPE_STRING, offsetof(NewsItem,text) },
{ "who", DBTYPE_BUFFER, offsetof(NewsItem,who), NICKMAX },
{ "time", DBTYPE_TIME, offsetof(NewsItem,time) },
{ NULL }
};
static DBTable news_dbtable = {
.name = "news",
.newrec = new_news,
.freerec = free_news,
.insert = (void *)add_news,
.first = (void *)first_news,
.next = (void *)next_news,
.fields = news_dbfields,
};
/*************************************************************************/
/***************************** News display ******************************/
/*************************************************************************/
static void display_news(User *u, int16 type, time_t min_time)
{
NewsItem *news, *disp[NEWS_DISPCOUNT];
int count = 0; /* Number we're going to show--not more than 3 */
int msg;
if (type == NEWS_LOGON) {
msg = NEWS_LOGON_TEXT;
} else if (type == NEWS_OPER) {
msg = NEWS_OPER_TEXT;
} else {
module_log("Invalid type (%d) to display_news()", type);
return;
}
for (news = first_news(); news; news = next_news()) {
if (count >= NEWS_DISPCOUNT)
break;
if (news->type == type && (!min_time || news->time > min_time)) {
disp[count] = news;
count++;
}
}
while (--count >= 0) {
char timebuf[BUFSIZE];
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_SHORT_DATE_FORMAT, disp[count]->time);
notice_lang(s_GlobalNoticer, u, msg, timebuf, disp[count]->text);
}
}
/*************************************************************************/
/***************************** News editing ******************************/
/*************************************************************************/
static void do_logonnews(User *u)
{
do_news(u, NEWS_LOGON);
}
static void do_opernews(User *u)
{
do_news(u, NEWS_OPER);
}
/*************************************************************************/
/* Main news command handling routine. */
static void do_news(User *u, int16 type)
{
const char *cmd = strtok(NULL, " ");
char *typename;
int *msgs;
msgs = findmsgs(type, &typename);
if (!msgs) {
module_log("Invalid type to do_news()");
return;
}
if (!cmd)
cmd = "";
if (stricmp(cmd, "LIST") == 0) {
do_news_list(u, type, msgs);
} else if (stricmp(cmd, "ADD") == 0) {
if (is_services_oper(u))
do_news_add(u, type, msgs, typename);
else
notice_lang(s_OperServ, u, PERMISSION_DENIED);
} else if (stricmp(cmd, "DEL") == 0) {
if (is_services_oper(u))
do_news_del(u, type, msgs, typename);
else
notice_lang(s_OperServ, u, PERMISSION_DENIED);
} else {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_SYNTAX]);
}
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS LIST command. */
static void do_news_list(User *u, int16 type, int *msgs)
{
NewsItem *news;
int count = 0;
char timebuf[64];
for (news = first_news(); news; news = next_news()) {
if (news->type == type) {
if (count == 0)
notice_lang(s_OperServ, u, msgs[MSG_LIST_HEADER]);
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_DATE_TIME_FORMAT, news->time);
notice_lang(s_OperServ, u, msgs[MSG_LIST_ENTRY],
news->num, timebuf,
*news->who ? news->who : "<unknown>",
news->text);
count++;
}
}
if (count == 0)
notice_lang(s_OperServ, u, msgs[MSG_LIST_NONE]);
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS ADD command. */
static void do_news_add(User *u, int16 type, int *msgs, const char *typename)
{
char *text = strtok_remaining();
if (!text) {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_ADD_SYNTAX]);
} else {
int n = add_newsitem(u, text, type);
if (n < 0)
notice_lang(s_OperServ, u, msgs[MSG_ADD_FULL]);
else
notice_lang(s_OperServ, u, msgs[MSG_ADDED], n);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
}
/* Actually add a news item. Return the number assigned to the item, or -1
* if the news list is full (MAX_NEWS items).
*/
static int add_newsitem(User *u, const char *text, int16 type)
{
NewsItem *news;
int num;
if (news_count() >= MAX_NEWS)
return -1;
num = 0;
for (news = first_news(); news; news = next_news()) {
if (news->type == type && num < news->num)
num = news->num;
}
if (num+1 < num) {
module_log("BUG: add_newsitem(): news number overflow (MAX_NEWS"
" too small?)");
return -1;
}
news = scalloc(1, sizeof(*news));
news->type = type;
news->num = num+1;
news->text = sstrdup(text);
news->time = time(NULL);
strbcpy(news->who, u->nick);
add_news(news);
return num+1;
}
/*************************************************************************/
/* Handle a {LOGON,OPER}NEWS DEL command. */
static void do_news_del(User *u, int16 type, int *msgs, const char *typename)
{
char *text = strtok(NULL, " ");
if (!text) {
char buf[32];
snprintf(buf, sizeof(buf), "%sNEWS", typename);
syntax_error(s_OperServ, u, buf, msgs[MSG_DEL_SYNTAX]);
} else {
if (stricmp(text, "ALL") != 0) {
int num = atoi(text);
if (num > 0 && del_newsitem(num, type))
notice_lang(s_OperServ, u, msgs[MSG_DELETED], num);
else
notice_lang(s_OperServ, u, msgs[MSG_DEL_NOT_FOUND], num);
} else {
if (del_newsitem(0, type))
notice_lang(s_OperServ, u, msgs[MSG_DELETED_ALL]);
else
notice_lang(s_OperServ, u, msgs[MSG_DEL_NONE]);
}
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
}
/* Actually delete a news item. If `num' is 0, delete all news items of
* the given type. Returns the number of items deleted.
*/
static int del_newsitem(int num, int16 type)
{
NewsItem *news;
int count = 0;
for (news = first_news(); news; news = next_news()) {
if (news->type == type && (num == 0 || news->num == num)) {
del_news(news);
count++;
}
}
return count;
}
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Callback for users logging on. */
static int new_user_callback(User *u, int ac, char **av, int reconnect)
{
display_news(u, NEWS_LOGON, reconnect ? u->signon : 0);
return 0;
}
/*************************************************************************/
/* Callback to watch for mode +o to send oper news. */
static int user_mode_callback(User *u, int modechar, int add)
{
if (modechar == 'o' && add)
display_news(u, NEWS_OPER, 0);
return 0;
}
/*************************************************************************/
/* OperServ STATS ALL callback. */
static int do_stats_all(User *user, const char *s_OperServ)
{
int32 count, mem;
NewsItem *news;
count = mem = 0;
for (news = first_news(); news; news = next_news()) {
count++;
mem += sizeof(*news);
if (news->text)
mem += strlen(news->text)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_NEWS_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
int init_module()
{
module_operserv = find_module("operserv/main");
if (!module_operserv) {
module_log("Main OperServ module not loaded");
return 0;
}
use_module(module_operserv);
if (!register_commands(module_operserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "user create", new_user_callback)
|| !add_callback(NULL, "user MODE", user_mode_callback)
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&news_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&news_dbtable);
clean_dbtables();
remove_callback(NULL, "user create", new_user_callback);
remove_callback(NULL, "user MODE", user_mode_callback);
if (module_operserv) {
remove_callback(module_operserv, "STATS ALL", do_stats_all);
unregister_commands(module_operserv, cmds);
unuse_module(module_operserv);
module_operserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

63
modules/operserv/news.h Normal file
View File

@ -0,0 +1,63 @@
/* News data structures and constants.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef NEWS_H
#define NEWS_H
/*************************************************************************/
typedef struct newsitem_ NewsItem;
struct newsitem_ {
NewsItem *next, *prev;
int16 type;
int32 num; /* Numbering is separate for login and oper news */
char *text;
char who[NICKMAX];
time_t time;
};
#define MAX_NEWS 32767
/* Maximum number of news items to display */
#define NEWS_DISPCOUNT 3
/*************************************************************************/
/* Constants for news types. */
#define NEWS_INVALID -1 /* Used as placeholder */
#define NEWS_LOGON 0
#define NEWS_OPER 1
/*************************************************************************/
/* Database functions: */
E NewsItem *add_news(NewsItem *news);
E void del_news(NewsItem *news);
E NewsItem *get_news(int16 type, int32 num);
E NewsItem *put_news(NewsItem *news);
E NewsItem *first_news(void);
E NewsItem *next_news(void);
E int news_count(void);
/*************************************************************************/
#endif /* NEWS_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,47 @@
/* Include file for OperServ.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef OPERSERV_H
#define OPERSERV_H
/*************************************************************************/
/* Constants for use with get_operserv_data(): */
#define OSDATA_MAXUSERCNT 1 /* int32 */
#define OSDATA_MAXUSERTIME 2 /* time_t */
#define OSDATA_SUPASS 3 /* Password * */
/*************************************************************************/
/* Exports: */
E char *s_OperServ;
E char *s_GlobalNoticer;
E char *ServicesRoot;
E int get_operserv_data(int what, void *ret);
E int put_operserv_data(int what, void *ptr);
E int is_services_root(const User *u);
E int is_services_admin(const User *u);
E int is_services_oper(const User *u);
E int nick_is_services_admin(const NickInfo *ni);
/*************************************************************************/
#endif /* OPERSERV_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

931
modules/operserv/sessions.c Normal file
View File

@ -0,0 +1,931 @@
/* Session limiting module.
* Based on code copyright (c) 1999-2000 Andrew Kempe (TheShadow)
* E-mail: <andrewk@isdial.net>
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "databases.h"
#include "language.h"
#include "operserv.h"
#include "maskdata.h"
#include "akill.h"
/*************************************************************************/
/* SESSION LIMITING
*
* The basic idea of session limiting is to prevent one host from having more
* than a specified number of sessions (client connections/clones) on the
* network at any one time. To do this we have a list of sessions and
* exceptions. Each session structure records information about a single host,
* including how many clients (sessions) that host has on the network. When a
* host reaches it's session limit, no more clients from that host will be
* allowed to connect.
*
* When a client connects to the network, we check to see if their host has
* reached the default session limit per host, and thus whether it is allowed
* any more. If it has reached the limit, we kill the connecting client; all
* the other clients are left alone. Otherwise we simply increment the counter
* within the session structure. When a client disconnects, we decrement the
* counter. When the counter reaches 0, we free the session.
*
* Exceptions allow one to specify custom session limits for a specific host
* or a range thereof. The first exception that the host matches is the one
* used.
*
* "Session Limiting" is likely to slow down services when there are frequent
* client connects and disconnects. The size of the exception list can also
* play a large role in this performance decrease. It is therefore recommened
* that you keep the number of exceptions to a minimum. A very simple hashing
* method is currently used to store the list of sessions. I'm sure there is
* room for improvement and optimisation of this, along with the storage of
* exceptions. Comments and suggestions are more than welcome!
*
* -TheShadow (02 April 1999)
*/
/*************************************************************************/
static Module *module_operserv;
static Module *module_akill;
/* create_kill() imported from operserv/akill */
static void (*p_create_akill)(char *mask, const char *reason, const char *who,
time_t expiry);
static int WallOSException;
static int WallExceptionExpire;
static int32 DefSessionLimit;
static time_t ExceptionExpiry;
static int32 MaxSessionLimit;
static char * SessionLimitExceeded;
static char * SessionLimitDetailsLoc;
static int SessionLimitAutokill;
static time_t SessionLimitMinKillTime;
static int32 SessionLimitMaxKillCount;
static time_t SessionLimitAutokillExpiry;
static char * SessionLimitAutokillReason;
/*************************************************************************/
typedef struct session_ Session;
struct session_ {
Session *prev, *next;
char *host;
int count; /* Number of clients with this host */
int killcount; /* Number of kills for this session */
time_t lastkill; /* Time of last kill */
};
#define HASH_STATIC static
#include "hash.h"
#define add_session _add_session
#define del_session _del_session
DEFINE_HASH(session, Session, host)
#undef del_session
#undef add_session
static void do_session(User *u);
static void do_exception(User *u);
static Command cmds[] = {
{"SESSION", do_session, is_services_oper, OPER_HELP_SESSION, -1,-1},
{"EXCEPTION", do_exception, is_services_oper, OPER_HELP_EXCEPTION, -1,-1},
{NULL}
};
/*************************************************************************/
/************************* Session list display **************************/
/*************************************************************************/
/* Syntax: SESSION LIST threshold
* Lists all sessions with atleast threshold clients.
* The threshold value must be greater than 1. This is to prevent
* accidental listing of the large number of single client sessions.
*
* Syntax: SESSION VIEW host
* Displays detailed session information about the supplied host.
*/
static void do_session(User *u)
{
Session *session;
MaskData *exception;
const char *cmd = strtok(NULL, " ");
const char *param1 = strtok(NULL, " ");
int mincount;
if (!cmd)
cmd = "";
if (stricmp(cmd, "LIST") == 0) {
if (!param1) {
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_LIST_SYNTAX);
} else if ((mincount = atoi(param1)) <= 1) {
notice_lang(s_OperServ, u, OPER_SESSION_INVALID_THRESHOLD);
} else {
notice_lang(s_OperServ, u, OPER_SESSION_LIST_HEADER, mincount);
notice_lang(s_OperServ, u, OPER_SESSION_LIST_COLHEAD);
for (session = first_session(); session; session = next_session()){
if (session->count >= mincount)
notice_lang(s_OperServ, u, OPER_SESSION_LIST_FORMAT,
session->count, session->host);
}
}
} else if (stricmp(cmd, "VIEW") == 0) {
if (!param1) {
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_VIEW_SYNTAX);
} else {
session = get_session(param1);
if (!session) {
notice_lang(s_OperServ, u, OPER_SESSION_NOT_FOUND, param1);
} else {
exception = get_matching_maskdata(MD_EXCEPTION, param1);
notice_lang(s_OperServ, u, OPER_SESSION_VIEW_FORMAT,
param1, session->count,
exception ? exception->limit : DefSessionLimit);
put_maskdata(exception);
}
}
} else {
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_SYNTAX);
}
}
/*************************************************************************/
/********************* Internal session functions ************************/
/*************************************************************************/
/* Free a session structure. Separate from del_session() because the
* module cleanup code also uses it.
*/
static inline void free_session(Session *session)
{
free(session->host);
free(session);
}
/*************************************************************************/
/* Attempt to add a host to the session list. If the addition of the new host
* causes the the session limit to be exceeded, kill the connecting user.
* Returns 1 if the host was added or 0 if the user was killed.
*/
static int add_session(const char *nick, const char *host)
{
Session *session;
MaskData *exception;
int sessionlimit = 0;
char buf[BUFSIZE];
time_t now = time(NULL);
session = get_session(host);
if (session) {
exception = get_matching_maskdata(MD_EXCEPTION, host);
sessionlimit = exception ? exception->limit : DefSessionLimit;
put_maskdata(exception);
if (sessionlimit != 0 && session->count >= sessionlimit) {
if (SessionLimitExceeded)
notice(s_OperServ, nick, SessionLimitExceeded, host);
if (SessionLimitDetailsLoc)
notice(s_OperServ, nick, SessionLimitDetailsLoc);
if (SessionLimitAutokill && module_akill) {
if (now <= session->lastkill + SessionLimitMinKillTime) {
session->killcount++;
if (session->killcount >= SessionLimitMaxKillCount) {
snprintf(buf, sizeof(buf), "*@%s", host);
p_create_akill(buf,SessionLimitAutokillReason,
s_OperServ,
now + SessionLimitAutokillExpiry);
session->killcount = 0;
}
} else {
session->killcount = 1;
}
session->lastkill = now;
}
/* We don't use kill_user() because a user stucture has not yet
* been created. Simply kill the user. -TheShadow */
send_cmd(s_OperServ, "KILL %s :%s (Session limit exceeded)",
nick, s_OperServ);
return 0;
} else {
session->count++;
return 1;
}
/* not reached */
}
/* Session does not exist, so create it */
session = scalloc(sizeof(Session), 1);
session->host = sstrdup(host);
session->count = 1;
session->killcount = 0;
session->lastkill = 0;
_add_session(session);
return 1;
}
/*************************************************************************/
static void del_session(const char *host)
{
Session *session;
module_log_debug(2, "del_session() called");
session = get_session(host);
if (!session) {
wallops(s_OperServ,
"WARNING: Tried to delete non-existent session: \2%s", host);
module_log("Tried to delete non-existent session: %s", host);
return;
}
if (session->count > 1) {
session->count--;
return;
}
_del_session(session);
module_log_debug(2, "del_session(): free session structure");
free_session(session);
module_log_debug(2, "del_session() done");
}
/*************************************************************************/
/************************ Exception manipulation *************************/
/*************************************************************************/
/* Syntax: EXCEPTION ADD [+expiry] mask limit reason
* Adds mask to the exception list with limit as the maximum session
* limit and +expiry as an optional expiry time.
*
* Syntax: EXCEPTION DEL mask
* Deletes the first exception that matches mask exactly.
*
* Syntax: EXCEPTION MOVE num newnum
* Moves the exception with number num to have number newnum.
*
* Syntax: EXCEPTION LIST [mask]
* Lists all exceptions or those matching mask.
*
* Syntax: EXCEPTION VIEW [mask]
* Displays detailed information about each exception or those matching
* mask.
*
* Syntax: EXCEPTION CHECK host
* Displays masks which the given host matches.
*
* Syntax: EXCEPTION COUNT
* Displays the number of entries in the exception list.
*/
static void do_exception_add(User *u);
static void do_exception_del(User *u);
static void do_exception_clear(User *u);
static void do_exception_move(User *u);
static void do_exception_list(User *u, int is_view);
static void do_exception_check(User *u);
static int exception_del_callback(int num, va_list args);
static int exception_list(User *u, MaskData *except, int *count, int skip,
int is_view);
static int exception_list_callback(int num, va_list args);
static void do_exception(User *u)
{
const char *cmd = strtok(NULL, " ");
if (!cmd)
cmd = "";
if (stricmp(cmd, "ADD") == 0) {
do_exception_add(u);
} else if (stricmp(cmd, "DEL") == 0) {
do_exception_del(u);
} else if (stricmp(cmd, "CLEAR") == 0) {
do_exception_clear(u);
} else if (stricmp(cmd, "MOVE") == 0) {
do_exception_move(u);
} else if (stricmp(cmd, "LIST") == 0 || stricmp(cmd, "VIEW") == 0) {
do_exception_list(u, stricmp(cmd,"VIEW")==0);
} else if (stricmp(cmd, "CHECK") == 0) {
do_exception_check(u);
} else if (stricmp(cmd, "COUNT") == 0) {
int count = maskdata_count(MD_EXCEPTION);
if (count)
notice_lang(s_OperServ, u, OPER_EXCEPTION_COUNT, count);
else
notice_lang(s_OperServ, u, OPER_EXCEPTION_EMPTY);
} else {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_SYNTAX);
}
}
/*************************************************************************/
static void do_exception_add(User *u)
{
char *mask, *reason, *expiry, *limitstr;
time_t expires;
int limit, i;
MaskData *except;
time_t now = time(NULL);
if (maskdata_count(MD_EXCEPTION) >= MAX_MASKDATA) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_TOO_MANY);
return;
}
mask = strtok(NULL, " ");
if (mask && *mask == '+') {
expiry = mask+1;
mask = strtok(NULL, " ");
} else {
expiry = NULL;
}
limitstr = strtok(NULL, " ");
reason = strtok_remaining();
if (!mask || !limitstr || !reason) {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_ADD_SYNTAX);
return;
}
expires = expiry ? dotime(expiry) : ExceptionExpiry;
if (expires < 0) {
notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
return;
} else if (expires > 0) {
expires += now;
}
limit = (limitstr && isdigit(*limitstr)) ? atoi(limitstr) : -1;
if (limit < 0 || limit > MaxSessionLimit) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_INVALID_LIMIT,
MaxSessionLimit);
return;
} else if (strchr(mask, '!') || strchr(mask, '@')) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_INVALID_HOSTMASK);
return;
} else if (put_maskdata(get_maskdata(MD_EXCEPTION, strlower(mask)))) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_ALREADY_PRESENT, mask);
} else {
/* Get highest maskdata number */
i = 0;
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
if (except->num > i) /* should be in order, but let's be safe */
i = except->num;
}
/* Add exception with next number */
except = scalloc(1, sizeof(*except));
except->mask = sstrdup(mask);
except->limit = limit;
except->reason = sstrdup(reason);
except->time = now;
strbcpy(except->who, u->nick);
except->expires = expires;
except->num = i+1;
add_maskdata(MD_EXCEPTION, except);
if (WallOSException) {
char buf[BUFSIZE];
expires_in_lang(buf, sizeof(buf), NULL, expires);
wallops(s_OperServ, "%s added a session limit exception of"
" \2%d\2 for \2%s\2 (%s)", u->nick, limit, mask, buf);
}
notice_lang(s_OperServ, u, OPER_EXCEPTION_ADDED, mask, limit);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
}
/*************************************************************************/
static void do_exception_del(User *u)
{
char *mask;
MaskData *except;
int deleted = 0;
mask = strtok(NULL, " ");
if (!mask) {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_DEL_SYNTAX);
return;
}
if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
int count, last = -1;
deleted = process_numlist(mask, &count, exception_del_callback, &last);
if (deleted == 0) {
if (count == 1) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_SUCH_ENTRY, last);
} else {
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_MATCH);
}
} else if (deleted == 1) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED_ONE);
} else {
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED_SEVERAL,
deleted);
}
} else {
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
if (stricmp(mask, except->mask) == 0) {
del_maskdata(MD_EXCEPTION, except);
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED, mask);
deleted = 1;
break;
}
}
if (deleted == 0)
notice_lang(s_OperServ, u, OPER_EXCEPTION_NOT_FOUND, mask);
}
if (deleted && readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
/* Renumber the exception list. I don't believe in having holes in
* lists - it makes code more complex, harder to debug and we end up
* with huge index numbers. Imho, fixed numbering is only beneficial
* when one doesn't have range capable manipulation. -TheShadow */
/* That works fine up until two people do deletes at the same time
* and shoot themselves in the collective foot; and just because
* you have range handling doesn't mean someone won't do "DEL 5"
* followed by "DEL 7" and, again, shoot themselves in the foot.
* Besides, there's nothing wrong with complexity if it serves a
* purpose. Removed. --AC */
}
static int exception_del_callback(int num, va_list args)
{
MaskData *except;
int *last = va_arg(args, int *);
*last = num;
if ((except = get_exception_by_num(num)) != NULL) {
del_maskdata(MD_EXCEPTION, except);
return 1;
} else {
return 0;
}
}
/*************************************************************************/
static void do_exception_clear(User *u)
{
char *mask;
MaskData *except;
mask = strtok(NULL, " ");
if (!mask || stricmp(mask,"ALL") != 0) {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_CLEAR_SYNTAX);
return;
}
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
del_maskdata(MD_EXCEPTION, except);
}
notice_lang(s_OperServ, u, OPER_EXCEPTION_CLEARED);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
/*************************************************************************/
static void do_exception_move(User *u)
{
MaskData *except;
char *n1str = strtok(NULL, " "); /* From index */
char *n2str = strtok(NULL, " "); /* To index */
int n1, n2;
if (!n1str || !n2str) {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_MOVE_SYNTAX);
return;
}
n1 = atoi(n1str);
n2 = atoi(n2str);
if (n1 == n2 || n1 <= 0 || n2 <= 0) {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_MOVE_SYNTAX);
return;
}
if (!(except = get_exception_by_num(n1))) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_SUCH_ENTRY, n1);
return;
}
except = move_exception(except, n2);
notice_lang(s_OperServ, u, OPER_EXCEPTION_MOVED,
except->mask, n1, n2);
put_maskdata(except);
if (readonly)
notice_lang(s_OperServ, u, READ_ONLY_MODE);
}
/*************************************************************************/
static void do_exception_list(User *u, int is_view)
{
char *mask = strtok(NULL, " ");
char *s;
MaskData *except;
int count = 0, skip = 0;
int noexpire = 0;
if (mask && *mask == '+') {
skip = (int)atolsafe(mask+1, 0, INT_MAX);
if (skip < 0) {
syntax_error(s_OperServ, u, "EXCEPTION",
OPER_EXCEPTION_LIST_SYNTAX);
return;
}
mask = strtok(NULL, " ");
}
if (mask)
strlower(mask);
if ((s = strtok(NULL," ")) != NULL && stricmp(s,"NOEXPIRE") == 0)
noexpire = 1;
if (mask && strspn(mask, "1234567890,-") == strlen(mask)) {
if (skip) {
syntax_error(s_OperServ, u, "EXCEPTION",
OPER_EXCEPTION_LIST_SYNTAX);
return;
}
process_numlist(mask, NULL, exception_list_callback, u,
&count, skip, noexpire, is_view);
} else {
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
if ((!mask || match_wild(mask, except->mask))
&& (!noexpire || !except->expires))
exception_list(u, except, &count, skip, is_view);
}
}
if (!count)
notice_lang(s_OperServ, u,
mask ? OPER_EXCEPTION_NO_MATCH : OPER_EXCEPTION_EMPTY);
}
static int exception_list(User *u, MaskData *except, int *count, int skip,
int is_view)
{
if (!*count) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_HEADER);
if (!is_view)
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_COLHEAD);
}
(*count)++;
if (*count > skip && *count <= skip+ListMax) {
if (is_view) {
char timebuf[BUFSIZE], expirebuf[BUFSIZE];
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
STRFTIME_SHORT_DATE_FORMAT, except->time);
expires_in_lang(expirebuf, sizeof(expirebuf), u->ngi,
except->expires);
notice_lang(s_OperServ, u, OPER_EXCEPTION_VIEW_FORMAT,
except->num, except->mask,
*except->who ? except->who : "<unknown>",
timebuf, expirebuf, except->limit, except->reason);
} else { /* list */
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_FORMAT,
except->num, except->limit, except->mask);
}
}
return 1;
}
static int exception_list_callback(int num, va_list args)
{
User *u = va_arg(args, User *);
int *count = va_arg(args, int *);
int skip = va_arg(args, int);
int noexpire = va_arg(args, int);
int is_view = va_arg(args, int);
MaskData *except;
int retval = 0;
if ((except = get_exception_by_num(num)) != NULL) {
if (!noexpire || !except->expires)
retval = exception_list(u, except, count, skip, is_view);
put_maskdata(except);
}
return retval;
}
/*************************************************************************/
static void do_exception_check(User *u)
{
char *host;
MaskData *except;
int sent_header = 0, count = 0;
host = strtok(NULL, " ");
if (host) {
strlower(host);
} else {
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_CHECK_SYNTAX);
return;
}
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
if (match_wild(except->mask, host)) {
if (!sent_header) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_HEADER, host);
sent_header = 1;
}
notice(s_OperServ, u->nick, " %s", except->mask);
count++;
}
}
if (sent_header) {
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_TRAILER, count);
} else {
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_NO_MATCH, host);
}
}
/*************************************************************************/
/*************************** Callback routines ***************************/
/*************************************************************************/
/* Callback to check session limiting for new users. */
static int check_sessions(int ac, char **av)
{
return !add_session(av[0], av[4]);
}
/*************************************************************************/
/* Callback to remove a quitting user's session. */
static int remove_session(User *u, char *reason_unused)
{
del_session(u->host);
return 0;
}
/*************************************************************************/
/* Callback for exception expiration. */
static int do_expire_maskdata(uint32 type, MaskData *md)
{
if (type == MD_EXCEPTION) {
if (WallExceptionExpire)
wallops(s_OperServ, "Session limit exception for %s has expired",
md->mask);
}
return 0;
}
/*************************************************************************/
/* Callback for OperServ STATS ALL command. */
static int do_stats_all(User *u)
{
int32 count, mem;
Session *session;
MaskData *md;
count = mem = 0;
for (session = first_session(); session; session = next_session()) {
count++;
mem += sizeof(*session) + strlen(session->host)+1;
}
notice_lang(s_OperServ, u, OPER_STATS_ALL_SESSION_MEM,
count, (mem+512) / 1024);
for (md = first_maskdata(MD_EXCEPTION); md;
md = next_maskdata(MD_EXCEPTION)
) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, u, OPER_STATS_ALL_EXCEPTION_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
static void insert_exception(void *md) {
MaskData *except;
int num = 0;
for (except = first_maskdata(MD_EXCEPTION); except;
except = next_maskdata(MD_EXCEPTION)
) {
if (except->num > num) /* should be in order, but let's be safe */
num = except->num;
}
((MaskData *)md)->num = num+1;
add_maskdata(MD_EXCEPTION, md);
}
static void *first_exception(void)
{ return first_maskdata(MD_EXCEPTION); }
static void *next_exception(void)
{ return next_maskdata(MD_EXCEPTION); }
static DBField exception_dbfields[] = {
{ "mask", DBTYPE_STRING, offsetof(MaskData,mask) },
{ "limit", DBTYPE_INT16, offsetof(MaskData,limit) },
{ "reason", DBTYPE_STRING, offsetof(MaskData,reason) },
{ "who", DBTYPE_BUFFER, offsetof(MaskData,who), NICKMAX },
{ "time", DBTYPE_TIME, offsetof(MaskData,time) },
{ "expires", DBTYPE_TIME, offsetof(MaskData,expires) },
{ "lastused", DBTYPE_TIME, offsetof(MaskData,lastused) },
{ NULL }
};
static DBTable exception_dbtable = {
.name = "exception",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_exception,
.first = first_exception,
.next = next_exception,
.fields = exception_dbfields,
};
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "DefSessionLimit", { { CD_INT, 0, &DefSessionLimit } } },
{ "ExceptionExpiry", { { CD_TIME, 0, &ExceptionExpiry } } },
{ "MaxSessionLimit", { { CD_POSINT, 0, &MaxSessionLimit } } },
{ "SessionLimitAutokill",{{CD_SET, 0, &SessionLimitAutokill },
{ CD_TIME, 0, &SessionLimitMinKillTime },
{ CD_POSINT, 0, &SessionLimitMaxKillCount },
{ CD_TIME, 0, &SessionLimitAutokillExpiry },
{ CD_STRING, 0, &SessionLimitAutokillReason } } },
{ "SessionLimitDetailsLoc",{{CD_STRING, 0, &SessionLimitDetailsLoc } } },
{ "SessionLimitExceeded",{{CD_STRING, 0, &SessionLimitExceeded } } },
{ "WallExceptionExpire",{{CD_SET, 0, &WallExceptionExpire } } },
{ "WallOSException", { { CD_SET, 0, &WallOSException } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *name)
{
if (strcmp(name, "operserv/akill") == 0) {
p_create_akill = get_module_symbol(mod, "create_akill");
if (p_create_akill)
module_akill = mod;
else
module_log("Symbol `create_akill' not found, automatic autokill"
" addition will not be available");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_akill) {
p_create_akill = NULL;
module_akill = NULL;
}
return 0;
}
/*************************************************************************/
int init_module()
{
if (!MaxSessionLimit)
MaxSessionLimit = MAX_MASKDATA_LIMIT;
module_operserv = find_module("operserv/main");
if (!module_operserv) {
module_log("Main OperServ module not loaded");
return 0;
}
use_module(module_operserv);
if (!register_commands(module_operserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
/* Add user check callback at priority -10 so it runs after all the
* autokill/S-line/whatever checks (otherwise we get users added to
* sessions and then killed by S-lines, leaving the session count
* jacked up).
*/
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback_pri(NULL, "user check", check_sessions, -10)
|| !add_callback(NULL, "user delete", remove_session)
|| !add_callback(module_operserv, "expire maskdata", do_expire_maskdata)
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
module_akill = find_module("operserv/akill");
if (module_akill)
do_load_module(module_akill, "operserv/akill");
if (!register_dbtable(&exception_dbtable)) {
module_log("Unable to register database table");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
Session *session;
unregister_dbtable(&exception_dbtable);
if (module_akill)
do_unload_module(module_akill);
for (session = first_session(); session; session = next_session()) {
_del_session(session);
free_session(session);
}
remove_callback(NULL, "user delete", remove_session);
remove_callback(NULL, "user check", check_sessions);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
if (module_operserv) {
remove_callback(module_operserv, "STATS ALL", do_stats_all);
remove_callback(module_operserv, "expire maskdata",do_expire_maskdata);
unregister_commands(module_operserv, cmds);
unuse_module(module_operserv);
module_operserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

711
modules/operserv/sline.c Normal file
View File

@ -0,0 +1,711 @@
/* S-line module.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "commands.h"
#include "databases.h"
#include "language.h"
#include "operserv.h"
#define NEED_MAKE_REASON
#include "maskdata.h"
#include "sline.h"
/*************************************************************************/
static Module *module_operserv;
static Module *module_nickserv;
static int cb_send_sgline = -1;
static int cb_send_sqline = -1;
static int cb_send_szline = -1;
static int cb_cancel_sgline = -1;
static int cb_cancel_sqline = -1;
static int cb_cancel_szline = -1;
static char * SGlineReason;
static char * SQlineReason;
static char * SZlineReason;
static int ImmediatelySendSline;
static time_t SGlineExpiry;
static time_t SQlineExpiry;
static time_t SZlineExpiry;
static int WallOSSline;
static int WallSlineExpire;
static int SQlineIgnoreOpers;
static int SQlineKill;
/* Set nonzero if we don't have client IP info:
* -1 = no IP info and no SZLINE-like command either
* +1 = no IP info, but SZLINE command available
*/
static int no_szline = 0;
static void do_sgline(User *u);
static void do_sqline(User *u);
static void do_szline(User *u);
static uint8 sline_types[3] = {MD_SGLINE, MD_SQLINE, MD_SZLINE};
static Command cmds[] = {
{"SGLINE", do_sgline, is_services_oper, OPER_HELP_SGLINE, -1,-1},
{"SQLINE", do_sqline, is_services_oper, OPER_HELP_SQLINE, -1,-1},
{"SZLINE", do_szline, is_services_oper, OPER_HELP_SZLINE, -1,-1},
{ NULL }
};
/*************************************************************************/
/************************** Internal functions ***************************/
/*************************************************************************/
/* Send an S-line to the uplink server. */
static void send_sline(uint8 type, const MaskData *sline)
{
int cb;
const char *reason;
if (type == MD_SGLINE) {
cb = cb_send_sgline;
reason = SGlineReason;
} else if (type == MD_SQLINE && !SQlineKill) {
cb = cb_send_sqline;
reason = SQlineReason;
} else if (type == MD_SZLINE) {
cb = cb_send_szline;
reason = SZlineReason;
} else {
return;
}
call_callback_4(cb, sline->mask, sline->expires, sline->who,
make_reason(reason, sline));
}
/*************************************************************************/
/* Remove an S-line from the uplink server. */
static void cancel_sline(uint8 type, char *mask)
{
int cb;
if (type == MD_SGLINE) {
cb = cb_cancel_sgline;
} else if (type == MD_SQLINE) {
cb = cb_cancel_sqline;
} else if (type == MD_SZLINE) {
cb = cb_cancel_szline;
} else {
return;
}
call_callback_1(cb, mask);
}
/*************************************************************************/
/* SQline checker, shared by do_user_check() and do_user_nickchange_after().
* Returns the reason string to be used if the user is to be killed, else
* NULL. `new_oper' indicates whether a new user (who doesn't have a User
* record yet) is an oper.
*/
static char *check_sqline(const char *nick, int new_oper)
{
User *u;
MaskData *sline;
if (SQlineIgnoreOpers && (new_oper || ((u=get_user(nick)) && is_oper(u))))
return NULL;
sline = get_matching_maskdata(MD_SQLINE, nick);
if (sline) {
char *retval = NULL;
/* Don't kill/nickchange users if they just got changed to a guest
* nick */
if (!is_guest_nick(nick)) {
char *reason = make_reason(SQlineReason, sline);
if (!SQlineKill && (protocol_features & PF_CHANGENICK)) {
send_cmd(ServerName, "432 %s %s Invalid nickname (%s)",
nick, nick, reason);
send_nickchange_remote(nick, make_guest_nick());
} else {
/* User is to be killed */
retval = reason;
}
}
send_sline(MD_SQLINE, sline);
time(&sline->lastused);
put_maskdata(sline);
return retval;
}
return NULL;
}
/*************************************************************************/
/************************** Callback functions ***************************/
/*************************************************************************/
/* Does the user match any S-lines? Return 1 (and kill the user) if so,
* else 0. Note that if a Q:line (and no G:line or Z:line) is matched, and
* SQlineKill is not set, we send a 432 (erroneous nickname) reply to the
* client and change their nick if the ircd supports forced nick changing.
*/
static int do_user_check(int ac, char **av)
{
const char *nick = av[0], *name = av[6], *ip = (ac>=9 ? av[8] : NULL);
int new_oper = (ac>=10 && av[9] ? strchr(av[9],'o') != NULL : 0);
MaskData *sline;
char *reason;
if (noakill)
return 0;
if (ip) {
sline = get_matching_maskdata(MD_SZLINE, ip);
if (sline) {
send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ,
make_reason(SZlineReason, sline));
send_sline(MD_SZLINE, sline);
time(&sline->lastused);
put_maskdata(sline);
return 1;
}
} else {
if (!no_szline) {
if (protocol_features & PF_SZLINE) {
if (!ImmediatelySendSline) {
wallops(s_OperServ,
"\2WARNING\2: Client IP addresses are not"
" available with this IRC server; SZLINEs"
" cannot be used unless ImmediatelySendSline"
" is enabled in %s.", MODULES_CONF);
no_szline = -1;
} else {
no_szline = 1;
}
} else {
wallops(s_OperServ,
"\2WARNING:\2 Client IP addresses are not available"
" with this IRC server; SZLINEs cannot be used.");
no_szline = -1;
}
module_log("warning: client IP addresses not available with"
" this IRC server");
}
}
sline = get_matching_maskdata(MD_SGLINE, name);
if (sline) {
/* Don't use kill_user(); that's for people who have already signed
* on. This is called before the User structure is created. */
send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ,
make_reason(SGlineReason, sline));
send_sline(MD_SGLINE, sline);
time(&sline->lastused);
put_maskdata(sline);
return 1;
}
put_maskdata(sline);
reason = check_sqline(nick, new_oper);
if (reason) {
send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ, reason);
return 1;
}
return 0;
}
/*************************************************************************/
/* Does the user (who has just changed their nick) match any SQlines? If
* so, send them a 432 and, on servers supporting forced nick changing,
* change their nick (if !SQlineKill).
*/
static int do_user_nickchange_after(User *u, const char *oldnick)
{
char *reason = check_sqline(u->nick, 0);
if (reason) {
kill_user(s_OperServ, u->nick, reason);
return 1;
}
return 0;
}
/*************************************************************************/
/* Callback for NickServ REGISTER/LINK check; we disallow
* registration/linking of SQlined nicknames.
*/
static int do_reglink_check(const User *u, const char *nick,
const char *pass, const char *email)
{
MaskData *sline = get_matching_maskdata(MD_SQLINE, nick);
put_maskdata(sline);
return sline != NULL;
}
/*************************************************************************/
/************************** S-line list editing **************************/
/*************************************************************************/
/* Note that all string parameters are assumed to be non-NULL; expiry must
* be set to the time when the S-line should expire (0 for none). Mask
* is converted to lowercase on return.
*/
EXPORT_FUNC(create_sline)
void create_sline(uint8 type, char *mask, const char *reason,
const char *who, time_t expiry)
{
MaskData *sline;
strlower(mask);
if (maskdata_count(type) >= MAX_MASKDATA) {
module_log("Attempt to add S%cLINE to full list!", type);
return;
}
sline = scalloc(1, sizeof(*sline));
sline->mask = sstrdup(mask);
sline->reason = sstrdup(reason);
sline->time = time(NULL);
sline->expires = expiry;
strbcpy(sline->who, who);
sline = add_maskdata(type, sline);
if (ImmediatelySendSline)
send_sline(type, sline);
}
/*************************************************************************/
/* Handle an OperServ S*LINE command. */
static void do_sline(uint8 type, User *u);
static void do_sgline(User *u) { do_sline(MD_SGLINE, u); }
static void do_sqline(User *u) { do_sline(MD_SQLINE, u); }
static void do_szline(User *u) {
if (no_szline < 0)
notice_lang(s_OperServ, u, OPER_SZLINE_NOT_AVAIL);
else
do_sline(MD_SZLINE, u);
}
static int check_add_sline(const User *u, uint8 type, char *mask,
time_t *expiry_ptr);
static void do_add_sline(const User *u, uint8 type, MaskData *md);
static void do_del_sline(const User *u, uint8 type, MaskData *md);
static MaskDataCmdInfo sline_cmd_info = {
/* Name, type, and expiry time pointer are overwritten with data for
* the particular command used right before calling do_maskdata_cmd() */
.name = "SxLINE",
.md_type = 0,
.def_expiry_ptr = NULL,
.msg_add_too_many = OPER_TOO_MANY_SLINES,
.msg_add_exists = OPER_SLINE_EXISTS,
.msg_added = OPER_SLINE_ADDED,
.msg_del_not_found = OPER_SLINE_NOT_FOUND,
.msg_deleted = OPER_SLINE_REMOVED,
.msg_cleared = OPER_SLINE_CLEARED,
.msg_list_header = OPER_SLINE_LIST_HEADER,
.msg_list_empty = OPER_SLINE_LIST_EMPTY,
.msg_list_no_match = OPER_SLINE_LIST_NO_MATCH,
.msg_check_no_match = OPER_SLINE_CHECK_NO_MATCH,
.msg_check_header = OPER_SLINE_CHECK_HEADER,
.msg_check_count = OPER_SLINE_CHECK_TRAILER,
.msg_count = OPER_SLINE_COUNT,
.mangle_mask = NULL,
.check_add_mask = check_add_sline,
.do_add_mask = do_add_sline,
.do_del_mask = do_del_sline,
.do_unknown_cmd = NULL,
};
static void do_sline(uint8 type, User *u)
{
char sxline[7];
sprintf(sxline, "S%cLINE", type); /* safe, for obvious reasons */
sline_cmd_info.name = sxline;
sline_cmd_info.md_type = type;
switch (type) {
case MD_SGLINE:
sline_cmd_info.def_expiry_ptr = &SGlineExpiry;
break;
case MD_SQLINE:
sline_cmd_info.def_expiry_ptr = &SQlineExpiry;
break;
case MD_SZLINE:
sline_cmd_info.def_expiry_ptr = &SZlineExpiry;
break;
default:
module_log("do_sline(): bad type value (%u)", type);
notice_lang(s_OperServ, u, INTERNAL_ERROR);
return;
}
do_maskdata_cmd(&sline_cmd_info, u);
}
/*************************************************************************/
static int check_add_sline(const User *u, uint8 type, char *mask,
time_t *expiry_ptr)
{
char *t;
/* Make sure mask is not too general. */
if (strchr(mask,'*') != NULL && mask[strspn(mask,"*?")] == 0
&& ((t = strchr(mask,'?')) == NULL || strchr(t+1,'?') == NULL)
) {
char cmdname[7];
snprintf(cmdname, sizeof(cmdname), "S%cLINE", (char)type);
notice_lang(s_OperServ, u, OPER_SLINE_MASK_TOO_GENERAL, cmdname);
return 0;
}
return 1;
}
/*************************************************************************/
static void do_add_sline(const User *u, uint8 type, MaskData *md)
{
if (WallOSSline) {
char buf[128];
expires_in_lang(buf, sizeof(buf), NULL, md->expires);
wallops(s_OperServ, "%s added an S%cLINE for \2%s\2 (%s)",
u->nick, type, md->mask, buf);
}
if (ImmediatelySendSline)
send_sline(type, md);
}
/*************************************************************************/
static void do_del_sline(const User *u, uint8 type, MaskData *md)
{
cancel_sline(type, md->mask);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
/* Callback on connection to uplink server. */
static int do_connect(void)
{
if (ImmediatelySendSline) {
MaskData *sline;
static uint8 types[] = {MD_SGLINE, MD_SQLINE, MD_SZLINE};
int i;
for (i = 0; i < lenof(types); i++) {
for (sline = first_maskdata(types[i]); sline;
sline = next_maskdata(types[i])
) {
send_sline(types[i], sline);
}
}
}
return 0;
}
/*************************************************************************/
/* Callback for S-line expiration. */
static int do_expire_maskdata(uint32 type, MaskData *md)
{
int i;
for (i = 0; i < lenof(sline_types); i++) {
if (type == sline_types[i]) {
if (WallSlineExpire)
wallops(s_OperServ, "S%cLINE on %s has expired",
sline_types[i], md->mask);
cancel_sline((uint8)type, md->mask);
}
}
return 0;
}
/*************************************************************************/
/* OperServ HELP callback, to handle HELP SQLINE (complex). */
static int do_help(User *u, char *param)
{
/* param should always be non-NULL here, but let's be paranoid */
if (param && stricmp(param,"SQLINE") == 0) {
notice_help(s_OperServ, u, OPER_HELP_SQLINE);
if (SQlineKill)
notice_help(s_OperServ, u, OPER_HELP_SQLINE_KILL);
else
notice_help(s_OperServ, u, OPER_HELP_SQLINE_NOKILL);
if (SQlineIgnoreOpers)
notice_help(s_OperServ, u, OPER_HELP_SQLINE_IGNOREOPERS);
notice_help(s_OperServ, u, OPER_HELP_SQLINE_END);
return 1;
}
return 0;
}
/*************************************************************************/
static int do_stats_all(User *user, const char *s_OperServ)
{
int32 count, mem;
MaskData *md;
count = mem = 0;
for (md = first_maskdata(MD_SGLINE); md; md = next_maskdata(MD_SGLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SGLINE_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_SQLINE); md; md = next_maskdata(MD_SQLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SQLINE_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_SZLINE); md; md = next_maskdata(MD_SZLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SZLINE_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/**************************** Database stuff *****************************/
/*************************************************************************/
static void insert_sgline(void *md) { add_maskdata(MD_SGLINE, md); }
static void *first_sgline(void) { return first_maskdata(MD_SGLINE); }
static void *next_sgline(void) { return next_maskdata(MD_SGLINE); }
static void insert_sqline(void *md) { add_maskdata(MD_SQLINE, md); }
static void *first_sqline(void) { return first_maskdata(MD_SQLINE); }
static void *next_sqline(void) { return next_maskdata(MD_SQLINE); }
static void insert_szline(void *md) { add_maskdata(MD_SZLINE, md); }
static void *first_szline(void) { return first_maskdata(MD_SZLINE); }
static void *next_szline(void) { return next_maskdata(MD_SZLINE); }
static DBField sline_dbfields[] = {
{ "mask", DBTYPE_STRING, offsetof(MaskData,mask) },
{ "reason", DBTYPE_STRING, offsetof(MaskData,reason) },
{ "who", DBTYPE_BUFFER, offsetof(MaskData,who), NICKMAX },
{ "time", DBTYPE_TIME, offsetof(MaskData,time) },
{ "expires", DBTYPE_TIME, offsetof(MaskData,expires) },
{ "lastused", DBTYPE_TIME, offsetof(MaskData,lastused) },
{ NULL }
};
static DBTable sgline_dbtable = {
.name = "sgline",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_sgline,
.first = first_sgline,
.next = next_sgline,
.fields = sline_dbfields,
};
static DBTable sqline_dbtable = {
.name = "sqline",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_sqline,
.first = first_sqline,
.next = next_sqline,
.fields = sline_dbfields,
};
static DBTable szline_dbtable = {
.name = "szline",
.newrec = new_maskdata,
.freerec = free_maskdata,
.insert = insert_szline,
.first = first_szline,
.next = next_szline,
.fields = sline_dbfields,
};
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "ImmediatelySendSline",{{CD_SET, 0, &ImmediatelySendSline } } },
{ "SGlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } },
{ "SGlineReason", { { CD_STRING, CF_DIRREQ, &SGlineReason } } },
{ "SQlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } },
{ "SQlineIgnoreOpers",{ { CD_SET, 0, &SQlineIgnoreOpers } } },
{ "SQlineKill", { { CD_SET, 0, &SQlineKill } } },
{ "SQlineReason", { { CD_STRING, CF_DIRREQ, &SQlineReason } } },
{ "SZlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } },
{ "SZlineReason", { { CD_STRING, CF_DIRREQ, &SZlineReason } } },
{ "WallSlineExpire", { { CD_SET, 0, &WallSlineExpire } } },
{ "WallOSSline", { { CD_SET, 0, &WallOSSline } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "nickserv/main") == 0) {
module_nickserv = mod;
if (!add_callback(mod, "REGISTER/LINK check", do_reglink_check))
module_log("Unable to register NickServ REGISTER/LINK check"
" callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_nickserv) {
module_nickserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
module_operserv = find_module("operserv/main");
if (!module_operserv) {
module_log("Main OperServ module not loaded");
return 0;
}
use_module(module_operserv);
if (!register_commands(module_operserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
cb_send_sgline = register_callback("send_sgline");
cb_send_sqline = register_callback("send_sqline");
cb_send_szline = register_callback("send_szline");
cb_cancel_sgline = register_callback("cancel_sgline");
cb_cancel_sqline = register_callback("cancel_sqline");
cb_cancel_szline = register_callback("cancel_szline");
if (cb_send_sgline < 0 || cb_send_sqline < 0 || cb_send_szline < 0
|| cb_cancel_sgline < 0 || cb_cancel_sqline < 0 || cb_cancel_szline < 0
) {
module_log("Unable to register callbacks");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "connect", do_connect)
|| !add_callback(NULL, "user check", do_user_check)
|| !add_callback(NULL, "user nickchange (after)", do_user_nickchange_after)
|| !add_callback(module_operserv, "expire maskdata", do_expire_maskdata)
|| !add_callback(module_operserv, "HELP", do_help)
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
) {
module_log("Unable to add callbacks");
exit_module(0);
return 0;
}
if (!register_dbtable(&sgline_dbtable)
|| !register_dbtable(&sqline_dbtable)
|| !register_dbtable(&szline_dbtable)
) {
module_log("Unable to register database tables");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
unregister_dbtable(&szline_dbtable);
unregister_dbtable(&sqline_dbtable);
unregister_dbtable(&sgline_dbtable);
if (module_nickserv)
do_unload_module(module_nickserv);
remove_callback(NULL, "user nickchange (after)", do_user_nickchange_after);
remove_callback(NULL, "user check", do_user_check);
remove_callback(NULL, "connect", do_connect);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_callback(cb_cancel_szline);
unregister_callback(cb_cancel_sqline);
unregister_callback(cb_cancel_sgline);
unregister_callback(cb_send_szline);
unregister_callback(cb_send_sqline);
unregister_callback(cb_send_sgline);
if (module_operserv) {
remove_callback(module_operserv, "STATS ALL", do_stats_all);
remove_callback(module_operserv, "HELP", do_help);
remove_callback(module_operserv,"expire maskdata",do_expire_maskdata);
unregister_commands(module_operserv, cmds);
unuse_module(module_operserv);
module_operserv = NULL;
}
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

30
modules/operserv/sline.h Normal file
View File

@ -0,0 +1,30 @@
/* S-line data structure and interface.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef SLINE_H
#define SLINE_H
/*************************************************************************/
E void create_sline(uint8 type, char *mask, const char *reason,
const char *who, time_t expiry);
/*************************************************************************/
#endif /* SLINE_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

83
modules/protocol/Makefile Normal file
View File

@ -0,0 +1,83 @@
# Makefile for protocol modules.
#
# IRC Services is copyright (c) 1996-2009 Andrew Church.
# E-mail: <achurch@achurch.org>
# Parts written by Andrew Kempe and others.
# This program is free but copyrighted software; see the file GPL.txt for
# details.
###########################################################################
include ../../Makefile.inc
MODULES = bahamut.so dalnet.so dreamforge.so hybrid.so inspircd.so \
monkey.so ptlink.so ratbox.so rfc1459.so solidircd.so trircd.so \
ts8.so undernet-p9.so unreal.so
INCLUDES-bahamut.o = $(BANEXCEPT_C) $(INVITEMASK_C) $(SJOIN_C) $(SVSNICK_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-dalnet.o = $(TOPDIR)/messages.h $(TOPDIR)/language.h
INCLUDES-dreamforge.o = $(SVSNICK_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/nickserv/nickserv.h
INCLUDES-hybrid.o = $(BANEXCEPT_C) $(INVITEMASK_C) $(SJOIN_C) $(SVSNICK_C) \
$(TOPDIR)/messages.h
INCLUDES-inspircd.o = $(BANEXCEPT_C) $(CHANPROT_C) $(HALFOP_C) \
$(INVITEMASK_C) $(SVSNICK_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h $(TOPDIR)/timeout.h \
$(TOPDIR)/version.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-monkey.o = $(HALFOP_C) $(SJOIN_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/nickserv/nickserv.h
INCLUDES-ptlink.o = $(BANEXCEPT_C) $(SJOIN_C) $(SVSNICK_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h $(TOPDIR)/version.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-ratbox.o = $(BANEXCEPT_C) $(INVITEMASK_C) $(SJOIN_C) \
$(TOPDIR)/messages.h
INCLUDES-solidircd.o = $(BANEXCEPT_C) $(HALFOP_C) $(INVITEMASK_C) \
$(SJOIN_C) $(SVSNICK_C) $(TOPDIR)/messages.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INCLUDES-trircd.o = $(BANEXCEPT_C) $(CHANPROT_C) $(HALFOP_C) \
$(INVITEMASK_C) $(SJOIN_C) $(SVSNICK_C) $(TOKEN_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/nickserv/nickserv.h
INCLUDES-undernet-p9.o = $(TOPDIR)/messages.h $(TOPDIR)/language.h
INCLUDES-unreal.o = $(BANEXCEPT_C) $(CHANPROT_C) $(HALFOP_C) \
$(INVITEMASK_C) $(SJOIN_C) $(SVSNICK_C) $(TOKEN_C) \
$(TOPDIR)/messages.h $(TOPDIR)/language.h $(TOPDIR)/timeout.h \
$(TOPDIR)/modules/operserv/operserv.h \
$(TOPDIR)/modules/operserv/maskdata.h \
$(TOPDIR)/modules/nickserv/nickserv.h \
$(TOPDIR)/modules/chanserv/chanserv.h
BANEXCEPT_C = banexcept.c banexcept.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/chanserv/chanserv.h
CHANPROT_C = chanprot.c chanprot.h $(TOPDIR)/language.h
HALFOP_C = halfop.c halfop.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/chanserv/chanserv.h
INVITEMASK_C = invitemask.c invitemask.h $(TOPDIR)/language.h \
$(TOPDIR)/modules/chanserv/chanserv.h
SJOIN_C = sjoin.c sjoin.h $(TOPDIR)/modules/chanserv/chanserv.h
SVSNICK_C = svsnick.c svsnick.h
TOKEN_C = token.c token.h $(TOPDIR)/messages.h
include ../Makerules
###########################################################################

1005
modules/protocol/bahamut.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
/* Ban exception related functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "modules/chanserv/chanserv.h"
#include "banexcept.h"
/*************************************************************************/
static Module *banexcept_module_chanserv;
static const char **banexcept_p_s_ChanServ = NULL; /* never used if NULL */
#define s_ChanServ (*banexcept_p_s_ChanServ)
/*************************************************************************/
/*************************************************************************/
/* Callback to handle MODE +/-e. */
static int banexcept_channel_mode(const char *source, Channel *chan,
int modechar, int add, char **av)
{
if (modechar == 'e') {
if (add) {
ARRAY_EXTEND(chan->excepts);
chan->excepts[chan->excepts_count-1] = sstrdup(av[0]);
} else {
int i;
ARRAY_SEARCH_PLAIN(chan->excepts, av[0], irc_stricmp, i);
if (i < chan->excepts_count) {
free(chan->excepts[i]);
ARRAY_REMOVE(chan->excepts, i);
} else {
module_log("banexcept: MODE %s -e %s: exception not found",
chan->name, *av);
}
}
return 0;
}
return 0;
}
/*************************************************************************/
/* Callback to handle clearing exceptions for clear_channel(). */
static void clear_excepts(const char *sender, Channel *chan, const User *u);
static int banexcept_clear_channel(const char *sender, Channel *chan, int what,
const void *param)
{
if (what & (CLEAR_USERS | CLEAR_EXCEPTS))
clear_excepts(sender, chan, (what & CLEAR_EXCEPTS) ? param : NULL);
return 0;
}
static void clear_excepts(const char *sender, Channel *chan, const User *u)
{
int i, count;
char **excepts;
if (!chan->excepts_count)
return;
count = chan->excepts_count;
excepts = smalloc(sizeof(char *) * count);
memcpy(excepts, chan->excepts, sizeof(char *) * count);
for (i = 0; i < count; i++) {
if (!u || match_usermask(excepts[i], u))
set_cmode(sender, chan, "-e", excepts[i]);
}
free(excepts);
}
/*************************************************************************/
/* Callback to handle ChanServ CLEAR EXCEPTIONS. */
static int banexcept_cs_clear(User *u, Channel *c, const char *what)
{
if (stricmp(what, "EXCEPTIONS") == 0) {
clear_excepts(s_ChanServ, c, NULL);
set_cmode(NULL, c);
notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTIONS, c->name);
return 1;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Callback to watch for modules being loaded. */
static int banexcept_load_module(Module *mod, const char *name)
{
if (strcmp(name, "chanserv/main") == 0) {
banexcept_module_chanserv = mod;
banexcept_p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (banexcept_p_s_ChanServ) {
if (!(add_callback(mod, "CLEAR", banexcept_cs_clear)))
module_log("banexcept: Unable to add ChanServ CLEAR callback");
} else {
module_log("banexcept: Symbol `s_ChanServ' not found, CLEAR"
" EXCEPTIONS will not be available");
}
}
return 0;
}
/*************************************************************************/
/* Callback to watch for modules being unloaded. */
static int banexcept_unload_module(Module *mod)
{
if (mod == banexcept_module_chanserv) {
banexcept_p_s_ChanServ = NULL;
banexcept_module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
int init_banexcept(void)
{
if (!add_callback(NULL, "channel MODE", banexcept_channel_mode)
|| !add_callback(NULL, "clear channel", banexcept_clear_channel)
|| !add_callback(NULL, "load module", banexcept_load_module)
|| !add_callback(NULL, "unload module", banexcept_unload_module)
) {
module_log("banexcept: Unable to add callbacks");
exit_banexcept();
return 0;
}
protocol_features |= PF_BANEXCEPT;
return 1;
}
/*************************************************************************/
void exit_banexcept(void)
{
remove_callback(NULL, "unload module", banexcept_unload_module);
remove_callback(NULL, "load module", banexcept_load_module);
remove_callback(NULL, "clear channel", banexcept_clear_channel);
remove_callback(NULL, "channel MODE", banexcept_channel_mode);
}
/*************************************************************************/
#undef s_ChanServ
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,31 @@
/* banexcept.c header file.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef BANEXCEPT_H
#define BANEXCEPT_H
/* Note that we need to rename all of our exported symbols to avoid
* conflicts when the .c file is included in multiple modules */
#define init_banexcept RENAME_SYMBOL(init_banexcept)
#define exit_banexcept RENAME_SYMBOL(exit_banexcept)
extern int init_banexcept(void);
extern void exit_banexcept(void);
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,50 @@
/* Channel protect/founder (+a/+q) related functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "chanprot.h"
/*************************************************************************/
static int chanprot_old_HELP_SOP_MID1 = -1;
/*************************************************************************/
int init_chanprot(void)
{
protocol_features |= PF_CHANPROT;
chanprot_old_HELP_SOP_MID1 =
mapstring(CHAN_HELP_SOP_MID1, CHAN_HELP_SOP_MID1_CHANPROT);
return 1;
}
/*************************************************************************/
void exit_chanprot(void)
{
if (chanprot_old_HELP_SOP_MID1 >= 0)
mapstring(CHAN_HELP_SOP_MID1, chanprot_old_HELP_SOP_MID1);
chanprot_old_HELP_SOP_MID1 = -1;
protocol_features &= ~PF_CHANPROT;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,28 @@
/* chanprot.c header file.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef CHANPROT_H
#define CHANPROT_H
#define init_chanprot RENAME_SYMBOL(init_chanprot)
#define exit_chanprot RENAME_SYMBOL(exit_chanprot)
extern int init_chanprot(void);
extern void exit_chanprot(void);
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

340
modules/protocol/dalnet.c Normal file
View File

@ -0,0 +1,340 @@
/* DALnet (4.4.13-) protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
/*************************************************************************/
static char *NetworkDomain = NULL;
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
};
static const struct modedata_init new_chanmodes[] = {
};
static const struct modedata_init new_chanusermodes[] = {
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 7) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
do_nick(source, ac, av);
}
/*************************************************************************/
static Message dalnet_messages[] = {
{ "AKILL", NULL },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "NICK", m_nick },
{ "RAKILL", NULL },
{ "SILENCE", NULL },
{ "SQLINE", NULL },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
send_cmd(NULL, "NICK %s 1 %ld %s %s %s :%s", nick, (long)time(NULL),
user, host, server, name);
if (modes)
send_cmd(nick, "MODE %s +%s", nick, modes);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS :%s", RemotePassword);
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (c->topic_time && t >= c->topic_time)
t = c->topic_time - 1; /* Force topic change */
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
send_cmd(ServerName, "AKILL %s %s :%s", host, username, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "RAKILL %s %s", host, username);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
return 0;
}
/*************************************************************************/
int init_module(void)
{
protocol_name = "DALnet";
protocol_version = "4.4.13-";
protocol_features = 0;
protocol_nickmax = 30;
if (!register_messages(dalnet_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
init_modes();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
valid_chan_table['+'] = 3;
valid_chan_table[':'] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 0;
mapstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(dalnet_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,484 @@
/* Dreamforge protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
#include "modules/operserv/operserv.h"
#include "modules/nickserv/nickserv.h"
#include "svsnick.c"
/*************************************************************************/
static Module *module_operserv;
static char *NetworkDomain = NULL;
/*************************************************************************/
/***************** Local interface to external routines ******************/
/*************************************************************************/
static typeof(is_services_admin) *p_is_services_admin = NULL;
static int local_is_services_admin(User *u)
{
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
{'a', {0x00000040}}, /* Services admin */
{'A', {0x00000080}}, /* Server admin */
};
static const struct modedata_init new_chanmodes[] = {
{'R', {0x00000100,0,0,0,MI_REGNICKS_ONLY}},
/* Only identified users can join */
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
};
static const struct modedata_init new_chanusermodes[] = {
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char *tmp;
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 8) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
/* Swap parameters so the Services timestamp is last. */
tmp = av[6];
av[6] = av[7];
av[7] = tmp;
do_nick(source, ac, av);
}
/*************************************************************************/
static void m_svsmode(char *source, int ac, char **av)
{
if (*av[0] == '#' || *av[0] == '&') {
module_log("SVSMODE from %s for invalid target (channel %s): %s",
source, av[0], merge_args(ac-1,av+1));
} else {
if (ac < 2)
return;
do_umode(source, ac, av);
}
}
/*************************************************************************/
static Message dreamforge_messages[] = {
{ "AKILL", NULL },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "NICK", m_nick },
{ "PROTOCTL", NULL },
{ "RAKILL", NULL },
{ "SILENCE", NULL },
{ "SQLINE", NULL },
{ "SVSMODE", m_svsmode },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
send_cmd(NULL, "NICK %s 1 %ld %s %s %s 0 :%s", nick, (long)time(NULL),
user, host, server, name);
if (modes)
send_cmd(nick, "MODE %s +%s", nick, modes);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS :%s", RemotePassword);
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_user_servicestamp_change(User *user)
{
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick, user->servicestamp);
return 0;
}
/*************************************************************************/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'd':
module_log("MODE tried to change services stamp for %s", user->nick);
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick,
user->servicestamp);
return 0;
case 'o':
if (add) {
user->mode |= UMODE_o;
if (add && user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_nick_identified(User *u, int old_status)
{
if (is_oper(u) && is_services_admin(u))
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
return 0;
}
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
if (c->topic_time && t >= c->topic_time)
t = c->topic_time - 1; /* Force topic change */
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
send_cmd(ServerName, "AKILL %s %s :%s", host, username, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "RAKILL %s %s", host, username);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
unsigned char c;
protocol_name = "Dreamforge/DALnet";
protocol_version = "4.4.15+";
protocol_features = 0;
protocol_nickmax = 30;
if (!register_messages(dreamforge_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "user servicestamp change",
do_user_servicestamp_change)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_svsnick("SVSNICK")) {
exit_module(1);
return 0;
}
init_modes();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table['+'] = 3;
valid_chan_table[':'] = 0;
valid_chan_table[160] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 0;
mapstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_svsnick();
remove_callback(NULL, "load module", do_load_module);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "user servicestamp change",
do_user_servicestamp_change);
remove_callback(NULL, "set topic", do_set_topic);
unregister_messages(dreamforge_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

152
modules/protocol/halfop.c Normal file
View File

@ -0,0 +1,152 @@
/* Halfop (+h) related functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "modules/chanserv/chanserv.h"
#include "halfop.h"
/*************************************************************************/
static Module *halfop_module_chanserv;
static int halfop_old_XOP_REACHED_LIMIT = -1;
static int halfop_old_XOP_NICKS_ONLY = -1;
static int halfop_old_HELP_SOP_MID2 = -1;
static int halfop_old_HELP_AOP_MID = -1;
static const char **halfop_p_s_ChanServ = NULL; /* never used if NULL */
#define s_ChanServ (*halfop_p_s_ChanServ)
/*************************************************************************/
/*************************************************************************/
/* Callback to handle ChanServ CLEAR HALFOPS. */
static void clear_halfops(Channel *chan);
static int halfop_cs_clear(User *u, Channel *c, const char *what)
{
if (stricmp(what, "HALFOPS") == 0) {
clear_halfops(c);
set_cmode(NULL, c);
notice_lang(s_ChanServ, u, CHAN_CLEARED_HALFOPS, c->name);
return 1;
}
return 0;
}
static void clear_halfops(Channel *chan)
{
struct c_userlist *cu;
static int32 mode_h = -1;
if (mode_h < 0)
mode_h = mode_char_to_flag('h', MODE_CHANUSER);
LIST_FOREACH (cu, chan->users) {
if (cu->mode & mode_h)
set_cmode(s_ChanServ, chan, "-h", cu->user->nick);
}
}
/*************************************************************************/
/*************************************************************************/
/* Callback to watch for modules being loaded. */
static int halfop_load_module(Module *mod, const char *name)
{
if (strcmp(name, "chanserv/main") == 0) {
halfop_module_chanserv = mod;
halfop_p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (halfop_p_s_ChanServ) {
if (!(add_callback(mod, "CLEAR", halfop_cs_clear)))
module_log("halfop: Unable to add ChanServ CLEAR callback");
} else {
module_log("halfop: Symbol `s_ChanServ' not found, CLEAR"
" HALFOPS will not be available");
}
}
return 0;
}
/*************************************************************************/
/* Callback to watch for modules being unloaded. */
static int halfop_unload_module(Module *mod)
{
if (mod == halfop_module_chanserv) {
halfop_p_s_ChanServ = NULL;
halfop_module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_halfop(void)
{
if (!add_callback(NULL, "load module", halfop_load_module)
|| !add_callback(NULL, "unload module", halfop_unload_module)
) {
module_log("halfop: Unable to add callbacks");
exit_halfop();
return 0;
}
protocol_features |= PF_HALFOP;
halfop_old_XOP_REACHED_LIMIT =
mapstring(CHAN_XOP_REACHED_LIMIT, CHAN_XOP_REACHED_LIMIT_HOP);
halfop_old_XOP_NICKS_ONLY =
mapstring(CHAN_XOP_NICKS_ONLY, CHAN_XOP_NICKS_ONLY_HOP);
halfop_old_HELP_SOP_MID2 =
mapstring(CHAN_HELP_SOP_MID2, CHAN_HELP_SOP_MID2_HALFOP);
halfop_old_HELP_AOP_MID =
mapstring(CHAN_HELP_AOP_MID, CHAN_HELP_AOP_MID_HALFOP);
return 1;
}
/*************************************************************************/
void exit_halfop(void)
{
if (halfop_module_chanserv)
halfop_unload_module(halfop_module_chanserv);
if (halfop_old_HELP_AOP_MID >= 0)
mapstring(CHAN_HELP_AOP_MID, halfop_old_HELP_AOP_MID);
if (halfop_old_HELP_SOP_MID2 >= 0)
mapstring(CHAN_HELP_SOP_MID2, halfop_old_HELP_SOP_MID2);
if (halfop_old_XOP_NICKS_ONLY >= 0)
mapstring(CHAN_XOP_NICKS_ONLY, halfop_old_XOP_NICKS_ONLY);
if (halfop_old_XOP_REACHED_LIMIT >= 0)
mapstring(CHAN_XOP_REACHED_LIMIT, halfop_old_XOP_REACHED_LIMIT);
halfop_old_HELP_AOP_MID = -1;
halfop_old_HELP_SOP_MID2 = -1;
halfop_old_XOP_NICKS_ONLY = -1;
halfop_old_XOP_REACHED_LIMIT = -1;
remove_callback(NULL, "unload module", halfop_unload_module);
remove_callback(NULL, "load module", halfop_load_module);
}
/*************************************************************************/
#undef s_ChanServ
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

28
modules/protocol/halfop.h Normal file
View File

@ -0,0 +1,28 @@
/* halfop.c header file.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef HALFOP_H
#define HALFOP_H
#define init_halfop RENAME_SYMBOL(init_halfop)
#define exit_halfop RENAME_SYMBOL(exit_halfop)
extern int init_halfop(void);
extern void exit_halfop(void);
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

455
modules/protocol/hybrid.c Normal file
View File

@ -0,0 +1,455 @@
/* Hybrid 7 protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "messages.h"
#define BAHAMUT_HACK
#include "banexcept.c"
#include "invitemask.c"
#include "sjoin.c"
#include "svsnick.c"
/*************************************************************************/
static Module *module_operserv;
static char **p_s_OperServ = &ServerName;
#define s_OperServ (*p_s_OperServ)
static char *NetworkDomain = NULL;
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'a', {0x00000008,0,0}}, /* Server admin */
};
static const struct modedata_init new_chanmodes[] = {
{'a', {0x00000100,0,0}}, /* Hide ops */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Ban exceptions */
{'I', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Auto-invite masks (Hybrid calls these "invite exceptions") */
};
static const struct modedata_init new_chanusermodes[] = {
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char *newav[10];
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
if (ac != 8) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
newav[0] = av[0]; /* Nick */
newav[1] = av[1]; /* Hop count */
newav[2] = av[2]; /* Timestamp */
newav[3] = av[4]; /* Username */
newav[4] = av[5]; /* Hostname */
newav[5] = av[6]; /* Server */
newav[6] = av[7]; /* Real name */
newav[7] = NULL; /* Services stamp */
newav[8] = NULL; /* IP address */
newav[9] = av[3]; /* Modes */
do_nick(source, 10, newav);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
char *s;
int has_tburst = 0;
if (ac != 1)
return;
for (s = strtok(av[0]," "); s; s = strtok(NULL," ")) {
if (stricmp(s, "EX") == 0) {
protocol_features |= PF_BANEXCEPT;
init_banexcept();
} else if (stricmp(s, "IE") == 0) {
protocol_features |= PF_INVITEMASK;
init_invitemask();
} else if (stricmp(s, "QS") == 0) {
protocol_features |= PF_NOQUIT;
} else if (stricmp(s, "TBURST") == 0) {
has_tburst = 1;
}
}
if (!has_tburst) {
send_error("TBURST support required");
strbcpy(quitmsg,
"Remote server does not support TBURST (see the manual)");
quitting = 1;
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac < 4) {
module_log_debug(1, "SJOIN: expected >=4 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
static void m_tburst(char *source, int ac, char **av)
{
if (ac != 5)
return;
av[0] = av[1];
av[1] = av[3];
av[3] = av[4];
do_topic(source, 4, av);
}
/*************************************************************************/
static void m_hybrid_topic(char *source, int ac, char **av)
{
char *newav[4];
char timebuf[32];
if (ac != 2)
return;
newav[0] = av[0];
newav[1] = source;
snprintf(timebuf, sizeof(timebuf), "%ld", (long)time(NULL));
newav[2] = timebuf;
newav[3] = av[1];
do_topic(source, 4, newav);
}
/*************************************************************************/
static Message hybrid_messages[] = {
{ "CAPAB", m_capab },
{ "GLINE", NULL },
{ "NICK", m_nick },
{ "SJOIN", m_sjoin },
{ "SVINFO", NULL },
{ "TBURST", m_tburst },
{ "TOPIC", m_hybrid_topic },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <server> :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s :%s", nick,
(long)time(NULL), modes, user, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS %s :TS", RemotePassword);
send_cmd(NULL, "CAPAB :EX IE KLN UNKLN HUB TBURST");
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
send_cmd(NULL, "SVINFO 5 3 0 :%ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS. */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "OPERWALL :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $$*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $$*.com :%s", msgbuf);
send_cmd(source, "NOTICE $$*.net :%s", msgbuf);
send_cmd(source, "NOTICE $$*.org :%s", msgbuf);
send_cmd(source, "NOTICE $$*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TBURST %ld %s %ld %s :%s", (long)c->creation_time,
c->name, (long)c->topic_time, c->topic_setter,
c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
if (expires) {
expires -= time(NULL);
if (expires < 1)
expires = 1;
}
send_cmd(s_OperServ, "KLINE * %ld %s %s :%s", (long)expires, username,
host, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(s_OperServ, "UNKLINE * %s %s", username, host);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_s_OperServ = get_module_symbol(mod, "s_OperServ");
if (!p_s_OperServ)
p_s_OperServ = &ServerName;
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_s_OperServ = &ServerName;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
protocol_name = "Hybrid";
protocol_version = "7.0";
protocol_features = 0;
protocol_nickmax = 30;
if (!register_messages(hybrid_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_sjoin() || !init_svsnick("SVSNICK")) {
return 0;
}
init_modes();
irc_lowertable['^'] = '~';
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "+i";
enforcer_modes = "+i";
pseudoclient_oper = 1;
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
if (protocol_features & PF_INVITEMASK)
exit_invitemask();
if (protocol_features & PF_BANEXCEPT)
exit_banexcept();
exit_svsnick();
exit_sjoin();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(hybrid_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

1131
modules/protocol/inspircd.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
/* Invite mask (+I) related functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "language.h"
#include "modules/chanserv/chanserv.h"
#include "invitemask.h"
/*************************************************************************/
static Module *invitemask_module_chanserv;
static const char **invitemask_p_s_ChanServ = NULL; /* never used if NULL */
#define s_ChanServ (*invitemask_p_s_ChanServ)
/*************************************************************************/
/*************************************************************************/
/* Callback to handle MODE +/-I. */
static int invitemask_channel_mode(const char *source, Channel *chan,
int modechar, int add, char **av)
{
if (modechar == 'I') {
if (add) {
ARRAY_EXTEND(chan->invites);
chan->invites[chan->invites_count-1] = sstrdup(av[0]);
} else {
int i;
ARRAY_SEARCH_PLAIN(chan->invites, av[0], irc_stricmp, i);
if (i < chan->invites_count) {
free(chan->invites[i]);
ARRAY_REMOVE(chan->invites, i);
} else {
module_log("invitemask: MODE %s -I %s: mask not found",
chan->name, *av);
}
}
return 0;
}
return 0;
}
/*************************************************************************/
/* Callback to handle clearing invite masks for clear_channel(). */
static void clear_invites(const char *sender, Channel *chan, const User *u);
static int invitemask_clear_channel(const char *sender, Channel *chan,
int what, const void *param)
{
if (what & (CLEAR_USERS | CLEAR_INVITES))
clear_invites(sender, chan, (what & CLEAR_INVITES) ? param : NULL);
return 0;
}
static void clear_invites(const char *sender, Channel *chan, const User *u)
{
int i, count;
char **invites;
if (!chan->invites_count)
return;
count = chan->invites_count;
invites = smalloc(sizeof(char *) * count);
memcpy(invites, chan->invites, sizeof(char *) * count);
for (i = 0; i < count; i++) {
if (!u || match_usermask(invites[i], u))
set_cmode(sender, chan, "-I", invites[i]);
}
free(invites);
}
/*************************************************************************/
/* Callback to handle ChanServ CLEAR INVITES. */
static int invitemask_cs_clear(User *u, Channel *c, const char *what)
{
if (stricmp(what, "INVITES") == 0) {
clear_invites(s_ChanServ, c, NULL);
set_cmode(NULL, c);
notice_lang(s_ChanServ, u, CHAN_CLEARED_INVITES, c->name);
return 1;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Callback to watch for modules being loaded. */
static int invitemask_load_module(Module *mod, const char *name)
{
if (strcmp(name, "chanserv/main") == 0) {
invitemask_module_chanserv = mod;
invitemask_p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (invitemask_p_s_ChanServ) {
if (!(add_callback(mod, "CLEAR", invitemask_cs_clear)))
module_log("invitemask: Unable to add ChanServ CLEAR"
" callback");
} else {
module_log("invitemask: Symbol `s_ChanServ' not found, CLEAR"
" INVITES will not be available");
}
}
return 0;
}
/*************************************************************************/
/* Callback to watch for modules being unloaded. */
static int invitemask_unload_module(Module *mod)
{
if (mod == invitemask_module_chanserv) {
invitemask_p_s_ChanServ = NULL;
invitemask_module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
int init_invitemask(void)
{
if (!add_callback(NULL, "channel MODE", invitemask_channel_mode)
|| !add_callback(NULL, "clear channel", invitemask_clear_channel)
|| !add_callback(NULL, "load module", invitemask_load_module)
|| !add_callback(NULL, "unload module", invitemask_unload_module)
) {
module_log("invitemask: Unable to add callbacks");
exit_invitemask();
return 0;
}
protocol_features |= PF_INVITEMASK;
return 1;
}
/*************************************************************************/
void exit_invitemask(void)
{
remove_callback(NULL, "unload module", invitemask_unload_module);
remove_callback(NULL, "load module", invitemask_load_module);
remove_callback(NULL, "clear channel", invitemask_clear_channel);
remove_callback(NULL, "channel MODE", invitemask_channel_mode);
}
/*************************************************************************/
#undef s_ChanServ
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,28 @@
/* invitemask.c header file.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef INVITEMASK_H
#define INVITEMASK_H
#define init_invitemask RENAME_SYMBOL(init_invitemask)
#define exit_invitemask RENAME_SYMBOL(exit_invitemask)
extern int init_invitemask(void);
extern void exit_invitemask(void);
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

586
modules/protocol/monkey.c Normal file
View File

@ -0,0 +1,586 @@
/* Chunky Monkey IRCD (1.0.2 or greater) protocol module for IRC Services
*
* This module is largely based on the bahamut module.
* It is maintained by Chris Plant
* E-mail: <chris@monkeyircd.org>
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
#include "modules/operserv/operserv.h"
#include "modules/nickserv/nickserv.h"
#include "sjoin.c"
#include "halfop.c"
/*************************************************************************/
static Module *module_operserv;
static char *NetworkDomain = NULL;
/*************************************************************************/
/***************** Local interface to external routines ******************/
/*************************************************************************/
static typeof(is_services_admin) *p_is_services_admin = NULL;
static int local_is_services_admin(User *u)
{
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
};
static const struct modedata_init new_chanmodes[] = {
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
};
static const struct modedata_init new_chanusermodes[] = {
{'h', {0x00000004,1,1,'%'} },
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char *newav[10];
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 9) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
newav[0] = av[0]; /* Nick */
newav[1] = av[1]; /* Hop count */
newav[2] = av[2]; /* Timestamp */
newav[3] = av[4]; /* Username */
newav[4] = av[5]; /* Hostname */
newav[5] = av[6]; /* Server */
newav[6] = av[7]; /* Real name */
newav[7] = NULL; /* Services stamp */
newav[8] = NULL; /* IP address */
newav[9] = av[3]; /* Modes */
do_nick(source, 10, newav);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
int i;
for (i = 0; i < ac; i++) {
if (stricmp(av[i], "TS4") == 0)
protocol_features |= PF_NOQUIT;
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac == 3 || ac < 2) {
module_log_debug(1, "SJOIN: expected 2 or >=4 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
/* SVSMODE message handler. */
static void m_svsmode(char *source, int ac, char **av)
{
if (*av[0] == '#') {
if (ac >= 3 && strcmp(av[1],"-b") == 0) {
Channel *c = get_channel(av[0]);
User *u = get_user(av[2]);
if (c && u)
clear_channel(c, CLEAR_BANS, u);
} else {
module_log("Invalid SVSMODE from %s for channel %s: %s",
source, av[0], merge_args(ac-1,av+1));
}
} else if (*av[0] == '&') {
module_log("SVSMODE from %s for invalid target (channel %s): %s",
source, av[0], merge_args(ac-1,av+1));
} else {
if (ac < 2)
return;
if (ac >= 3 && (*av[2] == '+' || *av[2] == '-')) {
/* Move the timestamp to the end */
char *ts = av[1];
memmove(av+1, av+2, sizeof(char *) * (ac-2));
av[ac-1] = ts;
}
do_umode(source, ac, av);
}
}
/*************************************************************************/
static Message monkeyircd_messages[] = {
{ "CAPAB", m_capab },
{ "NICK", m_nick },
{ "SILENCE", NULL },
{ "SJOIN", m_sjoin },
{ "SVINFO", NULL },
{ "SVSMODE", m_svsmode },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <server> <svsid>
* :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s 0 0 :%s", nick,
(long)time(NULL), modes, user, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
sleep(5); /* Hack, give monkeyircd time to resolve properly */
send_cmd(NULL, "PASS %s 0280ircservices CMIRCD|Nothing", RemotePassword);
send_cmd(NULL, "CAPAB :TS4");
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
send_cmd(NULL, "SVINFO 4 4 0 :%ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(ServerName, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_user_servicestamp_change(User *user)
{
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick, user->servicestamp);
return 0;
}
/*************************************************************************/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'd':
module_log("MODE tried to change services stamp for %s", user->nick);
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick,
user->servicestamp);
return 0;
case 'o':
if (add) {
user->mode |= UMODE_o;
if (add && user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_nick_identified(User *u, int old_status)
{
if (is_oper(u) && is_services_admin(u))
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
return 0;
}
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
time_t now = time(NULL);
send_cmd(ServerName, "AKILL %s %s %ld %s %ld :%s", host, username,
(long)((expires && expires > now) ? expires - now : 0),
who ? who : "<unknown>", (long)now, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "RAKILL %s %s", host, username);
return 1;
}
/*************************************************************************/
static int do_send_sgline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SGLINE %d :%s:%s", (int)strlen(mask), mask, reason);
return 1;
}
static int do_send_sqline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SQLINE %s :%s", mask, reason);
return 1;
}
static int do_send_szline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SZLINE %s :%s", mask, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_sgline(const char *mask)
{
send_cmd(ServerName, "UNSGLINE :%s", mask);
return 1;
}
static int do_cancel_sqline(const char *mask)
{
send_cmd(ServerName, "UNSQLINE %s", mask);
return 1;
}
static int do_cancel_szline(const char *mask)
{
send_cmd(ServerName, "UNSZLINE %s", mask);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
} else if (strcmp(modname, "operserv/sline") == 0) {
if (!add_callback(mod, "send_sgline", do_send_sgline))
module_log("Unable to add send_sgline callback");
if (!add_callback(mod, "send_sqline", do_send_sqline))
module_log("Unable to add send_sqline callback");
if (!add_callback(mod, "send_szline", do_send_szline))
module_log("Unable to add send_szline callback");
if (!add_callback(mod, "cancel_sgline", do_cancel_sgline))
module_log("Unable to add cancel_sgline callback");
if (!add_callback(mod, "cancel_sqline", do_cancel_sqline))
module_log("Unable to add cancel_sqline callback");
if (!add_callback(mod, "cancel_szline", do_cancel_szline))
module_log("Unable to add cancel_szline callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
unsigned char c;
protocol_name = "Chunky Monkey IRCD";
protocol_version = "1.0+";
protocol_features = 0;
protocol_nickmax = 30;
if (!register_messages(monkeyircd_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "user servicestamp change",
do_user_servicestamp_change)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_halfop() || !init_sjoin()) {
exit_module(1);
return 0;
}
init_modes();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
valid_nick_table['}'] = 0; /* off-by-one bug in 1.0.4 */
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table[160] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 0;
mapstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_sjoin();
exit_halfop();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "user servicestamp change",
do_user_servicestamp_change);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(monkeyircd_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

762
modules/protocol/ptlink.c Normal file
View File

@ -0,0 +1,762 @@
/* PTlink protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
#include "version.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "banexcept.c"
#include "sjoin.c"
#include "svsnick.c"
/*************************************************************************/
/* "Nick" to use to indicate Services-set GLINEs */
#define GLINE_WHO "<ircservices>"
static Module *module_operserv;
static char *NetworkDomain = NULL;
static int32 usermode_admin = 0; /* +aANT */
static int32 usermode_hiding = 0; /* +S */
static int32 chanmode_admins_only = 0; /* +A */
/*************************************************************************/
/***************** Local interface to external routines ******************/
/*************************************************************************/
static typeof(is_services_admin) *p_is_services_admin = NULL;
static int local_is_services_admin(User *u)
{
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
#define MI_ADMIN 0x01000000 /* Usermode given to admins */
#define MI_HIDING 0x02000000 /* Usermode for hiding users */
#define MI_ADMINS_ONLY 0x01000000 /* Chanmode for admin-only channels */
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
{'a', {0x00000040}}, /* Services admin */
{'A', {0x00000080}}, /* Server admin */
{'N', {0x00000100,0,0,0,MI_ADMIN}},
/* Network admin */
{'T', {0x00000200,0,0,0,MI_ADMIN}},
/* Technical admin */
/* Flags in this range are used by Unreal */
{'S', {0x00002000,0,0,0,MI_HIDING}},
/* Stealth mode (hides joins etc) */
{'B', {0x00004000}}, /* Is a bot ("deaf" in Unreal) */
{'R', {0x00008000}}, /* Allow PRIVMSGs from +r clients only */
{'p', {0x00010000}}, /* Private (don't show channels in /whois) */
{'v', {0x00020000}}, /* Don't allow DCCs */
};
static const struct modedata_init new_chanmodes[] = {
{'R', {0x00000100,0,0,0,MI_REGNICKS_ONLY}},
/* Only identified users can join */
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
{'c', {0x00000400,0,0}}, /* No ANSI colors in channel */
{'O', {0x00000800,0,0,0,MI_OPERS_ONLY}},
/* Only opers can join channel */
{'A', {0x00001000,0,0,0,MI_OPERS_ONLY|MI_ADMINS_ONLY}},
/* Only admins can join channel */
{'c', {0x00100000,0,0}}, /* Strip colors */
{'d', {0x00800000,0,0}}, /* No flooding */
{'S', {0x04000000,0,0}}, /* No spam */
{'q', {0x08000000,0,0}}, /* No quits */
{'K', {0x10000000,0,0}}, /* Send knock message on failed join */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Ban exceptions */
};
static const struct modedata_init new_chanusermodes[] = {
{'a', {0x00000010,1,1,'.'}}, /* Channel owner */
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++) {
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
if (new_usermodes[i].data.info & MI_ADMIN)
usermode_admin |= new_usermodes[i].data.flag;
if (new_usermodes[i].data.info & MI_HIDING)
usermode_hiding |= new_usermodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanmodes); i++) {
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
if (new_chanmodes[i].data.info & MI_ADMINS_ONLY)
chanmode_admins_only |= new_chanmodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char *newav[11];
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 9) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
newav[ 0] = av[0]; /* Nick */
newav[ 1] = av[1]; /* Hop count */
newav[ 2] = av[2]; /* Timestamp */
newav[ 3] = av[4]; /* Username */
newav[ 4] = av[5]; /* Hostname */
newav[ 5] = av[7]; /* Server */
newav[ 6] = av[8]; /* Real name */
newav[ 7] = NULL; /* Services stamp */
newav[ 8] = NULL; /* IP address */
newav[ 9] = av[3]; /* Modes */
newav[10] = av[6]; /* User area (fake hostname) */
do_nick(source, 11, newav);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
char *s;
int got_PTS4 = 0;
int got_QS = 0;
int got_EX = 0;
if (ac < 1) {
module_log("received CAPAB with no parameters--broken ircd?");
} else {
for (s = strtok(av[0]," "); s; s = strtok(NULL," ")) {
if (stricmp(s, "PTS4") == 0)
got_PTS4 = 1;
else if (stricmp(s, "QS") == 0)
got_QS = 1;
else if (stricmp(s, "EX") == 0)
got_EX = 1;
}
}
if (!got_PTS4 || !got_QS || !got_EX) {
module_log("CAPAB: capabilities missing:%s%s%s",
got_PTS4 ? "" : " PTS4",
got_QS ? "" : " QS",
got_EX ? "" : " EX");
send_error("Need PTS4/QS/EX capabilities");
strbcpy(quitmsg, "Remote server doesn't support all of PTS4/QS/EX");
quitting = 1;
}
}
/*************************************************************************/
static void m_svinfo(char *source, int ac, char **av)
{
if (ac < 2) {
module_log("received SVINFO with <2 parameters--broken ircd?");
send_error("Invalid SVINFO received (at least 2 parameters needed)");
quitting = 1;
} else {
if (atoi(av[1]) > 6 || atoi(av[0]) < 6) {
send_error("Need protocol version 6 support");
strbcpy(quitmsg,
"Remote server doesn't support protocol version 6");
quitting = 1;
}
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac < 4) {
module_log("SJOIN: expected >=4 params, got %d (broken ircd?)", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
static void m_newmask(char *source, int ac, char **av)
{
char *newuser, *newhost;
User *u;
if (ac < 1) {
module_log("NEWUSER: parameters missing--broken ircd?");
return;
}
if (!(u = get_user(source))) {
module_log("got NEWUSER from nonexistent user %s", source);
return;
}
newuser = av[0];
newhost = strchr(newuser, '@');
if (newhost)
*newhost++ = 0;
else
newhost = "";
free(u->username);
u->username = sstrdup(newuser);
free(u->host);
u->host = sstrdup(newhost);
}
/*************************************************************************/
/* GLINE/SGLINE/SQLINE handling: cancel any Services-set lines from remote
* servers (see comments in unreal.c/m_tkl() for details).
*/
static void m_gline(char *source, int ac, char **av)
{
typeof(get_maskdata) *p_get_maskdata = NULL;
typeof(put_maskdata) *p_put_maskdata = NULL;
if (ac < 3 || strcmp(av[2], GLINE_WHO) != 0)
return;
if (!(p_get_maskdata = get_module_symbol(NULL, "get_maskdata")))
return;
if (!(p_put_maskdata = get_module_symbol(NULL, "put_maskdata")))
return;
if ((*p_put_maskdata)((*p_get_maskdata)(MD_AKILL, av[0])))
return;
send_cmd(ServerName, "UNGLINE :%s", av[0]);
}
static void m_sgline(char *source, int ac, char **av)
{
typeof(get_maskdata) *p_get_maskdata = NULL;
typeof(put_maskdata) *p_put_maskdata = NULL;
int masklen;
if (ac < 3)
return;
masklen = atoi(av[1]);
if (masklen < strlen(av[2]))
av[2][masklen] = 0;
if (!(p_get_maskdata = get_module_symbol(NULL, "get_maskdata")))
return;
if (!(p_put_maskdata = get_module_symbol(NULL, "put_maskdata")))
return;
if ((*p_put_maskdata)((*p_get_maskdata)(MD_SGLINE, av[2])))
return;
send_cmd(ServerName, "UNSGLINE :%s", av[2]);
}
static void m_sqline(char *source, int ac, char **av)
{
typeof(get_maskdata) *p_get_maskdata = NULL;
typeof(put_maskdata) *p_put_maskdata = NULL;
if (ac < 1)
return;
if (!(p_get_maskdata = get_module_symbol(NULL, "get_maskdata")))
return;
if (!(p_put_maskdata = get_module_symbol(NULL, "put_maskdata")))
return;
if ((*p_put_maskdata)((*p_get_maskdata)(MD_SQLINE, av[0])))
return;
send_cmd(ServerName, "UNSQLINE :%s", av[0]);
}
/*************************************************************************/
static Message ptlink_messages[] = {
{ "CAPAB", m_capab },
{ "GLINE", m_gline },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "NEWMASK", m_newmask },
{ "NICK", m_nick },
{ "SILENCE", NULL },
{ "SJOIN", m_sjoin },
{ "SGLINE", m_sgline },
{ "SQLINE", m_sqline },
{ "SVINFO", m_svinfo },
{ "SVLINE", NULL },
{ "UNGLINE", NULL },
{ "UNSGLINE", NULL },
{ "UNSQLINE", NULL },
{ "UNSVLINE", NULL },
{ "UNZOMBIE", NULL },
{ "ZOMBIE", NULL },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <fakehost> <server>
* :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s %s 0 :%s", nick,
(long)time(NULL), modes, user, host, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
Module *mod;
int32 maxusercnt = 0;
send_cmd(NULL, "PASS %s :TS", RemotePassword);
send_cmd(NULL, "CAPAB :PTS4 QS EX");
send_cmd(NULL, "SERVER %s 1 ircservices-%s :%s", ServerName,
version_number, ServerDesc);
send_cmd(NULL, "SVINFO 6 6");
if ((mod = find_module("operserv/main")) != NULL) {
typeof(get_operserv_data) *p_get_operserv_data;
p_get_operserv_data = get_module_symbol(mod, "get_operserv_data");
if (p_get_operserv_data)
p_get_operserv_data(OSDATA_MAXUSERCNT, &maxusercnt);
}
send_cmd(NULL, "SVSINFO %ld %d", (long)start_time, maxusercnt);
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(ServerName, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_user_create(User *user, int ac, char **av)
{
user->fakehost = sstrdup(av[9]);
return 0;
}
/*************************************************************************/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'o':
if (add) {
user->mode |= UMODE_o;
if (add && user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_nick_identified(User *u, int old_status)
{
if (is_oper(u) && is_services_admin(u))
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
return 0;
}
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_check_chan_user_modes(const char *source, User *user,
Channel *c, int32 modes)
{
/* Don't do anything to hiding users */
return ((user->mode & usermode_hiding) ? 1 : 0);
}
/*************************************************************************/
static int do_check_kick(User *user, const char *chan, ChannelInfo *ci,
char **mask_ret, const char **reason_ret)
{
Channel *c = get_channel(chan);
/* Don't let plain opers into +A (admin only) channels */
if ((((c?c->mode:0) | (ci?ci->mlock.on:0)) & chanmode_admins_only)
&& !(user->mode & usermode_admin)
) {
*mask_ret = create_mask(user, 1);
*reason_ret = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
return 1;
}
return 0;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
time_t now = time(NULL);
send_cmd(ServerName, "GLINE %s@%s %ld %s :%s", username, host,
(long)((expires && expires > now) ? expires - now : 0),
GLINE_WHO, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "UNGLINE %s@%s", username, host);
return 1;
}
/*************************************************************************/
static int do_send_sgline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SGLINE %d :%s:%s", (int)strlen(mask), mask, reason);
return 1;
}
static int do_send_sqline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SQLINE %s :%s", mask, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_sgline(const char *mask)
{
send_cmd(ServerName, "UNSGLINE :%s", mask);
return 1;
}
static int do_cancel_sqline(const char *mask)
{
send_cmd(ServerName, "UNSQLINE %s", mask);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
} else if (strcmp(modname, "operserv/sline") == 0) {
if (!add_callback(mod, "send_sgline", do_send_sgline))
module_log("Unable to add send_sgline callback");
if (!add_callback(mod, "send_sqline", do_send_sqline))
module_log("Unable to add send_sqline callback");
if (!add_callback(mod, "cancel_sgline", do_cancel_sgline))
module_log("Unable to add cancel_sgline callback");
if (!add_callback(mod, "cancel_sqline", do_cancel_sqline))
module_log("Unable to add cancel_sqline callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
} else if (strcmp(modname, "chanserv/main") == 0) {
if (!add_callback(mod, "check_chan_user_modes",
do_check_chan_user_modes))
module_log("Unable to add ChanServ check_chan_user_modes"
" callback");
if (!add_callback(mod, "check_kick", do_check_kick))
module_log("Unable to add ChanServ check_kick callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
unsigned char c;
protocol_name = "PTlink";
protocol_version = "6.x";
protocol_features = PF_BANEXCEPT | PF_NOQUIT;
protocol_nickmax = 20;
if (!register_messages(ptlink_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "user create", do_user_create)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_banexcept() || !init_sjoin() || !init_svsnick("SVSNICK")) {
exit_module(1);
return 0;
}
init_modes();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
/* Note that PTlink extended character sets are not supported */
valid_nick_table['\\'] = 0;
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table['\\'] = 0;
valid_chan_table[160] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 0;
mapstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_svsnick();
exit_sjoin();
exit_banexcept();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "user create", do_user_create);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(ptlink_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

518
modules/protocol/ratbox.c Normal file
View File

@ -0,0 +1,518 @@
/* ircd-ratbox protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "messages.h"
#define BAHAMUT_HACK
#include "banexcept.c"
#include "invitemask.c"
#include "sjoin.c"
/*************************************************************************/
static Module *module_operserv;
static char **p_s_OperServ = &ServerName;
#define s_OperServ (*p_s_OperServ)
static char *NetworkDomain = NULL;
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'a', {0x00000008,0,0}}, /* Server admin */
};
static const struct modedata_init new_chanmodes[] = {
{'a', {0x00000100,0,0}}, /* Hide ops */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Ban exceptions */
{'I', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Auto-invite masks (Ratbox calls these "invite exceptions") */
};
static const struct modedata_init new_chanusermodes[] = {
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char *newav[10];
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
if (ac != 8) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
newav[0] = av[0]; /* Nick */
newav[1] = av[1]; /* Hop count */
newav[2] = av[2]; /* Timestamp */
newav[3] = av[4]; /* Username */
newav[4] = av[5]; /* Hostname */
newav[5] = av[6]; /* Server */
newav[6] = av[7]; /* Real name */
newav[7] = NULL; /* Services stamp */
newav[8] = NULL; /* IP address */
newav[9] = av[3]; /* Modes */
do_nick(source, 10, newav);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
char *s;
int has_tburst = 0;
if (ac != 1)
return;
for (s = strtok(av[0]," "); s; s = strtok(NULL," ")) {
if (stricmp(s, "EX") == 0) {
protocol_features |= PF_BANEXCEPT;
init_banexcept();
} else if (stricmp(s, "IE") == 0) {
protocol_features |= PF_INVITEMASK;
init_invitemask();
} else if (stricmp(s, "QS") == 0) {
protocol_features |= PF_NOQUIT;
} else if (stricmp(s, "TB") == 0) {
has_tburst = 1;
} else if (stricmp(s, "RSFNC") == 0) {
protocol_features |= PF_CHANGENICK;
}
}
if (!has_tburst) {
send_error("TB (topic burst) support required");
strbcpy(quitmsg, "Remote server does not support TB (see the manual)");
quitting = 1;
}
}
/*************************************************************************/
/* Note: this function only handles the ENCAP GCAP message. */
static void m_encap(char *source, int ac, char **av)
{
char *s;
int has_rsfnc = 0;
if (ac != 3 || stricmp(av[1], "GCAP") != 0)
return;
for (s = strtok(av[2]," "); s; s = strtok(NULL," ")) {
if (stricmp(s, "RSFNC") == 0) {
has_rsfnc = 1;
}
}
if ((protocol_features & PF_CHANGENICK) && !has_rsfnc) {
wallops(NULL, "Server %s does not support RSFNC, disabling"
" forced nickname changing", source);
protocol_features &= ~PF_CHANGENICK;
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac < 4) {
module_log_debug(1, "SJOIN: expected >=4 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
static void m_tb(char *source, int ac, char **av)
{
if (ac != 5)
return;
av[0] = av[1];
av[1] = av[3];
av[3] = av[4];
do_topic(source, 4, av);
}
/*************************************************************************/
static void m_ratbox_topic(char *source, int ac, char **av)
{
char *newav[4];
char timebuf[32];
if (ac != 2)
return;
newav[0] = av[0];
newav[1] = source;
snprintf(timebuf, sizeof(timebuf), "%ld", (long)time(NULL));
newav[2] = timebuf;
newav[3] = av[1];
do_topic(source, 4, newav);
}
/*************************************************************************/
static Message ratbox_messages[] = {
{ "CAPAB", m_capab },
{ "ENCAP", m_encap },
{ "GLINE", NULL },
{ "NICK", m_nick },
{ "SJOIN", m_sjoin },
{ "SVINFO", NULL },
{ "TB", m_tb },
{ "TOPIC", m_ratbox_topic },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <server> :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s :%s", nick,
(long)time(NULL), modes, user, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change the nickname of a user on another server. */
static void do_send_nickchange_remote(const char *nick, const char *newnick)
{
const User *u = get_user(nick);
send_cmd(NULL, "ENCAP %s RSFNC %s %s %ld %ld",
u->server->name, nick, newnick, (long)time(NULL) - 60,
(long)u->signon);
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS %s :TS", RemotePassword);
send_cmd(NULL, "CAPAB :EX IE KLN UNKLN TB ENCAP");
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
send_cmd(NULL, "SVINFO 5 3 0 :%ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS. */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(ServerName, "WALLOPS :%s%s%s", source ? source : "", source ? ": " : "", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $$*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $$*.com :%s", msgbuf);
send_cmd(source, "NOTICE $$*.net :%s", msgbuf);
send_cmd(source, "NOTICE $$*.org :%s", msgbuf);
send_cmd(source, "NOTICE $$*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter) {
/* If no topic exists, set the topic timestamp to the current time
* plus 1 to avoid getting a timestamp of -1 when we send it below */
if (!c->topic)
c->topic_time = time(NULL) + 1;
return 0;
}
if (c->topic_time <= t)
t = c->topic_time - 1;
c->topic_time = t;
send_cmd(ServerName, "TB %s %ld %s :%s", c->name, (long)c->topic_time,
c->topic_setter, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
if (expires) {
expires -= time(NULL);
if (expires < 1)
expires = 1;
}
send_cmd(s_OperServ, "ENCAP * KLINE %ld * %s %s :%s",
(long)expires, username, host, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(s_OperServ, "ENCAP * UNKLINE %s %s", username, host);
return 1;
}
/*************************************************************************/
static int do_set_identified(User *u, int old_authstat)
{
send_cmd(ServerName, "ENCAP * SU %s %s", u->nick, ngi_mainnick(u->ngi));
return 0;
}
/*************************************************************************/
static int do_cancel_user(User *u, int old_status, int old_authstat)
{
send_cmd(ServerName, "ENCAP * SU %s", u->nick);
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_s_OperServ = get_module_symbol(mod, "s_OperServ");
if (!p_s_OperServ)
p_s_OperServ = &ServerName;
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "set identified", do_set_identified))
module_log("Unable to add NickServ set identified callback");
if (!add_callback(mod, "cancel user", do_cancel_user))
module_log("Unable to add NickServ cancel user callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_s_OperServ = &ServerName;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
protocol_name = "ircd-ratbox";
protocol_version = "2.2";
protocol_features = PF_CHANGENICK;
protocol_nickmax = 9;
if (!register_messages(ratbox_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_sjoin()) {
return 0;
}
init_modes();
irc_lowertable['^'] = '~';
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_nickchange_remote = do_send_nickchange_remote;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "+iSD";
enforcer_modes = "+i";
pseudoclient_oper = 1;
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
if (protocol_features & PF_INVITEMASK)
exit_invitemask();
if (protocol_features & PF_BANEXCEPT)
exit_banexcept();
exit_sjoin();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(ratbox_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

257
modules/protocol/rfc1459.c Normal file
View File

@ -0,0 +1,257 @@
/* RFC1459 protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "messages.h"
/*************************************************************************/
static char *NetworkDomain = NULL;
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
if (*source) {
/* Old user changing nicks. */
if (ac != 1) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. We get the information we want from the USER command, so
* we don't do anything else here. */
}
/*************************************************************************/
static void m_user(char *source, int ac, char **av)
{
char *new_av[7];
if (ac != 4)
return;
new_av[0] = source; /* Nickname */
new_av[1] = (char *)"0"; /* # of hops (was in NICK command... we lose) */
new_av[2] = (char *)"0"; /* Timestamp */
new_av[3] = av[0]; /* Username */
new_av[4] = av[1]; /* Hostname */
new_av[5] = av[2]; /* Server */
new_av[6] = av[3]; /* Real name */
do_nick("", 7, new_av);
}
/*************************************************************************/
static Message rfc1459_messages[] = {
{ "NICK", m_nick },
{ "USER", m_user },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
send_cmd(NULL, "NICK %s :1", nick);
send_cmd(nick, "USER %s %s %s :%s", user, host, server, name);
if (modes)
send_cmd(nick, "MODE %s +%s", nick, modes);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s", newnick);
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS :%s", RemotePassword);
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS. */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "WALLOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s :%s", c->name,
c->topic_setter, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
protocol_name = "RFC1459";
protocol_version = "";
protocol_features = 0;
protocol_nickmax = 9;
if (!register_messages(rfc1459_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "set topic", do_set_topic)) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 1;
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
remove_callback(NULL, "set topic", do_set_topic);
unregister_messages(rfc1459_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

260
modules/protocol/sjoin.c Normal file
View File

@ -0,0 +1,260 @@
/* SJOIN-related functions.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
/* If this file is compiled with UNREAL_HACK defined, the create-channel
* callback, do_channel_create(), will be modified to force Unreal to
* update channel times properly. Defining BAHAMUT_HACK does the same
* thing for Bahamut.
*/
#include "services.h"
#include "modules.h"
#include "modules/chanserv/chanserv.h"
#include "sjoin.h"
/*************************************************************************/
static Module *sjoin_module_chanserv;
int CSSetChannelTime;
/*************************************************************************/
/* Handle an SJOIN command.
* Bahamut no-SSJOIN format:
* av[0] = TS3 timestamp
* av[1] = TS3 timestamp (channel creation time)
* av[2] = channel
* av[3] = channel modes
* av[4] = limit / key (depends on modes in av[3])
* av[5] = limit / key (depends on modes in av[3])
* av[ac-1] = nickname(s), with modes, joining channel
* Bahamut SSJOIN (server source) / Hybrid / PTlink / Unreal SJOIN2/SJ3 format:
* av[0] = TS3 timestamp (channel creation time)
* av[1] = channel
* av[2] = modes (limit/key in av[3]/av[4] as needed)
* av[ac-1] = users
* (Note that Unreal may omit the modes if there aren't any.
* Unreal SJ3 also includes bans and exceptions in av[ac-1]
* with prefix characters of & and " respectively.)
* Bahamut SSJOIN format (client source):
* av[0] = TS3 timestamp (channel creation time)
* av[1] = channel
*/
static void do_sjoin(const char *source, int ac, char **av)
{
User *user;
char *t, *nick;
char *channel;
Channel *c = NULL, *ctemp;
if (isdigit(av[1][0])) {
/* Plain SJOIN format, zap join timestamp */
memmove(&av[0], &av[1], sizeof(char *) * (ac-1));
ac--;
}
channel = av[1];
if (ac >= 3) {
/* SJOIN from server: nick list in last param */
t = av[ac-1];
} else {
/* SJOIN for a single client: source is nick */
/* We assume the nick has no spaces, so we can discard const and
* use it in the loop */
if (strchr(source, ' '))
fatal("sjoin: source nick contains spaces!");
t = (char *)source;
}
while (*(nick=t)) {
int32 modes = 0, thismode;
t = nick + strcspn(nick, " ");
if (*t)
*t++ = 0;
if (*nick == '&' || *nick == '"') {
/* Ban (&) or exception (") */
char *av[3];
av[0] = channel;
av[1] = (char *)((*nick=='&') ? "+b" : "+e");
av[2] = nick+1;
do_cmode(source, 3, av);
continue;
}
do {
thismode = cumode_prefix_to_flag(*nick);
modes |= thismode;
} while (thismode && *nick++); /* increment nick only if thismode!=0 */
user = get_user(nick);
if (!user) {
module_log("sjoin: SJOIN to channel %s for non-existent nick %s"
" (%s)", channel, nick, merge_args(ac-1, av));
continue;
}
module_log_debug(1, "%s SJOINs %s", nick, channel);
if ((ctemp = join_channel(user, channel, modes)) != NULL)
c = ctemp;
}
/* Did anyone actually join the channel? */
if (c) {
/* Store channel timestamp in channel structure, unless we set it
* ourselves. */
if (!c->ci)
c->creation_time = strtotime(av[0], NULL);
/* Set channel modes if there are any. Note how argument list is
* conveniently set up for do_cmode(). */
if (ac > 3)
do_cmode(source, ac-2, av+1);
}
}
/*************************************************************************/
/* Clear out all channel modes using an SJOIN (for CLEAR_USERS). */
static int sjoin_clear_users(const char *sender, Channel *chan, int what,
const void *param)
{
if (what & CLEAR_USERS) {
int i;
send_cmd(ServerName, "SJOIN %ld %s + :",
(long)(chan->creation_time - 1), chan->name);
ARRAY_FOREACH (i, chan->excepts)
free(chan->excepts[i]);
chan->excepts_count = 0;
}
return 0;
}
/*************************************************************************/
/*************************************************************************/
/* Callback to set the creation time for a registered channel to the
* channel's registration time. This callback is added before the main
* ChanServ module is loaded, so c->ci will not yet be set.
*/
static typeof(get_channelinfo) *sjoin_p_get_channelinfo = NULL;
static typeof(put_channelinfo) *sjoin_p_put_channelinfo = NULL;
static int sjoin_channel_create(Channel *c, User *u, int32 modes)
{
if (CSSetChannelTime && sjoin_p_get_channelinfo) {
ChannelInfo *ci = sjoin_p_get_channelinfo(c->name);
if (ci) {
c->creation_time = ci->time_registered;
#ifdef UNREAL_HACK
/* NOTE: this is a bit of a kludge, since Unreal's SJOIN
* doesn't let us set just the channel creation time while
* leaving the modes alone. */
send_cmd(ServerName, "SJOIN %ld %s %co %s :",
(long)c->creation_time, c->name,
(modes & CUMODE_o ? '+' : '-'), u->nick);
#else
send_cmd(ServerName, "SJOIN %ld %s + :%s%s",
(long)c->creation_time, c->name,
(modes & CUMODE_o ? "@" : ""), u->nick);
#endif
#ifdef BAHAMUT_HACK
if (modes & CUMODE_o) {
/* Bahamut ignores users in the user list which aren't on
* or behind the server sending the SJOIN, so we need an
* extra MODE to explicitly give ops back to the initial
* joining user. */
send_cmode_cmd(ServerName, c->name, "+o :%s", u->nick);
}
#endif
sjoin_p_put_channelinfo(ci);
} /* if (ci) */
} /* if (CSSetChannelTime) */
return 0;
}
/*************************************************************************/
/* Callback to watch for modules being loaded. */
static int sjoin_load_module(Module *mod, const char *name)
{
if (strcmp(name, "chanserv/main") == 0) {
sjoin_module_chanserv = mod;
sjoin_p_get_channelinfo = get_module_symbol(NULL, "get_channelinfo");
if (!sjoin_p_get_channelinfo)
module_log("sjoin: symbol `get_channelinfo' not found, channel"
" time setting disabled");
sjoin_p_put_channelinfo = get_module_symbol(NULL, "put_channelinfo");
if (!sjoin_p_get_channelinfo)
module_log("sjoin: symbol `put_channelinfo' not found, channel"
" time setting disabled");
}
return 0;
}
/*************************************************************************/
/* Callback to watch for modules being unloaded. */
static int sjoin_unload_module(Module *mod)
{
if (mod == sjoin_module_chanserv) {
sjoin_p_get_channelinfo = NULL;
sjoin_module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
/* Initialization. */
static void exit_sjoin(void);
static int init_sjoin(void)
{
sjoin_module_chanserv = NULL;
sjoin_p_get_channelinfo = NULL;
if (!add_callback(NULL, "load module", sjoin_load_module)
|| !add_callback(NULL, "unload module", sjoin_unload_module)
|| !add_callback(NULL, "channel create", sjoin_channel_create)
|| !add_callback(NULL, "clear channel", sjoin_clear_users)
) {
module_log("sjoin: Unable to add callbacks");
exit_sjoin();
return 0;
}
return 1;
}
/*************************************************************************/
/* Cleanup. */
static void exit_sjoin(void)
{
remove_callback(NULL, "clear channel", sjoin_clear_users);
remove_callback(NULL, "channel create", sjoin_channel_create);
remove_callback(NULL, "unload module", sjoin_unload_module);
remove_callback(NULL, "load module", sjoin_load_module);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

30
modules/protocol/sjoin.h Normal file
View File

@ -0,0 +1,30 @@
/* sjoin.c header file.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef SJOIN_H
#define SJOIN_H
#define do_sjoin RENAME_SYMBOL(do_sjoin)
#define init_sjoin RENAME_SYMBOL(init_sjoin)
#define exit_sjoin RENAME_SYMBOL(exit_sjoin)
#define SJOIN_CONFIG \
{ "CSSetChannelTime", { { CD_SET, 0, &CSSetChannelTime } } }
#endif
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,949 @@
/* solid-ircd protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#define BAHAMUT_HACK /* For SJOIN; see comments in sjoin.c */
#include "banexcept.c"
#include "halfop.c"
#include "invitemask.c"
#include "sjoin.c"
#include "svsnick.c"
/*************************************************************************/
static Module *module_operserv;
static Module *module_chanserv;
static char *NetworkDomain = NULL;
static int32 usermode_secure = 0; /* +z */
static int32 chanmode_secure_only = 0; /* +S */
#define MI_SECURE 0x01000000
#define MI_SECURE_ONLY 0x02000000
/*************************************************************************/
/***************** Local interface to external routines ******************/
/*************************************************************************/
static typeof(is_services_admin) *p_is_services_admin = NULL;
static int local_is_services_admin(User *u)
{
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
static char **p_s_ChanServ = &ServerName;
#define s_ChanServ (*p_s_ChanServ)
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
{'a', {0x00000040}}, /* Services admin */
{'A', {0x00000080}}, /* Server admin */
{'z', {0x00000100,0,0,0,MI_SECURE}},
/* Secure connection */
};
static const struct modedata_init new_chanmodes[] = {
{'R', {0x00000100,0,0,0,MI_REGNICKS_ONLY}},
/* Only identified users can join */
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
{'c', {0x00000400,0,0}}, /* No ANSI colors in channel */
{'O', {0x00000800,0,0,0,MI_OPERS_ONLY}},
/* Only opers can join channel */
{'S', {0x00002000,0,0,0,MI_SECURE_ONLY}},
/* Only secure (+z) users allowed */
{'N', {0x00080000,0,0}}, /* No nick changing in channel */
{'M', {0x02000000,0,0}}, /* Moderated to unregged nicks */
{'j', {0x04000000,1,0}}, /* /join rate limit */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}},
/* Ban exceptions */
{'I', {0x80000000,1,1,0,MI_MULTIPLE}},
/* INVITE hosts */
};
static const struct modedata_init new_chanusermodes[] = {
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++) {
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
if (new_usermodes[i].data.info & MI_SECURE)
usermode_secure |= new_usermodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanmodes); i++) {
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
if (new_chanmodes[i].data.info & MI_SECURE_ONLY)
chanmode_secure_only |= new_chanmodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
char ipbuf[16], *s;
uint32 ip;
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 10) {
module_log_debug(1, "NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
/* Move modes to the end, like do_nick() wants */
s = av[3];
memmove(&av[3], &av[4], sizeof(char *) * (ac-4));
av[9] = s;
/* Convert IP address from 32 bit integer to string format */
ip = strtoul(av[7], &s, 10);
if (*s) {
wallops(NULL,
"\2WARNING\2: invalid IP address `%s' for new nick %s",
av[7], av[0]);
module_log("WARNING: invalid IP address `%s' for new nick %s",
av[7], av[0]);
s = NULL;
} else if (!ip && find_module("operserv/sline")) {
static int warned_no_nickip = 0;
if (!warned_no_nickip) {
wallops(NULL, "\2WARNING\2: missing IP address for new nick %s",
av[0]);
warned_no_nickip = 1;
}
module_log("WARNING: missing IP address for new nick %s", av[0]);
s = strcpy(ipbuf, "0.0.0.0");
} else {
uint8 rawip[4];
rawip[0] = ip>>24;
rawip[1] = ip>>16;
rawip[2] = ip>>8;
rawip[3] = ip;
s = unpack_ip(rawip);
if (!s || strlen(s) > sizeof(ipbuf)-1) {
/* super duper paranoia */
module_log("WARNING: unpack_ip() returned overlong or null"
" string: %s", s ? s : "(null)");
s = NULL;
} else {
strcpy(ipbuf, s); /* safe: we checked length above */
s = ipbuf;
}
}
/* Rearrange parameters for do_nick() (IP address is in `s') */
av[7] = av[6];
av[6] = av[8];
av[8] = s;
do_nick(source, ac, av);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
int i;
for (i = 0; i < ac; i++) {
if (stricmp(av[i], "NOQUIT") == 0)
protocol_features |= PF_NOQUIT;
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac == 3 || ac < 2) {
module_log_debug(1, "SJOIN: expected 2 or >=4 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
/* SVSMODE message handler. */
static void m_svsmode(char *source, int ac, char **av)
{
if (*av[0] == '#') {
if (ac >= 3 && strcmp(av[1],"-b") == 0) {
Channel *c = get_channel(av[0]);
User *u = get_user(av[2]);
if (c && u)
clear_channel(c, CLEAR_BANS, u);
} else {
module_log("Invalid SVSMODE from %s for channel %s: %s",
source, av[0], merge_args(ac-1,av+1));
}
} else if (*av[0] == '&') {
module_log("SVSMODE from %s for invalid target (channel %s): %s",
source, av[0], merge_args(ac-1,av+1));
} else {
if (ac < 2)
return;
if (ac >= 3 && (*av[2] == '+' || *av[2] == '-')) {
/* Move the timestamp to the end */
char *ts = av[1];
memmove(av+1, av+2, sizeof(char *) * (ac-2));
av[ac-1] = ts;
}
do_umode(source, ac, av);
}
}
/*************************************************************************/
/* SGLINE/SQLINE message handlers. These remove any SGLINE/SQLINE received
* which does not exist in Services' databases, to avoid servers which were
* split during an UNSGLINE/UNSQLINE re-propogating the mask across the
* network. See m_tkl() in unreal.c for details.
*/
static void do_sgqline(char *source, int ac, char **av, int type)
{
typeof(get_maskdata) *p_get_maskdata = NULL;
typeof(put_maskdata) *p_put_maskdata = NULL;
char *mask;
if (ac != 2)
return;
if (type == MD_SGLINE) {
int masklen = strtol(av[0], NULL, 10);
mask = av[1];
if (masklen <= 0)
return;
mask[masklen] = 0;
} else { /* MD_SQLINE */
mask = av[0];
}
if (!(p_get_maskdata = get_module_symbol(NULL, "get_maskdata")))
return;
if (!(p_put_maskdata = get_module_symbol(NULL, "put_maskdata")))
return;
if ((*p_put_maskdata)((*p_get_maskdata)(type, mask)))
return;
send_cmd(ServerName, "UNS%cLINE :%s", type==MD_SGLINE ? 'G' : 'Q', mask);
}
static void m_sgline(char *source, int ac, char **av) {
do_sgqline(source, ac, av, MD_SGLINE);
}
static void m_sqline(char *source, int ac, char **av) {
do_sgqline(source, ac, av, MD_SQLINE);
}
/*************************************************************************/
/* Abbreviated message handlers. See bahamut.c for why we don't allow
* SERVICESHUB. */
static void no_serviceshub(char *source, int ac, char **av)
{
fatal("IRC Services will not function correctly when solid-ircd is"
" configured as a SERVICESHUB. Please reconfigure solid-ircd as"
" a regular hub and restart Services.");
}
#define m_ns no_serviceshub
#define m_cs no_serviceshub
#define m_ms no_serviceshub
#define m_rs no_serviceshub
#define m_os no_serviceshub
#define m_ss no_serviceshub
#define m_hs no_serviceshub
/*************************************************************************/
/* SQUIT handler, for remote SQUITs. This has to be implemented as a
* "receive message" callback because we can't layer message functions on
* top of each other. */
static void do_bahamut_squit(char *source, int ac, char **av)
{
Server *server;
if (ac < 1) /* also checked in do_receive_message, but for completeness */
return;
server = get_server(av[0]);
if (!server) {
send_cmd(ServerName, "402 %s %s :No such server", source, av[0]);
} else if (!server->fake || server == get_server(ServerName)) {
/* We should never see an SQUIT for ourselves, since the remote
* server will disconnect us instead, but just in case... */
send_cmd(ServerName, "402 %s %s :Not a juped server", source, av[0]);
} else {
do_squit(source, ac, av);
send_cmd(NULL, "SQUIT %s :%s", av[0], av[1] ? av[1] : "");
}
}
static int do_receive_message(char *source, char *cmd, int ac, char **av)
{
if (irc_stricmp(cmd,"SQUIT") != 0)
return 0;
if (!source || !*source || ac < 1 || irc_stricmp(source,av[0]) == 0)
return 0;
do_bahamut_squit(source, ac, av);
return 1;
}
/*************************************************************************/
static Message bahamut_messages[] = {
{ "AKILL", NULL },
{ "CAPAB", m_capab },
{ "CS", m_cs },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "HS", m_hs },
{ "LOCKLUSERS",NULL },
{ "MS", m_ms },
{ "NICK", m_nick },
{ "NS", m_ns },
{ "OS", m_os },
{ "RAKILL", NULL },
{ "RS", m_rs },
{ "SGLINE", m_sgline },
{ "SILENCE", NULL },
{ "SJOIN", m_sjoin },
{ "SQLINE", m_sqline },
{ "SS", m_ss },
{ "SVINFO", NULL },
{ "SVSMODE", m_svsmode },
{ "UNSGLINE", NULL },
{ "UNSQLINE", NULL },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <server> <svsid> <ip>
* :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s 0 0 :%s", nick,
(long)time(NULL), modes, user, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS %s :TS", RemotePassword);
send_cmd(NULL, "CAPAB TS3 SSJOIN NICKIP NOQUIT TSMODE UNCONNECT");
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
send_cmd(NULL, "SVINFO 3 3 0 :%ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_user_servicestamp_change(User *user)
{
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick, user->servicestamp);
return 0;
}
/*************************************************************************/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'd':
module_log("MODE tried to change services stamp for %s", user->nick);
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick,
user->servicestamp);
return 0;
case 'o':
if (add) {
user->mode |= UMODE_o;
if (add && user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
} /* switch (mode) */
return 0;
}
/*************************************************************************/
static int do_channel_mode(const char *source, Channel *channel,
int modechar, int add, char **av)
{
int32 flag = mode_char_to_flag(modechar, MODE_CHANNEL);
switch (modechar) {
case 'j':
if (add) {
int ok = 0;
char *s;
int joinrate1 = strtol(av[0], &s, 0);
if (*s == ':') {
int joinrate2 = strtol(s+1, &s, 0);
if (!*s) {
if (joinrate1 && joinrate2) {
channel->mode |= flag;
channel->joinrate1 = joinrate1;
channel->joinrate2 = joinrate2;
} else {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
ok = 1;
}
ok = 1;
}
} else if (joinrate1 == 0) {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
ok = 1;
}
if (!ok) {
module_log("warning: invalid MODE +j %s for %s", av[0],
channel->name);
}
} else {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
}
return 1;
} /* switch (modechar) */
return 0;
}
/*************************************************************************/
static int do_check_kick(User *user, const char *chan, ChannelInfo *ci,
char **mask_ret, const char **reason_ret)
{
Channel *c = get_channel(chan);
if ((((c?c->mode:0) | (ci?ci->mlock.on:0)) & chanmode_secure_only)
&& !(user->mode & usermode_secure)
) {
*mask_ret = create_mask(user, 1);
*reason_ret = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
return 1;
}
return 0;
}
/*************************************************************************/
static int do_nick_identified(User *u, int old_status)
{
if (is_oper(u) && is_services_admin(u))
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
return 0;
}
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_check_modes(Channel *c, ChannelInfo *ci, int add, int32 flag)
{
if (add) {
switch (mode_flag_to_char(flag, MODE_CHANNEL)) {
case 'j':
if (sgn(ci->mlock.joinrate1) != sgn(ci->mlock.joinrate2)) {
module_log("warning: removing +j from channel %s mode lock"
" (invalid parameter: %d:%d)", ci->name,
ci->mlock.joinrate1, ci->mlock.joinrate2);
ci->mlock.on &= ~mode_char_to_flag('j', MODE_CHANNEL);
ci->mlock.joinrate1 = ci->mlock.joinrate2 = 0;
} else if (ci->mlock.joinrate1 < 0) {
if (c->joinrate1 || c->joinrate2)
set_cmode(s_ChanServ, c, "-j");
} else {
if (c->joinrate1 != ci->mlock.joinrate1
|| c->joinrate2 != ci->mlock.joinrate2
) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "%d:%d",
ci->mlock.joinrate1, ci->mlock.joinrate2);
set_cmode(s_ChanServ, c, "+j", buf);
}
}
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_set_mlock(User *u, ChannelInfo *ci, int mode, int add, char **av)
{
if (!mode) {
/* Final check of new mode lock--nothing to do */
return 0;
}
/* Single mode set/clear */
if (add) {
switch (mode) {
case 'j': {
int ok = 0;
char *s;
ci->mlock.joinrate1 = strtol(av[0], &s, 0);
if (ci->mlock.joinrate1 > 0 && *s == ':') {
ci->mlock.joinrate2 = strtol(s+1, &s, 0);
if (ci->mlock.joinrate2 > 0 && !*s)
ok = 1;
}
if (!ok) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode);
return 1;
}
break;
} /* case 'j' */
} /* switch (mode) */
} else { /* !add -> lock off */
switch (mode) {
case 'j':
ci->mlock.joinrate1 = ci->mlock.joinrate2 = -1;
break;
} /* switch (mode) */
} /* if (add) */
return 0;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
time_t now = time(NULL);
/* Bahamut 1.8.0 has a "feature" which converts an expiration delay of
* zero (supposedly "unlimited") into a delay of one week. Therefore,
* we have a "feature" which sends an unlimited expiration time as
* many years in the future. */
time_t length = ((expires && expires > now) ? expires - now : 0);
if (length == 0 && now < 0x7FFFFFFF)
length = 0x7FFFFFFF - now;
send_cmd(ServerName, "AKILL %s %s %ld %s %ld :%s", host, username,
(long)length, who ? who : "<unknown>", (long)now, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "RAKILL %s %s", host, username);
return 1;
}
/*************************************************************************/
static int do_send_sgline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SGLINE %d :%s:%s", (int)strlen(mask), mask, reason);
return 1;
}
static int do_send_sqline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SQLINE %s :%s", mask, reason);
return 1;
}
static int do_send_szline(const char *mask, time_t expires, const char *who,
const char *reason)
{
return do_send_akill("*", mask, expires, who, reason);
}
/*************************************************************************/
static int do_cancel_sgline(const char *mask)
{
send_cmd(ServerName, "UNSGLINE :%s", mask);
return 1;
}
static int do_cancel_sqline(const char *mask)
{
send_cmd(ServerName, "UNSQLINE %s", mask);
return 1;
}
static int do_cancel_szline(const char *mask)
{
return do_cancel_akill("*", mask);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
} else if (strcmp(modname, "operserv/sline") == 0) {
if (!add_callback(mod, "send_sgline", do_send_sgline))
module_log("Unable to add send_sgline callback");
if (!add_callback(mod, "send_sqline", do_send_sqline))
module_log("Unable to add send_sqline callback");
if (!add_callback(mod, "send_szline", do_send_szline))
module_log("Unable to add send_szline callback");
if (!add_callback(mod, "cancel_sgline", do_cancel_sgline))
module_log("Unable to add cancel_sgline callback");
if (!add_callback(mod, "cancel_sqline", do_cancel_sqline))
module_log("Unable to add cancel_sqline callback");
if (!add_callback(mod, "cancel_szline", do_cancel_szline))
module_log("Unable to add cancel_szline callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
} else if (strcmp(modname, "chanserv/main") == 0) {
module_chanserv = mod;
p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (!p_s_ChanServ)
p_s_ChanServ = &ServerName;
if (!add_callback(mod, "check_modes", do_check_modes))
module_log("Unable to add ChanServ check_modes callback");
if (!add_callback(mod, "check_kick", do_check_kick))
module_log("Unable to add ChanServ check_kick callback");
if (!add_callback(mod, "SET MLOCK", do_set_mlock))
module_log("Unable to add ChanServ SET MLOCK callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
} else if (mod == module_chanserv) {
module_chanserv = NULL;
p_s_ChanServ = &ServerName;
}
return 0;
}
/*************************************************************************/
int init_module(void)
{
unsigned char c;
protocol_name = "Bahamut";
protocol_version = "1.8.0+";
protocol_features = PF_SZLINE | PF_MODETS_FIRST;
protocol_nickmax = 30;
if (!register_messages(bahamut_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "receive message", do_receive_message)
|| !add_callback(NULL, "user servicestamp change",
do_user_servicestamp_change)
|| !add_callback(NULL, "channel MODE", do_channel_mode)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_banexcept()
|| !init_invitemask()
|| !init_sjoin()
|| !init_svsnick("SVSNICK")
) {
exit_module(1);
return 0;
}
init_modes();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table[160] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 0;
mapstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_svsnick();
exit_sjoin();
exit_invitemask();
exit_banexcept();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "channel MODE", do_channel_mode);
remove_callback(NULL, "user servicestamp change",
do_user_servicestamp_change);
remove_callback(NULL, "receive message", do_receive_message);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(bahamut_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,59 @@
/* Code for servers supporting the ircd.dal SVSNICK command.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "svsnick.h"
/* Command name for SVSNICK */
static const char *cmd_SVSNICK = "SVSNICK";
/*************************************************************************/
/*************************************************************************/
/* Send a command to change the nickname of a user on another server. */
static void svsnick_send_nickchange_remote(const char *nick,
const char *newnick)
{
send_cmd(ServerName, "%s %s %s :%ld", cmd_SVSNICK, nick, newnick,
(long)time(NULL));
}
/*************************************************************************/
/*************************************************************************/
int init_svsnick(const char *cmdname)
{
cmd_SVSNICK = cmdname;
send_nickchange_remote = svsnick_send_nickchange_remote;
protocol_features |= PF_CHANGENICK;
return 1;
}
/*************************************************************************/
void exit_svsnick(void)
{
protocol_features &= ~PF_CHANGENICK;
send_nickchange_remote = NULL;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

View File

@ -0,0 +1,28 @@
/* SVSNICK support external declarations.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef SVSNICK_H
#define SVSNICK_H
#define init_svsnick RENAME_SYMBOL(init_svsnick)
#define exit_svsnick RENAME_SYMBOL(exit_svsnick)
extern int init_svsnick(const char *cmdname);
extern void exit_svsnick(void);
#endif /* SVSNICK_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

99
modules/protocol/token.c Normal file
View File

@ -0,0 +1,99 @@
/* Routines for processing message tokens.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "messages.h"
#include "token.h"
/*************************************************************************/
/* Array of all possible 1- and 2-character tokens. */
static MessageFunc tokentable[65536];
/* Macro to get the token array entry for a given token. May be used as
* an lvalue. */
#define TOKEN_ENTRY(token) \
tokentable[(uint8)((token)[0])<<8 | (uint8)((token)[1])]
/* Dummy value to indicate a recognized-but-has-no-handler message. */
#define MSGFUNC_NONE ((MessageFunc)-1)
/*************************************************************************/
/*************************************************************************/
/* Callback to check for and handle tokens. */
static int token_receive_message(char *source, char *cmd, int ac, char **av)
{
MessageFunc func;
if ((cmd[1] && cmd[2]) || !(func = TOKEN_ENTRY(cmd)))
return 0;
if (func != MSGFUNC_NONE)
func(source, ac, av);
return 1;
}
/*************************************************************************/
/* Set up token handling. */
int init_token(TokenInfo *tokens)
{
int32 i;
/* Clear out token array, then copy token handler functions into array.
* ANSI says NULL might not be binary 0, and this section isn't
* particularly speed-critical, so we don't use memset(). */
for (i = 0; i < 65536; i++)
tokentable[i] = NULL;
for (i = 0; tokens[i].token; i++) {
if (strlen(tokens[i].token) > 2) {
module_log("warning: init_token(): token name `%s' too long"
" (maximum 2 characters)", tokens[i].token);
} else if (tokens[i].message) {
Message *m = find_message(tokens[i].message);
if (m) {
TOKEN_ENTRY(tokens[i].token) =
m->func ? m->func : MSGFUNC_NONE;
}
}
}
/* Add receive-message callback. */
if (!add_callback(NULL, "receive message", token_receive_message)) {
module_log("Unable to add callback");
return 0;
}
return 1;
}
/*************************************************************************/
/* Clean up on module unload. */
void exit_token(void)
{
remove_callback(NULL, "receive message", token_receive_message);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

33
modules/protocol/token.h Normal file
View File

@ -0,0 +1,33 @@
/* Declarations for token handling.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#ifndef TOKEN_H
#define TOKEN_H
/* Information about a token (what message it corresponds to): */
typedef struct {
const char *token, *message;
} TokenInfo;
#define init_token RENAME_SYMBOL(init_token)
#define exit_token RENAME_SYMBOL(exit_token)
extern int init_token(TokenInfo *tokens);
extern void exit_token(void);
#endif /* TOKEN_H */
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/

1299
modules/protocol/trircd.c Normal file

File diff suppressed because it is too large Load Diff

260
modules/protocol/ts8.c Normal file
View File

@ -0,0 +1,260 @@
/* RFC1459+TS8 protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "messages.h"
/*************************************************************************/
static char *NetworkDomain = NULL;
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
static void m_nick(char *source, int ac, char **av)
{
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
module_log_debug(1, "NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. We get the information we want from the USER command, so
* we don't do anything else here. */
}
/*************************************************************************/
static void m_user(char *source, int ac, char **av)
{
char *new_av[7];
if (ac != 5)
return;
new_av[0] = source; /* Nickname */
new_av[1] = (char *)"0"; /* # of hops (was in NICK command... we lose) */
new_av[2] = av[0]; /* Timestamp */
new_av[3] = av[1]; /* Username */
new_av[4] = av[2]; /* Hostname */
new_av[5] = av[3]; /* Server */
new_av[6] = av[4]; /* Real name */
do_nick("", 7, new_av);
}
/*************************************************************************/
static Message ts8_messages[] = {
{ "NICK", m_nick },
{ "USER", m_user },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
send_cmd(NULL, "NICK %s :1", nick);
send_cmd(nick, "USER %ld %s %s %s :%s", (long)time(NULL),
user, host, server, name);
if (modes)
send_cmd(nick, "MODE %s +%s", nick, modes);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS :%s", RemotePassword);
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS. */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "WALLOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (c->topic_time && t >= c->topic_time)
t = c->topic_time - 1; /* Force topic change */
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
{ NULL }
};
/*************************************************************************/
int init_module(void)
{
protocol_name = "RFC1459+TS8";
protocol_version = "ircd2.8.x";
protocol_features = 0;
protocol_nickmax = 9;
if (!register_messages(ts8_messages)) {
module_log("Unable to register messages");
exit_module(1);
return 0;
}
if (!add_callback(NULL, "set topic", do_set_topic)) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
pseudoclient_oper = 1;
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
remove_callback(NULL, "set topic", do_set_topic);
unregister_messages(ts8_messages);
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/