summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile107
-rw-r--r--src/actions.c95
-rwxr-xr-xsrc/bin/am420
-rwxr-xr-xsrc/bin/anoperc136
-rwxr-xr-xsrc/bin/cp-recursive21
-rwxr-xr-xsrc/bin/langtool239
-rwxr-xr-xsrc/bin/mydbgen207
-rwxr-xr-xsrc/bin/register101
-rw-r--r--src/botserv.c2593
-rw-r--r--src/channels.c1633
-rw-r--r--src/chanserv.c6257
-rw-r--r--src/commands.c178
-rw-r--r--src/compat.c212
-rw-r--r--src/config.c1333
-rw-r--r--src/converter.c398
-rw-r--r--src/datafiles.c510
-rw-r--r--src/encrypt.c434
-rw-r--r--src/helpserv.c83
-rw-r--r--src/hostserv.c1107
-rw-r--r--src/init.c849
-rw-r--r--src/language.c265
-rw-r--r--src/list.c178
-rw-r--r--src/log.c298
-rw-r--r--src/mail.c246
-rw-r--r--src/main.c537
-rw-r--r--src/memory.c96
-rw-r--r--src/memoserv.c1382
-rw-r--r--src/messages.c1284
-rw-r--r--src/misc.c711
-rw-r--r--src/modules.c2023
-rw-r--r--src/modules/Makefile32
-rw-r--r--src/modules/README1
-rwxr-xr-xsrc/modules/compile.sh37
-rwxr-xr-xsrc/modules/configure21
-rw-r--r--src/modules/hs_moo.c104
-rw-r--r--src/modules/ircd_catserv.c128
-rw-r--r--src/modules/module.h9
-rw-r--r--src/mysql.c1638
-rw-r--r--src/news.c541
-rw-r--r--src/nickserv.c4169
-rw-r--r--src/operserv.c5064
-rw-r--r--src/process.c268
-rw-r--r--src/protocol.c168
-rw-r--r--src/proxy.c797
-rw-r--r--src/rdb.c466
-rw-r--r--src/send.c240
-rw-r--r--src/servers.c259
-rw-r--r--src/sessions.c834
-rw-r--r--src/slist.c325
-rw-r--r--src/sockutil.c549
-rw-r--r--src/timeout.c130
-rw-r--r--src/users.c1161
-rw-r--r--src/vsnprintf.c518
53 files changed, 41392 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 000000000..6b77b5bcf
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,107 @@
+MYSQL_OBJ = $(MYSQL:.c=.o)
+RDB_OBJ = $(RDB:.c=.o)
+OBJS = actions.o botserv.o channels.o chanserv.o commands.o compat.o converter.o \
+ config.o datafiles.o encrypt.o helpserv.o hostserv.o init.o language.o list.o log.o mail.o main.o \
+ memory.o memoserv.o messages.o misc.o modules.o news.o nickserv.o operserv.o \
+ process.o protocol.o proxy.o send.o servers.o sessions.o slist.o sockutil.o \
+ timeout.o users.o \
+ $(VSNPRINTF_O) $(RDB_OBJ) $(MYSQL_OBJ)
+SRCS = actions.c botserv.c channels.c chanserv.c commands.c compat.c converter.c \
+ config.c datafiles.c encrypt.c helpserv.c hostserv.c init.c language.c list.c log.c mail.c main.c \
+ memory.c memoserv.c messages.c misc.c modules.c news.c nickserv.c operserv.c \
+ process.c protocol.c proxy.c send.c servers.c sessions.c slist.c sockutil.c \
+ timeout.c users.c \
+ $(VSNPRINTF_C) $(RDB) $(MYSQL)
+
+INCLUDES = ../include/commands.h ../include/defs.h ../include/language.h \
+ ../include/pseudo.h ../include/sysconf.h ../include/config.h \
+ ../include/encrypt.h ../include/messages.h ../include/services.h \
+ ../include/timeout.h ../include/datafiles.h ../include/extern.h \
+ ../include/modules.h ../include/slist.h ../include/version.h
+
+MAKEARGS = 'CFLAGS=${CFLAGS}' 'CC=${CC}' 'ANOPELIBS=${ANOPELIBS}' \
+ 'LDFLAGS=${LDFLAGS}' 'BINDEST=${BINDEST}' 'INSTALL=${INSTALL}' \
+ 'INCLUDEDIR=${INCLUDEDIR}' 'RM=${RM}' 'CP=${CP}' \
+ 'TOUCH=${TOUCH}' 'SHELL=${SHELL}' 'DATDEST=${DATDEST}' \
+ 'RUNGROUP=${RUNGROUP}' 'MODULE_PATH=${MODULE_PATH}' 'MYSQL=${MYSQL}'\
+ 'RDB=${RDB}'
+
+.c.o:
+ $(CC) $(CFLAGS) -I../include/ -c $<
+
+all: services
+
+services: $(OBJS)
+ $(CC) $(CFLAGS) $(OBJS) $(ANOPELIBS) $(MLIBS) -o $@ $(ELIBS)
+
+$(OBJS): Makefile
+actions.o: actions.c $(INCLUDES)
+botserv.o: botserv.c $(INCLUDES)
+channels.o: channels.c $(INCLUDES)
+chanserv.o: chanserv.c $(INCLUDES)
+commands.o: commands.c $(INCLUDES)
+compat.o: compat.c $(INCLUDES)
+config.o: config.c $(INCLUDES)
+converter.o: converter.c $(INCLUDES)
+datafiles.o: datafiles.c $(INCLUDES)
+encrypt.o: encrypt.c $(INCLUDES)
+init.o: init.c $(INCLUDES)
+hostserv.o: hostserv.c $(INCLUDES)
+language.o: language.c $(INCLUDES)
+list.o: list.c $(INCLUDES)
+log.o: log.c $(INCLUDES)
+mail.o: mail.c $(INCLUDES)
+main.o: main.c $(INCLUDES)
+memory.o: memory.c $(INCLUDES)
+memoserv.o: memoserv.c $(INCLUDES)
+messages.o: messages.c $(INCLUDES)
+misc.o: misc.c $(INCLUDES)
+news.o: news.c $(INCLUDES)
+nickserv.o: nickserv.c $(INCLUDES)
+operserv.o: operserv.c $(INCLUDES)
+process.o: process.c $(INCLUDES)
+protocol.o: protocol.c $(INCLUDES)
+proxy.o: proxy.c $(INCLUDES)
+send.o: send.c $(INCLUDES)
+sessions.o: sessions.c $(INCLUDES)
+slist.o: slist.c $(INCLUDES)
+sockutil.o: sockutil.c $(INCLUDES)
+timeout.o: timeout.c $(INCLUDES)
+users.o: users.c $(INCLUDES)
+vsnprintf.o: vsnprintf.c $(INCLUDES)
+mysql.o: mysql.c $(INCLUDES)
+rdb.o: rdb.c $(INCLUDES)
+
+modules: DUMMY
+ (cd modules ; ./configure ; ${MAKE} ${MAKEARGS} all)
+
+clean:
+ (cd modules ; ${MAKE} ${MAKEARGS} clean)
+ rm -f *.o services a.out
+
+install: services
+ test -d ${BINDEST} || mkdir ${BINDEST}
+ $(INSTALL) services $(BINDEST)/services
+ $(INSTALL) bin/anoperc $(BINDEST)/anoperc
+ rm -f $(BINDEST)/listnicks $(BINDEST)/listchans
+ ln $(BINDEST)/services $(BINDEST)/listnicks
+ ln $(BINDEST)/services $(BINDEST)/listchans
+ (cd ../lang ; $(MAKE) install)
+ $(CP) ../data/* $(DATDEST)
+ test -d $(DATDEST)/backups || mkdir $(DATDEST)/backups
+ test -d $(DATDEST)/logs || mkdir $(DATDEST)/logs
+ @if [ "$(MODULE_PATH)" ] ; then \
+ test -d ${MODULE_PATH} || mkdir ${MODULE_PATH} ; \
+ test -d ${MODULE_PATH}/runtime || mkdir ${MODULE_PATH}/runtime ; \
+ (cd modules ; $(MAKE) install) ; \
+ fi
+ @if [ "$(RUNGROUP)" ] ; then \
+ echo chgrp -R $(RUNGROUP) $(DATDEST) ; \
+ chgrp -R $(RUNGROUP) $(DATDEST) ; \
+ echo chmod -R g+rw $(DATDEST) ; \
+ chmod -R g+rw $(DATDEST) ; \
+ echo find $(DATDEST) -type d -exec chmod g+xs \'\{\}\' \\\; ; \
+ find $(DATDEST) -type d -exec chmod g+xs '{}' \; ; \
+ fi
+
+DUMMY:
diff --git a/src/actions.c b/src/actions.c
new file mode 100644
index 000000000..cb7fd0851
--- /dev/null
+++ b/src/actions.c
@@ -0,0 +1,95 @@
+/* Various routines to perform simple actions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+
+/* Note a bad password attempt for the given user. If they've used up
+ * their limit, toss them off.
+ */
+
+void bad_password(User * u)
+{
+ time_t now = time(NULL);
+
+ if (!BadPassLimit)
+ return;
+
+ if (BadPassTimeout > 0 && u->invalid_pw_time > 0
+ && u->invalid_pw_time < now - BadPassTimeout)
+ u->invalid_pw_count = 0;
+ u->invalid_pw_count++;
+ u->invalid_pw_time = now;
+ if (u->invalid_pw_count >= BadPassLimit)
+ kill_user(NULL, u->nick, "Too many invalid passwords");
+}
+
+/*************************************************************************/
+
+void change_user_mode(User * u, char *modes, char *arg)
+{
+#ifndef IRC_HYBRID
+ int ac = 1;
+ char *av[2];
+
+ av[0] = modes;
+ if (arg) {
+ av[1] = arg;
+ ac++;
+ }
+#ifdef IRC_BAHAMUT
+ send_cmd(ServerName, "SVSMODE %s %ld %s%s%s", u->nick, u->timestamp,
+ av[0], (ac == 2 ? " " : ""), (ac == 2 ? av[1] : ""));
+#else
+ send_cmd(ServerName, "SVSMODE %s %s%s%s", u->nick, av[0],
+ (ac == 2 ? " " : ""), (ac == 2 ? av[1] : ""));
+#endif
+ set_umode(u, ac, av);
+#endif
+}
+
+/*************************************************************************/
+
+/* Remove a user from the IRC network. `source' is the nick which should
+ * generate the kill, or NULL for a server-generated kill.
+ */
+
+void kill_user(const char *source, const char *user, const char *reason)
+{
+#ifdef IRC_BAHAMUT
+ /* Bahamut uses SVSKILL as a better way to kill users. It sends back
+ * a QUIT message that Anope uses to clean up after the kill is done.
+ */
+ send_cmd(NULL, "SVSKILL %s :%s", user, reason);
+#else
+ char *av[2];
+ char buf[BUFSIZE];
+
+ if (!user || !*user)
+ return;
+ if (!source || !*source)
+ source = ServerName;
+ if (!reason)
+ reason = "";
+ snprintf(buf, sizeof(buf), "%s (%s)", source, reason);
+ av[0] = sstrdup(user);
+ av[1] = buf;
+ send_cmd(source, "KILL %s :%s", user, av[1]);
+ do_kill(source, 2, av);
+ free(av[0]);
+#endif
+}
+
+/*************************************************************************/
diff --git a/src/bin/am b/src/bin/am
new file mode 100755
index 000000000..14b6d8638
--- /dev/null
+++ b/src/bin/am
@@ -0,0 +1,420 @@
+#!/usr/bin/env perl
+
+# ====================================================================
+# anomgr: Anope Manager. Used to manage anope revision on SubVersion.
+#
+# For usage, see the usage subroutine or run the script with no
+# command line arguments.
+#
+# $Id$
+#
+# ====================================================================
+require 5.6.0;
+use strict;
+use Getopt::Std;
+use Net::FTP;
+use Cwd;
+
+######################################################################
+# Configuration section.
+my $myver="1.0";
+my $svnrev="http://www.zero.org/anosvn.php";
+my $fhint="version.log";
+
+# Default values, change or use environment variables instead.
+my $copy="anope";
+my $svnuser="";
+my $svnpath="/usr/bin";
+my $svnroot="svn://zero.org/repos/$copy";
+my $editor="/usr/bin/vi";
+
+# Environment variables SVNBINDIR and SVNROOT override the above
+# hardcoded values.
+$svnuser="$ENV{SVNUSER}" if ($ENV{SVNUSER});
+$svnpath="$ENV{SVNBINDIR}" if ($ENV{SVNBINDIR});
+$svnroot="$ENV{SVNROOT}" if ($ENV{SVNROOT});
+$editor="$ENV{EDITOR}" if ($ENV{EDITOR});
+
+# Svnlook path.
+my $svnlook = "$svnpath/svnlook";
+
+# Svn path.
+my $svn = "$svnpath/svn";
+
+# wget path. Need to change to a perl module instead...
+my $wget = "$svnpath/wget";
+
+my (
+ $rev,
+ $branch,
+ $tag,
+ $ftp,
+ $dst,
+ $ver_major,
+ $ver_minor,
+ $ver_patch,
+ $ver_build,
+ $ver_revision,
+ $ver_comment,
+ $svn_comment,
+ $cver,
+ $nver,
+ $ctrlfile,
+ $tmpfile,
+ @source,
+ %opt
+);
+
+{
+ my $ok = 1;
+ foreach my $program ($svnlook, $svn, $editor)
+ {
+ if (-e $program)
+ {
+ unless (-x $program)
+ {
+ warn "$0: required program `$program' is not executable, ",
+ "edit $0.\n";
+ $ok = 0;
+ }
+ }
+ else
+ {
+ warn "$0: required program `$program' does not exist, edit $0.\n";
+ $ok = 0;
+ }
+ }
+ exit 1 unless $ok;
+}
+
+sub usage()
+{
+ # More features to add:
+ # --diff N:M to produce a diff between revisions
+ # --bugs CLI method to add bug number to the commit message
+ # --mesg CLI methos to add the commit message
+ # --create-branch to create a branch
+ # --create-tag to create a tag
+ # --switch to switch between branches/tags
+ print "Usage: $0 <-g | -p | -f> [-r revision | -b branch | -t tag] <destination>\n";
+ print " Operations:\n";
+ print " -g Get Operation\n";
+ print " -p Put Operation\n";
+ print " -f[tar|diff] FTP Operation, retrieve latest tar or diff\n";
+ print " Selector:\n";
+ print " -r revision Retrieve by revision number\n";
+ print " -b branch Retrieve by branch name\n";
+ print " -t tag Retrieve by tag name\n";
+ print " Destination:\n";
+ print " The working copy to perform the operation in or to. The script will \n";
+ print " try to guess where that is, unless you provide a specific path.\n";
+ exit;
+}
+
+sub banner() {
+
+ print "Anope Source Managemnt Utility - Version $myver\n\n";
+
+}
+
+sub getans {
+ my $ans;
+ while (! (($ans =~ /y/) || ($ans =~ /n/))) {
+ print "*** Ready to continue? (y/n): ";
+ $ans = <STDIN>;
+ chomp($ans);
+ $ans = lc($ans);
+ # $ans = &getans();
+ }
+
+ # return $ans;
+ return ($ans eq "y") ? 1 : 0
+}
+
+sub find_conflict() {
+
+ my $filename=shift;
+ my $retval=0;
+ open (IN2, "$filename") || die "Can't open $filename\n";
+ while (<IN2>) {
+ if (/^<<<<<<</) {
+ $retval=1;
+ }
+ }
+ close(IN2);
+
+ return $retval;
+}
+
+sub do_ftp() {
+
+ my $ftpc;
+ $ftpc = Net::FTP->new("ftp.zero.org");
+ $ftpc->login("ftp","-anonymou@");
+ $ftpc->cwd("/incoming");
+
+ if ( lc($ftp) eq "tar" ) {
+ print "Retrieving latest tar ball...\n";
+ $ftpc->get("anope.tgz");
+ } elsif ( lc($ftp) eq "diff" ) {
+ print "Retrieving latest patch file...\n";
+ $ftpc->get("anope.diff");
+ } else {
+ print "Unknown type $ftp, aborting...\n";
+ }
+ $ftpc->quit();
+}
+
+sub do_get() {
+
+ my $options = "" ; # Options to be passed to the svn command
+ my $selector = "" ; # Selector to be passed to the svn command
+
+ if ($rev) {
+ $options .= "-r $rev";
+ $selector = "trunk";
+ $copy = $copy . "-$rev";
+ } elsif ($tag) {
+ $selector = "tags/$tag";
+ $copy = $copy . "-$tag";
+ } elsif ($branch) {
+ $selector = "branches/$branch";
+ $copy = $copy . "-$branch";
+ } else {
+ $selector = "trunk";
+ }
+
+ if ($dst eq undef) {
+ my $cwd = &Cwd::cwd();
+ if (-f "$cwd/$fhint") {
+ system("$svn update $options $cwd");
+ } elsif (-f "$cwd/$copy/$fhint") {
+ system("$svn update $options $cwd/$copy");
+ } else {
+ system("$svn checkout $svnroot/$selector $options $cwd/$copy");
+ }
+ } else {
+ $dst = &Cwd::cwd() if ($dst eq "\.") ;
+ if (-f "$dst/$fhint") {
+ system("$svn update $options $dst");
+ } else {
+ system("$svn checkout $svnroot/$selector $options $dst");
+ }
+ }
+}
+
+sub do_put() {
+
+ if ($dst eq undef) {
+ my $cwd = &Cwd::cwd();
+ if (-f "$cwd/$fhint") {
+ $dst = "$cwd";
+ } elsif (-f "$cwd/$copy/$fhint") {
+ $dst .= "$cwd/$copy";
+ } else {
+ print "Error: Unable to determine your working copy location.\n";
+ exit;
+ }
+ } else {
+ $dst = &Cwd::cwd() if ($dst eq "\.") ;
+ if (! -f "$dst/$fhint") {
+ print "Error: Unable to determine your working copy location.\n";
+ exit;
+ }
+ }
+
+ # Check to see if we need to update our working copy first.
+ my $nupdate;
+ open (IN, "$svn status --show-updates --verbose $dst|");
+ while (<IN>) {
+ if (/\*/) {
+ $nupdate .= "$_";
+ }
+ }
+ close(IN);
+
+ if ($nupdate ne undef) {
+ print "*** Warning: There are files modified in the repository that need\n";
+ print "*** to be merged back to your working copy before the commit can\n";
+ print "*** take place. These files are:\n";
+ print $nupdate;
+ print "Please use: $0 -g $dst\n";
+ exit;
+ }
+
+ # Get a prelim diff of the changes...
+ my $dcount=0;
+ my $conflict;
+ # open (IN, "$svn diff $dst|");
+ open (IN, "$svn status $dst|");
+ while (<IN>) {
+ if (!/^\?/) {
+ $dcount++;
+ }
+
+ if (/^C/) {
+ $_ =~ s/^C\s+//;
+ chomp($_);
+ # I don't want to use grep. But my find_conflict sub
+ # does not seem to work :( Too bad
+ if (`grep "^<<<<<<<" $_`) {
+ $conflict .= "$_\n" ;
+ } else {
+ system("$svn resolved $_");
+ }
+ }
+ }
+ close(IN);
+
+ if ($dcount == 0) {
+ print "*** Warning: There are no modified files to be commited. Are you\n";
+ print "*** sure you are in the right working copy? Verify changes with:\n";
+ print "*** $svn diff $dst\n";
+ exit;
+ }
+
+ if ($conflict ne undef) {
+ print "*** Warning: There are merge conflicts to be resolved! Please take\n";
+ print "*** a look at the following files and resolve them manually:\n\n";
+ print "$conflict\n";
+ exit;
+ }
+
+ $ctrlfile = "$dst/$fhint";
+ # Grab the current revision number. Clunky way, I know!
+ $ver_revision=`$wget -qO - $svnrev`;
+ chomp($ver_revision);
+
+ unless ($ver_revision =~ /^\d+/ and $ver_revision > 0)
+ {
+ print "*** Error: Got bogus result $ver_revision from $svnrev.\n";
+ exit;
+ }
+
+ $ver_revision++;
+ open (REV, "$ctrlfile") || die "Can't open $ctrlfile\n";
+ while (<REV>) {
+ push (@source, $_);
+ $ver_major = $_ if (/VERSION_MAJOR/);
+ $ver_minor = $_ if (/VERSION_MINOR/);
+ $ver_patch = $_ if (/VERSION_PATCH/);
+ $ver_build = $_ if (/VERSION_BUILD/);
+ }
+ close(REV);
+
+ my $junk;
+ ($junk, $ver_major) = split('"', $ver_major);
+ ($junk, $ver_minor) = split('"', $ver_minor);
+ ($junk, $ver_patch) = split('"', $ver_patch);
+ ($junk, $ver_build) = split('"', $ver_build);
+
+ $cver = "$ver_major.$ver_minor.$ver_patch ($ver_build)";
+ $nver = "$ver_major.$ver_minor.$ver_patch ($ver_revision)";
+
+ # Greet the developer
+ banner();
+ print "*** Repository : $svnroot \n";
+ print "*** Working copy : $dst \n" ;
+ print "*** Current ver. : $cver \n";
+ print "*** Updated ver. : $nver \n";
+ print "*** Files Changed: $dcount (before indent and version change)\n";
+ die ("Aborting...\n") unless &getans();
+
+ # Need to add a clause for -c "comment" and -b "buglist"
+
+ # Get developers input for commit
+ $tmpfile=".commit";
+ open (OUT, ">$tmpfile") or die ("*** Error! Unable to open $tmpfile file\n");
+ print OUT "# Anope commit utility. Please use this template for your commits.
+# Add Bugzilla bugs separated by spaces. The note part is free form.
+BUILD : $nver
+BUGS :
+NOTES : ";
+ close(OUT);
+
+ system("$editor $tmpfile");
+
+ my $tmp_comment="";
+ $ver_comment="#\n";
+ $svn_comment="";
+ open (IN, "$tmpfile") or die ("*** Error! Unable to open $tmpfile file\n");
+ while (<IN>) {
+ if ( !/^#/) {
+ $tmp_comment.="$_";
+ chomp($_);
+ $_ =~ s/\t/ /g;
+ $ver_comment.="# $_\n";
+ $svn_comment.="$_ ";
+ }
+ }
+ close(IN);
+
+ $svn_comment =~ s/\s\s+/ /g;
+ # Confirm the commit one last time...
+ print "*** Ready to commit, please verify:\n";
+ print "\n$tmp_comment\n";
+
+ die ("Aborting...\n") unless &getans();
+
+ print "*** Running Indent...\n";
+ system("indent -kr -nut *.c");
+ system("rm -f *~");
+
+ print "*** Bumping the revision number...\n";
+ # Re-write the control file
+ open(OUT, ">$ctrlfile") or die ("*** Error! Unable to open $ctrlfile ... aborting");
+ foreach (@source) {
+ if (/^VERSION_BUILD/) {
+ $_ =~ s/\"\d+\"/\"$ver_revision\"/;
+ } elsif (/# \$Log\$/) {
+ $_ .= "$ver_comment";
+ }
+ print OUT $_;
+ }
+ close(OUT);
+
+ print "*** Starting the upload...\n\n";
+ my $rval=system("$svn commit $dst --username='$svnuser' --message '$svn_comment'");
+ if ( $rval ) {
+ print "*** Error: Unable to complete commit. Rolling back....\n\n";
+ system("$svn revert $ctrlfile");
+ }
+
+}
+
+{
+ usage() if (! @ARGV);
+
+ my $opt = 'hgpf:r:b:t:';
+ getopts ("$opt", \%opt) or usage();
+ usage() if $opt{h};
+
+ usage() if ($opt{g} && $opt{p});
+ usage() if ($opt{g} && $opt{f});
+ usage() if ($opt{p} && $opt{f});
+ usage() if ($opt{r} && $opt{b});
+ usage() if ($opt{r} && $opt{t});
+ usage() if ($opt{b} && $opt{t});
+
+ $rev = $opt{r} ;
+ $branch = $opt{b} ;
+ $tag = $opt{t} ;
+ $ftp = $opt{f} ;
+ $dst = shift;
+
+ if ($rev ne undef) {
+ unless ($rev =~ /^\d+/ and $rev > 0)
+ {
+ print "*** Error: Revision number '$rev' must be an integer > 0.\n";
+ exit;
+ }
+ }
+
+ do_ftp() if $opt{f};
+ do_get() if $opt{g};
+ do_put() if $opt{p};
+ print "*** Done!\n";
+
+}
+
+
diff --git a/src/bin/anoperc b/src/bin/anoperc
new file mode 100755
index 000000000..bc0704f12
--- /dev/null
+++ b/src/bin/anoperc
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+###############################################
+# Set Variables
+###############################################
+
+# PID FILE NAME (e.g. services.pid)
+PIDFILE="services.pid"
+
+# FULL PATH TO ANOPE DIRECTORY e.g. /home/ribosome/services/
+# YOU MUST INCLUDE TRAILING SLASH
+ANOPEBIN=""
+
+# SERVICES EXECUTABLE NAME (e.g. services)
+ANOPROG="services"
+
+# SCRIPT VERSION NUMBER (DO NOT ALTER)
+ARCVERSION="1.1"
+
+
+################################################
+# END OF CONFIGURATION
+# YOU ARE NOT REQUIRED TO CHANGE ANYTHING BELOW
+################################################
+
+isAnopeRunning () {
+if [ ! -f $ANOPEBIN$PIDFILE ] ; then
+ echo "Warning: Anope is not currently running"
+ exit 1
+fi
+
+PID=`cat $ANOPEBIN$PIDFILE`
+
+if [ ! `ps auxw | grep $ANOPROG | grep $PID | grep -v -c grep` ] ; then
+ echo "Warning: Anope is not currently running"
+ exit 1
+fi
+}
+
+if [ "$ANOPEBIN" = "" ] ; then
+ echo "Error: Please open this file set the variables correctly";
+ exit 1
+fi
+
+if [ ! -f $ANOPEBIN$ANOPROG ] ; then
+ echo "Error: $ANOPEBIN$ANOPROG cannot be accessed"
+ exit 1
+fi
+
+if [ "$1" = "start" ] ; then
+
+if [ -f $ANOPEBIN$PIDFILE ] ; then
+ PID=`cat $ANOPEBIN$PIDFILE`
+ if [ `ps auxw | grep $ANOPROG | grep $PID | grep -v -c grep` = 1 ] ; then
+ echo "Warning! Anope is already running"
+ exit 1
+ fi
+fi
+ echo "Starting Anope"
+ shift
+ $ANOPEBIN$ANOPROG $*
+ sleep 1
+ if [ ! -f $ANOPEBIN$PIDFILE ] ; then
+ echo "Unfortunately it seems Anope did not start successfully"
+ echo "This error has been logged in your Anope Log file"
+ echo "Located in "$ANOPEBIN"logs/"
+ echo "This may help you diagnose the problem"
+ echo "Further help may be available from http://www.anope.org"
+ exit 1
+ fi
+ PID=`cat $ANOPEBIN$PIDFILE`
+ if [ ! `ps auxw | grep $ANOPROG | grep $PID | grep -v -c grep` ] ; then
+ echo "Unfortunately it seems Anope did not start successfully"
+ echo "This error has been logged in your Anope Log file"
+ echo "Located in "$ANOPEBIN"logs/"
+ echo "This may help you diagnose the problem"
+ echo "Further help may be available from http://www.anope.org"
+ exit 1
+ fi
+elif [ "$1" = "stop" ] ; then
+ isAnopeRunning
+ echo "Terminating Anope"
+ PID=`cat $ANOPEBIN$PIDFILE`
+ kill -SIGTERM $PID
+
+elif [ "$1" = "status" ] ; then
+ if [ -f $ANOPEBIN$PIDFILE ] ; then
+ PID=`cat $ANOPEBIN$PIDFILE`
+ if [ `ps auxw | grep $ANOPROG | grep $PID | grep -v -c grep` = 1 ] ; then
+ echo "Anope is currently running"
+ exit 1
+ fi
+ fi
+
+ echo "Anope is not currently running"
+
+elif [ "$1" = "restart" ] ; then
+ isAnopeRunning
+ echo "Restarting Anope"
+ PID=`cat $ANOPEBIN$PIDFILE`
+ kill -SIGHUP $PID
+
+elif [ "$1" = "rehash" ] ; then
+ isAnopeRunning
+ echo "Saving Databases and Rehashing Configuration"
+ PID=`cat $ANOPEBIN$PIDFILE`
+ kill -SIGUSR2 $PID
+
+elif [ "$1" = "version" ] ; then
+ $ANOPEBIN$ANOPROG -version
+
+elif [ "$1" = "help" ] ; then
+ if [ "$2" = "paramlist" ] ; then
+ $ANOPEBIN$ANOPROG -help
+ else
+ echo "AnopeRC is a remote control script for easy"
+ echo "controlling of Anope from the command console"
+ echo "$0 start Start Anope"
+ echo " Additional parameters may be passed"
+ echo " (e.g. $0 start -nofork)"
+ echo " For a list of type $0 $1 paramlist"
+ echo "$0 stop Shutdown Anope"
+ echo "$0 status Show Anope's Status"
+ echo "$0 restart Restart Anope (Databases will be saved)"
+ echo "$0 rehash Rehash Configuration and Save Databases"
+ echo "$0 version Return Anope Version and Build Information"
+ echo "$0 help Show this help menu"
+ echo "If you need further help please check the /docs/"
+ echo "folder or make use of our extensive online support at"
+ echo "http://www.anope.org"
+ fi
+
+else
+ echo "Anope Remote Control ($ARCVERSION)"
+ echo "Usage: $0 [start|stop|status|restart|rehash|version|help]"
+fi
diff --git a/src/bin/cp-recursive b/src/bin/cp-recursive
new file mode 100755
index 000000000..e51230db7
--- /dev/null
+++ b/src/bin/cp-recursive
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+if [ $1 = "-t" ] ; then
+ shift
+fi
+if [ ! "$2" ] ; then
+ echo >&2 Usage: $0 '<sourcedir> <targetdir>'
+ exit 1
+fi
+if [ -d "$1" ] ; then
+ dir="$1"
+else
+ dir="`echo $1 | sed 's#\(.*\)/.*#\1#'`"
+fi
+while [ "$2" ] ; do
+ shift
+done
+if [ ! -d $1 ] ; then
+ mkdir -p $1 || exit 1
+fi
+/bin/tar -Ccf $dir - . | /bin/tar -Cxf $1 -
diff --git a/src/bin/langtool b/src/bin/langtool
new file mode 100755
index 000000000..c1072760c
--- /dev/null
+++ b/src/bin/langtool
@@ -0,0 +1,239 @@
+#!/usr/bin/perl
+#
+# Ribosome's all in one language tool
+# 1) Compares to check for missing lines
+# 2) Compares to check for equal lines
+# 3) Fixes (on request) missing lines
+# 4) Checks for errors in lines
+# 5) Checks for long lines (>60 characters)
+#
+
+# Check to see if we have received arguments
+if (!$ARGV[0]) {
+print "Usage: $0 [language file]\n";
+exit;
+}
+
+# If yes, set $sourcefile to the language filename
+$testagainst = $ARGV[0];
+
+# Set $sourcefile to en_us language file
+$sourcefile = "en_us.l";
+
+# Number of Keys in File, Number of Equal Lines, Number of Missing Lines
+# Fixed Lines (optional) and Number of Format Error Lines, Number of Long Lines
+my $numberlines = 0;
+my $equallines = 0;
+my $missinglines = 0;
+my $fixedlines = 0;
+my $errorline = 0;
+my $longlines = 0;
+
+# Array to hold the lines from the source file and explosion array for long line check
+
+my @sourcearray;
+my @explosion;
+
+# Create associative arrays which will contain keys and definitions
+# One for the control source and one for what is being tested against
+
+my %sourcehash= ();
+my %testhash= ();
+
+# Check if Files Exist
+
+if (! -f $sourcefile) {
+print "Critical Error: The source file does not exist or is unreadable.\nNote: This tool must be run from the language directory.\n";
+exit;
+}
+
+if (! -f $testagainst) {
+print "Critical Error: The language file does not exist or is unreadable\n";
+exit;
+}
+###########################################
+# Function to load sourcefile into memory #
+###########################################
+
+sub load_sourcefile {
+
+ my $_key; # This variable will hold the key
+ my $_def; # This variable will hold the definition
+ my $line; # This variable will hold the current line
+
+ open (SOURCEFILE, "< $sourcefile"); # Open the source file
+ while (<SOURCEFILE>) { # For each line the source file
+ $line = $_; # $line is set to the line in the source file
+
+ # Skip comments
+ if (/^#/) { # This checks for # which indicates comment
+ next; # If detected, the next line is skipped to
+ }
+
+ # Start of a key
+ if (/^[A-Z]/) { # Checks to see if the line is a key
+ chomp($line); # Removes things that shouldn't be at the end
+ push (@sourcearray, $line); # Puts the line into the source array
+ # If a key is defined, load definition into the hash
+ if ($_key ne undef) { # If the key is defined
+ $sourcehash{$_key} = $_def; # Add the definition to the associative array
+ }
+ $_key=$line; # Set the key to the line
+ $_def=""; # Reset the definition
+ next; # Move onto the next line
+ }
+
+ if ($_key ne undef) { # If the key is set already
+ $_def.=$line; # The definition is the current line
+ }
+
+ $sourcehash{$_key} = $_def; # Load the definition into the associative array
+ } # End of while which reads lines
+
+ close (SOURCEFILE); # Close the source file
+
+} # End of load_sourcefile function
+
+#################################################
+# Function to load testagainst file into memory #
+#################################################
+
+sub load_testagainst {
+
+ my $_key; # This variable will hold the key
+ my $_def; # This variable will hold the definition
+ my $line; # This variable will hold the current line
+
+ open (TESTAGAINSTFILE, "< $testagainst"); # Open the file containing the control lines
+ while (<TESTAGAINSTFILE>) { # For each line in the file
+ $line = $_; # $line is set to the lines value
+
+
+ # Skip comments
+ if (/^#/) { # This checks for # which indicates comment
+ next; # If detected, the next line is skipped to
+ }
+
+ # Start of a key
+ if (/^[A-Z]/) { # Checks to see if the line is a key
+ chomp($line); # Removes things that shouldn't be at the end
+
+
+ if ($_key ne undef) { # If the key is defined
+ $testhash{$_key} = $_def; # Add the definition to the associative array
+ }
+ $_key=$line; # Set the key to the line
+ $_def=""; # Reset the definition
+ next; # Move onto the next line
+ }
+
+ if ($_key ne undef) { # If the key is set already
+ $_def.=$line; # The definition is the current line
+ }
+
+ $testhash{$_key} = $_def; # Load the definition into the associative array
+ } # End of while which reads lines
+ close (TESTAGAINSTFILE); # Close the source file
+
+}
+
+
+sub get_format { # Function to get the formatting from a string
+ my $fmt="";
+ my $str=shift; # Get the input
+
+ while ($str =~ m/%\w/g) {
+ $fmt .= $&;
+ }
+
+ return $fmt; # Return the formatting
+}
+
+
+load_sourcefile; # Call function to load the source file
+load_testagainst; # Call function to load the test file
+
+if (!grep(/^LANG_NAME/,%testhash)) {
+print "Critical Error: $ARGV[0] is not a valid language file!\n";
+exit;
+}
+
+open (LOG,"> langcheck.log"); # Open logfile for writing
+
+foreach $sourcearray (@sourcearray) { # For each key stored in the source array
+ my $_key=$_; # Store key from source array in $_key
+
+ if ((get_format($sourcehash{$sourcearray})) ne (get_format($testhash{$sourcearray}))) {
+ if ($sourcearray !~ STRFTIME ) {
+ $errorline++;
+ print LOG "FORMAT: $sourcearray - (expecting '".get_format($sourcehash{$sourcearray})."' and got '".get_format($testhash{$sourcearray})."')\n";
+ } }
+
+ if ($testhash{$sourcearray} eq $sourcehash{$sourcearray} ) {
+ # If the definitions between the source and the test are equal
+ $equallines++; # Number of equal lines increases
+ print LOG "EQUAL: $sourcearray\n"; # Line is written to logfile
+ }
+
+ if (!grep(/^$sourcearray/,%testhash)) { # If the key is found in the testhash associative array
+ $missinglines++; # Increase missing lines
+ print LOG "MISSING: $sourcearray\n"; # Line is written to logfile
+ }
+
+ @explosion = split(/\n/, $testhash{$sourcearray});
+ foreach $explosion (@explosion) {
+ $explosion =~ s/\002//g;
+ $explosion =~ s/\037//g;
+ if (length($explosion) > 61) {
+ print LOG "LONGLINE: $sourcearray (".substr($explosion,1,30)."...)\n";
+ $longlines++;
+ }
+ }
+
+$numberlines++; # Increase the number of lines tested by 1
+}
+close (LOG); # Close the logfile
+
+
+#########################
+# Show the test results #
+#########################
+
+print "Calculation Results:\n";
+print "----------------------------------\n";
+print "[$numberlines] line(s) were compared\n";
+print "[$equallines] line(s) were found to equal their $sourcefile counterpart(s)\n";
+print "[$missinglines] line(s) were found to be missing\n";
+print "[$longlines] line(s) were found to be long (>60 chars)\n";
+print "[$errorline] line(s) were found to have formatting errors\n";
+print "The specific details of the test have been saved in langcheck.log\n";
+
+if ($missinglines) { # If missing lines exist
+print "----------------------------------\n";
+print "Missing line(s) have been detected in this test, would you like to fix the file?\n";
+print "This automatically inserts values from the control file ($sourcefile) for the missing values\n";
+print "y/N? (Default: No) ";
+
+my $input = <STDIN>; # Ask if the file should be fixed
+if ((substr($input,0,1) eq "y") || (substr($input,0,1) eq "Y")) { # If Yes...
+
+open (FIX, ">> $testagainst"); # Open the file and append changes
+
+foreach $sourcearray (@sourcearray) { # For each key stored in the source array
+ my $_key=$_; # Store key from source array in $_key
+
+ if (!grep(/^$sourcearray/,%testhash)) { # If the key is not found in the testhash associative array
+ print FIX "$sourcearray\n$sourcehash{$sourcearray}"; # Add the line(s) to the language file
+ $fixedlines++; # Increase the fixed line count
+ }
+}
+
+close (FIX); # Close file after fixing
+
+print "Fixing Compete. [$fixedlines] line(s) fixed.\n";
+exit; # And Exit!
+}
+ # Otherwise, quit without fixing
+print "Exiting. The language file has NOT been fixed.\n";
+
+}
diff --git a/src/bin/mydbgen b/src/bin/mydbgen
new file mode 100755
index 000000000..dcff99407
--- /dev/null
+++ b/src/bin/mydbgen
@@ -0,0 +1,207 @@
+#!/bin/sh
+#
+# $Id$
+
+# Location of the .sql file with the schema
+DBSQL="tables.sql"
+
+# Schema Version
+SVER="1"
+
+# Local Version, defaults to 0
+LVER="0"
+
+TFILE="/tmp/.anopedb.$$"
+
+if [ "`eval echo -n 'a'`" = "-n a" ] ; then
+ c="\c"
+else
+ n="-n"
+fi
+
+# Fix for bug 10
+for try in HOME/services anope/data ../data data .. .
+do
+ if [ -f "$try/$DBSQL" ]; then
+ DBFILE="$try/$DBSQL"
+ fi
+done
+
+if [ ! -f "./$DBFILE" ] ; then
+ echo "Error: Required file $DBSQL was not found!";
+ exit
+fi
+
+echo ""
+echo "This script will guide you through the process of configuring your Anope"
+echo "installation to make use of MySQL support. This script must be used for both"
+echo "new installs as well as for upgrading for users who have a previous version"
+echo "of Anope installed"
+
+while [ -z "$SQLHOST" ] ; do
+ echo ""
+ echo "What is the hostname of your MySQL server?"
+ echo $n "-> $c"
+ read cc
+ if [ ! -z "$cc" ] ; then
+ SQLHOST=$cc
+ fi
+done
+
+while [ -z "$SQLUSER" ] ; do
+ echo ""
+ echo "What is your MySQL username?"
+ echo $n "-> $c"
+ read cc
+ if [ ! -z "$cc" ] ; then
+ SQLUSER=$cc
+ fi
+done
+
+OLD_TTY=`stty -g`
+
+echo ""
+echo "What is your MySQL password?"
+echo $n "-> $c"
+stty -echo echonl
+read cc
+if [ ! -z "$cc" ] ; then
+ SQLPASS=$cc
+fi
+stty $OLD_TTY
+
+mysqlshow -h$SQLHOST -u$SQLUSER -p$SQLPASS >/dev/null 2>&1
+if test "$?" = "1" ; then
+ echo "Error: Unable to login, verify your login/password and hostname"
+ exit
+fi
+
+while [ -z "$SQLDB" ] ; do
+ echo ""
+ echo "What is the name of the Anope SQL database?"
+ echo $n "-> $c"
+ read cc
+ if [ ! -z "$cc" ] ; then
+ SQLDB=$cc
+ fi
+done
+
+MYSQLDUMP="mysqldump -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB"
+MYSQLSHOW="mysqlshow -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB"
+MYSQL="mysql -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB"
+
+echo ""
+
+$MYSQLSHOW | grep -q $SQLDB
+if test "$?" = "1" ; then
+ echo -n "Unable to find databse, creating... "
+ mysql -h$SQLHOST -u$SQLUSER -p$SQLPASS -Bs -e "create database $SQLDB" >/dev/null 2>&1
+ if test "$?" = "0" ; then
+ echo "done!"
+ else
+ echo "failed!"
+ FAILED="$FAILED 'database creation'"
+ fi
+fi
+
+$MYSQL -Bs -e "show tables like 'anope_os_core'" | grep -q anope_os_core
+if test "$?" = "1" ; then
+ echo -n "Unable to find Anope schema, creating... "
+ $MYSQL < $DBFILE
+ if test "$?" = "0" ; then
+ echo "done!"
+ else
+ echo "failed!"
+ FAILED="$FAILED 'schema creation'"
+ fi
+else
+ # Introduced on Anope 1.6.0 -> Table anope_info
+ $MYSQL -Bs -e "show tables like 'anope_info'" | grep -q anope_info
+ if test "$?" = "1" ; then
+ echo -n "Unable to find Anope info table, creating... "
+ echo "CREATE TABLE anope_info (version int, date datetime) TYPE=MyISAM" > $TFILE
+ mysql -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB < $TFILE >/dev/null 2>&1
+ if test "$?" = "0" ; then
+ echo "done!"
+
+ else
+ echo "failed!"
+ FAILED="$FAILED 'anope_info table'"
+ fi
+ else
+ LVER="$($MYSQL -sB -e "select version from anope_info")"
+ if test "x$LVER" = "x" ; then
+ LVER=0
+ fi
+ fi
+
+ # Introduced on Anope 1.5.14.5 -> anope_cs_info.memomax
+ $MYSQL -Bs -e "describe anope_cs_info memomax" 2> /dev/null | grep -q memomax
+ if test "$?" = "1" ; then
+ echo -n "Unable to find anope_cs_info.memomax, altering... "
+ echo "ALTER TABLE anope_cs_info ADD memomax smallint unsigned NOT NULL default 0" > $TFILE
+ mysql -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB < $TFILE >/dev/null 2>&1
+ if test "$?" = "0" ; then
+ echo "done!"
+
+ else
+ echo "failed!"
+ FAILED="$FAILED 'anope_cs_info.memomax alter'"
+ fi
+ fi
+
+ # Introduced on Anope 1.5.14.5 -> anope_cs_info.ttb
+ $MYSQL -Bs -e "describe anope_cs_info ttb" 2> /dev/null | grep -q ttb
+ if test "$?" = "1" ; then
+ echo -n "Unable to find anope_cs_info.ttb, altering... "
+ echo "ALTER TABLE anope_cs_info ADD ttb smallint NOT NULL default 0" > $TFILE
+ mysql -h$SQLHOST -u$SQLUSER -p$SQLPASS $SQLDB < $TFILE >/dev/null 2>&1
+ if test "$?" = "0" ; then
+ echo "done!"
+
+ else
+ echo "failed!"
+ FAILED="$FAILED 'anope_cs_info.ttb alter'"
+ fi
+ fi
+
+
+fi
+
+# Insert initial version number. This will have to be redesigned for 1.7
+if [ $LVER -ne $SVER ]; then
+echo -n "Inserting initial version number... "
+$MYSQL -Bs -e "delete from anope_info"
+echo "INSERT INTO anope_info (version, date) VALUES ($SVER, now())" > $TFILE
+$MYSQL < $TFILE >/dev/null 2>&1
+if test "$?" = "0" ; then
+ echo "done!"
+else
+ echo "failed!"
+ FAILED="$FAILED 'version insert'"
+fi
+fi
+
+rm -f $TFILE
+if test "x$FAILED" = "x" ; then
+ # Try to find out more about this installation
+ SQLSOCK="$(mysql_config --socket 2> /dev/null)"
+ SQLPORT="$(mysql_config --port 2> /dev/null)"
+ echo ""
+ echo "Your MySQL setup is complete and your Anope schema is up to date. Make"
+ echo "sure you configure MySQL on your services.conf file prior to launching"
+ echo "Anope with MySQL support. Your configuration values are:"
+ echo ""
+ echo "MysqlHost \"$SQLHOST\""
+ echo "MysqlUser \"$SQLUSER\""
+ echo "MysqlPass \"$SQLPASS\""
+ echo "MysqlName \"$SQLDB\""
+ echo "MysqlSock \"$SQLSOCK\""
+ echo "MysqlPort \"$SQLPORT\""
+ echo ""
+else
+ echo "The following operations failed:"
+ echo "$FAILED"
+fi
+
+exit
diff --git a/src/bin/register b/src/bin/register
new file mode 100755
index 000000000..5162e44e4
--- /dev/null
+++ b/src/bin/register
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+###############################################
+# Set Variables
+###############################################
+
+# CONFIGURATION CACHE (e.g. ../config.cache)
+CACHEFILE="../config.cache"
+
+# REGISTRATION INFORMATION CACHE FILE (no need to alter this)
+REGCACHE="register.cache"
+
+# SENDMAIL PATH
+SENDMAIL="/usr/sbin/sendmail"
+
+# SCRIPT VERSION NUMBER (DO NOT ALTER)
+REGISTERVERSION="1.2"
+
+# DO NOT CHANGE IF YOU WANT TO REGISTER WITH ANOPE
+REGISTRYADDRESS="register@anope.org"
+
+################################################
+# END OF CONFIGURATION
+# YOU ARE NOT REQUIRED TO CHANGE ANYTHING BELOW
+################################################
+
+if [ $0 != "./register" ] ; then
+ echo "Warning: Run this file while in the /bin/ directory (e.g. ./register)"
+ exit 1
+fi
+
+if [ ! -f $CACHEFILE ] ; then
+ echo "Warning: Configuration cache file missing. Run ./configure"
+ exit 1
+fi
+
+if [ ! -f $SENDMAIL ] ; then
+ echo "Warning: Sendmail cannot be found. Please open this file and set variable correctly"
+ exit 1;
+fi
+
+clear
+
+if [ -f $REGCACHE ] ; then
+ echo "Previous registration cache file found. Removing..."
+ rm $REGCACHE
+fi
+
+ echo "##################################################"
+ echo "Anope registration script (v$REGISTERVERSION)"
+ echo "##################################################"
+ echo "This script allows you to register your network"
+ echo "with the Anope central registry. This gives us"
+ echo "an idea of how many networks use Anope and what options"
+ echo "they compile with so we can spend more time developing"
+ echo "options that are more widely used. Note: The options"
+ echo "you selected in ./configure will be sent."
+ echo "You will be asked a series of questions, if you wish"
+ echo "to be listed in the public network database all the"
+ echo "information will be required."
+ echo "NOTE: NO PRIVATE OR SENSITIVE INFORMATION WILL BE SENT"
+ echo "##################################################"
+ echo "Would you like to register? [Type YES to continue]"
+ read answer
+
+if [ $answer = "YES" ] ; then
+
+ echo "Beginning registration..."
+ echo "1. What is your network name? (e.g. Anope IRC Network)"
+ read NETWORKNAME
+ echo CONNECTADDRESS=\"$NETWORKNAME\" >> $REGCACHE
+ echo "2. What is your network's connection address (e.g. irc.anope.org)"
+ read CONNECTADDRESS
+ echo CONNECTADDRESS=\"$CONNECTADDRESS\" >> $REGCACHE
+ echo "3. Primary contact email address? (e.g. irc-admin@anope.org)"
+ read CONTACTEMAIL
+ echo CONTACTEMAIL=\"$CONTACTEMAIL\" >> $REGCACHE
+ echo "4. What is your network's website address (e.g. http://www.anope.org)"
+ read WEBSITEADDRESS
+ echo WEBSITEADDRESS=\"$WEBSITEADDRESS\" >> $REGCACHE
+ echo "5. Would you like your network to be listed in a public database?"
+ echo "[Please type YES if you would like to be listed]"
+ read LISTED
+ echo LISTED=\"$LISTED\" >> $REGCACHE
+ echo "6. (Bonus Devel-Only Question) Why did the chicken cross the road?!"
+ read BONUS
+ echo BONUS=\"$BONUS\" >> $REGCACHE
+ echo >> $REGCACHE
+ echo "Processing registration..."
+ cat $CACHEFILE >> $REGCACHE
+ $SENDMAIL $REGISTRYADDRESS < $REGCACHE
+ if [ -f $REGCACHE ] ; then
+ echo "Cleaning up..."
+ rm $REGCACHE
+ fi
+ echo "Registration Competed. Thank you for registering Anope."
+ exit 0;
+
+fi
+
+echo "Registration Cancelled"
diff --git a/src/botserv.c b/src/botserv.c
new file mode 100644
index 000000000..418e029d6
--- /dev/null
+++ b/src/botserv.c
@@ -0,0 +1,2593 @@
+/* BotServ functions
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+/*************************************************************************/
+
+#include "services.h"
+#include "pseudo.h"
+
+/**
+ * RFC: defination of a valid nick
+ * nickname = ( letter / special ) *8( letter / digit / special / "-" )
+ * letter = %x41-5A / %x61-7A ; A-Z / a-z
+ * digit = %x30-39 ; 0-9
+ * special = %x5B-60 / %x7B-7D ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
+ **/
+#define isvalidnick(c) ( isalnum(c) || ((c) >='\x5B' && (c) <='\x60') || ((c) >='\x7B' && (c) <='\x7D') || (c)=='-' )
+
+
+/*************************************************************************/
+
+BotInfo *botlists[256]; /* Hash list of bots */
+int nbots = 0;
+
+/*************************************************************************/
+
+BotInfo *makebot(char *nick);
+static UserData *get_user_data(Channel * c, User * u);
+static void unassign(User * u, ChannelInfo * ci);
+
+static void check_ban(ChannelInfo * ci, User * u, int ttbtype);
+static void bot_kick(ChannelInfo * ci, User * u, int message, ...);
+static void bot_raw_ban(User * requester, ChannelInfo * ci, char *nick,
+ char *reason);
+static void bot_raw_kick(User * requester, ChannelInfo * ci, char *nick,
+ char *reason);
+static void bot_raw_mode(User * requester, ChannelInfo * ci, char *mode,
+ char *nick);
+static void bot_raw_unban(ChannelInfo * ci, char *nick);
+
+static int do_help(User * u);
+static int do_bot(User * u);
+static int do_botlist(User * u);
+static int do_assign(User * u);
+static int do_unassign(User * u);
+static int do_info(User * u);
+static int do_set(User * u);
+static int do_kickcmd(User * u);
+static int do_badwords(User * u);
+static int do_say(User * u);
+static int do_act(User * u);
+void moduleAddBotServCmds(void);
+char *normalizeBuffer(char *);
+/*************************************************************************/
+/* *INDENT-OFF* */
+void moduleAddBotServCmds(void) {
+ Command *c;
+ c = createCommand("HELP",do_help,NULL, -1,-1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("BOTLIST", do_botlist, NULL, BOT_HELP_BOTLIST, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("ASSIGN", do_assign, NULL, BOT_HELP_ASSIGN, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("UNASSIGN", do_unassign, NULL, BOT_HELP_UNASSIGN, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("INFO", do_info, NULL, BOT_HELP_INFO, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET", do_set, NULL, BOT_HELP_SET,-1, BOT_SERVADMIN_HELP_SET,BOT_SERVADMIN_HELP_SET, BOT_SERVADMIN_HELP_SET); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET DONTKICKOPS", NULL, NULL, BOT_HELP_SET_DONTKICKOPS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET DONTKICKVOICES", NULL, NULL, BOT_HELP_SET_DONTKICKVOICES, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET FANTASY", NULL, NULL, BOT_HELP_SET_FANTASY, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET GREET", NULL, NULL, BOT_HELP_SET_GREET, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET SYMBIOSIS", NULL, NULL, BOT_HELP_SET_SYMBIOSIS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK", do_kickcmd, NULL, BOT_HELP_KICK, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK BADWORDS", NULL, NULL, BOT_HELP_KICK_BADWORDS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK BOLDS", NULL, NULL, BOT_HELP_KICK_BOLDS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK CAPS", NULL, NULL, BOT_HELP_KICK_CAPS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK COLORS", NULL, NULL, BOT_HELP_KICK_COLORS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK FLOOD", NULL, NULL, BOT_HELP_KICK_FLOOD, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK REPEAT", NULL, NULL, BOT_HELP_KICK_REPEAT, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK REVERSES", NULL, NULL, BOT_HELP_KICK_REVERSES, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("KICK UNDERLINES", NULL, NULL, BOT_HELP_KICK_UNDERLINES, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("BADWORDS", do_badwords, NULL, BOT_HELP_BADWORDS, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("SAY", do_say, NULL, BOT_HELP_SAY, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+ c = createCommand("ACT", do_act, NULL, BOT_HELP_ACT, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
+
+ /* Services admins commands */
+ c = createCommand("BOT", do_bot, is_services_admin, -1,-1, BOT_SERVADMIN_HELP_BOT,BOT_SERVADMIN_HELP_BOT, BOT_SERVADMIN_HELP_BOT); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET NOBOT", NULL, NULL, -1,-1, BOT_SERVADMIN_HELP_SET_NOBOT,BOT_SERVADMIN_HELP_SET_NOBOT, BOT_SERVADMIN_HELP_SET_NOBOT); addCoreCommand(BOTSERV,c);
+ c = createCommand("SET PRIVATE", NULL, NULL, -1,-1, BOT_SERVADMIN_HELP_SET_PRIVATE,BOT_SERVADMIN_HELP_SET_PRIVATE, BOT_SERVADMIN_HELP_SET_PRIVATE); addCoreCommand(BOTSERV,c);
+}
+/* *INDENT-ON* */
+/*************************************************************************/
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_botserv_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i;
+ BotInfo *bi;
+
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ count++;
+ mem += sizeof(*bi);
+ mem += strlen(bi->nick) + 1;
+ mem += strlen(bi->user) + 1;
+ mem += strlen(bi->host) + 1;
+ mem += strlen(bi->real) + 1;
+ }
+ }
+
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* BotServ initialization. */
+
+void bs_init(void)
+{
+ Command *cmd;
+ moduleAddBotServCmds();
+ cmd = findCommand(BOTSERV, "SET SYMBIOSIS");
+ if (cmd)
+ cmd->help_param1 = s_ChanServ;
+}
+
+/*************************************************************************/
+
+/* Main BotServ routine. */
+
+void botserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_BotServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_BotServ, u, SERVICE_OFFLINE, s_BotServ);
+ } else {
+ mod_run_cmd(s_BotServ, u, BOTSERV, cmd);
+ }
+
+}
+
+/*************************************************************************/
+
+/* Handles all messages sent to bots. (Currently only answers to pings ;) */
+
+void botmsgs(User * u, BotInfo * bi, char *buf)
+{
+ char *cmd = strtok(buf, " ");
+ char *s;
+
+ if (!cmd || !u)
+ return;
+
+ if (!stricmp(cmd, "\1PING")) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(bi->nick, u->nick, "\1PING %s", s);
+ }
+}
+
+/*************************************************************************/
+
+/* Handles all messages that are sent to registered channels where a
+ * bot is on.
+ *
+ */
+
+void botchanmsgs(User * u, ChannelInfo * ci, char *buf)
+{
+ int c;
+ int16 cstatus = 0;
+ char *cmd;
+ UserData *ud;
+
+ if (!u)
+ return;
+
+ /* Answer to ping if needed, without breaking the buffer. */
+ if (!strnicmp(buf, "\1PING", 5))
+ notice(ci->bi->nick, u->nick, buf);
+
+ /* If it's a /me, cut the CTCP part at the beginning (not
+ * at the end, because one character just doesn't matter,
+ * but the ACTION may create strange behaviours with the
+ * caps or badwords kickers */
+ if (!strnicmp(buf, "\1ACTION ", 8))
+ buf += 8;
+
+ /* Now we can make kicker stuff. We try to order the checks
+ * from the fastest one to the slowest one, since there's
+ * no need to process other kickers if an user is kicked before
+ * the last kicker check.
+ *
+ * But FIRST we check whether the user is protected in any
+ * way.
+ */
+
+ /* We first retrieve the user status on the channel if needed */
+ if (ci->botflags & (BS_DONTKICKOPS | BS_DONTKICKVOICES))
+ cstatus = chan_get_user_status(ci->c, u);
+
+ if (buf && !check_access(u, ci, CA_NOKICK) &&
+#ifdef HAS_HALFOP
+#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
+ (!(ci->botflags & BS_DONTKICKOPS)
+ || !(cstatus & (CUS_HALFOP | CUS_OP | CUS_OWNER | CUS_PROTECT)))
+# elif defined (IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ (!(ci->botflags & BS_DONTKICKOPS)
+ || !(cstatus & (CUS_HALFOP | CUS_OP | CUS_PROTECT)))
+# else
+ (!(ci->botflags & BS_DONTKICKOPS)
+ || !(cstatus & (CUS_HALFOP | CUS_OP)))
+# endif
+#else
+ (!(ci->botflags & BS_DONTKICKOPS) || !(cstatus & CUS_OP))
+#endif
+ && (!(ci->botflags & BS_DONTKICKVOICES) || !(cstatus & CUS_VOICE))) {
+ /* Bolds kicker */
+ if ((ci->botflags & BS_KICK_BOLDS) && strchr(buf, 2)) {
+ check_ban(ci, u, TTB_BOLDS);
+ bot_kick(ci, u, BOT_REASON_BOLD);
+ return;
+ }
+
+ /* Color kicker */
+ if ((ci->botflags & BS_KICK_COLORS) && strchr(buf, 3)) {
+ check_ban(ci, u, TTB_COLORS);
+ bot_kick(ci, u, BOT_REASON_COLOR);
+ return;
+ }
+
+ /* Reverses kicker */
+ if ((ci->botflags & BS_KICK_REVERSES) && strchr(buf, 22)) {
+ check_ban(ci, u, TTB_REVERSES);
+ bot_kick(ci, u, BOT_REASON_REVERSE);
+ return;
+ }
+
+ /* Underlines kicker */
+ if ((ci->botflags & BS_KICK_UNDERLINES) && strchr(buf, 31)) {
+ check_ban(ci, u, TTB_UNDERLINES);
+ bot_kick(ci, u, BOT_REASON_UNDERLINE);
+ return;
+ }
+
+ /* Caps kicker */
+ if ((ci->botflags & BS_KICK_CAPS)
+ && ((c = strlen(buf)) >= ci->capsmin)) {
+ int i = 0;
+ char *s = buf;
+
+ do {
+ if (isupper(*s))
+ i++;
+ } while (*s++);
+
+ if (i >= ci->capsmin && i * 100 / c >= ci->capspercent) {
+ check_ban(ci, u, TTB_CAPS);
+ bot_kick(ci, u, BOT_REASON_CAPS);
+ return;
+ }
+ }
+
+ /* Bad words kicker */
+ if (ci->botflags & BS_KICK_BADWORDS) {
+ int i;
+ int mustkick = 0;
+ char *nbuf;
+ BadWord *bw;
+
+ /* Normalize the buffer */
+ nbuf = normalizeBuffer(buf);
+
+ for (i = 0, bw = ci->badwords; i < ci->bwcount; i++, bw++) {
+ if (!bw->in_use)
+ continue;
+
+ if (bw->type == BW_ANY
+ && ((BSCaseSensitive && strstr(nbuf, bw->word))
+ || (!BSCaseSensitive && stristr(nbuf, bw->word)))) {
+ mustkick = 1;
+ } else if (bw->type == BW_SINGLE) {
+ int len = strlen(bw->word);
+
+ if ((BSCaseSensitive && strstr(nbuf, bw->word))
+ || (!BSCaseSensitive
+ && (!stricmp(nbuf, bw->word)))) {
+ mustkick = 1;
+ /* two next if are quite odd isn't it? =) */
+ } else if ((strchr(nbuf, ' ') == nbuf + len)
+ &&
+ ((BSCaseSensitive
+ && (strstr(nbuf, bw->word) == nbuf))
+ || (!BSCaseSensitive
+ && (stristr(nbuf, bw->word) ==
+ nbuf)))) {
+ mustkick = 1;
+ } else
+ if ((strrchr(nbuf, ' ') ==
+ nbuf + strlen(nbuf) - len - 1)
+ &&
+ ((BSCaseSensitive
+ && (strstr(nbuf, bw->word) ==
+ nbuf + strlen(nbuf) - len))
+ || (!BSCaseSensitive
+ && (stristr(nbuf, bw->word) ==
+ nbuf + strlen(nbuf) - len)))) {
+ mustkick = 1;
+ } else {
+ char *wordbuf = scalloc(len + 3, 1);
+
+ wordbuf[0] = ' ';
+ wordbuf[len + 1] = ' ';
+ wordbuf[len + 2] = '\0';
+ memcpy(wordbuf + 1, bw->word, len);
+
+ if ((BSCaseSensitive && (strstr(nbuf, wordbuf)))
+ || (!BSCaseSensitive
+ && (stristr(nbuf, wordbuf))))
+ mustkick = 1;
+ }
+ } else if (bw->type == BW_START) {
+ int len = strlen(bw->word);
+
+ if ((BSCaseSensitive
+ && (!strncmp(nbuf, bw->word, len)))
+ || (!BSCaseSensitive
+ && (!strnicmp(nbuf, bw->word, len)))) {
+ mustkick = 1;
+ } else {
+ char *wordbuf = scalloc(len + 2, 1);
+
+ memcpy(wordbuf + 1, bw->word, len);
+ wordbuf[0] = ' ';
+ wordbuf[len + 1] = '\0';
+
+ if ((BSCaseSensitive && (strstr(nbuf, wordbuf)))
+ || (!BSCaseSensitive
+ && (stristr(nbuf, wordbuf))))
+ mustkick = 1;
+
+ free(wordbuf);
+ }
+ } else if (bw->type == BW_END) {
+ int len = strlen(bw->word);
+
+ if ((BSCaseSensitive
+ &&
+ (!strncmp
+ (nbuf + strlen(nbuf) - len, bw->word, len)))
+ || (!BSCaseSensitive
+ &&
+ (!strnicmp
+ (nbuf + strlen(nbuf) - len, bw->word,
+ len)))) {
+ mustkick = 1;
+ } else {
+ char *wordbuf = scalloc(len + 2, 1);
+
+ memcpy(wordbuf, bw->word, len);
+ wordbuf[len] = ' ';
+ wordbuf[len + 1] = '\0';
+
+ if ((BSCaseSensitive && (strstr(nbuf, wordbuf)))
+ || (!BSCaseSensitive
+ && (stristr(nbuf, wordbuf))))
+ mustkick = 1;
+
+ free(wordbuf);
+ }
+ }
+
+ if (mustkick) {
+ check_ban(ci, u, TTB_BADWORDS);
+ if (BSGentleBWReason)
+ bot_kick(ci, u, BOT_REASON_BADWORD_GENTLE);
+ else
+ bot_kick(ci, u, BOT_REASON_BADWORD, bw->word);
+ return;
+ }
+ }
+
+ /* Free the normalized buffer */
+ if (nbuf)
+ free(nbuf);
+ }
+
+ /* Flood kicker */
+ if (ci->botflags & BS_KICK_FLOOD) {
+ time_t now = time(NULL);
+
+ ud = get_user_data(ci->c, u);
+ if (!ud)
+ return;
+
+ if (now - ud->last_start > ci->floodsecs) {
+ ud->last_start = time(NULL);
+ ud->lines = 0;
+ }
+
+ ud->lines++;
+ if (ud->lines >= ci->floodlines) {
+ check_ban(ci, u, TTB_FLOOD);
+ bot_kick(ci, u, BOT_REASON_FLOOD);
+ return;
+ }
+ }
+
+ /* Repeat kicker */
+ if (ci->botflags & BS_KICK_REPEAT) {
+ ud = get_user_data(ci->c, u);
+ if (!ud)
+ return;
+
+ if (ud->lastline && stricmp(ud->lastline, buf)) {
+ free(ud->lastline);
+ ud->lastline = sstrdup(buf);
+ ud->times = 0;
+ } else {
+ if (!ud->lastline)
+ ud->lastline = sstrdup(buf);
+ ud->times++;
+ }
+
+ if (ud->times >= ci->repeattimes) {
+ check_ban(ci, u, TTB_REPEAT);
+ bot_kick(ci, u, BOT_REASON_REPEAT);
+ return;
+ }
+ }
+ }
+
+
+ /* return if the user is on the ignore list */
+ if (get_ignore(u->nick) != NULL) {
+ return;
+ }
+
+ /* Fantaisist commands */
+
+ if (buf && (ci->botflags & BS_FANTASY) && *buf == '!'
+ && check_access(u, ci, CA_FANTASIA)) {
+ cmd = strtok(buf, " ");
+
+ if (cmd) {
+#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
+ if (!stricmp(cmd, "!deowner")) {
+ if (is_founder(u, ci))
+ bot_raw_mode(u, ci, "-q", u->nick);
+ } else
+#endif
+ if (!stricmp(cmd, "!kb")) {
+ char *target = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ if (!target && check_access(u, ci, CA_BANME)) {
+ bot_raw_ban(u, ci, u->nick, "Requested");
+ } else if (target && check_access(u, ci, CA_BAN)) {
+ if (!stricmp(target, ci->bi->nick)) {
+ bot_raw_ban(u, ci, u->nick, "Oops!");
+ } else {
+ if (!reason)
+ bot_raw_ban(u, ci, target, "Requested");
+ else
+ bot_raw_ban(u, ci, target, reason);
+ }
+ }
+ } else if ((!stricmp(cmd, "!kick")) || (!stricmp(cmd, "!k"))) {
+ char *target = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ if (!target && check_access(u, ci, CA_KICKME)) {
+ bot_raw_kick(u, ci, u->nick, "Requested");
+ } else if (target && check_access(u, ci, CA_KICK)) {
+ if (!stricmp(target, ci->bi->nick))
+ bot_raw_kick(u, ci, u->nick, "Oops!");
+ else if (!reason)
+ bot_raw_kick(u, ci, target, "Requested");
+ else
+ bot_raw_kick(u, ci, target, reason);
+ }
+#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
+ } else if (!stricmp(cmd, "!owner")) {
+ if (is_founder(u, ci))
+ bot_raw_mode(u, ci, "+q", u->nick);
+#endif
+ } else if (!stricmp(cmd, "!seen")) {
+ char *target = strtok(NULL, " ");
+ char buf[BUFSIZE];
+
+ if (target) {
+ User *u2;
+ NickAlias *na;
+ ChanAccess *access;
+
+ if (!stricmp(ci->bi->nick, target)) {
+ /* If we look for the bot */
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_BOT), u->nick);
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ buf);
+ } else if (!(na = findnick(target))
+ || (na->status & NS_VERBOTEN)) {
+ /* If the nick is not registered or forbidden */
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_UNKNOWN),
+ target);
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ buf);
+ } else if ((u2 = nc_on_chan(ci->c, na->nc))) {
+ /* If the nick we're looking for is on the channel,
+ * there are three possibilities: it's yourself,
+ * it's the nick we look for, it's an alias of the
+ * nick we look for.
+ */
+ if (u == u2 || (u->na && u->na->nc == na->nc))
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_YOU),
+ u->nick);
+ else if (!stricmp(u2->nick, target))
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_ON_CHANNEL),
+ u2->nick);
+ else
+ snprintf(buf, sizeof(buf),
+ getstring(u->na,
+ BOT_SEEN_ON_CHANNEL_AS),
+ target, u2->nick);
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ buf);
+ } else if ((access = get_access_entry(na->nc, ci))) {
+ /* User is on the access list but not present actually.
+ Special case: if access->last_seen is 0 it's that we
+ never seen the user.
+ */
+ if (access->last_seen) {
+ char durastr[192];
+ duration(u->na, durastr, sizeof(durastr),
+ time(NULL) - access->last_seen);
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_ON), target,
+ durastr);
+ } else {
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_NEVER),
+ target);
+ }
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ buf);
+ } else {
+ /* All other cases */
+ snprintf(buf, sizeof(buf),
+ getstring(u->na, BOT_SEEN_UNKNOWN),
+ target);
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ buf);
+ }
+ }
+ } else if (!stricmp(cmd, "!unban")
+ && check_access(u, ci, CA_UNBAN)) {
+ char *target = strtok(NULL, " ");
+
+ if (!target)
+ bot_raw_unban(ci, u->nick);
+ else
+ bot_raw_unban(ci, target);
+ } else {
+ CSModeUtil *util = csmodeutils;
+
+ do {
+ if (!stricmp(cmd, util->bsname)) {
+ char *target = strtok(NULL, " ");
+
+ if (!target
+ && check_access(u, ci, util->levelself))
+ bot_raw_mode(u, ci, util->mode, u->nick);
+ else if (target
+ && check_access(u, ci, util->level))
+ bot_raw_mode(u, ci, util->mode, target);
+ }
+ } while ((++util)->name != NULL);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Load/save data files. */
+
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", BotDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+void load_bs_dbase(void)
+{
+ dbFILE *f;
+ int c, ver;
+ int16 tmp16;
+ int32 tmp32;
+ BotInfo *bi;
+ int failed = 0;
+
+ if (!(f = open_db(s_BotServ, BotDBName, "r", BOT_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+
+ while (!failed && (c = getc_db(f)) != 0) {
+ char *s;
+
+ if (c != 1)
+ fatal("Invalid format in %s %d", BotDBName, c);
+
+ SAFE(read_string(&s, f));
+ bi = makebot(s);
+ free(s);
+ SAFE(read_string(&bi->user, f));
+ SAFE(read_string(&bi->host, f));
+ SAFE(read_string(&bi->real, f));
+ if (ver >= 10) {
+ SAFE(read_int16(&tmp16, f));
+ bi->flags = tmp16;
+ }
+ SAFE(read_int32(&tmp32, f));
+ bi->created = tmp32;
+ SAFE(read_int16(&tmp16, f));
+ bi->chancount = tmp16;
+ }
+
+ close_db(f);
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", BotDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", BotDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_bs_dbase(void)
+{
+ dbFILE *f;
+ BotInfo *bi;
+ static time_t lastwarn = 0;
+ int i;
+
+ if (!(f = open_db(s_BotServ, BotDBName, "w", BOT_VERSION)))
+ return;
+
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ SAFE(write_int8(1, f));
+ SAFE(write_string(bi->nick, f));
+ SAFE(write_string(bi->user, f));
+ SAFE(write_string(bi->host, f));
+ SAFE(write_string(bi->real, f));
+ SAFE(write_int16(bi->flags, f));
+ SAFE(write_int32(bi->created, f));
+ SAFE(write_int16(bi->chancount, f));
+ }
+ }
+ SAFE(write_int8(0, f));
+
+ close_db(f);
+
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+void save_bs_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ int i;
+ BotInfo *bi;
+
+ if (!rdb_open())
+ return;
+
+ rdb_clear_table("anope_bs_core");
+
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ rdb_save_bs_core(bi);
+ }
+ }
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+
+/* Inserts a bot in the bot list. I can't be much explicit mh? */
+
+static void insert_bot(BotInfo * bi)
+{
+ BotInfo *ptr, *prev;
+
+ for (prev = NULL, ptr = botlists[tolower(*bi->nick)];
+ ptr != NULL && stricmp(ptr->nick, bi->nick) < 0;
+ prev = ptr, ptr = ptr->next);
+ bi->prev = prev;
+ bi->next = ptr;
+ if (!prev)
+ botlists[tolower(*bi->nick)] = bi;
+ else
+ prev->next = bi;
+ if (ptr)
+ ptr->prev = bi;
+}
+
+/*************************************************************************/
+
+BotInfo *makebot(char *nick)
+{
+ BotInfo *bi;
+
+ bi = scalloc(sizeof(BotInfo), 1);
+ bi->nick = sstrdup(nick);
+ bi->lastmsg = time(NULL);
+ insert_bot(bi);
+ nbots++;
+ return bi;
+}
+
+/*************************************************************************/
+
+static void change_bot_nick(BotInfo * bi, char *newnick)
+{
+ if (bi->next)
+ bi->next->prev = bi->prev;
+ if (bi->prev)
+ bi->prev->next = bi->next;
+ else
+ botlists[tolower(*bi->nick)] = bi->next;
+
+ if (bi->nick)
+ free(bi->nick);
+ bi->nick = sstrdup(newnick);
+
+ insert_bot(bi);
+}
+
+/*************************************************************************/
+
+static int delbot(BotInfo * bi)
+{
+ cs_remove_bot(bi);
+
+ if (bi->next)
+ bi->next->prev = bi->prev;
+ if (bi->prev)
+ bi->prev->next = bi->next;
+ else
+ botlists[tolower(*bi->nick)] = bi->next;
+
+ nbots--;
+
+ free(bi->nick);
+ free(bi->user);
+ free(bi->host);
+ free(bi->real);
+
+ free(bi);
+
+ return 1;
+}
+
+/*************************************************************************/
+
+BotInfo *findbot(char *nick)
+{
+ BotInfo *bi;
+
+ if (!nick || !*nick)
+ return NULL;
+
+ for (bi = botlists[tolower(*nick)]; bi; bi = bi->next)
+ if (!stricmp(nick, bi->nick))
+ return bi;
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Unassign a bot from a channel. Assumes u, ci and ci->bi are not NULL */
+
+static void unassign(User * u, ChannelInfo * ci)
+{
+ if (ci->c && ci->c->usercount >= BSMinUsers) {
+ send_cmd(ci->bi->nick, "PART %s :UNASSIGN from %s", ci->name,
+ u->nick);
+ }
+ ci->bi->chancount--;
+ ci->bi = NULL;
+}
+
+/*************************************************************************/
+
+/* Returns ban data associated with an user if it exists, allocates it
+ otherwise. */
+
+static BanData *get_ban_data(Channel * c, User * u)
+{
+ char mask[BUFSIZE];
+ BanData *bd, *next;
+ time_t now = time(NULL);
+
+ if (!c || !u)
+ return NULL;
+
+ snprintf(mask, sizeof(mask), "%s@%s", u->username, GetHost(u));
+
+ for (bd = c->bd; bd; bd = next) {
+ if (now - bd->last_use > BSKeepData) {
+ if (bd->next)
+ bd->next->prev = bd->prev;
+ if (bd->prev)
+ bd->prev->next = bd->next;
+ else
+ c->bd = bd->next;
+ if (bd->mask)
+ free(bd->mask);
+ next = bd->next;
+ free(bd);
+ continue;
+ }
+ if (!stricmp(bd->mask, mask)) {
+ bd->last_use = now;
+ return bd;
+ }
+ next = bd->next;
+ }
+
+ /* If we fall here it is that we haven't found the record */
+ bd = scalloc(sizeof(BanData), 1);
+ bd->mask = sstrdup(mask);
+ bd->last_use = now;
+
+ bd->prev = NULL;
+ bd->next = c->bd;
+ if (bd->next)
+ bd->next->prev = bd;
+ c->bd = bd;
+
+ return bd;
+}
+
+/*************************************************************************/
+
+/* Returns BotServ data associated with an user on a given channel.
+ * Allocates it if necessary.
+ */
+
+static UserData *get_user_data(Channel * c, User * u)
+{
+ struct c_userlist *user;
+
+ if (!c || !u)
+ return NULL;
+
+ for (user = c->users; user; user = user->next) {
+ if (user->user == u) {
+ if (user->ud) {
+ time_t now = time(NULL);
+
+ /* Checks whether data is obsolete */
+ if (now - user->ud->last_use > BSKeepData) {
+ if (user->ud->lastline)
+ free(user->ud->lastline);
+ /* We should not free and realloc, but reset to 0
+ instead. */
+ memset(user->ud, 0, sizeof(UserData));
+ user->ud->last_use = now;
+ }
+
+ return user->ud;
+ } else {
+ user->ud = scalloc(sizeof(UserData), 1);
+ user->ud->last_use = time(NULL);
+ return user->ud;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Makes the bot join a channel and op himself. */
+
+void bot_join(ChannelInfo * ci)
+{
+ int i;
+
+ if (!ci || !ci->c || !ci->bi)
+ return;
+
+ if (BSSmartJoin) {
+ /* We check for bans */
+ int count = ci->c->bancount;
+ if (count) {
+ char botmask[BUFSIZE];
+ char **bans = scalloc(sizeof(char *) * count, 1);
+ char *av[3];
+
+ memcpy(bans, ci->c->bans, sizeof(char *) * count);
+ snprintf(botmask, sizeof(botmask), "%s!%s@%s", ci->bi->nick,
+ ci->bi->user, ci->bi->host);
+
+ av[0] = ci->c->name;
+ av[1] = sstrdup("-b");
+ for (i = 0; i < count; i++) {
+ if (match_wild_nocase(ci->c->bans[i], botmask)) {
+ send_mode(ci->bi->nick, ci->name, "%s", bans[i]);
+ av[2] = sstrdup(bans[i]);
+ do_cmode(ci->bi->nick, 3, av);
+ free(av[2]);
+ }
+ }
+ free(av[1]);
+ free(bans);
+ }
+
+ /* Should we be invited? */
+ if ((ci->c->mode & CMODE_i)
+ || (ci->c->limit && ci->c->usercount >= ci->c->limit))
+ send_cmd(NULL, "NOTICE @%s :%s invited %s into the channel.",
+ ci->c->name, ci->bi->nick, ci->bi->nick);
+ }
+#ifdef IRC_BAHAMUT
+ send_cmd(ci->bi->nick, "SJOIN %ld %s", ci->c->creation_time,
+ ci->c->name);
+#elif defined(IRC_HYBRID)
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), ci->c->name,
+ ci->bi->nick);
+#else
+ send_cmd(ci->bi->nick, "JOIN %s", ci->c->name);
+#endif
+
+#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
+ send_mode(ci->bi->nick, ci->c->name, "+ao %s %s", ci->bi->nick,
+ ci->bi->nick);
+#elif defined(IRC_PTLINK)
+ /* PTLinks requieres an IRCop to u-line changes, so use ChanServ */
+ send_mode(s_ChanServ, ci->c->name, "+ao %s %s", ci->bi->nick,
+ ci->bi->nick);
+#else
+ send_mode(ci->bi->nick, ci->c->name, "+o %s", ci->bi->nick);
+#endif
+}
+
+/*************************************************************************/
+
+/* This makes the bot rejoin all channel he is on when he gets killed
+ * or changed.
+ */
+
+void bot_rejoin_all(BotInfo * bi)
+{
+ int i;
+ ChannelInfo *ci;
+
+ for (i = 0; i < 256; i++)
+ for (ci = chanlists[i]; ci; ci = ci->next)
+ if (ci->bi == bi && ci->c && (ci->c->usercount >= BSMinUsers))
+ bot_join(ci);
+}
+
+/*************************************************************************/
+
+/* This makes a ban if the user has to have one. In every cases it increments
+ the kick count for the user. */
+
+static void check_ban(ChannelInfo * ci, User * u, int ttbtype)
+{
+ BanData *bd = get_ban_data(ci->c, u);
+
+ if (!bd)
+ return;
+
+ bd->ttb[ttbtype]++;
+ if (bd->ttb[ttbtype] == ci->ttb[ttbtype]) {
+ char *av[3];
+ char mask[BUFSIZE];
+
+ bd->ttb[ttbtype] = 0;
+
+ av[0] = ci->name;
+ av[1] = sstrdup("+b");
+ get_idealban(ci, u, mask, sizeof(mask));
+ av[2] = mask;
+ send_mode(ci->bi->nick, av[0], "+b %s", av[2]);
+ do_cmode(ci->bi->nick, 3, av);
+ free(av[1]);
+ }
+}
+
+/*************************************************************************/
+
+/* This makes a bot kick an user. Works somewhat like notice_lang in fact ;) */
+
+static void bot_kick(ChannelInfo * ci, User * u, int message, ...)
+{
+ va_list args;
+ char buf[1024];
+ const char *fmt;
+ char *av[3];
+
+ if (!ci || !ci->bi || !ci->c || !u)
+ return;
+
+ va_start(args, message);
+ fmt = getstring(u->na, message);
+ if (!fmt)
+ return;
+ vsnprintf(buf, sizeof(buf), fmt, args);
+
+ av[0] = ci->name;
+ av[1] = u->nick;
+ av[2] = buf;
+ send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
+ do_kick(ci->bi->nick, 3, av);
+}
+
+/*************************************************************************/
+
+/* Makes a simple ban and kicks the target */
+
+static void bot_raw_ban(User * requester, ChannelInfo * ci, char *nick,
+ char *reason)
+{
+ char *av[3];
+ char mask[BUFSIZE];
+ User *u = finduser(nick);
+
+ if (!u)
+ return;
+
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ if (is_protected(u) && (requester != u)) {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ getstring2(NULL, PERMISSION_DENIED));
+ return;
+ }
+#endif
+
+ if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
+ && (get_access(u, ci) >= get_access(requester, ci)))
+ return;
+
+#ifdef HAS_EXCEPT
+ if (is_excepted(ci, u) == 1) {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ getstring2(NULL, BOT_EXCEPT));
+ return;
+ }
+#endif
+
+ av[0] = ci->name;
+ av[1] = sstrdup("+b");
+ get_idealban(ci, u, mask, sizeof(mask));
+ av[2] = mask;
+ send_mode(ci->bi->nick, av[0], "+b %s", av[2]);
+ do_cmode(ci->bi->nick, 3, av);
+ free(av[1]);
+
+ av[0] = ci->name;
+ av[1] = nick;
+
+ if (!reason) {
+ av[2] = ci->bi->nick;
+ } else {
+ if (strlen(reason) > 200)
+ reason[200] = '\0';
+ av[2] = reason;
+ }
+
+ send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
+ do_kick(ci->bi->nick, 3, av);
+}
+
+/*************************************************************************/
+
+/* Makes a kick with a "dynamic" reason ;) */
+
+static void bot_raw_kick(User * requester, ChannelInfo * ci, char *nick,
+ char *reason)
+{
+ char *av[3];
+ User *u = finduser(nick);
+
+ if (!u || !is_on_chan(ci->c, u))
+ return;
+
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ if (is_protected(u) && (requester != u)) {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ getstring2(NULL, PERMISSION_DENIED));
+ return;
+ }
+#endif
+
+ if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
+ && (get_access(u, ci) >= get_access(requester, ci)))
+ return;
+
+ av[0] = ci->name;
+ av[1] = nick;
+
+ if (!reason) {
+ av[2] = ci->bi->nick;
+ } else {
+ if (strlen(reason) > 200)
+ reason[200] = '\0';
+ av[2] = reason;
+ }
+
+ send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
+ do_kick(ci->bi->nick, 3, av);
+}
+
+/*************************************************************************/
+
+/* Makes a mode operation on a channel for a nick */
+
+static void bot_raw_mode(User * requester, ChannelInfo * ci, char *mode,
+ char *nick)
+{
+ char *av[3];
+ User *u = finduser(nick);
+
+ if (!u || !is_on_chan(ci->c, u))
+ return;
+
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ if (is_protected(u) && *mode == '-' && (requester != u)) {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
+ getstring2(NULL, PERMISSION_DENIED));
+ return;
+ }
+#endif
+
+ if (*mode == '-' && (ci->flags & CI_PEACE)
+ && stricmp(requester->nick, nick)
+ && (get_access(u, ci) >= get_access(requester, ci)))
+ return;
+
+ av[0] = ci->name;
+ av[1] = mode;
+ av[2] = nick;
+
+ send_mode(ci->bi->nick, av[0], "%s %s", av[1], av[2]);
+ do_cmode(ci->bi->nick, 3, av);
+}
+
+/*************************************************************************/
+
+/* Removes all bans for a nick on a channel */
+
+static void bot_raw_unban(ChannelInfo * ci, char *nick)
+{
+#ifndef IRC_BAHAMUT
+ int count, i;
+ char *av[3], **bans;
+ User *u;
+#endif
+
+ if (!ci || !ci->c || !ci->bi || !nick)
+ return;
+#ifndef IRC_BAHAMUT
+ if (!(u = finduser(nick)))
+ return;
+#else
+ if (!finduser(nick))
+ return;
+#endif
+
+#ifndef IRC_BAHAMUT
+ av[0] = ci->name;
+ av[1] = sstrdup("-b");
+
+ count = ci->c->bancount;
+ bans = scalloc(sizeof(char *) * count, 1);
+ memcpy(bans, ci->c->bans, sizeof(char *) * count);
+
+ for (i = 0; i < count; i++) {
+ if (match_usermask(bans[i], u)) {
+ send_mode(ci->bi->nick, ci->name, "-b %s", bans[i]);
+ av[2] = bans[i];
+ do_cmode(ci->bi->nick, 3, av);
+ }
+ }
+ free(bans);
+ free(av[1]);
+#else
+ send_cmd(ServerName, "SVSMODE %s -b %s", ci->name, nick);
+#endif
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_BotServ, u, BOT_HELP, BSMinUsers);
+ if (is_services_oper(u))
+ notice_help(s_BotServ, u, BOT_SERVADMIN_HELP);
+ moduleDisplayHelp(4, u);
+ } else {
+ mod_help_cmd(s_BotServ, u, BOTSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_bot(User * u)
+{
+ BotInfo *bi;
+ char *cmd = strtok(NULL, " ");
+ char *ch = NULL;
+
+ if (!cmd)
+ syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
+ else if (!stricmp(cmd, "ADD")) {
+ char *nick = strtok(NULL, " ");
+ char *user = strtok(NULL, " ");
+ char *host = strtok(NULL, " ");
+ char *real = strtok(NULL, "");
+
+ if (!nick || !user || !host || !real)
+ syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
+ else if (readonly)
+ notice_lang(s_BotServ, u, BOT_BOT_READONLY);
+ else if (findbot(nick))
+ notice_lang(s_BotServ, u, BOT_BOT_ALREADY_EXISTS, nick);
+ else {
+ NickAlias *na;
+
+ /**
+ * Check the nick is valid re RFC 2812
+ **/
+ if (isdigit(nick[0]) || nick[0] == '-') {
+ notice_lang(s_BotServ, u, BOT_BAD_NICK);
+ return MOD_CONT;
+ }
+ for (ch = nick; *ch && (ch - nick) < NICKMAX; ch++) {
+ if (!isvalidnick(*ch)) {
+ notice_lang(s_BotServ, u, BOT_BAD_NICK);
+ return MOD_CONT;
+ }
+ }
+ if (!isValidHost(host, 3)) {
+ notice_lang(s_BotServ, u, BOT_BAD_HOST);
+ return MOD_CONT;
+ }
+ for (ch = user; *ch && (ch - user) < USERMAX; ch++) {
+ if (!isalnum(*ch)) {
+ notice_lang(s_BotServ, u, BOT_BAD_IDENT);
+ return MOD_CONT;
+ }
+ }
+
+ /**
+ * Check the host is valid re RFC 2812
+ **/
+
+ /* Check whether it's a services client's nick and return if so - Certus */
+
+ if ((s_NickServ && (stricmp(nick, s_NickServ) == 0))
+ || (s_NickServAlias && !stricmp(nick, s_NickServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_ChanServ && (stricmp(nick, s_ChanServ) == 0))
+ || (s_ChanServAlias
+ && !stricmp(nick, s_ChanServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_MemoServ && (stricmp(nick, s_MemoServ) == 0))
+ || (s_MemoServAlias
+ && !stricmp(nick, s_MemoServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_BotServ && (stricmp(nick, s_BotServ) == 0))
+ || (s_BotServAlias
+ && !stricmp(nick, s_BotServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_HelpServ && (stricmp(nick, s_HelpServ) == 0))
+ || (s_HelpServAlias
+ && !stricmp(nick, s_HelpServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_OperServ && (stricmp(nick, s_OperServ) == 0))
+ || (s_OperServAlias
+ && !stricmp(nick, s_OperServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else
+ if ((s_GlobalNoticer
+ && (stricmp(nick, s_GlobalNoticer) == 0))
+ || (s_GlobalNoticerAlias
+ && !stricmp(nick, s_GlobalNoticerAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_HostServ && (stricmp(nick, s_HostServ) == 0))
+ || (s_HostServAlias
+ && !stricmp(nick, s_HostServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ }
+
+ /* We check whether the nick is registered, and drop it if so. */
+ if ((na = findnick(nick))) {
+ if (NSSecureAdmins && nick_is_services_admin(na->nc)
+ && !is_services_root(u)) {
+ notice_lang(s_BotServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ delnick(na);
+ }
+
+ bi = makebot(nick);
+ if (!bi) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ }
+
+ bi->user = sstrdup(user);
+ bi->host = sstrdup(host);
+ bi->real = sstrdup(real);
+ bi->created = time(NULL);
+ bi->chancount = 0;
+
+ /* We check whether user with this nick is online, and kill it if so */
+ EnforceQlinedNick(nick, s_BotServ);
+
+ /* We make the bot online, ready to serve */
+ NEWNICK(bi->nick, bi->user, bi->host, bi->real,
+ BOTSERV_BOTS_MODE, 1);
+
+ notice_lang(s_BotServ, u, BOT_BOT_ADDED, bi->nick, bi->user,
+ bi->host, bi->real);
+ }
+ } else if (!stricmp(cmd, "CHANGE")) {
+ char *oldnick = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ char *user = strtok(NULL, " ");
+ char *host = strtok(NULL, " ");
+ char *real = strtok(NULL, "");
+
+ if (!oldnick || !nick)
+ syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
+ else if (readonly)
+ notice_lang(s_BotServ, u, BOT_BOT_READONLY);
+ else if (!(bi = findbot(oldnick)))
+ notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, oldnick);
+ else {
+ NickAlias *na;
+
+ /* Checks whether there *are* changes.
+ * Case sensitive because we may want to change just the case.
+ * And we must finally check that the nick is not already
+ * taken by another bot.
+ */
+ if (!strcmp(bi->nick, nick)
+ && ((user) ? !strcmp(bi->user, user) : 1)
+ && ((host) ? !strcmp(bi->host, host) : 1)
+ && ((real) ? !strcmp(bi->real, real) : 1)) {
+ notice_lang(s_BotServ, u, BOT_BOT_ANY_CHANGES);
+ return MOD_CONT;
+ }
+
+ /* Check whether it's a services client's nick and return if so - Certus */
+ if ((s_NickServ && !stricmp(nick, s_NickServ))
+ || (s_NickServAlias && !stricmp(nick, s_NickServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_ChanServ && !stricmp(nick, s_ChanServ))
+ || (s_ChanServAlias
+ && !stricmp(nick, s_ChanServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_MemoServ && !stricmp(nick, s_MemoServ))
+ || (s_MemoServAlias
+ && !stricmp(nick, s_MemoServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_BotServ && !stricmp(nick, s_BotServ))
+ || (s_BotServAlias
+ && !stricmp(nick, s_BotServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_HelpServ && !stricmp(nick, s_HelpServ))
+ || (s_HelpServAlias
+ && !stricmp(nick, s_HelpServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_OperServ && !stricmp(nick, s_OperServ))
+ || (s_OperServAlias
+ && !stricmp(nick, s_OperServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_GlobalNoticer && !stricmp(nick, s_GlobalNoticer))
+ || (s_GlobalNoticerAlias
+ && !stricmp(nick, s_GlobalNoticerAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ } else if ((s_HostServ && !stricmp(nick, s_HostServ))
+ || (s_HostServAlias
+ && !stricmp(nick, s_HostServAlias))) {
+ notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
+ return MOD_CONT;
+ }
+
+ /**
+ * Check the nick is valid re RFC 2812
+ **/
+ if (isdigit(nick[0]) || nick[0] == '-') {
+ notice_lang(s_BotServ, u, BOT_BAD_NICK);
+ return MOD_CONT;
+ }
+ for (ch = nick; *ch && (ch - nick) < NICKMAX; ch++) {
+ if (!isvalidnick(*ch)) {
+ notice_lang(s_BotServ, u, BOT_BAD_NICK);
+ return MOD_CONT;
+ }
+ }
+ if (!isValidHost(host, 3)) {
+ notice_lang(s_BotServ, u, BOT_BAD_HOST);
+ return MOD_CONT;
+ }
+
+ for (ch = user; *ch && (ch - user) < USERMAX; ch++) {
+ if (!isalnum(*ch)) {
+ notice_lang(s_BotServ, u, BOT_BAD_IDENT);
+ return MOD_CONT;
+ }
+ }
+
+ if (stricmp(bi->nick, nick) && findbot(nick)) {
+ notice_lang(s_BotServ, u, BOT_BOT_ALREADY_EXISTS, nick);
+ return MOD_CONT;
+ }
+
+ if (stricmp(bi->nick, nick)) {
+ /* The new nick is really different, so we remove the Q line for
+ the old nick. */
+#ifndef IRC_HYBRID
+ send_cmd(NULL, "UNSQLINE %s", bi->nick);
+#endif
+
+ /* We check whether the nick is registered, and drop it if so */
+ if ((na = findnick(nick)))
+ delnick(na);
+
+ /* We check whether user with this nick is online, and kill it if so */
+ EnforceQlinedNick(nick, s_BotServ);
+ }
+
+ if (strcmp(nick, bi->nick))
+ change_bot_nick(bi, nick);
+
+ if (user && strcmp(user, bi->user)) {
+ free(bi->user);
+ bi->user = sstrdup(user);
+ }
+ if (host && strcmp(host, bi->host)) {
+ free(bi->host);
+ bi->host = sstrdup(host);
+ }
+ if (real && strcmp(real, bi->real)) {
+ free(bi->real);
+ bi->real = sstrdup(real);
+ }
+
+ /* If only the nick changes, we just make the bot change his nick,
+ else we must make it quit and rejoin. */
+ if (!user)
+ send_cmd(oldnick, "NICK %s", bi->nick);
+ else {
+ send_cmd(oldnick, "QUIT :Quit: Be right back");
+
+ NEWNICK(bi->nick, bi->user, bi->host, bi->real,
+ BOTSERV_BOTS_MODE, 1);
+ bot_rejoin_all(bi);
+ }
+
+ notice_lang(s_BotServ, u, BOT_BOT_CHANGED, oldnick, bi->nick,
+ bi->user, bi->host, bi->real);
+ }
+ } else if (!stricmp(cmd, "DEL")) {
+ char *nick = strtok(NULL, " ");
+
+ if (!nick)
+ syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
+ else if (readonly)
+ notice_lang(s_BotServ, u, BOT_BOT_READONLY);
+ else if (!(bi = findbot(nick)))
+ notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, nick);
+ else {
+ send_cmd(bi->nick,
+ "QUIT :Quit: Help! I'm being deleted by %s!",
+ u->nick);
+#ifndef IRC_HYBRID
+ send_cmd(NULL, "UNSQLINE %s", bi->nick);
+#endif
+ delbot(bi);
+
+ notice_lang(s_BotServ, u, BOT_BOT_DELETED, nick);
+ }
+ } else if (!stricmp(cmd, "LIST"))
+ do_botlist(u);
+ else
+ syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
+
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_botlist(User * u)
+{
+ int i, count = 0;
+ BotInfo *bi;
+
+ if (!nbots) {
+ notice_lang(s_BotServ, u, BOT_BOTLIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ if (!(bi->flags & BI_PRIVATE)) {
+ if (!count)
+ notice_lang(s_BotServ, u, BOT_BOTLIST_HEADER);
+ count++;
+ notice_user(s_BotServ, u, " %-15s (%s@%s)", bi->nick,
+ bi->user, bi->host);
+ }
+ }
+ }
+
+ if (is_oper(u) && count < nbots) {
+ notice_lang(s_BotServ, u, BOT_BOTLIST_PRIVATE_HEADER);
+
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ if (bi->flags & BI_PRIVATE) {
+ notice_user(s_BotServ, u, " %-15s (%s@%s)",
+ bi->nick, bi->user, bi->host);
+ count++;
+ }
+ }
+ }
+ }
+
+ if (!count)
+ notice_lang(s_BotServ, u, BOT_BOTLIST_EMPTY);
+ else
+ notice_lang(s_BotServ, u, BOT_BOTLIST_FOOTER, count);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_assign(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ BotInfo *bi;
+ ChannelInfo *ci;
+
+ if (readonly)
+ notice_lang(s_BotServ, u, BOT_ASSIGN_READONLY);
+ else if (!chan || !nick)
+ syntax_error(s_BotServ, u, "ASSIGN", BOT_ASSIGN_SYNTAX);
+ else if (!(bi = findbot(nick)))
+ notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, nick);
+ else if (bi->flags & BI_PRIVATE && !is_oper(u))
+ notice_lang(s_BotServ, u, PERMISSION_DENIED);
+ else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if ((ci->bi) && (stricmp(ci->bi->nick, nick) == 0))
+ notice_lang(s_BotServ, u, BOT_ASSIGN_ALREADY, ci->bi->nick, chan);
+ else if ((ci->botflags & BS_NOBOT)
+ || (!check_access(u, ci, CA_ASSIGN) && !is_services_admin(u)))
+ notice_lang(s_BotServ, u, PERMISSION_DENIED);
+ else {
+ if (ci->bi)
+ unassign(u, ci);
+ ci->bi = bi;
+ bi->chancount++;
+ if (ci->c && ci->c->usercount >= BSMinUsers) {
+ bot_join(ci);
+ }
+ notice_lang(s_BotServ, u, BOT_ASSIGN_ASSIGNED, bi->nick, ci->name);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_unassign(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ ChannelInfo *ci;
+
+ if (readonly)
+ notice_lang(s_BotServ, u, BOT_ASSIGN_READONLY);
+ else if (!chan)
+ syntax_error(s_BotServ, u, "UNASSIGN", BOT_UNASSIGN_SYNTAX);
+ else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if (!is_services_admin(u) && !check_access(u, ci, CA_ASSIGN))
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ else {
+ if (ci->bi)
+ unassign(u, ci);
+ notice_lang(s_BotServ, u, BOT_UNASSIGN_UNASSIGNED, ci->name);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static void send_bot_channels(User * u, BotInfo * bi)
+{
+ int i;
+ ChannelInfo *ci;
+ char buf[307], *end;
+
+ *buf = 0;
+ end = buf;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ if (ci->bi == bi) {
+ if (strlen(buf) + strlen(ci->name) > 300) {
+ notice_user(s_BotServ, u, buf);
+ *buf = 0;
+ end = buf;
+ }
+ end +=
+ snprintf(end, sizeof(buf) - (end - buf), " %s ",
+ ci->name);
+ }
+ }
+ }
+
+ if (*buf)
+ notice_user(s_BotServ, u, buf);
+ return;
+}
+
+static int do_info(User * u)
+{
+ BotInfo *bi;
+ ChannelInfo *ci;
+ char *query = strtok(NULL, " ");
+
+ int need_comma = 0, is_servadmin = is_services_admin(u);
+ char buf[BUFSIZE], *end;
+ const char *commastr = getstring(u->na, COMMA_SPACE);
+
+ if (!query)
+ syntax_error(s_BotServ, u, "INFO", BOT_INFO_SYNTAX);
+ else if ((bi = findbot(query))) {
+ char buf[BUFSIZE];
+ struct tm *tm;
+
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_HEADER, bi->nick);
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_MASK, bi->user, bi->host);
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_REALNAME, bi->real);
+ tm = localtime(&bi->created);
+ strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_CREATED, buf);
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_OPTIONS,
+ getstring(u->na,
+ (bi->
+ flags & BI_PRIVATE) ? BOT_INFO_OPT_PRIVATE :
+ BOT_INFO_OPT_NONE));
+ notice_lang(s_BotServ, u, BOT_INFO_BOT_USAGE, bi->chancount);
+
+ if (is_services_admin(u))
+ send_bot_channels(u, bi);
+ } else if ((ci = cs_findchan(query))) {
+ if (!is_servadmin && !is_founder(u, ci)) {
+ notice_lang(s_BotServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_HEADER, ci->name);
+ if (ci->bi)
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_BOT, ci->bi->nick);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_BOT_NONE);
+
+ if (ci->botflags & BS_KICK_BADWORDS) {
+ if (ci->ttb[TTB_BADWORDS])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_BADWORDS]);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS,
+ getstring(u->na, BOT_INFO_ACTIVE));
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_BOLDS) {
+ if (ci->ttb[TTB_BOLDS])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_BOLDS]);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS,
+ getstring(u->na, BOT_INFO_ACTIVE));
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_CAPS) {
+ if (ci->ttb[TTB_CAPS])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_CAPS], ci->capsmin,
+ ci->capspercent);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_ON,
+ getstring(u->na, BOT_INFO_ACTIVE), ci->capsmin,
+ ci->capspercent);
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_OFF,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_COLORS) {
+ if (ci->ttb[TTB_COLORS])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_COLORS]);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS,
+ getstring(u->na, BOT_INFO_ACTIVE));
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_FLOOD) {
+ if (ci->ttb[TTB_FLOOD])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_FLOOD], ci->floodlines,
+ ci->floodsecs);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_ON,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->floodlines, ci->floodsecs);
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_OFF,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_REPEAT) {
+ if (ci->ttb[TTB_REPEAT])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_REPEAT], ci->repeattimes);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_ON,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->repeattimes);
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_OFF,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_REVERSES) {
+ if (ci->ttb[TTB_REVERSES])
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_REVERSES]);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES,
+ getstring(u->na, BOT_INFO_ACTIVE));
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES,
+ getstring(u->na, BOT_INFO_INACTIVE));
+ if (ci->botflags & BS_KICK_UNDERLINES) {
+ if (ci->ttb[TTB_UNDERLINES])
+ notice_lang(s_BotServ, u,
+ BOT_INFO_CHAN_KICK_UNDERLINES_BAN,
+ getstring(u->na, BOT_INFO_ACTIVE),
+ ci->ttb[TTB_UNDERLINES]);
+ else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_UNDERLINES,
+ getstring(u->na, BOT_INFO_ACTIVE));
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_UNDERLINES,
+ getstring(u->na, BOT_INFO_INACTIVE));
+
+ end = buf;
+ *end = 0;
+ if (ci->botflags & BS_DONTKICKOPS) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s",
+ getstring(u->na, BOT_INFO_OPT_DONTKICKOPS));
+ need_comma = 1;
+ }
+ if (ci->botflags & BS_DONTKICKVOICES) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, BOT_INFO_OPT_DONTKICKVOICES));
+ need_comma = 1;
+ }
+ if (ci->botflags & BS_FANTASY) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, BOT_INFO_OPT_FANTASY));
+ need_comma = 1;
+ }
+ if (ci->botflags & BS_GREET) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, BOT_INFO_OPT_GREET));
+ need_comma = 1;
+ }
+ if (ci->botflags & BS_NOBOT) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, BOT_INFO_OPT_NOBOT));
+ need_comma = 1;
+ }
+ if (ci->botflags & BS_SYMBIOSIS) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, BOT_INFO_OPT_SYMBIOSIS));
+ need_comma = 1;
+ }
+ notice_lang(s_BotServ, u, BOT_INFO_CHAN_OPTIONS,
+ *buf ? buf : getstring(u->na, BOT_INFO_OPT_NONE));
+
+ } else
+ notice_lang(s_BotServ, u, BOT_INFO_NOT_FOUND, query);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *option = strtok(NULL, " ");
+ char *value = strtok(NULL, " ");
+ int is_servadmin = is_services_admin(u);
+
+ ChannelInfo *ci;
+
+ if (readonly)
+ notice_lang(s_BotServ, u, BOT_SET_DISABLED);
+ else if (!chan || !option || !value)
+ syntax_error(s_BotServ, u, "SET", BOT_SET_SYNTAX);
+ else if (is_servadmin && !stricmp(option, "PRIVATE")) {
+ BotInfo *bi;
+
+ if ((bi = findbot(chan))) {
+ if (!stricmp(value, "ON")) {
+ bi->flags |= BI_PRIVATE;
+ notice_lang(s_BotServ, u, BOT_SET_PRIVATE_ON, bi->nick);
+ } else if (!stricmp(value, "OFF")) {
+ bi->flags &= ~BI_PRIVATE;
+ notice_lang(s_BotServ, u, BOT_SET_PRIVATE_OFF, bi->nick);
+ } else {
+ syntax_error(s_BotServ, u, "SET PRIVATE",
+ BOT_SET_PRIVATE_SYNTAX);
+ }
+ } else {
+ notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, chan);
+ }
+ return MOD_CONT;
+ } else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if (!is_servadmin && !check_access(u, ci, CA_SET))
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ else {
+ if (!stricmp(option, "DONTKICKOPS")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_DONTKICKOPS;
+ notice_lang(s_BotServ, u, BOT_SET_DONTKICKOPS_ON,
+ ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_DONTKICKOPS;
+ notice_lang(s_BotServ, u, BOT_SET_DONTKICKOPS_OFF,
+ ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET DONTKICKOPS",
+ BOT_SET_DONTKICKOPS_SYNTAX);
+ }
+ } else if (!stricmp(option, "DONTKICKVOICES")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_DONTKICKVOICES;
+ notice_lang(s_BotServ, u, BOT_SET_DONTKICKVOICES_ON,
+ ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_DONTKICKVOICES;
+ notice_lang(s_BotServ, u, BOT_SET_DONTKICKVOICES_OFF,
+ ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET DONTKICKVOICES",
+ BOT_SET_DONTKICKVOICES_SYNTAX);
+ }
+ } else if (!stricmp(option, "FANTASY")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_FANTASY;
+ notice_lang(s_BotServ, u, BOT_SET_FANTASY_ON, ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_FANTASY;
+ notice_lang(s_BotServ, u, BOT_SET_FANTASY_OFF, ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET FANTASY",
+ BOT_SET_FANTASY_SYNTAX);
+ }
+ } else if (!stricmp(option, "GREET")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_GREET;
+ notice_lang(s_BotServ, u, BOT_SET_GREET_ON, ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_GREET;
+ notice_lang(s_BotServ, u, BOT_SET_GREET_OFF, ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET GREET",
+ BOT_SET_GREET_SYNTAX);
+ }
+ } else if (is_servadmin && !stricmp(option, "NOBOT")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_NOBOT;
+ if (ci->bi)
+ unassign(u, ci);
+ notice_lang(s_BotServ, u, BOT_SET_NOBOT_ON, ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_NOBOT;
+ notice_lang(s_BotServ, u, BOT_SET_NOBOT_OFF, ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET NOBOT",
+ BOT_SET_NOBOT_SYNTAX);
+ }
+ } else if (!stricmp(option, "SYMBIOSIS")) {
+ if (!stricmp(value, "ON")) {
+ ci->botflags |= BS_SYMBIOSIS;
+ notice_lang(s_BotServ, u, BOT_SET_SYMBIOSIS_ON, ci->name);
+ } else if (!stricmp(value, "OFF")) {
+ ci->botflags &= ~BS_SYMBIOSIS;
+ notice_lang(s_BotServ, u, BOT_SET_SYMBIOSIS_OFF, ci->name);
+ } else {
+ syntax_error(s_BotServ, u, "SET SYMBIOSIS",
+ BOT_SET_SYMBIOSIS_SYNTAX);
+ }
+ } else {
+ notice_help(s_BotServ, u, BOT_SET_UNKNOWN, option);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_kickcmd(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *option = strtok(NULL, " ");
+ char *value = strtok(NULL, " ");
+ char *ttb = strtok(NULL, " ");
+
+ ChannelInfo *ci;
+
+ if (readonly)
+ notice_lang(s_BotServ, u, BOT_KICK_DISABLED);
+ else if (!chan || !option || !value)
+ syntax_error(s_BotServ, u, "KICK", BOT_KICK_SYNTAX);
+ else if (stricmp(value, "ON") && stricmp(value, "OFF"))
+ syntax_error(s_BotServ, u, "KICK", BOT_KICK_SYNTAX);
+ else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if (!is_services_admin(u) && !check_access(u, ci, CA_SET))
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ else {
+ if (!stricmp(option, "BADWORDS")) {
+ if (!stricmp(value, "ON")) {
+ if (ttb) {
+ ci->ttb[TTB_BADWORDS] = atol(ttb);
+ if (ci->ttb[TTB_BADWORDS] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_BADWORDS] = 0;
+ ci->botflags |= BS_KICK_BADWORDS;
+ if (ci->ttb[TTB_BADWORDS])
+ notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_ON_BAN,
+ ci->ttb[TTB_BADWORDS]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_ON);
+ } else {
+ ci->botflags &= ~BS_KICK_BADWORDS;
+ notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_OFF);
+ }
+ } else if (!stricmp(option, "BOLDS")) {
+ if (!stricmp(value, "ON")) {
+ if (ttb) {
+ ci->ttb[TTB_BOLDS] = atol(ttb);
+ if (ci->ttb[TTB_BOLDS] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_BOLDS] = 0;
+ ci->botflags |= BS_KICK_BOLDS;
+ if (ci->ttb[TTB_BOLDS])
+ notice_lang(s_BotServ, u, BOT_KICK_BOLDS_ON_BAN,
+ ci->ttb[TTB_BOLDS]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_BOLDS_ON);
+ } else {
+ ci->botflags &= ~BS_KICK_BOLDS;
+ notice_lang(s_BotServ, u, BOT_KICK_BOLDS_OFF);
+ }
+ } else if (!stricmp(option, "CAPS")) {
+ if (!stricmp(value, "ON")) {
+ char *min = strtok(NULL, " ");
+ char *percent = strtok(NULL, " ");
+
+ if (ttb) {
+ ci->ttb[TTB_CAPS] = atol(ttb);
+ if (ci->ttb[TTB_CAPS] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_CAPS] = 0;
+
+ if (!min)
+ ci->capsmin = 10;
+ else
+ ci->capsmin = atol(min);
+ if (ci->capsmin < 1)
+ ci->capsmin = 10;
+
+ if (!percent)
+ ci->capspercent = 25;
+ else
+ ci->capspercent = atol(percent);
+ if (ci->capspercent < 1 || ci->capspercent > 100)
+ ci->capspercent = 25;
+
+ ci->botflags |= BS_KICK_CAPS;
+ if (ci->ttb[TTB_CAPS])
+ notice_lang(s_BotServ, u, BOT_KICK_CAPS_ON_BAN,
+ ci->capsmin, ci->capspercent,
+ ci->ttb[TTB_CAPS]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_CAPS_ON,
+ ci->capsmin, ci->capspercent);
+ } else {
+ ci->botflags &= ~BS_KICK_CAPS;
+ notice_lang(s_BotServ, u, BOT_KICK_CAPS_OFF);
+ }
+ } else if (!stricmp(option, "COLORS")) {
+ if (!stricmp(value, "ON")) {
+ if (ttb) {
+ ci->ttb[TTB_COLORS] = atol(ttb);
+ if (ci->ttb[TTB_COLORS] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_COLORS] = 0;
+ ci->botflags |= BS_KICK_COLORS;
+ if (ci->ttb[TTB_COLORS])
+ notice_lang(s_BotServ, u, BOT_KICK_COLORS_ON_BAN,
+ ci->ttb[TTB_COLORS]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_COLORS_ON);
+ } else {
+ ci->botflags &= ~BS_KICK_COLORS;
+ notice_lang(s_BotServ, u, BOT_KICK_COLORS_OFF);
+ }
+ } else if (!stricmp(option, "FLOOD")) {
+ if (!stricmp(value, "ON")) {
+ char *lines = strtok(NULL, " ");
+ char *secs = strtok(NULL, " ");
+
+ if (ttb) {
+ ci->ttb[TTB_FLOOD] = atol(ttb);
+ if (ci->ttb[TTB_FLOOD] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_FLOOD] = 0;
+
+ if (!lines)
+ ci->floodlines = 6;
+ else
+ ci->floodlines = atol(lines);
+ if (ci->floodlines < 2)
+ ci->floodlines = 6;
+
+ if (!secs)
+ ci->floodsecs = 10;
+ else
+ ci->floodsecs = atol(secs);
+ if (ci->floodsecs < 1 || ci->floodsecs > BSKeepData)
+ ci->floodsecs = 10;
+
+ ci->botflags |= BS_KICK_FLOOD;
+ if (ci->ttb[TTB_FLOOD])
+ notice_lang(s_BotServ, u, BOT_KICK_FLOOD_ON_BAN,
+ ci->floodlines, ci->floodsecs,
+ ci->ttb[TTB_FLOOD]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_FLOOD_ON,
+ ci->floodlines, ci->floodsecs);
+ } else {
+ ci->botflags &= ~BS_KICK_FLOOD;
+ notice_lang(s_BotServ, u, BOT_KICK_FLOOD_OFF);
+ }
+ } else if (!stricmp(option, "REPEAT")) {
+ if (!stricmp(value, "ON")) {
+ char *times = strtok(NULL, " ");
+
+ if (ttb) {
+ ci->ttb[TTB_REPEAT] = atol(ttb);
+ if (ci->ttb[TTB_REPEAT] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_REPEAT] = 0;
+
+ if (!times)
+ ci->repeattimes = 3;
+ else
+ ci->repeattimes = atol(times);
+ if (ci->repeattimes < 2)
+ ci->repeattimes = 3;
+
+ ci->botflags |= BS_KICK_REPEAT;
+ if (ci->ttb[TTB_REPEAT])
+ notice_lang(s_BotServ, u, BOT_KICK_REPEAT_ON_BAN,
+ ci->repeattimes, ci->ttb[TTB_REPEAT]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_REPEAT_ON,
+ ci->repeattimes);
+ } else {
+ ci->botflags &= ~BS_KICK_REPEAT;
+ notice_lang(s_BotServ, u, BOT_KICK_REPEAT_OFF);
+ }
+ } else if (!stricmp(option, "REVERSES")) {
+ if (!stricmp(value, "ON")) {
+ if (ttb) {
+ ci->ttb[TTB_REVERSES] = atol(ttb);
+ if (ci->ttb[TTB_REVERSES] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_REVERSES] = 0;
+ ci->botflags |= BS_KICK_REVERSES;
+ if (ci->ttb[TTB_REVERSES])
+ notice_lang(s_BotServ, u, BOT_KICK_REVERSES_ON_BAN,
+ ci->ttb[TTB_REVERSES]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_REVERSES_ON);
+ } else {
+ ci->botflags &= ~BS_KICK_REVERSES;
+ notice_lang(s_BotServ, u, BOT_KICK_REVERSES_OFF);
+ }
+ } else if (!stricmp(option, "UNDERLINES")) {
+ if (!stricmp(value, "ON")) {
+ if (ttb) {
+ ci->ttb[TTB_UNDERLINES] = atol(ttb);
+ if (ci->ttb[TTB_UNDERLINES] < 0) {
+ notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
+ return MOD_CONT;
+ }
+ } else
+ ci->ttb[TTB_UNDERLINES] = 0;
+ ci->botflags |= BS_KICK_UNDERLINES;
+ if (ci->ttb[TTB_UNDERLINES])
+ notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_ON_BAN,
+ ci->ttb[TTB_UNDERLINES]);
+ else
+ notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_ON);
+ } else {
+ ci->botflags &= ~BS_KICK_UNDERLINES;
+ notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_OFF);
+ }
+ } else
+ notice_help(s_BotServ, u, BOT_KICK_UNKNOWN, option);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int badwords_del_callback(User * u, int num, va_list args)
+{
+ BadWord *bw;
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *last = va_arg(args, int *);
+ if (num < 1 || num > ci->bwcount)
+ return 0;
+ *last = num;
+
+ bw = &ci->badwords[num - 1];
+ if (bw->word)
+ free(bw->word);
+ bw->word = NULL;
+ bw->in_use = 0;
+
+ return 1;
+}
+
+static int badwords_list(User * u, int index, ChannelInfo * ci,
+ int *sent_header)
+{
+ BadWord *bw = &ci->badwords[index];
+
+ if (!bw->in_use)
+ return 0;
+ if (!*sent_header) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_HEADER, ci->name);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_FORMAT, index + 1,
+ bw->word,
+ ((bw->type ==
+ BW_SINGLE) ? "(SINGLE)" : ((bw->type ==
+ BW_START) ? "(START)"
+ : ((bw->type ==
+ BW_END) ? "(END)" : "")))
+ );
+ return 1;
+}
+
+static int badwords_list_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *sent_header = va_arg(args, int *);
+ if (num < 1 || num > ci->bwcount)
+ return 0;
+ return badwords_list(u, num - 1, ci, sent_header);
+}
+
+static int do_badwords(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *word = strtok(NULL, "");
+ ChannelInfo *ci;
+ BadWord *bw;
+
+ int i;
+ int need_args = (cmd
+ && (!stricmp(cmd, "LIST") || !stricmp(cmd, "CLEAR")));
+
+ if (!cmd || (need_args ? 0 : !word)) {
+ syntax_error(s_BotServ, u, "BADWORDS", BOT_BADWORDS_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!check_access(u, ci, CA_BADWORDS)
+ && (!need_args || !is_services_admin(u))) {
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ } else if (stricmp(cmd, "ADD") == 0) {
+
+ char *opt, *pos;
+ int type = BW_ANY;
+
+ if (readonly) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
+ return MOD_CONT;
+ }
+
+ pos = strrchr(word, ' ');
+ if (pos) {
+ opt = pos + 1;
+ if (*opt) {
+ if (!stricmp(opt, "SINGLE"))
+ type = BW_SINGLE;
+ else if (!stricmp(opt, "START"))
+ type = BW_START;
+ else if (!stricmp(opt, "END"))
+ type = BW_END;
+ if (type != BW_ANY)
+ *pos = 0;
+ }
+ }
+
+ for (bw = ci->badwords, i = 0; i < ci->bwcount; bw++, i++) {
+ if (bw->word && ((BSCaseSensitive && (!strcmp(bw->word, word)))
+ || (!BSCaseSensitive
+ && (!stricmp(bw->word, word))))) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_ALREADY_EXISTS,
+ bw->word, ci->name);
+ return MOD_CONT;
+ }
+ }
+
+ for (i = 0; i < ci->bwcount; i++) {
+ if (!ci->badwords[i].in_use)
+ break;
+ }
+ if (i == ci->bwcount) {
+ if (i < BSBadWordsMax) {
+ ci->bwcount++;
+ ci->badwords =
+ srealloc(ci->badwords, sizeof(BadWord) * ci->bwcount);
+ } else {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_REACHED_LIMIT,
+ BSBadWordsMax);
+ return MOD_CONT;
+ }
+ }
+ bw = &ci->badwords[i];
+ bw->in_use = 1;
+ bw->word = sstrdup(word);
+ bw->type = type;
+
+ notice_lang(s_BotServ, u, BOT_BADWORDS_ADDED, bw->word, ci->name);
+
+ } else if (stricmp(cmd, "DEL") == 0) {
+
+ if (readonly) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
+ return MOD_CONT;
+ }
+
+ /* Special case: is it a number/list? Only do search if it isn't. */
+ if (isdigit(*word) && strspn(word, "1234567890,-") == strlen(word)) {
+ int count, deleted, last = -1;
+ deleted =
+ process_numlist(word, &count, badwords_del_callback, u, ci,
+ &last);
+ if (!deleted) {
+ if (count == 1) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_NO_SUCH_ENTRY,
+ last, ci->name);
+ } else {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_NO_MATCH,
+ ci->name);
+ }
+ } else if (deleted == 1) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED_ONE,
+ ci->name);
+ } else {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED_SEVERAL,
+ deleted, ci->name);
+ }
+ } else {
+ for (i = 0; i < ci->bwcount; i++) {
+ if (ci->badwords[i].in_use
+ && !stricmp(ci->badwords[i].word, word))
+ break;
+ }
+ if (i == ci->bwcount) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_NOT_FOUND, word,
+ chan);
+ return MOD_CONT;
+ }
+ bw = &ci->badwords[i];
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED, bw->word,
+ ci->name);
+ if (bw->word)
+ free(bw->word);
+ bw->word = NULL;
+ bw->in_use = 0;
+ }
+
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int sent_header = 0;
+
+ if (ci->bwcount == 0) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+ if (word && strspn(word, "1234567890,-") == strlen(word)) {
+ process_numlist(word, NULL, badwords_list_callback, u, ci,
+ &sent_header);
+ } else {
+ for (i = 0; i < ci->bwcount; i++) {
+ if (!(ci->badwords[i].in_use))
+ continue;
+ if (word && ci->badwords[i].word
+ && !match_wild_nocase(word, ci->badwords[i].word))
+ continue;
+ badwords_list(u, i, ci, &sent_header);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_BotServ, u, BOT_BADWORDS_NO_MATCH, chan);
+
+ } else if (stricmp(cmd, "CLEAR") == 0) {
+
+ if (readonly) {
+ notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
+ return MOD_CONT;
+ }
+
+ for (i = 0; i < ci->bwcount; i++)
+ if (ci->badwords[i].word)
+ free(ci->badwords[i].word);
+
+ free(ci->badwords);
+ ci->badwords = NULL;
+ ci->bwcount = 0;
+
+ notice_lang(s_BotServ, u, BOT_BADWORDS_CLEAR);
+
+ } else {
+ syntax_error(s_BotServ, u, "BADWORDS", BOT_BADWORDS_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_say(User * u)
+{
+ ChannelInfo *ci;
+
+ char *chan = strtok(NULL, " ");
+ char *text = strtok(NULL, "");
+
+ if (!chan || !text)
+ syntax_error(s_BotServ, u, "SAY", BOT_SAY_SYNTAX);
+ else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if (!ci->bi)
+ notice_help(s_BotServ, u, BOT_NOT_ASSIGNED);
+ else if (!ci->c || ci->c->usercount < BSMinUsers)
+ notice_lang(s_BotServ, u, BOT_NOT_ON_CHANNEL, ci->name);
+ else if (!check_access(u, ci, CA_SAY))
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ else {
+ if (text[0] != '\001') {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name, text);
+ ci->bi->lastmsg = time(NULL);
+ if (logchan && LogBot)
+ send_cmd(ci->bi->nick, "PRIVMSG %s :SAY %s %s %s",
+ LogChannel, u->nick, ci->name, text);
+ } else {
+ syntax_error(s_BotServ, u, "SAY", BOT_SAY_SYNTAX);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_act(User * u)
+{
+ ChannelInfo *ci;
+
+ char *chan = strtok(NULL, " ");
+ char *text = strtok(NULL, "");
+
+ if (!chan || !text)
+ syntax_error(s_BotServ, u, "ACT", BOT_ACT_SYNTAX);
+ else if (!(ci = cs_findchan(chan)))
+ notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
+ else if (ci->flags & CI_VERBOTEN)
+ notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
+ else if (!ci->bi)
+ notice_help(s_BotServ, u, BOT_NOT_ASSIGNED);
+ else if (!ci->c || ci->c->usercount < BSMinUsers)
+ notice_lang(s_BotServ, u, BOT_NOT_ON_CHANNEL, ci->name);
+ else if (!check_access(u, ci, CA_SAY))
+ notice_lang(s_BotServ, u, ACCESS_DENIED);
+ else {
+ send_cmd(ci->bi->nick, "PRIVMSG %s :%cACTION %s%c", ci->name, 1,
+ text, 1);
+ ci->bi->lastmsg = time(NULL);
+ if (logchan && LogBot)
+ send_cmd(ci->bi->nick, "PRIVMSG %s :ACT %s %s %s", LogChannel,
+ u->nick, ci->name, text);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+/**
+ * Normalize buffer stripping control characters and colors
+ * @param A string to be parsed for control and color codes
+ * @return A string stripped of control and color codes
+ */
+char *normalizeBuffer(char *buf)
+{
+ char *newbuf;
+ int i, len, j = 0;
+
+ len = strlen(buf);
+ newbuf = (char *) malloc((len + 1) * sizeof(char));
+
+ for (i = 0; i < len; i++) {
+ switch (buf[i]) {
+ /* Bold ctrl char */
+ case 2:
+ break;
+ /* Color ctrl char */
+ case 3:
+ /* If the next character is a digit, its also removed */
+ if (isdigit(buf[i + 1])) {
+ i++;
+
+ /* Check for background color code
+ * and remove it as well
+ */
+ if (buf[i + 1] == ',') {
+ i++;
+
+ if (isdigit(buf[i + 1]))
+ i++;
+ }
+ }
+
+ break;
+
+ /* Reverse ctrl char */
+ case 22:
+ break;
+ /* Underline ctrl char */
+ case 31:
+ break;
+ /* A valid char gets copied into the new buffer */
+ default:
+ newbuf[j] = buf[i];
+ j++;
+ }
+ }
+
+ /* Terminate the string */
+ newbuf[j] = 0;
+
+ return (newbuf);
+}
diff --git a/src/channels.c b/src/channels.c
new file mode 100644
index 000000000..9fff738b4
--- /dev/null
+++ b/src/channels.c
@@ -0,0 +1,1633 @@
+/* Channel-handling routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "language.h"
+
+Channel *chanlist[1024];
+
+#define HASH(chan) ((chan)[1] ? ((chan)[1]&31)<<5 | ((chan)[2]&31) : 0)
+
+static void add_ban(Channel * chan, char *mask);
+#ifdef HAS_EXCEPT
+static void add_exception(Channel * chan, char *mask);
+#endif
+static void chan_adduser2(User * user, Channel * c);
+static Channel *chan_create(const char *chan);
+static void chan_delete(Channel * c);
+static void del_ban(Channel * chan, char *mask);
+#ifdef HAS_EXCEPT
+static void del_exception(Channel * chan, char *mask);
+#endif
+#ifdef HAS_FMODE
+static char *get_flood(Channel * chan);
+#endif
+static char *get_key(Channel * chan);
+static char *get_limit(Channel * chan);
+#ifdef HAS_LMODE
+static char *get_redirect(Channel * chan);
+#endif
+static Channel *join_user_update(User * user, Channel * chan, char *name);
+#ifdef HAS_FMODE
+static void set_flood(Channel * chan, char *value);
+#endif
+static void set_key(Channel * chan, char *value);
+static void set_limit(Channel * chan, char *value);
+#ifdef HAS_LMODE
+static void set_redirect(Channel * chan, char *value);
+#endif
+void do_mass_mode(char *modes);
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+
+CBMode cbmodes[128] = {
+ { 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 }, { 0 },
+ { 0 },
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { CMODE_A, CBM_NO_USER_MLOCK, NULL, NULL },
+#else
+ { 0 }, /* A */
+#endif
+ { 0 }, /* B */
+#if defined(IRC_UNREAL) || defined(IRC_RAGE2)
+ { CMODE_C, 0, NULL, NULL },
+#else
+ { 0 }, /* C */
+#endif
+ { 0 }, /* D */
+ { 0 }, /* E */
+ { 0 }, /* F */
+#ifdef IRC_UNREAL
+ { CMODE_G, 0, NULL, NULL },
+ { CMODE_H, CBM_NO_USER_MLOCK, NULL, NULL },
+#else
+ { 0 }, /* G */
+ { 0 }, /* H */
+#endif
+#ifdef IRC_ULTIMATE
+ { CMODE_I },
+#else
+ { 0 }, /* I */
+#endif
+ { 0 }, /* J */
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL)
+ { CMODE_K, 0, NULL, NULL },
+#else
+ { 0 }, /* K */
+#endif
+#ifdef HAS_LMODE
+ { CMODE_L, 0, set_redirect, cs_set_redirect },
+#else
+ { 0 }, /* L */
+#endif
+#ifdef IRC_BAHAMUT
+ { CMODE_M },
+#else
+ { 0 }, /* M */
+#endif
+#if defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined (IRC_PTLINK) || defined(IRC_RAGE2)
+ { CMODE_N, 0, NULL, NULL },
+#else
+ { 0 }, /* N */
+#endif
+#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { CMODE_O, CBM_NO_USER_MLOCK, NULL, NULL },
+#else
+ { 0 }, /* O */
+#endif
+ { 0 }, /* P */
+#ifdef IRC_UNREAL
+ { CMODE_Q, 0, NULL, NULL },
+#else
+ { 0 }, /* Q */
+#endif
+#ifndef IRC_HYBRID
+ { CMODE_R, 0, NULL, NULL }, /* R */
+#else
+ { 0 },
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined (IRC_PTLINK) || defined(IRC_RAGE2)
+ { CMODE_S, 0, NULL, NULL },
+#else
+ { 0 }, /* S */
+#endif
+ { 0 }, /* T */
+ { 0 }, /* U */
+#ifdef IRC_UNREAL
+ { CMODE_V, 0, NULL, NULL },
+#else
+ { 0 }, /* V */
+#endif
+ { 0 }, /* W */
+ { 0 }, /* X */
+ { 0 }, /* Y */
+ { 0 }, /* Z */
+ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 },
+#ifdef IRC_HYBRID
+ { CMODE_a, 0, NULL, NULL },
+#else
+ { 0 }, /* a */
+#endif
+ { 0 }, /* b */
+#if defined(IRC_BAHAMUT) || defined(IRC_UNREAL) || defined (IRC_PTLINK)
+ { CMODE_c, 0, NULL, NULL },
+#else
+ { 0 }, /* c */
+#endif
+#ifdef IRC_PTLINK
+ { CMODE_d, 0, NULL, NULL },
+#else
+ { 0 }, /* d */
+#endif
+ { 0 }, /* e */
+#ifdef HAS_FMODE
+ { CMODE_f, 0, set_flood, cs_set_flood },
+#else
+ { 0 }, /* f */
+#endif
+ { 0 }, /* g */
+ { 0 }, /* h */
+ { CMODE_i, 0, NULL, NULL },
+ { 0 }, /* j */
+ { CMODE_k, 0, set_key, cs_set_key },
+ { CMODE_l, CBM_MINUS_NO_ARG, set_limit, cs_set_limit },
+ { CMODE_m, 0, NULL, NULL },
+ { CMODE_n, 0, NULL, NULL },
+ { 0 }, /* o */
+ { CMODE_p, 0, NULL, NULL },
+#ifdef IRC_PTLINK
+ { CMODE_q, 0, NULL, NULL },
+#else
+ { 0 }, /* q */
+#endif
+#ifndef IRC_HYBRID
+ { CMODE_r, CBM_NO_MLOCK, NULL, NULL },
+#else
+ { 0 },
+#endif
+ { CMODE_s, 0, NULL, NULL },
+ { CMODE_t, 0, NULL, NULL },
+#ifdef IRC_UNREAL
+ { CMODE_u, 0, NULL, NULL },
+#else
+ { 0 },
+#endif
+ { 0 }, /* v */
+ { 0 }, /* w */
+#ifdef IRC_ULTIMATE
+ { CMODE_x },
+#else
+ { 0 }, /* x */
+#endif
+ { 0 }, /* y */
+#ifdef IRC_UNREAL
+ { CMODE_z, 0, NULL, NULL },
+#else
+ { 0 }, /* z */
+#endif
+ { 0 }, { 0 }, { 0 }, { 0 }
+};
+
+CBModeInfo cbmodeinfos[] = {
+#if defined(IRC_HYBRID)
+ { 'a', CMODE_a, 0, NULL, NULL },
+#endif
+#if defined(IRC_BAHAMUT) || defined(IRC_UNREAL) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+ { 'c', CMODE_c, 0, NULL, NULL },
+#endif
+#if defined(IRC_PTLINK)
+ { 'd', CMODE_d, 0, NULL, NULL },
+#endif
+#ifdef HAS_FMODE
+ { 'f', CMODE_f, 0, get_flood, cs_get_flood },
+#endif
+ { 'i', CMODE_i, 0, NULL, NULL },
+ { 'k', CMODE_k, 0, get_key, cs_get_key },
+ { 'l', CMODE_l, CBM_MINUS_NO_ARG, get_limit, cs_get_limit },
+ { 'm', CMODE_m, 0, NULL, NULL },
+ { 'n', CMODE_n, 0, NULL, NULL },
+ { 'p', CMODE_p, 0, NULL, NULL },
+#ifdef IRC_PTLINK
+ { 'q', CMODE_q, 0, NULL, NULL },
+#endif
+#ifndef IRC_HYBRID
+ { 'r', CMODE_r, 0, NULL, NULL },
+#endif
+ { 's', CMODE_s, 0, NULL, NULL },
+ { 't', CMODE_t, 0, NULL, NULL },
+#ifdef IRC_UNREAL
+ { 'u', CMODE_u, 0, NULL, NULL },
+#endif
+#ifdef IRC_ULTIMATE
+ { 'x', CMODE_x, 0, NULL, NULL },
+#endif
+#ifdef IRC_UNREAL
+ { 'z', CMODE_z, 0, NULL, NULL },
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+ { 'A', CMODE_A, 0, NULL, NULL },
+#endif
+#if defined(IRC_UNREAL) || defined(IRC_RAGE2)
+ { 'C', CMODE_C, 0, NULL, NULL },
+#endif
+#ifdef IRC_UNREAL
+ { 'G', CMODE_G, 0, NULL, NULL },
+ { 'H', CMODE_H, 0, NULL, NULL },
+#endif
+#ifdef IRC_ULTIMATE
+ { 'I', CMODE_I, 0, NULL, NULL },
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_PTLINK) || defined(IRC_ULTIMATE3)
+ { 'K', CMODE_K, 0, NULL, NULL },
+#endif
+#ifdef HAS_LMODE
+ { 'L', CMODE_L, 0, get_redirect, cs_get_redirect },
+#endif
+#ifdef IRC_BAHAMUT
+#ifndef IRC_ULTIMATE3
+ { 'M', CMODE_M, 0, NULL, NULL },
+#endif
+#endif
+#if defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+ { 'N', CMODE_N, 0, NULL, NULL },
+#endif
+#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { 'O', CMODE_O, 0, NULL, NULL },
+#endif
+#ifdef IRC_UNREAL
+ { 'Q', CMODE_Q, 0, NULL, NULL },
+#endif
+#ifndef IRC_HYBRID
+ { 'R', CMODE_R, 0, NULL, NULL },
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+ { 'S', CMODE_S, 0, NULL, NULL },
+#endif
+#ifdef IRC_UNREAL
+ { 'V', CMODE_V, 0, NULL, NULL },
+#endif
+ { 0 }
+};
+
+static CMMode cmmodes[128] = {
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL },
+ { NULL },
+ { add_ban, del_ban },
+ { NULL },
+ { NULL },
+#ifdef HAS_EXCEPT
+ { add_exception, del_exception },
+#endif
+ { NULL },
+ { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
+ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }
+};
+
+#if defined(IRC_BAHAMUT) || defined(IRC_HYBRID) || defined(IRC_PTLINK)
+
+static char csmodes[128] = {
+ 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,
+ #if defined(IRC_ULTIMATE3) || defined(IRC_HYBRID)
+ 'a', /* (33) ! Channel Admins */
+ #else
+ 0,
+ #endif
+ 0, 0, 0,
+ #if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ 'h', /* (37) % Channel halfops */
+ #else
+ 0,
+ #endif
+ 0, 0, 0, 0,
+ #if defined(IRC_RAGE2)
+ 'a', /* * Channel Admins */
+ #else
+ 0,
+ #endif
+
+ 'v', 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 'o', 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
+};
+
+#endif
+
+static CUMode cumodes[128] = {
+ { 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 }, { 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 },
+
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+ { CUS_PROTECT, CUF_PROTECT_BOTSERV, check_valid_op },
+#else
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { CUS_PROTECT, CUF_PROTECT_BOTSERV, check_valid_admin },
+#else
+ { 0 }, /* a */
+#endif
+#endif
+ { 0 }, /* b */
+ { 0 }, /* c */
+ { 0 }, /* d */
+ { 0 }, /* e */
+ { 0 }, /* f */
+ { 0 }, /* g */
+#ifdef HAS_HALFOP
+ { CUS_HALFOP, 0, check_valid_op },
+#else
+ { 0 }, /* h */
+#endif
+ { 0 }, /* i */
+ { 0 }, /* j */
+ { 0 }, /* k */
+ { 0 }, /* l */
+ { 0 }, /* m */
+ { 0 }, /* n */
+ { CUS_OP, CUF_PROTECT_BOTSERV, check_valid_op },
+ { 0 }, /* p */
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+ { CUS_OWNER, 0, check_valid_op },
+#else
+ { 0 }, /* q */
+#endif
+ { 0 }, /* r */
+ { 0 }, /* s */
+ { 0 }, /* t */
+ { 0 }, /* u */
+ { CUS_VOICE, 0, NULL },
+ { 0 }, /* w */
+ { 0 }, /* x */
+ { 0 }, /* y */
+ { 0 }, /* z */
+ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }
+};
+
+/* *INDENT-ON* */
+/*************************************************************************/
+/**************************** External Calls *****************************/
+/*************************************************************************/
+
+void chan_deluser(User * user, Channel * c)
+{
+ struct c_userlist *u;
+
+ if (c->ci)
+ update_cs_lastseen(user, c->ci);
+
+ for (u = c->users; u && u->user != user; u = u->next);
+ if (!u)
+ return;
+
+ if (u->ud) {
+ if (u->ud->lastline)
+ free(u->ud->lastline);
+ free(u->ud);
+ }
+
+ if (u->next)
+ u->next->prev = u->prev;
+ if (u->prev)
+ u->prev->next = u->next;
+ else
+ c->users = u->next;
+ free(u);
+ c->usercount--;
+
+ if (s_BotServ && c->ci && c->ci->bi && c->usercount == BSMinUsers - 1) {
+ send_cmd(c->ci->bi->nick, "PART %s", c->name);
+ }
+
+ if (!c->users)
+ chan_delete(c);
+}
+
+/*************************************************************************/
+
+/* Returns a fully featured binary modes string. If complete is 0, the
+ * eventual parameters won't be added to the string.
+ */
+
+char *chan_get_modes(Channel * chan, int complete, int plus)
+{
+ static char res[BUFSIZE];
+ char *end = res;
+
+ if (chan->mode) {
+ int n = 0;
+ CBModeInfo *cbmi = cbmodeinfos;
+
+ do {
+ if (chan->mode & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+
+ if (complete) {
+ cbmi = cbmodeinfos;
+
+ do {
+ if (cbmi->getvalue && (chan->mode & cbmi->flag) &&
+ (plus || !(cbmi->flags & CBM_MINUS_NO_ARG))) {
+ char *value = cbmi->getvalue(chan);
+
+ if (value) {
+ *end++ = ' ';
+ while (*value)
+ *end++ = *value++;
+ }
+ }
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+ }
+ }
+
+ *end = 0;
+
+ return res;
+}
+
+/*************************************************************************/
+
+/* Retrieves the status of an user on a channel */
+
+int chan_get_user_status(Channel * chan, User * user)
+{
+ struct u_chanlist *uc;
+
+ for (uc = user->chans; uc; uc = uc->next)
+ if (uc->chan == chan)
+ return uc->status;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Has the given user the given status on the given channel? :p */
+
+int chan_has_user_status(Channel * chan, User * user, int16 status)
+{
+ struct u_chanlist *uc;
+
+ for (uc = user->chans; uc; uc = uc->next)
+ if (uc->chan == chan)
+ return (uc->status & status);
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Remove the status of an user on a channel */
+
+void chan_remove_user_status(Channel * chan, User * user, int16 status)
+{
+ struct u_chanlist *uc;
+
+ for (uc = user->chans; uc; uc = uc->next) {
+ if (uc->chan == chan) {
+ uc->status &= ~status;
+ break;
+ }
+ }
+}
+
+/*************************************************************************/
+
+void chan_set_modes(const char *source, Channel * chan, int ac, char **av,
+ int check)
+{
+ int add = 1;
+ int servermode = !!strchr(source, '.');
+ char *modes = av[0], mode;
+ CBMode *cbm;
+ CMMode *cmm;
+ CUMode *cum;
+
+ if (debug)
+ alog("debug: Changing modes for %s to %s", chan->name,
+ merge_args(ac, av));
+
+ ac--;
+
+ while ((mode = *modes++)) {
+
+ switch (mode) {
+ case '+':
+ add = 1;
+ continue;
+ case '-':
+ add = 0;
+ continue;
+ }
+
+ if (((int) mode) < 0) {
+ if (debug)
+ alog("Debug: Malformed mode detected on %s.", chan->name);
+ continue;
+ }
+
+ if ((cum = &cumodes[(int) mode])->status != 0) {
+ User *user;
+
+ if (ac == 0) {
+ alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
+ continue;
+ }
+ ac--;
+ av++;
+
+ if ((cum->flags & CUF_PROTECT_BOTSERV) && !add) {
+ BotInfo *bi;
+
+ if ((bi = findbot(*av))) {
+ send_mode(bi->nick, chan->name, "+%c %s", mode,
+ bi->nick);
+ continue;
+ }
+ }
+
+ if (!(user = finduser(*av))) {
+ alog("channel: MODE %s %c%c for nonexistent user %s",
+ chan->name, (add ? '+' : '-'), mode, *av);
+ continue;
+ }
+
+ if (debug)
+ alog("debug: Setting %c%c on %s for %s", (add ? '+' : '-'),
+ mode, chan->name, user->nick);
+
+ if (add) {
+ if (check && cum->is_valid
+ && !cum->is_valid(user, chan, servermode))
+ continue;
+ chan_set_user_status(chan, user, cum->status);
+ } else {
+ chan_remove_user_status(chan, user, cum->status);
+ }
+ } else if ((cbm = &cbmodes[(int) mode])->flag != 0) {
+ if (add)
+ chan->mode |= cbm->flag;
+ else
+ chan->mode &= ~cbm->flag;
+
+ if (cbm->setvalue) {
+ if (add || !(cbm->flags & CBM_MINUS_NO_ARG)) {
+ if (ac == 0) {
+ alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
+ continue;
+ }
+ ac--;
+ av++;
+ }
+ cbm->setvalue(chan, add ? *av : NULL);
+ }
+ } else if ((cmm = &cmmodes[(int) mode])->addmask) {
+ if (ac == 0) {
+ alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name);
+ continue;
+ }
+
+ ac--;
+ av++;
+ add ? cmm->addmask(chan, *av) : cmm->delmask(chan, *av);
+ }
+ }
+
+ if (check)
+ check_modes(chan);
+}
+
+/*************************************************************************/
+
+/* Set the status of an user on a channel */
+
+void chan_set_user_status(Channel * chan, User * user, int16 status)
+{
+ struct u_chanlist *uc;
+
+ if (HelpChannel && status == CUS_OP
+ && !stricmp(chan->name, HelpChannel))
+ change_user_mode(user, "+h", NULL);
+
+ for (uc = user->chans; uc; uc = uc->next) {
+ if (uc->chan == chan) {
+ uc->status |= status;
+ break;
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Return the Channel structure corresponding to the named channel, or NULL
+ * if the channel was not found. chan is assumed to be non-NULL and valid
+ * (i.e. pointing to a channel name of 2 or more characters). */
+
+Channel *findchan(const char *chan)
+{
+ Channel *c;
+
+ if (debug >= 3)
+ alog("debug: findchan(%p)", chan);
+ c = chanlist[HASH(chan)];
+ while (c) {
+ if (stricmp(c->name, chan) == 0)
+ return c;
+ c = c->next;
+ }
+ if (debug >= 3)
+ alog("debug: findchan(%s) -> %p", chan, c);
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Iterate over all channels in the channel list. Return NULL at end of
+ * list.
+ */
+
+static Channel *current;
+static int next_index;
+
+Channel *firstchan(void)
+{
+ next_index = 0;
+ while (next_index < 1024 && current == NULL)
+ current = chanlist[next_index++];
+ if (debug >= 3)
+ alog("debug: firstchan() returning %s",
+ current ? current->name : "NULL (end of list)");
+ return current;
+}
+
+Channel *nextchan(void)
+{
+ if (current)
+ current = current->next;
+ if (!current && next_index < 1024) {
+ while (next_index < 1024 && current == NULL)
+ current = chanlist[next_index++];
+ }
+ if (debug >= 3)
+ alog("debug: nextchan() returning %s",
+ current ? current->name : "NULL (end of list)");
+ return current;
+}
+
+/*************************************************************************/
+
+/* Return statistics. Pointers are assumed to be valid. */
+
+void get_channel_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ Channel *chan;
+ struct c_userlist *cu;
+ BanData *bd;
+ int i, j;
+
+ for (i = 0; i < 1024; i++) {
+ for (chan = chanlist[i]; chan; chan = chan->next) {
+ count++;
+ mem += sizeof(*chan);
+ if (chan->topic)
+ mem += strlen(chan->topic) + 1;
+ if (chan->key)
+ mem += strlen(chan->key) + 1;
+#ifdef HAS_FMODE
+ if (chan->flood)
+ mem += strlen(chan->flood) + 1;
+#endif
+#ifdef HAS_LMODE
+ if (chan->redirect)
+ mem += strlen(chan->redirect) + 1;
+#endif
+ mem += sizeof(char *) * chan->bansize;
+ for (j = 0; j < chan->bancount; j++) {
+ if (chan->bans[j])
+ mem += strlen(chan->bans[j]) + 1;
+ }
+#ifdef HAS_EXCEPT
+ mem += sizeof(char *) * chan->exceptsize;
+ for (j = 0; j < chan->exceptcount; j++) {
+ if (chan->excepts[j])
+ mem += strlen(chan->excepts[j]) + 1;
+ }
+#endif
+ for (cu = chan->users; cu; cu = cu->next) {
+ mem += sizeof(*cu);
+ if (cu->ud) {
+ mem += sizeof(*cu->ud);
+ if (cu->ud->lastline)
+ mem += strlen(cu->ud->lastline) + 1;
+ }
+ }
+ for (bd = chan->bd; bd; bd = bd->next) {
+ if (bd->mask)
+ mem += strlen(bd->mask) + 1;
+ mem += sizeof(*bd);
+ }
+ }
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+
+/* Is the given nick on the given channel? */
+
+int is_on_chan(Channel * c, User * u)
+{
+ struct u_chanlist *uc;
+
+ for (uc = u->chans; uc; uc = uc->next)
+ if (uc->chan == c)
+ return 1;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Is the given nick on the given channel?
+ This function supports links. */
+
+User *nc_on_chan(Channel * c, NickCore * nc)
+{
+ struct c_userlist *u;
+
+ if (!c || !nc)
+ return NULL;
+
+ for (u = c->users; u; u = u->next) {
+ if (u->user->na && u->user->na->nc == nc
+ && nick_recognized(u->user))
+ return u->user;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+/*************************** Message Handling ****************************/
+/*************************************************************************/
+
+/* Handle a JOIN command.
+ * av[0] = channels to join
+ */
+
+void do_join(const char *source, int ac, char **av)
+{
+ User *user;
+ char *s, *t;
+ struct u_chanlist *c, *nextc;
+
+ user = finduser(source);
+ if (!user) {
+ alog("user: JOIN from nonexistent user %s: %s", source,
+ merge_args(ac, av));
+ return;
+ }
+
+ t = av[0];
+ while (*(s = t)) {
+ t = s + strcspn(s, ",");
+ if (*t)
+ *t++ = 0;
+ if (debug)
+ alog("debug: %s joins %s", source, s);
+
+ if (*s == '0') {
+ c = user->chans;
+ while (c) {
+ nextc = c->next;
+ chan_deluser(user, c->chan);
+ free(c);
+ c = nextc;
+ }
+ user->chans = NULL;
+ continue;
+ }
+
+ /* Make sure check_kick comes before chan_adduser, so banned users
+ * don't get to see things like channel keys. */
+ if (check_kick(user, s))
+ continue;
+
+/* chan_adduser(user, s); */
+ join_user_update(user, findchan(s), s);
+
+
+/* c = scalloc(sizeof(*c), 1);
+ c->next = user->chans;
+ if (user->chans)
+ user->chans->prev = c;
+ user->chans = c;
+ c->chan = findchan(s); */
+ }
+}
+
+/*************************************************************************/
+
+/* Handle a KICK command.
+ * av[0] = channel
+ * av[1] = nick(s) being kicked
+ * av[2] = reason
+ */
+
+void do_kick(const char *source, int ac, char **av)
+{
+ BotInfo *bi;
+ ChannelInfo *ci;
+ User *user;
+ char *s, *t;
+ struct u_chanlist *c;
+
+ t = av[1];
+ while (*(s = t)) {
+ t = s + strcspn(s, ",");
+ if (*t)
+ *t++ = 0;
+
+ /* If it is the bot that is being kicked, we make it rejoin the
+ * channel and stop immediately.
+ * --lara
+ */
+ if (s_BotServ && (bi = findbot(s)) && (ci = cs_findchan(av[0]))) {
+ bot_join(ci);
+ continue;
+ }
+
+ user = finduser(s);
+ if (!user) {
+ alog("user: KICK for nonexistent user %s on %s: %s", s, av[0],
+ merge_args(ac - 2, av + 2));
+ continue;
+ }
+ if (debug)
+ alog("debug: kicking %s from %s", s, av[0]);
+ for (c = user->chans; c && stricmp(av[0], c->chan->name) != 0;
+ c = c->next);
+ if (c) {
+ chan_deluser(user, c->chan);
+ if (c->next)
+ c->next->prev = c->prev;
+ if (c->prev)
+ c->prev->next = c->next;
+ else
+ user->chans = c->next;
+ free(c);
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Handle a PART command.
+ * av[0] = channels to leave
+ * av[1] = reason (optional)
+ */
+
+void do_part(const char *source, int ac, char **av)
+{
+ User *user;
+ char *s, *t;
+ struct u_chanlist *c;
+
+ user = finduser(source);
+ if (!user) {
+ alog("user: PART from nonexistent user %s: %s", source,
+ merge_args(ac, av));
+ return;
+ }
+ t = av[0];
+ while (*(s = t)) {
+ t = s + strcspn(s, ",");
+ if (*t)
+ *t++ = 0;
+ if (debug)
+ alog("debug: %s leaves %s", source, s);
+ for (c = user->chans; c && stricmp(s, c->chan->name) != 0;
+ c = c->next);
+ if (c) {
+ if (!c->chan) {
+ alog("user: BUG parting %s: channel entry found but c->chan NULL", s);
+ return;
+ }
+ chan_deluser(user, c->chan);
+ if (c->next)
+ c->next->prev = c->prev;
+ if (c->prev)
+ c->prev->next = c->next;
+ else
+ user->chans = c->next;
+ free(c);
+ }
+ }
+}
+
+/*************************************************************************/
+
+#if defined(IRC_BAHAMUT) || defined(IRC_HYBRID) || defined(IRC_PTLINK)
+
+/* Handle a SJOIN command.
+
+ On channel creation, syntax is:
+
+ av[0] = timestamp
+ av[1] = channel name
+ av[2|3|4] = modes \ depends of whether the modes k and l
+ av[3|4|5] = users / are set or not.
+
+ When a single user joins an (existing) channel, it is:
+
+ av[0] = timestamp
+ av[1] = user
+
+*/
+
+void do_sjoin(const char *source, int ac, char **av)
+{
+ Channel *c;
+ User *user;
+
+ int is_sqlined = 0;
+
+ /* Double check to avoid unknown modes that need parameters */
+ if (ac >= 4 && ac <= 6) {
+ char *s, *end, cubuf[CHAN_MAX_SYMBOLS + 2], *end2,
+ *cumodes[CHAN_MAX_SYMBOLS + 1];
+
+ c = findchan(av[1]);
+#ifndef IRC_HYBRID
+#ifndef IRC_PTLINK
+ if (!c)
+ is_sqlined = check_chan_sqline(av[1]);
+#endif
+#endif
+
+ cubuf[0] = '+';
+ cumodes[0] = cubuf;
+
+ /* We make all the users join */
+ s = av[ac - 1]; /* Users are always the last element */
+
+ while (*s) {
+ end = strchr(s, ' ');
+ if (end)
+ *end = 0;
+
+ end2 = cubuf + 1;
+ while (csmodes[(int) *s] != 0)
+ *end2++ = csmodes[(int) *s++];
+ *end2 = 0;
+
+ user = finduser(s);
+ if (!user) {
+ alog("user: SJOIN for nonexistent user %s on %s", s,
+ av[1]);
+ return;
+ }
+
+ if (is_sqlined && !is_oper(user)) {
+ send_cmd(s_OperServ, "KICK %s %s :Q-Lined", av[1], s);
+ } else {
+ if (!check_kick(user, av[1])) {
+ /* Make the user join; if the channel does not exist it
+ * will be created there. This ensures that the channel
+ * is not created to be immediately destroyed, and
+ * that the locked key or topic is not shown to anyone
+ * who joins the channel when empty.
+ */
+ c = join_user_update(user, c, av[1]);
+
+ /* We update user mode on the channel */
+ if (end2 - cubuf > 1) {
+ int i;
+
+ for (i = 1; i < end2 - cubuf; i++)
+ cumodes[i] = user->nick;
+ chan_set_modes(source, c, 1 + (end2 - cubuf - 1),
+ cumodes, 1);
+ }
+ }
+ }
+
+ if (!end)
+ break;
+ s = end + 1;
+ }
+
+ if (c) {
+ /* Set the timestamp */
+ c->creation_time = strtoul(av[0], NULL, 10);
+ /* We now update the channel mode. */
+ chan_set_modes(source, c, ac - 3, &av[2], 1);
+ }
+ } else if (ac == 2) {
+ user = finduser(source);
+ if (!user) {
+ alog("user: SJOIN for nonexistent user %s on %s", source,
+ av[1]);
+ return;
+ }
+
+ if (check_kick(user, av[1]))
+ return;
+
+ c = findchan(av[1]);
+#ifndef IRC_HYBRID
+#ifndef IRC_PTLINK
+ if (!c)
+ is_sqlined = check_chan_sqline(av[1]);
+#endif
+#endif
+ if (is_sqlined && !is_oper(user)) {
+ send_cmd(s_OperServ, "KICK %s %s :Q-Lined", av[1], user->nick);
+ } else {
+ c = join_user_update(user, c, av[1]);
+ c->creation_time = strtoul(av[0], NULL, 10);
+ }
+ }
+}
+
+#endif
+
+/*************************************************************************/
+
+/* Handle a channel MODE command. */
+
+void do_cmode(const char *source, int ac, char **av)
+{
+ Channel *chan;
+ ChannelInfo *ci = NULL;
+#ifdef IRC_BAHAMUT
+ int i;
+ char *t;
+
+ /* TSMODE for bahamut - leave this code out to break MODEs. -GD */
+ if (uplink_capab & CAPAB_TSMODE) {
+ for (i = 0; i < strlen(av[1]); i++) {
+ if (!isdigit(av[1][i]))
+ break;
+ }
+ if (av[1][i] == '\0') {
+ /* We have a valid TS field in av[1] now, so we can strip it off */
+ /* After we swap av[0] and av[1] ofcourse to not break stuff! :) */
+ t = av[0];
+ av[0] = av[1];
+ av[1] = t;
+ ac--;
+ av++;
+ } else {
+ alog("TSMODE enabled but MODE has no valid TS");
+ }
+ }
+#endif
+
+ chan = findchan(av[0]);
+ if (!chan) {
+ ci = cs_findchan(av[0]);
+ if (!(ci && (ci->flags & CI_VERBOTEN)))
+ alog("channel: MODE %s for nonexistent channel %s",
+ merge_args(ac - 1, av + 1), av[0]);
+ return;
+ }
+
+ /* This shouldn't trigger on +o, etc. */
+ if (strchr(source, '.') && !av[1][strcspn(av[1], "bovahq")]) {
+ if (time(NULL) != chan->server_modetime) {
+ chan->server_modecount = 0;
+ chan->server_modetime = time(NULL);
+ }
+ chan->server_modecount++;
+ }
+
+ ac--;
+ av++;
+ chan_set_modes(source, chan, ac, av, 1);
+}
+
+/*************************************************************************/
+
+/* Handle a TOPIC command. */
+
+void do_topic(const char *source, int ac, char **av)
+{
+ Channel *c = findchan(av[0]);
+ time_t topic_time = strtoul(av[2], NULL, 10);
+
+ if (!c) {
+ alog("channel: TOPIC %s for nonexistent channel %s",
+ merge_args(ac - 1, av + 1), av[0]);
+ return;
+ }
+
+ if (check_topiclock(c, topic_time))
+ return;
+
+ if (c->topic) {
+ free(c->topic);
+ c->topic = NULL;
+ }
+ if (ac > 3 && *av[3])
+ c->topic = sstrdup(av[3]);
+
+ strscpy(c->topic_setter, av[1], sizeof(c->topic_setter));
+ c->topic_time = topic_time;
+
+ record_topic(av[0]);
+}
+
+/*************************************************************************/
+/**************************** Internal Calls *****************************/
+/*************************************************************************/
+
+static void add_ban(Channel * chan, char *mask)
+{
+ if (s_BotServ && BSSmartJoin && chan->ci && chan->ci->bi
+ && chan->usercount >= BSMinUsers) {
+ char botmask[BUFSIZE];
+ BotInfo *bi = chan->ci->bi;
+
+ snprintf(botmask, sizeof(botmask), "%s!%s@%s", bi->nick, bi->user,
+ bi->host);
+ if (match_wild_nocase(mask, botmask)) {
+ send_mode(bi->nick, chan->name, "-b %s", mask);
+ return;
+ }
+ }
+
+ if (chan->bancount >= chan->bansize) {
+ chan->bansize += 8;
+ chan->bans = srealloc(chan->bans, sizeof(char *) * chan->bansize);
+ }
+ chan->bans[chan->bancount++] = sstrdup(mask);
+
+ if (debug)
+ alog("debug: Added ban %s to channel %s", mask, chan->name);
+}
+
+/*************************************************************************/
+
+#ifdef HAS_EXCEPT
+
+static void add_exception(Channel * chan, char *mask)
+{
+ if (chan->exceptcount >= chan->exceptsize) {
+ chan->exceptsize += 8;
+ chan->excepts =
+ srealloc(chan->excepts, sizeof(char *) * chan->exceptsize);
+ }
+ chan->excepts[chan->exceptcount++] = sstrdup(mask);
+
+ if (debug)
+ alog("debug: Added except %s to channel %s", mask, chan->name);
+}
+
+#endif
+
+/*************************************************************************/
+
+/* Add/remove a user to/from a channel, creating or deleting the channel as
+ * necessary. If creating the channel, restore mode lock and topic as
+ * necessary. Also check for auto-opping and auto-voicing.
+ * Modified, so ignored users won't get any status via services -certus */
+
+
+static void chan_adduser2(User * user, Channel * c)
+{
+ struct c_userlist *u;
+ char *chan = c->name;
+
+ if (get_ignore(user->nick) == NULL) {
+
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+ if (check_should_owner(user, chan)) {
+ chan_set_user_status(c, user, CUS_OWNER | CUS_OP);
+ } else
+#endif
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2) || defined(IRC_PTLINK)
+ if (check_should_protect(user, chan)) {
+ chan_set_user_status(c, user, CUS_PROTECT | CUS_OP);
+ } else
+#endif
+ if (check_should_op(user, chan)) {
+ chan_set_user_status(c, user, CUS_OP);
+ } else
+#ifdef HAS_HALFOP
+ if (check_should_halfop(user, chan)) {
+ chan_set_user_status(c, user, CUS_HALFOP);
+ } else
+#endif
+ if (check_should_voice(user, chan)) {
+ chan_set_user_status(c, user, CUS_VOICE);
+ }
+ }
+
+ u = scalloc(sizeof(struct c_userlist), 1);
+ u->next = c->users;
+ if (c->users)
+ c->users->prev = u;
+ c->users = u;
+ u->user = user;
+ c->usercount++;
+
+ if (get_ignore(user->nick) == NULL) {
+ if (c->ci && (check_access(user, c->ci, CA_MEMO))
+ && (c->ci->memos.memocount > 0)) {
+ if (c->ci->memos.memocount == 1) {
+ notice_lang(s_MemoServ, user, MEMO_X_ONE_NOTICE,
+ c->ci->memos.memocount, c->ci->name);
+ } else {
+ notice_lang(s_MemoServ, user, MEMO_X_MANY_NOTICE,
+ c->ci->memos.memocount, c->ci->name);
+ }
+ }
+ /* Added channelname to entrymsg - 30.03.2004, Certus */
+ if (c->ci && c->ci->entry_message)
+ notice_user(whosends(c->ci), user, "[%s] %s", c->name,
+ c->ci->entry_message);
+ }
+
+ /**
+ * We let the bot join even if it was an ignored user, as if we dont, and the ignored user dosnt just leave, the bot will never
+ * make it into the channel, leaving the channel botless even for legit users - Rob
+ **/
+ if (s_BotServ && c->ci && c->ci->bi) {
+ if (c->usercount == BSMinUsers)
+ bot_join(c->ci);
+ if (c->usercount >= BSMinUsers && (c->ci->botflags & BS_GREET)
+ && user->na && user->na->nc->greet
+ && check_access(user, c->ci, CA_GREET)) {
+ send_cmd(c->ci->bi->nick, "PRIVMSG %s :[%s] %s", c->name,
+ user->na->nick, user->na->nc->greet);
+ c->ci->bi->lastmsg = time(NULL);
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* This creates the channel structure (was originally in
+ chan_adduser, but splitted to make it more efficient to use for
+ SJOINs). */
+
+static Channel *chan_create(const char *chan)
+{
+ Channel *c;
+ Channel **list;
+
+ if (debug)
+ alog("debug: Creating channel %s", chan);
+ /* Allocate pre-cleared memory */
+ c = scalloc(sizeof(Channel), 1);
+ strscpy(c->name, chan, sizeof(c->name));
+ list = &chanlist[HASH(c->name)];
+ c->next = *list;
+ if (*list)
+ (*list)->prev = c;
+ *list = c;
+ c->creation_time = time(NULL);
+ /* Store ChannelInfo pointer in channel record */
+ c->ci = cs_findchan(chan);
+ if (c->ci)
+ c->ci->c = c;
+ /* Restore locked modes and saved topic */
+ if (c->ci) {
+ check_modes(c);
+ restore_topic(chan);
+ stick_all(c->ci);
+ }
+
+ return c;
+}
+
+/*************************************************************************/
+
+/* This destroys the channel structure, freeing everything in it. */
+
+static void chan_delete(Channel * c)
+{
+ BanData *bd, *next;
+ int i;
+
+ if (debug)
+ alog("debug: Deleting channel %s", c->name);
+
+ for (bd = c->bd; bd; bd = next) {
+ if (bd->mask)
+ free(bd->mask);
+ next = bd->next;
+ free(bd);
+ }
+
+ if (c->ci)
+ c->ci->c = NULL;
+
+ if (c->topic)
+ free(c->topic);
+
+ if (c->key)
+ free(c->key);
+#ifdef HAS_FMODE
+ if (c->flood)
+ free(c->flood);
+#endif
+#ifdef HAS_LMODE
+ if (c->redirect)
+ free(c->redirect);
+#endif
+
+ for (i = 0; i < c->bancount; ++i) {
+ if (c->bans[i])
+ free(c->bans[i]);
+ else
+ alog("channel: BUG freeing %s: bans[%d] is NULL!", c->name, i);
+ }
+ if (c->bansize)
+ free(c->bans);
+
+#ifdef HAS_EXCEPT
+ for (i = 0; i < c->exceptcount; ++i) {
+ if (c->excepts[i])
+ free(c->excepts[i]);
+ else
+ alog("channel: BUG freeing %s: exceps[%d] is NULL!", c->name,
+ i);
+ }
+ if (c->exceptsize)
+ free(c->excepts);
+#endif
+
+ if (c->next)
+ c->next->prev = c->prev;
+ if (c->prev)
+ c->prev->next = c->next;
+ else
+ chanlist[HASH(c->name)] = c->next;
+
+ free(c);
+}
+
+/*************************************************************************/
+
+static void del_ban(Channel * chan, char *mask)
+{
+ char **s = chan->bans;
+ int i = 0;
+ AutoKick *akick;
+
+ while (i < chan->bancount && strcmp(*s, mask) != 0) {
+ i++;
+ s++;
+ }
+
+ if (i < chan->bancount) {
+ chan->bancount--;
+ if (i < chan->bancount)
+ memmove(s, s + 1, sizeof(char *) * (chan->bancount - i));
+
+ if (debug)
+ alog("debug: Deleted ban %s from channel %s", mask,
+ chan->name);
+ }
+
+ if (chan->ci && (akick = is_stuck(chan->ci, mask)))
+ stick_mask(chan->ci, akick);
+}
+
+/*************************************************************************/
+
+#ifdef HAS_EXCEPT
+
+static void del_exception(Channel * chan, char *mask)
+{
+ int i;
+ int reset = 0;
+
+ for (i = 0; i < chan->exceptcount; i++) {
+ if ((!reset) && (stricmp(chan->excepts[i], mask) == 0)) {
+ free(chan->excepts[i]);
+ reset = 1;
+ }
+ if (reset)
+ chan->excepts[i] =
+ (i == chan->exceptcount) ? NULL : chan->excepts[i + 1];
+ }
+
+ if (reset)
+ chan->exceptcount--;
+
+ if (debug)
+ alog("debug: Deleted except %s to channel %s", mask, chan->name);
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef HAS_FMODE
+
+static char *get_flood(Channel * chan)
+{
+ return chan->flood;
+}
+
+#endif
+
+/*************************************************************************/
+
+static char *get_key(Channel * chan)
+{
+ return chan->key;
+}
+
+/*************************************************************************/
+
+static char *get_limit(Channel * chan)
+{
+ static char limit[16];
+
+ if (chan->limit == 0)
+ return NULL;
+
+ snprintf(limit, sizeof(limit), "%lu", chan->limit);
+ return limit;
+}
+
+/*************************************************************************/
+
+#ifdef HAS_LMODE
+
+static char *get_redirect(Channel * chan)
+{
+ return chan->redirect;
+}
+
+#endif
+
+/*************************************************************************/
+
+static Channel *join_user_update(User * user, Channel * chan, char *name)
+{
+ struct u_chanlist *c;
+
+ /* If it's a new channel, so we need to create it first. */
+ if (!chan)
+ chan = chan_create(name);
+
+ if (debug)
+ alog("debug: %s joins %s", user->nick, chan->name);
+
+ c = scalloc(sizeof(*c), 1);
+ c->next = user->chans;
+ if (user->chans)
+ user->chans->prev = c;
+ user->chans = c;
+ c->chan = chan;
+
+ chan_adduser2(user, chan);
+
+ return chan;
+}
+
+/*************************************************************************/
+
+#ifdef HAS_FMODE
+
+static void set_flood(Channel * chan, char *value)
+{
+ if (chan->flood)
+ free(chan->flood);
+ chan->flood = value ? sstrdup(value) : NULL;
+
+ if (debug)
+ alog("debug: Flood of channel %s set to %s", chan->name,
+ chan->flood ? chan->flood : "no flood settings");
+}
+
+#endif
+
+/*************************************************************************/
+
+static void set_key(Channel * chan, char *value)
+{
+ if (chan->key)
+ free(chan->key);
+ chan->key = value ? sstrdup(value) : NULL;
+
+ if (debug)
+ alog("debug: Key of channel %s set to %s", chan->name,
+ chan->key ? chan->key : "no key");
+}
+
+/*************************************************************************/
+
+static void set_limit(Channel * chan, char *value)
+{
+ chan->limit = value ? strtoul(value, NULL, 10) : 0;
+
+ if (debug)
+ alog("debug: Limit of channel %s set to %u", chan->name,
+ chan->limit);
+}
+
+/*************************************************************************/
+
+#ifdef HAS_LMODE
+
+static void set_redirect(Channel * chan, char *value)
+{
+ if (chan->redirect)
+ free(chan->redirect);
+ chan->redirect = value ? sstrdup(value) : NULL;
+
+ if (debug)
+ alog("debug: Redirect of channel %s set to %s", chan->name,
+ chan->redirect ? chan->redirect : "no redirect");
+}
+
+#endif
+
+void do_mass_mode(char *modes)
+{
+ int ac, i;
+ char **av;
+ Channel *c;
+ char *myModes;
+
+ if (!modes) {
+ return;
+ }
+
+ /* Prevent modes being altered by split_buf */
+ myModes = sstrdup(modes);
+ ac = split_buf(myModes, &av, 1);
+
+ for (i = 0; i < 1024; i++) {
+ for (c = chanlist[i]; c; c = c->next) {
+ if (c->bouncy_modes) {
+ return;
+ } else {
+ send_mode(s_OperServ, c->name, "%s", modes);
+ chan_set_modes(s_OperServ, c, ac, av, 1);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
diff --git a/src/chanserv.c b/src/chanserv.c
new file mode 100644
index 000000000..06236c547
--- /dev/null
+++ b/src/chanserv.c
@@ -0,0 +1,6257 @@
+/* ChanServ functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+/*************************************************************************/
+
+#include "services.h"
+#include "pseudo.h"
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+
+ChannelInfo *chanlists[256];
+
+static int def_levels[][2] = {
+ { CA_AUTOOP, 5 },
+ { CA_AUTOVOICE, 3 },
+ { CA_AUTODEOP, -1 },
+ { CA_NOJOIN, -2 },
+ { CA_INVITE, 5 },
+ { CA_AKICK, 10 },
+ { CA_SET, ACCESS_INVALID },
+ { CA_CLEAR, ACCESS_INVALID },
+ { CA_UNBAN, 5 },
+ { CA_OPDEOP, 5 },
+ { CA_ACCESS_LIST, 1 },
+ { CA_ACCESS_CHANGE, 10 },
+ { CA_MEMO, 10 },
+ { CA_ASSIGN, ACCESS_INVALID },
+ { CA_BADWORDS, 10 },
+ { CA_NOKICK, 1 },
+ { CA_FANTASIA, 3 },
+ { CA_SAY, 5 },
+ { CA_GREET, 5 },
+ { CA_VOICEME, 3 },
+ { CA_VOICE, 5 },
+ { CA_GETKEY, 5 },
+ { CA_AUTOHALFOP, 4 },
+ { CA_AUTOPROTECT, 10 },
+ { CA_OPDEOPME, 5 },
+ { CA_HALFOPME, 4 },
+ { CA_HALFOP, 5 },
+ { CA_PROTECTME, 10 },
+ { CA_PROTECT, ACCESS_INVALID },
+ { CA_KICKME, 5 },
+ { CA_KICK, 5 },
+ { CA_SIGNKICK, ACCESS_INVALID },
+ { CA_BANME, 5 },
+ { CA_BAN, 5 },
+ { CA_TOPIC, ACCESS_INVALID },
+ { CA_INFO, ACCESS_INVALID },
+ { -1 }
+};
+
+typedef struct {
+ int what;
+ char *name;
+ int desc;
+} LevelInfo;
+static LevelInfo levelinfo[] = {
+ { CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP },
+#ifdef HAS_HALFOP
+ { CA_AUTOHALFOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP },
+#endif
+ { CA_AUTOOP, "AUTOOP", CHAN_LEVEL_AUTOOP },
+#ifdef IRC_UNREAL
+ { CA_AUTOPROTECT, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT },
+#endif
+#ifdef IRC_VIAGRA
+ { CA_AUTOPROTECT, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT },
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { CA_AUTOPROTECT, "AUTOADMIN", CHAN_LEVEL_AUTOPROTECT },
+#endif
+ { CA_AUTOVOICE, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE },
+ { CA_NOJOIN, "NOJOIN", CHAN_LEVEL_NOJOIN },
+ { CA_SIGNKICK, "SIGNKICK", CHAN_LEVEL_SIGNKICK },
+
+ { CA_ACCESS_LIST, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST },
+ { CA_ACCESS_CHANGE, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE },
+ { CA_AKICK, "AKICK", CHAN_LEVEL_AKICK },
+ { CA_SET, "SET", CHAN_LEVEL_SET },
+
+ { CA_BAN, "BAN", CHAN_LEVEL_BAN },
+ { CA_BANME, "BANME", CHAN_LEVEL_BANME },
+ { CA_CLEAR, "CLEAR", CHAN_LEVEL_CLEAR },
+ { CA_GETKEY, "GETKEY", CHAN_LEVEL_GETKEY },
+#ifdef HAS_HALFOP
+ { CA_HALFOP, "HALFOP", CHAN_LEVEL_HALFOP },
+ { CA_HALFOPME, "HALFOPME", CHAN_LEVEL_HALFOPME },
+#endif
+ { CA_INFO, "INFO", CHAN_LEVEL_INFO },
+ { CA_KICK, "KICK", CHAN_LEVEL_KICK },
+ { CA_KICKME, "KICKME", CHAN_LEVEL_KICKME },
+ { CA_INVITE, "INVITE", CHAN_LEVEL_INVITE },
+ { CA_OPDEOP, "OPDEOP", CHAN_LEVEL_OPDEOP },
+ { CA_OPDEOPME, "OPDEOPME", CHAN_LEVEL_OPDEOPME },
+#ifdef IRC_UNREAL
+ { CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT },
+ { CA_PROTECTME, "PROTECTME", CHAN_LEVEL_PROTECTME },
+#endif
+#ifdef IRC_VIAGRA
+ { CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT },
+ { CA_PROTECTME, "PROTECTME", CHAN_LEVEL_PROTECTME },
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { CA_PROTECT, "ADMIN", CHAN_LEVEL_PROTECT },
+ { CA_PROTECTME, "ADMINME", CHAN_LEVEL_PROTECTME },
+#endif
+ { CA_TOPIC, "TOPIC", CHAN_LEVEL_TOPIC },
+ { CA_UNBAN, "UNBAN", CHAN_LEVEL_UNBAN },
+ { CA_VOICE, "VOICE", CHAN_LEVEL_VOICE },
+ { CA_VOICEME, "VOICEME", CHAN_LEVEL_VOICEME },
+
+ { CA_MEMO, "MEMO", CHAN_LEVEL_MEMO },
+
+ { CA_ASSIGN, "ASSIGN", CHAN_LEVEL_ASSIGN },
+ { CA_BADWORDS, "BADWORDS", CHAN_LEVEL_BADWORDS },
+ { CA_FANTASIA, "FANTASIA", CHAN_LEVEL_FANTASIA },
+ { CA_GREET, "GREET", CHAN_LEVEL_GREET },
+ { CA_NOKICK, "NOKICK", CHAN_LEVEL_NOKICK },
+ { CA_SAY, "SAY", CHAN_LEVEL_SAY },
+
+ { -1 }
+};
+static int levelinfo_maxwidth = 0;
+
+CSModeUtil csmodeutils[] = {
+ { "DEOP", "!deop", "-o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME },
+ { "OP", "!op", "+o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME },
+ { "DEVOICE", "!devoice", "-v", 0 , CA_VOICE, CA_VOICEME },
+ { "VOICE", "!voice", "+v", 0 , CA_VOICE, CA_VOICEME },
+#ifdef HAS_HALFOP
+ { "DEHALFOP", "!dehalfop", "-h", 0 , CA_HALFOP, CA_HALFOPME },
+ { "HALFOP", "!halfop", "+h", 0 , CA_HALFOP, CA_HALFOPME },
+#endif
+#ifdef IRC_UNREAL
+ { "DEPROTECT", "!deprotect", "-a", 0 , CA_PROTECT, CA_PROTECTME },
+ { "PROTECT", "!protect", "+a", 0 , CA_PROTECT, CA_PROTECTME },
+#endif
+#ifdef IRC_VIAGRA
+ { "DEPROTECT", "!deprotect", "-a", 0 , CA_PROTECT, CA_PROTECTME },
+ { "PROTECT", "!protect", "+a", 0 , CA_PROTECT, CA_PROTECTME },
+#endif
+#ifdef IRC_PTLINK
+ { "DEPROTECT", "!deprotect", "-a", 0 , CA_PROTECT, CA_PROTECTME },
+ { "PROTECT", "!protect", "+a", 0 , CA_PROTECT, CA_PROTECTME },
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ { "DEPROTECT", "!deadmin", "-a", 0 , CA_PROTECT, CA_PROTECTME },
+ { "PROTECT", "!admin", "+a", 0 , CA_PROTECT, CA_PROTECTME },
+#endif
+
+ { NULL }
+};
+
+int xop_msgs[4][14] = {
+ { CHAN_AOP_SYNTAX,
+ CHAN_AOP_DISABLED,
+ CHAN_AOP_NICKS_ONLY,
+ CHAN_AOP_ADDED,
+ CHAN_AOP_MOVED,
+ CHAN_AOP_NO_SUCH_ENTRY,
+ CHAN_AOP_NOT_FOUND,
+ CHAN_AOP_NO_MATCH,
+ CHAN_AOP_DELETED,
+ CHAN_AOP_DELETED_ONE,
+ CHAN_AOP_DELETED_SEVERAL,
+ CHAN_AOP_LIST_EMPTY,
+ CHAN_AOP_LIST_HEADER,
+ CHAN_AOP_CLEAR
+ },
+ { CHAN_SOP_SYNTAX,
+ CHAN_SOP_DISABLED,
+ CHAN_SOP_NICKS_ONLY,
+ CHAN_SOP_ADDED,
+ CHAN_SOP_MOVED,
+ CHAN_SOP_NO_SUCH_ENTRY,
+ CHAN_SOP_NOT_FOUND,
+ CHAN_SOP_NO_MATCH,
+ CHAN_SOP_DELETED,
+ CHAN_SOP_DELETED_ONE,
+ CHAN_SOP_DELETED_SEVERAL,
+ CHAN_SOP_LIST_EMPTY,
+ CHAN_SOP_LIST_HEADER,
+ CHAN_SOP_CLEAR
+ },
+ { CHAN_VOP_SYNTAX,
+ CHAN_VOP_DISABLED,
+ CHAN_VOP_NICKS_ONLY,
+ CHAN_VOP_ADDED,
+ CHAN_VOP_MOVED,
+ CHAN_VOP_NO_SUCH_ENTRY,
+ CHAN_VOP_NOT_FOUND,
+ CHAN_VOP_NO_MATCH,
+ CHAN_VOP_DELETED,
+ CHAN_VOP_DELETED_ONE,
+ CHAN_VOP_DELETED_SEVERAL,
+ CHAN_VOP_LIST_EMPTY,
+ CHAN_VOP_LIST_HEADER,
+ CHAN_VOP_CLEAR
+ },
+ { CHAN_HOP_SYNTAX,
+ CHAN_HOP_DISABLED,
+ CHAN_HOP_NICKS_ONLY,
+ CHAN_HOP_ADDED,
+ CHAN_HOP_MOVED,
+ CHAN_HOP_NO_SUCH_ENTRY,
+ CHAN_HOP_NOT_FOUND,
+ CHAN_HOP_NO_MATCH,
+ CHAN_HOP_DELETED,
+ CHAN_HOP_DELETED_ONE,
+ CHAN_HOP_DELETED_SEVERAL,
+ CHAN_HOP_LIST_EMPTY,
+ CHAN_HOP_LIST_HEADER,
+ CHAN_HOP_CLEAR
+ }
+};
+
+/* *INDENT-ON* */
+/*************************************************************************/
+
+void alpha_insert_chan(ChannelInfo * ci);
+static ChannelInfo *makechan(const char *chan);
+int delchan(ChannelInfo * ci);
+void reset_levels(ChannelInfo * ci);
+static int is_real_founder(User * user, ChannelInfo * ci);
+static int is_identified(User * user, ChannelInfo * ci);
+static void make_unidentified(User * u, ChannelInfo * ci);
+
+static int do_help(User * u);
+static int do_register(User * u);
+static int do_identify(User * u);
+static int do_logout(User * u);
+static int do_drop(User * u);
+static int do_set(User * u);
+static int do_set_founder(User * u, ChannelInfo * ci, char *param);
+static int do_set_successor(User * u, ChannelInfo * ci, char *param);
+static int do_set_password(User * u, ChannelInfo * ci, char *param);
+static int do_set_desc(User * u, ChannelInfo * ci, char *param);
+static int do_set_url(User * u, ChannelInfo * ci, char *param);
+static int do_set_email(User * u, ChannelInfo * ci, char *param);
+static int do_set_entrymsg(User * u, ChannelInfo * ci, char *param);
+static int do_set_bantype(User * u, ChannelInfo * ci, char *param);
+static int do_set_mlock(User * u, ChannelInfo * ci, char *param);
+static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param);
+static int do_set_topiclock(User * u, ChannelInfo * ci, char *param);
+static int do_set_private(User * u, ChannelInfo * ci, char *param);
+static int do_set_secureops(User * u, ChannelInfo * ci, char *param);
+static int do_set_securefounder(User * u, ChannelInfo * ci, char *param);
+static int do_set_restricted(User * u, ChannelInfo * ci, char *param);
+static int do_set_secure(User * u, ChannelInfo * ci, char *param);
+static int do_set_signkick(User * u, ChannelInfo * ci, char *param);
+static int do_set_opnotice(User * u, ChannelInfo * ci, char *param);
+static int do_set_xop(User * u, ChannelInfo * ci, char *param);
+static int do_set_peace(User * u, ChannelInfo * ci, char *param);
+static int do_set_noexpire(User * u, ChannelInfo * ci, char *param);
+static int do_xop(User * u, char *xname, int xlev, int *xmsgs);
+static int do_aop(User * u);
+#ifdef HAS_HALFOP
+static int do_hop(User * u);
+#endif
+static int do_sop(User * u);
+static int do_vop(User * u);
+static int do_access(User * u);
+static int do_akick(User * u);
+static int do_info(User * u);
+static int do_list(User * u);
+static int do_invite(User * u);
+static int do_levels(User * u);
+static int do_util(User * u, CSModeUtil * util);
+static int do_op(User * u);
+static int do_deop(User * u);
+static int do_voice(User * u);
+static int do_devoice(User * u);
+#ifdef HAS_HALFOP
+static int do_halfop(User * u);
+static int do_dehalfop(User * u);
+#endif
+#ifdef IRC_UNREAL
+static int do_protect(User * u);
+static int do_deprotect(User * u);
+static int do_owner(User * u);
+static int do_deowner(User * u);
+#endif
+#ifdef IRC_VIAGRA
+static int do_protect(User * u);
+static int do_deprotect(User * u);
+static int do_owner(User * u);
+static int do_deowner(User * u);
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2) || defined(IRC_PTLINK)
+static int do_protect(User * u);
+static int do_deprotect(User * u);
+#endif
+static int do_cs_kick(User * u);
+static int do_ban(User * u);
+static int do_cs_topic(User * u);
+static int do_unban(User * u);
+static int do_clear(User * u);
+static int do_getkey(User * u);
+static int do_getpass(User * u);
+static int do_sendpass(User * u);
+static int do_forbid(User * u);
+static int do_suspend(User * u);
+static int do_unsuspend(User * u);
+static int do_status(User * u);
+void moduleAddChanServCmds(void);
+/*************************************************************************/
+/* *INDENT-OFF* */
+void moduleAddChanServCmds(void) {
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("REGISTER", do_register, NULL, CHAN_HELP_REGISTER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("IDENTIFY", do_identify, NULL, CHAN_HELP_IDENTIFY, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("LOGOUT", do_logout, NULL, -1,CHAN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT,CHAN_SERVADMIN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT); addCoreCommand(CHANSERV,c);
+ c = createCommand("DROP", do_drop, NULL, -1,CHAN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP,CHAN_SERVADMIN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET", do_set, NULL, CHAN_HELP_SET,-1, CHAN_SERVADMIN_HELP_SET,CHAN_SERVADMIN_HELP_SET, CHAN_SERVADMIN_HELP_SET); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET FOUNDER", NULL, NULL, CHAN_HELP_SET_FOUNDER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET SUCCESSOR", NULL, NULL, CHAN_HELP_SET_SUCCESSOR, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET PASSWORD", NULL, NULL, CHAN_HELP_SET_PASSWORD, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET DESC", NULL, NULL, CHAN_HELP_SET_DESC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET URL", NULL, NULL, CHAN_HELP_SET_URL, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET EMAIL", NULL, NULL, CHAN_HELP_SET_EMAIL, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET ENTRYMSG", NULL, NULL, CHAN_HELP_SET_ENTRYMSG, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET BANTYPE", NULL, NULL, CHAN_HELP_SET_BANTYPE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET PRIVATE", NULL, NULL, CHAN_HELP_SET_PRIVATE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET KEEPTOPIC", NULL, NULL, CHAN_HELP_SET_KEEPTOPIC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET TOPICLOCK", NULL, NULL, CHAN_HELP_SET_TOPICLOCK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET MLOCK", NULL, NULL, CHAN_HELP_SET_MLOCK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET RESTRICTED", NULL, NULL, CHAN_HELP_SET_RESTRICTED, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET SECURE", NULL, NULL, CHAN_HELP_SET_SECURE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET SECUREOPS", NULL, NULL, CHAN_HELP_SET_SECUREOPS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET SECUREFOUNDER", NULL, NULL, CHAN_HELP_SET_SECUREFOUNDER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET SIGNKICK", NULL, NULL, CHAN_HELP_SET_SIGNKICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET OPNOTICE", NULL, NULL, CHAN_HELP_SET_OPNOTICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET XOP", NULL, NULL, CHAN_HELP_SET_XOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET PEACE", NULL, NULL, CHAN_HELP_SET_PEACE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SET NOEXPIRE", NULL, NULL, -1, -1,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE); addCoreCommand(CHANSERV,c);
+ c = createCommand("AOP", do_aop, NULL, CHAN_HELP_AOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#ifdef HAS_HALFOP
+ c = createCommand("HOP", do_hop, NULL, CHAN_HELP_HOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+ c = createCommand("SOP", do_sop, NULL, CHAN_HELP_SOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("VOP", do_vop, NULL, CHAN_HELP_VOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("ACCESS", do_access, NULL, CHAN_HELP_ACCESS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("ACCESS LEVELS", NULL, NULL, CHAN_HELP_ACCESS_LEVELS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("AKICK", do_akick, NULL, CHAN_HELP_AKICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("LEVELS", do_levels, NULL, CHAN_HELP_LEVELS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("INFO", do_info, NULL, CHAN_HELP_INFO,-1, CHAN_SERVADMIN_HELP_INFO, CHAN_SERVADMIN_HELP_INFO,CHAN_SERVADMIN_HELP_INFO); addCoreCommand(CHANSERV,c);
+ c = createCommand("LIST", do_list, NULL, -1,CHAN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST,CHAN_SERVADMIN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST); addCoreCommand(CHANSERV,c);
+ c = createCommand("OP", do_op, NULL, CHAN_HELP_OP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEOP", do_deop, NULL, CHAN_HELP_DEOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("VOICE", do_voice, NULL, CHAN_HELP_VOICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEVOICE", do_devoice, NULL, CHAN_HELP_DEVOICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#ifdef HAS_HALFOP
+ c = createCommand("HALFOP", do_halfop, NULL, CHAN_HELP_HALFOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEHALFOP", do_dehalfop, NULL, CHAN_HELP_DEHALFOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+#ifdef IRC_UNREAL
+ c = createCommand("PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("OWNER", do_owner, NULL, CHAN_HELP_OWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEOWNER", do_deowner, NULL, CHAN_HELP_DEOWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+#ifdef IRC_VIAGRA
+ c = createCommand("PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("OWNER", do_owner, NULL, CHAN_HELP_OWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEOWNER", do_deowner, NULL, CHAN_HELP_DEOWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+#ifdef IRC_PTLINK
+ c = createCommand("PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ c = createCommand("ADMIN", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("DEADMIN",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+#endif
+ c = createCommand("KICK", do_cs_kick, NULL, CHAN_HELP_KICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("BAN", do_ban, NULL, CHAN_HELP_BAN, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("TOPIC", do_cs_topic, NULL, CHAN_HELP_TOPIC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("INVITE", do_invite, NULL, CHAN_HELP_INVITE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("UNBAN", do_unban, NULL, CHAN_HELP_UNBAN, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("CLEAR", do_clear, NULL, CHAN_HELP_CLEAR, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("GETKEY", do_getkey, NULL, CHAN_HELP_GETKEY, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("SENDPASS", do_sendpass, NULL, CHAN_HELP_SENDPASS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
+ c = createCommand("GETPASS", do_getpass, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_GETPASS,CHAN_SERVADMIN_HELP_GETPASS, CHAN_SERVADMIN_HELP_GETPASS); addCoreCommand(CHANSERV,c);
+ c = createCommand("FORBID", do_forbid, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_FORBID,CHAN_SERVADMIN_HELP_FORBID, CHAN_SERVADMIN_HELP_FORBID); addCoreCommand(CHANSERV,c);
+ c = createCommand("SUSPEND", do_suspend, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_SUSPEND,CHAN_SERVADMIN_HELP_SUSPEND, CHAN_SERVADMIN_HELP_SUSPEND); addCoreCommand(CHANSERV,c);
+ c = createCommand("UNSUSPEND", do_unsuspend, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_UNSUSPEND,CHAN_SERVADMIN_HELP_UNSUSPEND, CHAN_SERVADMIN_HELP_UNSUSPEND); addCoreCommand(CHANSERV,c);
+ c = createCommand("STATUS", do_status, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_STATUS,CHAN_SERVADMIN_HELP_STATUS, CHAN_SERVADMIN_HELP_STATUS); addCoreCommand(CHANSERV,c);
+}
+
+/* *INDENT-ON* */
+/*************************************************************************/
+/*************************************************************************/
+
+/* Returns modes for mlock in a nice way. */
+
+static char *get_mlock_modes(ChannelInfo * ci, int complete)
+{
+ static char res[BUFSIZE];
+
+ char *end = res;
+
+ if (ci->mlock_on || ci->mlock_off) {
+ int n = 0;
+ CBModeInfo *cbmi = cbmodeinfos;
+
+ if (ci->mlock_on) {
+ *end++ = '+';
+ n++;
+
+ do {
+ if (ci->mlock_on & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+
+ cbmi = cbmodeinfos;
+ }
+
+ if (ci->mlock_off) {
+ *end++ = '-';
+ n++;
+
+ do {
+ if (ci->mlock_off & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+
+ cbmi = cbmodeinfos;
+ }
+
+ if (ci->mlock_on && complete) {
+ do {
+ if (cbmi->csgetvalue && (ci->mlock_on & cbmi->flag)) {
+ char *value = cbmi->csgetvalue(ci);
+
+ if (value) {
+ *end++ = ' ';
+ while (*value)
+ *end++ = *value++;
+ }
+ }
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+ }
+ }
+
+ *end = 0;
+
+ return res;
+}
+
+/* Display total number of registered channels and info about each; or, if
+ * a specific channel is given, display information about that channel
+ * (like /msg ChanServ INFO <channel>). If count_only != 0, then only
+ * display the number of registered channels (the channel parameter is
+ * ignored).
+ */
+
+void listchans(int count_only, const char *chan)
+{
+ int count = 0;
+ ChannelInfo *ci;
+ int i;
+
+ if (count_only) {
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next)
+ count++;
+ }
+ printf("%d channels registered.\n", count);
+
+ } else if (chan) {
+
+ struct tm *tm;
+ char buf[BUFSIZE];
+
+ if (!(ci = cs_findchan(chan))) {
+ printf("Channel %s not registered.\n", chan);
+ return;
+ }
+ if (ci->flags & CI_VERBOTEN) {
+ printf("Channel %s is FORBIDden.\n", ci->name);
+ } else {
+ printf("Information about channel %s:\n", ci->name);
+ printf(" Founder: %s\n", ci->founder->display);
+ printf(" Description: %s\n", ci->desc);
+ tm = localtime(&ci->time_registered);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Registered: %s\n", buf);
+ tm = localtime(&ci->last_used);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Last used: %s\n", buf);
+ if (ci->last_topic) {
+ printf(" Last topic: %s\n", ci->last_topic);
+ printf(" Topic set by: %s\n", ci->last_topic_setter);
+ }
+ if (ci->url)
+ printf(" URL: %s\n", ci->url);
+ if (ci->email)
+ printf(" E-mail address: %s\n", ci->email);
+ printf(" Options: ");
+ if (!ci->flags) {
+ printf("None\n");
+ } else {
+ int need_comma = 0;
+ static const char commastr[] = ", ";
+ if (ci->flags & CI_PRIVATE) {
+ printf("Private");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_KEEPTOPIC) {
+ printf("%sTopic Retention",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_TOPICLOCK) {
+ printf("%sTopic Lock", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECUREOPS) {
+ printf("%sSecure Ops", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_RESTRICTED) {
+ printf("%sRestricted Access",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECURE) {
+ printf("%sSecure", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_NO_EXPIRE) {
+ printf("%sNo Expire", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ printf("\n");
+ }
+ if (ci->mlock_on || ci->mlock_off)
+ printf(" Mode lock: %s\n", get_mlock_modes(ci, 1));
+ }
+
+ } else {
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ printf(" %s %-20s %s\n",
+ ci->flags & CI_NO_EXPIRE ? "!" : " ", ci->name,
+ ci->
+ flags & CI_VERBOTEN ? "Disallowed (FORBID)" : ci->
+ desc);
+ count++;
+ }
+ }
+ printf("%d channels registered.\n", count);
+
+ }
+}
+
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_chanserv_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i, j;
+ ChannelInfo *ci;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ count++;
+ mem += sizeof(*ci);
+ if (ci->desc)
+ mem += strlen(ci->desc) + 1;
+ if (ci->url)
+ mem += strlen(ci->url) + 1;
+ if (ci->email)
+ mem += strlen(ci->email) + 1;
+ mem += ci->accesscount * sizeof(ChanAccess);
+ mem += ci->akickcount * sizeof(AutoKick);
+ for (j = 0; j < ci->akickcount; j++) {
+ if (!(ci->akick[j].flags & AK_ISNICK)
+ && ci->akick[j].u.mask)
+ mem += strlen(ci->akick[j].u.mask) + 1;
+ if (ci->akick[j].reason)
+ mem += strlen(ci->akick[j].reason) + 1;
+ if (ci->akick[j].creator)
+ mem += strlen(ci->akick[j].creator) + 1;
+ }
+ if (ci->mlock_key)
+ mem += strlen(ci->mlock_key) + 1;
+#ifdef HAS_FMODE
+ if (ci->mlock_flood)
+ mem += strlen(ci->mlock_flood) + 1;
+#endif
+#ifdef HAS_LMODE
+ if (ci->mlock_redirect)
+ mem += strlen(ci->mlock_redirect) + 1;
+#endif
+ if (ci->last_topic)
+ mem += strlen(ci->last_topic) + 1;
+ if (ci->entry_message)
+ mem += strlen(ci->entry_message) + 1;
+ if (ci->forbidby)
+ mem += strlen(ci->forbidby) + 1;
+ if (ci->forbidreason)
+ mem += strlen(ci->forbidreason) + 1;
+ if (ci->levels)
+ mem += sizeof(*ci->levels) * CA_SIZE;
+ mem += ci->memos.memocount * sizeof(Memo);
+ for (j = 0; j < ci->memos.memocount; j++) {
+ if (ci->memos.memos[j].text)
+ mem += strlen(ci->memos.memos[j].text) + 1;
+ }
+ if (ci->ttb)
+ mem += sizeof(*ci->ttb) * TTB_SIZE;
+ mem += ci->bwcount * sizeof(BadWord);
+ for (j = 0; j < ci->bwcount; j++)
+ if (ci->badwords[j].word)
+ mem += strlen(ci->badwords[j].word) + 1;
+ }
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* ChanServ initialization. */
+
+void cs_init(void)
+{
+ Command *cmd;
+ moduleAddChanServCmds();
+ cmd = findCommand(CHANSERV, "REGISTER");
+ if (cmd)
+ cmd->help_param1 = s_NickServ;
+ cmd = findCommand(CHANSERV, "SET SECURE");
+ if (cmd)
+ cmd->help_param1 = s_NickServ;
+ cmd = findCommand(CHANSERV, "SET SUCCESSOR");
+ if (cmd)
+ cmd->help_param1 = (char *) (long) CSMaxReg;
+}
+
+/*************************************************************************/
+
+/* Main ChanServ routine. */
+
+void chanserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_ChanServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_ChanServ, u, SERVICE_OFFLINE, s_ChanServ);
+ } else {
+ mod_run_cmd(s_ChanServ, u, CHANSERV, cmd);
+ }
+}
+
+/*************************************************************************/
+
+/* Load/save data files. */
+
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", ChanDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+void load_cs_dbase(void)
+{
+ dbFILE *f;
+ int ver, i, j, c, m;
+ ChannelInfo *ci, **last, *prev;
+ int failed = 0;
+
+ if (!(f = open_db(s_ChanServ, ChanDBName, "r", CHAN_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+
+ for (i = 0; i < 256 && !failed; i++) {
+ int16 tmp16;
+ int32 tmp32;
+ int n_levels;
+ char *s;
+ NickAlias *na;
+
+ last = &chanlists[i];
+ prev = NULL;
+ while ((c = getc_db(f)) != 0) {
+ if (c != 1)
+ fatal("Invalid format in %s", ChanDBName);
+ ci = scalloc(sizeof(ChannelInfo), 1);
+ *last = ci;
+ last = &ci->next;
+ ci->prev = prev;
+ prev = ci;
+ SAFE(read_buffer(ci->name, f));
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->founder = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->founder = na->nc;
+ else
+ ci->founder = NULL;
+ }
+ free(s);
+ } else
+ ci->founder = NULL;
+ if (ver >= 7) {
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->successor = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->successor = na->nc;
+ else
+ ci->successor = NULL;
+ }
+ free(s);
+ } else
+ ci->successor = NULL;
+ } else {
+ ci->successor = NULL;
+ }
+ SAFE(read_buffer(ci->founderpass, f));
+ SAFE(read_string(&ci->desc, f));
+ if (!ci->desc)
+ ci->desc = sstrdup("");
+ SAFE(read_string(&ci->url, f));
+ SAFE(read_string(&ci->email, f));
+ SAFE(read_int32(&tmp32, f));
+ ci->time_registered = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ ci->last_used = tmp32;
+ SAFE(read_string(&ci->last_topic, f));
+ SAFE(read_buffer(ci->last_topic_setter, f));
+ SAFE(read_int32(&tmp32, f));
+ ci->last_topic_time = tmp32;
+ SAFE(read_int32(&ci->flags, f));
+#ifdef USE_ENCRYPTION
+ if (!(ci->flags & (CI_ENCRYPTEDPW | CI_VERBOTEN))) {
+ if (debug)
+ alog("debug: %s: encrypting password for %s on load",
+ s_ChanServ, ci->name);
+ if (encrypt_in_place(ci->founderpass, PASSMAX) < 0)
+ fatal("%s: load database: Can't encrypt %s password!",
+ s_ChanServ, ci->name);
+ ci->flags |= CI_ENCRYPTEDPW;
+ }
+#else
+ if (ci->flags & CI_ENCRYPTEDPW) {
+ /* Bail: it makes no sense to continue with encrypted
+ * passwords, since we won't be able to verify them */
+ fatal("%s: load database: password for %s encrypted "
+ "but encryption disabled, aborting",
+ s_ChanServ, ci->name);
+ }
+#endif
+ /* Leaveops cleanup */
+ if (ver <= 13 && (ci->flags & 0x00000020))
+ ci->flags &= ~0x00000020;
+ /* Temporary flags cleanup */
+ ci->flags &= ~CI_INHABIT;
+
+ if (ver >= 9) {
+ SAFE(read_string(&ci->forbidby, f));
+ SAFE(read_string(&ci->forbidreason, f));
+ } else {
+ ci->forbidreason = NULL;
+ ci->forbidby = NULL;
+ }
+ if (ver >= 9)
+ SAFE(read_int16(&tmp16, f));
+ else
+ tmp16 = CSDefBantype;
+ ci->bantype = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ n_levels = tmp16;
+ ci->levels = scalloc(2 * CA_SIZE, 1);
+ reset_levels(ci);
+ for (j = 0; j < n_levels; j++) {
+ if (j < CA_SIZE)
+ SAFE(read_int16(&ci->levels[j], f));
+ else
+ SAFE(read_int16(&tmp16, f));
+ }
+ /* To avoid levels list silly hacks */
+ if (ver < 10)
+ ci->levels[CA_OPDEOPME] = ci->levels[CA_OPDEOP];
+ if (ver < 11) {
+ ci->levels[CA_KICKME] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_KICK] = ci->levels[CA_OPDEOP];
+ }
+ if (ver < 15) {
+
+ /* Old Ultimate levels import */
+ /* We now conveniently use PROTECT internals for Ultimate's ADMIN support - ShadowMaster */
+ /* Doh, must of course be done before we change the values were trying to import - ShadowMaster */
+ ci->levels[CA_AUTOPROTECT] = ci->levels[32];
+ ci->levels[CA_PROTECTME] = ci->levels[33];
+ ci->levels[CA_PROTECT] = ci->levels[34];
+
+ ci->levels[CA_BANME] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_BAN] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_TOPIC] = ACCESS_INVALID;
+
+
+ }
+
+ SAFE(read_int16(&ci->accesscount, f));
+ if (ci->accesscount) {
+ ci->access = scalloc(ci->accesscount, sizeof(ChanAccess));
+ for (j = 0; j < ci->accesscount; j++) {
+ SAFE(read_int16(&ci->access[j].in_use, f));
+ if (ci->access[j].in_use) {
+ SAFE(read_int16(&ci->access[j].level, f));
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->access[j].nc = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->access[j].nc = na->nc;
+ else
+ ci->access[j].nc = NULL;
+ }
+ free(s);
+ }
+ if (ci->access[j].nc == NULL)
+ ci->access[j].in_use = 0;
+ if (ver >= 11) {
+ SAFE(read_int32(&tmp32, f));
+ ci->access[j].last_seen = tmp32;
+ } else {
+ ci->access[j].last_seen = 0; /* Means we have never seen the user */
+ }
+ }
+ }
+ } else {
+ ci->access = NULL;
+ }
+
+ SAFE(read_int16(&ci->akickcount, f));
+ if (ci->akickcount) {
+ ci->akick = scalloc(ci->akickcount, sizeof(AutoKick));
+ for (j = 0; j < ci->akickcount; j++) {
+ if (ver >= 15) {
+ SAFE(read_int16(&ci->akick[j].flags, f));
+ } else {
+ SAFE(read_int16(&tmp16, f));
+ if (tmp16)
+ ci->akick[j].flags |= AK_USED;
+ }
+ if (ci->akick[j].flags & AK_USED) {
+ if (ver < 15) {
+ SAFE(read_int16(&tmp16, f));
+ if (tmp16)
+ ci->akick[j].flags |= AK_ISNICK;
+ }
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_ISNICK) {
+ if (ver >= 13) {
+ ci->akick[j].u.nc = findcore(s);
+ } else {
+ na = findnick(s);
+ if (na)
+ ci->akick[j].u.nc = na->nc;
+ else
+ ci->akick[j].u.nc = NULL;
+ }
+ if (!ci->akick[j].u.nc)
+ ci->akick[j].flags &= ~AK_USED;
+ free(s);
+ } else {
+ ci->akick[j].u.mask = s;
+ }
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_USED)
+ ci->akick[j].reason = s;
+ else if (s)
+ free(s);
+ if (ver >= 9) {
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_USED) {
+ ci->akick[j].creator = s;
+ } else if (s) {
+ free(s);
+ }
+ SAFE(read_int32(&tmp32, f));
+ if (ci->akick[j].flags & AK_USED)
+ ci->akick[j].addtime = tmp32;
+ } else {
+ ci->akick[j].creator = NULL;
+ ci->akick[j].addtime = 0;
+ }
+ }
+
+ /* Bugfix */
+ if ((ver == 15) && ci->akick[j].flags > 8) {
+ ci->akick[j].flags = 0;
+ ci->akick[j].u.nc = NULL;
+ ci->akick[j].u.nc = NULL;
+ ci->akick[j].addtime = 0;
+ ci->akick[j].creator = NULL;
+ ci->akick[j].reason = NULL;
+ }
+ }
+ } else {
+ ci->akick = NULL;
+ }
+
+ if (ver >= 10) {
+ SAFE(read_int32(&ci->mlock_on, f));
+ SAFE(read_int32(&ci->mlock_off, f));
+ } else {
+ SAFE(read_int16(&tmp16, f));
+ ci->mlock_on = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->mlock_off = tmp16;
+ }
+ SAFE(read_int32(&ci->mlock_limit, f));
+ SAFE(read_string(&ci->mlock_key, f));
+ if (ver >= 10) {
+#ifdef HAS_FMODE
+ SAFE(read_string(&ci->mlock_flood, f));
+#else
+ SAFE(read_string(&s, f));
+ if (s)
+ free(s);
+#endif
+#ifdef HAS_LMODE
+ SAFE(read_string(&ci->mlock_redirect, f));
+#else
+ SAFE(read_string(&s, f));
+ if (s)
+ free(s);
+#endif
+ }
+
+ SAFE(read_int16(&ci->memos.memocount, f));
+ SAFE(read_int16(&ci->memos.memomax, f));
+ if (ci->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * ci->memos.memocount, 1);
+ ci->memos.memos = memos;
+ for (j = 0; j < ci->memos.memocount; j++, memos++) {
+ SAFE(read_int32(&memos->number, f));
+ SAFE(read_int16(&memos->flags, f));
+ SAFE(read_int32(&tmp32, f));
+ memos->time = tmp32;
+ SAFE(read_buffer(memos->sender, f));
+ SAFE(read_string(&memos->text, f));
+ for (m = 0; m < MAX_CMD_HASH; m++) {
+ memos->moduleData[m] = NULL;
+ }
+ }
+ }
+
+ SAFE(read_string(&ci->entry_message, f));
+
+ ci->c = NULL;
+
+ /* Some cleanup */
+ if (ver <= 11) {
+ /* Cleanup: Founder must be != than successor */
+ if (!(ci->flags & CI_VERBOTEN)
+ && ci->successor == ci->founder) {
+ alog("Warning: founder and successor of %s are equal. Cleaning up.", ci->name);
+ ci->successor = NULL;
+ }
+ }
+
+ /* BotServ options */
+
+ if (ver >= 8) {
+ int n_ttb;
+
+ SAFE(read_string(&s, f));
+ if (s) {
+ ci->bi = findbot(s);
+ free(s);
+ } else
+ ci->bi = NULL;
+
+ SAFE(read_int32(&tmp32, f));
+ ci->botflags = tmp32;
+ SAFE(read_int16(&tmp16, f));
+ n_ttb = tmp16;
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (j = 0; j < n_ttb; j++) {
+ if (j < TTB_SIZE)
+ SAFE(read_int16(&ci->ttb[j], f));
+ else
+ SAFE(read_int16(&tmp16, f));
+ }
+ for (j = n_ttb; j < TTB_SIZE; j++)
+ ci->ttb[j] = 0;
+ SAFE(read_int16(&tmp16, f));
+ ci->capsmin = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->capspercent = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->floodlines = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->floodsecs = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->repeattimes = tmp16;
+
+ SAFE(read_int16(&ci->bwcount, f));
+ if (ci->bwcount) {
+ ci->badwords = scalloc(ci->bwcount, sizeof(BadWord));
+ for (j = 0; j < ci->bwcount; j++) {
+ SAFE(read_int16(&ci->badwords[j].in_use, f));
+ if (ci->badwords[j].in_use) {
+ SAFE(read_string(&ci->badwords[j].word, f));
+ SAFE(read_int16(&ci->badwords[j].type, f));
+ }
+ }
+ } else {
+ ci->badwords = NULL;
+ }
+ } else {
+ ci->bi = NULL;
+ ci->botflags = 0;
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (j = 0; j < TTB_SIZE; j++)
+ ci->ttb[j] = 0;
+ ci->bwcount = 0;
+ ci->badwords = NULL;
+ }
+
+ } /* while (getc_db(f) != 0) */
+
+ *last = NULL;
+
+ } /* for (i) */
+
+ close_db(f);
+
+ /* Check for non-forbidden channels with no founder.
+ Makes also other essential tasks. */
+ for (i = 0; i < 256; i++) {
+ ChannelInfo *next;
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (!(ci->flags & CI_VERBOTEN) && !ci->founder) {
+ alog("%s: database load: Deleting founderless channel %s",
+ s_ChanServ, ci->name);
+ delchan(ci);
+ continue;
+ }
+ if (ver < 13) {
+ ChanAccess *access, *access2;
+ AutoKick *akick, *akick2;
+ int k;
+
+ if (ci->flags & CI_VERBOTEN)
+ continue;
+ /* Need to regenerate the channel count for the founder */
+ ci->founder->channelcount++;
+ /* Check for eventual double entries in access/akick lists. */
+ for (j = 0, access = ci->access; j < ci->accesscount;
+ j++, access++) {
+ if (!access->in_use)
+ continue;
+ for (k = 0, access2 = ci->access; k < j;
+ k++, access2++) {
+ if (access2->in_use && access2->nc == access->nc) {
+ alog("%s: deleting %s channel access entry of %s because it is already in the list (this is OK).", s_ChanServ, access->nc->display, ci->name);
+ memset(access, 0, sizeof(ChanAccess));
+ break;
+ }
+ }
+ }
+ for (j = 0, akick = ci->akick; j < ci->akickcount;
+ j++, akick++) {
+ if (!(akick->flags & AK_USED)
+ || !(akick->flags & AK_ISNICK))
+ continue;
+ for (k = 0, akick2 = ci->akick; k < j; k++, akick2++) {
+ if ((akick2->flags & AK_USED)
+ && (akick2->flags & AK_ISNICK)
+ && akick2->u.nc == akick->u.nc) {
+ alog("%s: deleting %s channel akick entry of %s because it is already in the list (this is OK).", s_ChanServ, akick->u.nc->display, ci->name);
+ if (akick->reason)
+ free(akick->reason);
+ if (akick->creator)
+ free(akick->creator);
+ memset(akick, 0, sizeof(AutoKick));
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", ChanDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", ChanDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_cs_dbase(void)
+{
+ dbFILE *f;
+ int i, j;
+ ChannelInfo *ci;
+ Memo *memos;
+ static time_t lastwarn = 0;
+
+ if (!(f = open_db(s_ChanServ, ChanDBName, "w", CHAN_VERSION)))
+ return;
+
+ for (i = 0; i < 256; i++) {
+ int16 tmp16;
+
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ SAFE(write_int8(1, f));
+ SAFE(write_buffer(ci->name, f));
+ if (ci->founder)
+ SAFE(write_string(ci->founder->display, f));
+ else
+ SAFE(write_string(NULL, f));
+ if (ci->successor)
+ SAFE(write_string(ci->successor->display, f));
+ else
+ SAFE(write_string(NULL, f));
+ SAFE(write_buffer(ci->founderpass, f));
+ SAFE(write_string(ci->desc, f));
+ SAFE(write_string(ci->url, f));
+ SAFE(write_string(ci->email, f));
+ SAFE(write_int32(ci->time_registered, f));
+ SAFE(write_int32(ci->last_used, f));
+ SAFE(write_string(ci->last_topic, f));
+ SAFE(write_buffer(ci->last_topic_setter, f));
+ SAFE(write_int32(ci->last_topic_time, f));
+ SAFE(write_int32(ci->flags, f));
+ SAFE(write_string(ci->forbidby, f));
+ SAFE(write_string(ci->forbidreason, f));
+ SAFE(write_int16(ci->bantype, f));
+
+ tmp16 = CA_SIZE;
+ SAFE(write_int16(tmp16, f));
+ for (j = 0; j < CA_SIZE; j++)
+ SAFE(write_int16(ci->levels[j], f));
+
+ SAFE(write_int16(ci->accesscount, f));
+ for (j = 0; j < ci->accesscount; j++) {
+ SAFE(write_int16(ci->access[j].in_use, f));
+ if (ci->access[j].in_use) {
+ SAFE(write_int16(ci->access[j].level, f));
+ SAFE(write_string(ci->access[j].nc->display, f));
+ SAFE(write_int32(ci->access[j].last_seen, f));
+ }
+ }
+
+ SAFE(write_int16(ci->akickcount, f));
+ for (j = 0; j < ci->akickcount; j++) {
+ SAFE(write_int16(ci->akick[j].flags, f));
+ if (ci->akick[j].flags & AK_USED) {
+ if (ci->akick[j].flags & AK_ISNICK)
+ SAFE(write_string(ci->akick[j].u.nc->display, f));
+ else
+ SAFE(write_string(ci->akick[j].u.mask, f));
+ SAFE(write_string(ci->akick[j].reason, f));
+ SAFE(write_string(ci->akick[j].creator, f));
+ SAFE(write_int32(ci->akick[j].addtime, f));
+ }
+ }
+
+ SAFE(write_int32(ci->mlock_on, f));
+ SAFE(write_int32(ci->mlock_off, f));
+ SAFE(write_int32(ci->mlock_limit, f));
+ SAFE(write_string(ci->mlock_key, f));
+#ifdef HAS_FMODE
+ SAFE(write_string(ci->mlock_flood, f));
+#else
+ SAFE(write_string(NULL, f));
+#endif
+#ifdef HAS_LMODE
+ SAFE(write_string(ci->mlock_redirect, f));
+#else
+ SAFE(write_string(NULL, f));
+#endif
+
+ SAFE(write_int16(ci->memos.memocount, f));
+ SAFE(write_int16(ci->memos.memomax, f));
+ memos = ci->memos.memos;
+ for (j = 0; j < ci->memos.memocount; j++, memos++) {
+ SAFE(write_int32(memos->number, f));
+ SAFE(write_int16(memos->flags, f));
+ SAFE(write_int32(memos->time, f));
+ SAFE(write_buffer(memos->sender, f));
+ SAFE(write_string(memos->text, f));
+ }
+
+ SAFE(write_string(ci->entry_message, f));
+
+ if (ci->bi)
+ SAFE(write_string(ci->bi->nick, f));
+ else
+ SAFE(write_string(NULL, f));
+
+ SAFE(write_int32(ci->botflags, f));
+
+ tmp16 = TTB_SIZE;
+ SAFE(write_int16(tmp16, f));
+ for (j = 0; j < TTB_SIZE; j++)
+ SAFE(write_int16(ci->ttb[j], f));
+
+ SAFE(write_int16(ci->capsmin, f));
+ SAFE(write_int16(ci->capspercent, f));
+ SAFE(write_int16(ci->floodlines, f));
+ SAFE(write_int16(ci->floodsecs, f));
+ SAFE(write_int16(ci->repeattimes, f));
+
+ SAFE(write_int16(ci->bwcount, f));
+ for (j = 0; j < ci->bwcount; j++) {
+ SAFE(write_int16(ci->badwords[j].in_use, f));
+ if (ci->badwords[j].in_use) {
+ SAFE(write_string(ci->badwords[j].word, f));
+ SAFE(write_int16(ci->badwords[j].type, f));
+ }
+ }
+ } /* for (chanlists[i]) */
+
+ SAFE(write_int8(0, f));
+
+ } /* for (i) */
+
+ close_db(f);
+
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+void save_cs_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ int i;
+ ChannelInfo *ci;
+
+ if (!rdb_open())
+ return;
+
+ rdb_tag_table("anope_cs_info");
+ rdb_scrub_table("anope_ms_info", "serv='CHAN'");
+ rdb_clear_table("anope_cs_access");
+ rdb_clear_table("anope_cs_levels");
+ rdb_clear_table("anope_cs_akicks");
+ rdb_clear_table("anope_cs_badwords");
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ rdb_save_cs_info(ci);
+ } /* for (chanlists[i]) */
+ } /* for (i) */
+
+ rdb_scrub_table("anope_cs_info", "active='0'");
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+
+/* Check the current modes on a channel; if they conflict with a mode lock,
+ * fix them. */
+
+void check_modes(Channel * c)
+{
+ char modebuf[64], argbuf[BUFSIZE], *end = modebuf, *end2 = argbuf;
+ uint32 modes;
+ ChannelInfo *ci;
+ CBModeInfo *cbmi;
+ CBMode *cbm;
+
+ if (c->bouncy_modes)
+ return;
+
+ /* 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' U:lines configured correctly?",
+ c->name);
+ alog("%s: Bouncy modes on channel %s", s_ChanServ, 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++;
+
+ if (!(ci = c->ci)) {
+#ifndef IRC_HYBRID
+ if (c->mode & CMODE_r) {
+ c->mode &= ~CMODE_r;
+ send_mode(whosends(ci), c->name, "-r");
+ }
+#endif
+ return;
+ }
+
+ modes = ~c->mode & ci->mlock_on;
+
+ *end++ = '+';
+ cbmi = cbmodeinfos;
+
+ do {
+ if (modes & cbmi->flag) {
+ *end++ = cbmi->mode;
+ c->mode |= cbmi->flag;
+
+ /* Add the eventual parameter and modify the Channel structure */
+ if (cbmi->getvalue && cbmi->csgetvalue) {
+ char *value = cbmi->csgetvalue(ci);
+
+ cbm = &cbmodes[(int) cbmi->mode];
+ cbm->setvalue(c, value);
+
+ if (value) {
+ *end2++ = ' ';
+ while (*value)
+ *end2++ = *value++;
+ }
+ }
+ } else if (cbmi->getvalue && cbmi->csgetvalue
+ && (ci->mlock_on & cbmi->flag)
+ && (c->mode & cbmi->flag)) {
+ char *value = cbmi->getvalue(c);
+ char *csvalue = cbmi->csgetvalue(ci);
+
+ /* Lock and actual values don't match, so fix the mode */
+ if (value && csvalue && strcmp(value, csvalue)) {
+ *end++ = cbmi->mode;
+
+ cbm = &cbmodes[(int) cbmi->mode];
+ cbm->setvalue(c, csvalue);
+
+ *end2++ = ' ';
+ while (*csvalue)
+ *end2++ = *csvalue++;
+ }
+ }
+ } while ((++cbmi)->flag != 0);
+
+ if (*(end - 1) == '+')
+ end--;
+
+ modes = c->mode & ci->mlock_off;
+
+ if (modes) {
+ *end++ = '-';
+ cbmi = cbmodeinfos;
+
+ do {
+ if (modes & cbmi->flag) {
+ *end++ = cbmi->mode;
+ c->mode &= ~cbmi->flag;
+
+ /* Add the eventual parameter and clean up the Channel structure */
+ if (cbmi->getvalue) {
+ cbm = &cbmodes[(int) cbmi->mode];
+
+ if (!(cbm->flags & CBM_MINUS_NO_ARG)) {
+ char *value = cbmi->getvalue(c);
+
+ if (value) {
+ *end2++ = ' ';
+ while (*value)
+ *end2++ = *value++;
+ }
+ }
+
+ cbm->setvalue(c, NULL);
+ }
+ }
+ } while ((++cbmi)->flag != 0);
+ }
+
+ if (end == modebuf)
+ return;
+
+ *end = 0;
+ *end2 = 0;
+
+ send_mode(whosends(ci), c->name, "%s%s", modebuf,
+ (end2 == argbuf ? "" : argbuf));
+}
+
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+
+int check_valid_admin(User * user, Channel * chan, int servermode)
+{
+ if (!chan->ci)
+ return 1;
+
+ /* They will be kicked; no need to deop, no need to update our internal struct too */
+ if (chan->ci->flags & CI_VERBOTEN)
+ return 0;
+
+ if (servermode && !check_access(user, chan->ci, CA_AUTOPROTECT)) {
+ notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
+ send_mode(whosends(chan->ci), chan->name, "-a %s", user->nick);
+ return 0;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP)) {
+ send_mode(whosends(chan->ci), chan->name, "-a %s", user->nick);
+ return 0;
+ }
+
+ return 1;
+}
+#endif
+
+/*************************************************************************/
+
+/* Check whether a user is allowed to be opped on a channel; if they
+ * aren't, deop them. If serverop is 1, the +o was done by a server.
+ * Return 1 if the user is allowed to be opped, 0 otherwise. */
+
+int check_valid_op(User * user, Channel * chan, int servermode)
+{
+ if (!chan->ci)
+ return 1;
+
+ /* They will be kicked; no need to deop, no need to update our internal struct too */
+ if (chan->ci->flags & CI_VERBOTEN)
+ return 0;
+
+ if (servermode && !check_access(user, chan->ci, CA_AUTOOP)) {
+ notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
+#ifdef HAS_HALFOP
+# if defined(IRC_UNREAL)
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ send_mode(whosends(chan->ci), chan->name, "-aoq %s %s %s",
+ user->nick, user->nick, user->nick);
+ } else {
+ send_mode(whosends(chan->ci), chan->name, "-ahoq %s %s %s %s",
+ user->nick, user->nick, user->nick, user->nick);
+ }
+# elif defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ send_mode(whosends(chan->ci), chan->name, "-ao %s %s",
+ user->nick, user->nick);
+ } else {
+ send_mode(whosends(chan->ci), chan->name, "-aoh %s %s %s",
+ user->nick, user->nick, user->nick);
+ }
+# else
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ send_mode(whosends(chan->ci), chan->name, "-o %s", user->nick);
+ } else {
+ send_mode(whosends(chan->ci), chan->name, "-ho %s %s",
+ user->nick, user->nick);
+ }
+# endif
+#else
+ send_mode(whosends(chan->ci), chan->name, "-o %s", user->nick);
+#endif
+ return 0;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP)) {
+#ifdef HAS_HALFOP
+# ifdef IRC_UNREAL
+ send_mode(whosends(chan->ci), chan->name, "-ahoq %s %s %s %s",
+ user->nick, user->nick, user->nick, user->nick);
+# else
+ send_mode(whosends(chan->ci), chan->name, "-ho %s %s", user->nick,
+ user->nick);
+# endif
+#else
+ send_mode(whosends(chan->ci), chan->name, "-o %s", user->nick);
+#endif
+ return 0;
+ }
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Check whether a user should be opped on a channel, and if so, do it.
+ * Return 1 if the user was opped, 0 otherwise. (Updates the channel's
+ * last used time if the user was opped.) */
+
+int check_should_op(User * user, const char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if ((ci->flags & CI_SECURE) && !nick_identified(user))
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOOP)) {
+ send_mode(whosends(ci), chan, "+o %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Check whether a user should be voiced on a channel, and if so, do it.
+ * Return 1 if the user was voiced, 0 otherwise. */
+
+int check_should_voice(User * user, const char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if ((ci->flags & CI_SECURE) && !nick_identified(user))
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOVOICE)) {
+ send_mode(whosends(ci), chan, "+v %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+#ifdef HAS_HALFOP
+
+int check_should_halfop(User * user, const char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOHALFOP)) {
+ send_mode(whosends(ci), chan, "+h %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+
+int check_should_owner(User * user, const char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (((ci->flags & CI_SECUREFOUNDER) && is_real_founder(user, ci))
+ || (!(ci->flags & CI_SECUREFOUNDER) && is_founder(user, ci))) {
+ send_mode(whosends(ci), chan, "+oq %s %s", user->nick, user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2) || defined(IRC_PTLINK)
+
+int check_should_protect(User * user, const char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOPROTECT)) {
+ send_mode(whosends(ci), chan, "+oa %s %s", user->nick, user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
+/*************************************************************************/
+
+/* Tiny helper routine to get ChanServ out of a channel after it went in. */
+
+static void timeout_leave(Timeout * to)
+{
+ char *chan = to->data;
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (ci) /* Check cos the channel may be dropped in the meantime */
+ ci->flags &= ~CI_INHABIT;
+
+ send_cmd(s_ChanServ, "PART %s", chan);
+ free(to->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. Note that this is called
+ * _before_ the user is added to internal channel lists (so do_kick() is
+ * not called).
+ */
+
+int check_kick(User * user, char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+ Channel *c;
+ AutoKick *akick;
+ int i;
+ NickCore *nc;
+ char *av[3];
+ char mask[BUFSIZE];
+ const char *reason;
+ Timeout *t;
+
+ if (!ci)
+ return 0;
+
+ if (is_oper(user) || is_services_admin(user))
+ return 0;
+
+ if (ci->flags & CI_VERBOTEN) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason =
+ ci->forbidreason ? ci->forbidreason : getstring(user->na,
+ CHAN_MAY_NOT_BE_USED);
+ goto kick;
+ }
+
+ if (ci->flags & CI_SUSPENDED) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason =
+ ci->forbidreason ? ci->forbidreason : getstring(user->na,
+ CHAN_MAY_NOT_BE_USED);
+ goto kick;
+ }
+
+ if (nick_recognized(user))
+ nc = user->na->nc;
+ else
+ nc = NULL;
+
+#ifdef HAS_EXCEPT
+ /*
+ * Before we go through akick lists, see if they're excepted FIRST
+ * We cannot kick excempted users that are akicked or not on the channel access list
+ * as that will start services <-> server wars which ends up as a DoS against services.
+ *
+ * UltimateIRCd 3.x at least informs channel staff when a joining user is matching an exempt.
+ */
+ if (is_excepted(ci, user) == 1) {
+ return 0;
+ }
+#endif
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if ((akick->flags & AK_ISNICK && akick->u.nc == nc)
+ || (!(akick->flags & AK_ISNICK)
+ && match_usermask(akick->u.mask, user))) {
+ if (debug >= 2)
+ alog("debug: %s matched akick %s", user->nick,
+ (akick->flags & AK_ISNICK) ? akick->u.nc->
+ display : akick->u.mask);
+ if (akick->flags & AK_ISNICK)
+ get_idealban(ci, user, mask, sizeof(mask));
+ else
+ strcpy(mask, akick->u.mask);
+ reason = akick->reason ? akick->reason : CSAutokickReason;
+ goto kick;
+ }
+ }
+
+ if (check_access(user, ci, CA_NOJOIN)) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason = getstring(user->na, CHAN_NOT_ALLOWED_TO_JOIN);
+ goto kick;
+ }
+
+ return 0;
+
+ kick:
+ if (debug)
+ alog("debug: channel: AutoKicking %s!%s@%s from %s", user->nick,
+ user->username, GetHost(user), chan);
+
+ /* Remember that the user has not been added to our channel user list
+ * yet, so we check whether the channel does not exist OR has no user
+ * on it (before SJOIN would have created the channel structure, while
+ * JOIN would not). */
+ /* Don't check for CI_INHABIT before for the Channel record cos else
+ * c may be NULL even if it exists */
+ if ((!(c = findchan(chan)) || c->usercount == 0)
+ && !(ci->flags & CI_INHABIT)) {
+#if defined(IRC_BAHAMUT)
+ send_cmd(s_ChanServ, "SJOIN %lu %s",
+ (c ? c->creation_time : time(NULL)), chan);
+#elif defined(IRC_HYBRID)
+ send_cmd(NULL, "SJOIN %ld %s + :@%s",
+ time(NULL), chan, s_ChanServ);
+#else
+ send_cmd(s_ChanServ, "JOIN %s", chan);
+#endif
+ t = add_timeout(CSInhabit, timeout_leave, 0);
+ t->data = sstrdup(chan);
+ ci->flags |= CI_INHABIT;
+ }
+
+ if (c) {
+ av[0] = chan;
+ av[1] = sstrdup("+b");
+ av[2] = mask;
+ do_cmode(whosends(ci), 3, av);
+ free(av[1]);
+ }
+
+ send_mode(whosends(ci), chan, "+b %s %lu", mask, time(NULL));
+ send_cmd(whosends(ci), "KICK %s %s :%s", chan, user->nick, reason);
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Record the current channel topic in the ChannelInfo structure. */
+
+void record_topic(const char *chan)
+{
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (readonly)
+ return;
+ c = findchan(chan);
+ if (!c || !(ci = c->ci))
+ return;
+ if (ci->last_topic)
+ free(ci->last_topic);
+ if (c->topic)
+ ci->last_topic = sstrdup(c->topic);
+ else
+ ci->last_topic = NULL;
+ strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
+ ci->last_topic_time = c->topic_time;
+}
+
+/*************************************************************************/
+
+/* Restore the topic in a channel when it's created, if we should. */
+
+void restore_topic(const char *chan)
+{
+ Channel *c = findchan(chan);
+ ChannelInfo *ci;
+
+ if (!c || !(ci = c->ci) || !(ci->flags & CI_KEEPTOPIC))
+ return;
+ if (c->topic)
+ free(c->topic);
+ if (ci->last_topic) {
+ c->topic = sstrdup(ci->last_topic);
+ strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
+ c->topic_time = ci->last_topic_time;
+ } else {
+ c->topic = NULL;
+ strscpy(c->topic_setter, s_ChanServ, NICKMAX);
+ }
+#ifdef IRC_HYBRID
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), chan, s_ChanServ);
+ send_mode(NULL, chan, "+o %s", s_ChanServ);
+ }
+ send_cmd(whosends(ci), "TOPIC %s :%s", chan, c->topic ? c->topic : "");
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(s_ChanServ, "PART %s", chan);
+ }
+#else
+ send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter,
+ c->topic_time, c->topic ? c->topic : "");
+#endif
+}
+
+/*************************************************************************/
+
+/* See if the topic is locked on the given channel, and return 1 (and fix
+ * the topic) if so. */
+
+int check_topiclock(Channel * c, time_t topic_time)
+{
+ ChannelInfo *ci;
+
+ if (!(ci = c->ci) || !(ci->flags & CI_TOPICLOCK))
+ return 0;
+
+ if (c->topic)
+ free(c->topic);
+ if (ci->last_topic)
+ c->topic = sstrdup(ci->last_topic);
+ else
+ c->topic = NULL;
+
+ strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
+#ifdef IRC_UNREAL
+ /* Because older timestamps are rejected */
+ c->topic_time = topic_time + 1;
+#else
+ c->topic_time = ci->last_topic_time;
+#endif
+
+#ifdef IRC_HYBRID
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name,
+ s_ChanServ);
+ send_mode(NULL, c->name, "+o %s", s_ChanServ);
+ }
+ send_cmd(whosends(ci), "TOPIC %s :%s", c->name,
+ c->topic ? c->topic : "");
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(s_ChanServ, "PART %s", c->name);
+ }
+#else
+ send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter,
+ c->topic_time, c->topic ? c->topic : "");
+#endif
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Remove all channels which have expired. */
+
+void expire_chans()
+{
+ ChannelInfo *ci, *next;
+ int i;
+ time_t now = time(NULL);
+
+ if (!CSExpire)
+ return;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (!ci->c && now - ci->last_used >= CSExpire
+ && !(ci->flags & (CI_VERBOTEN | CI_NO_EXPIRE))) {
+ alog("Expiring channel %s (founder: %s)", ci->name,
+ (ci->founder ? ci->founder->display : "(none)"));
+ delchan(ci);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Remove a (deleted or expired) nickname from all channel lists. */
+
+void cs_remove_nick(const NickCore * nc)
+{
+ int i, j;
+ ChannelInfo *ci, *next;
+ ChanAccess *ca;
+ AutoKick *akick;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (ci->founder == nc) {
+ if (ci->successor) {
+ NickCore *nc2 = ci->successor;
+ if (!nick_is_services_admin(nc2) && nc2->channelmax > 0
+ && nc2->channelcount >= nc2->channelmax) {
+ alog("%s: Successor (%s) of %s owns too many channels, " "deleting channel", s_ChanServ, nc2->display, ci->name);
+ delchan(ci);
+ continue;
+ } else {
+ alog("%s: Transferring foundership of %s from deleted " "nick %s to successor %s", s_ChanServ, ci->name, nc->display, nc2->display);
+ ci->founder = nc2;
+ ci->successor = NULL;
+ nc2->channelcount++;
+#ifdef USE_RDB
+ if (rdb_open()) {
+ rdb_cs_set_founder(ci->name, nc2->display);
+ rdb_close();
+ }
+#endif
+ }
+ } else {
+ alog("%s: Deleting channel %s owned by deleted nick %s", s_ChanServ, ci->name, nc->display);
+#ifndef IRC_HYBRID
+ /* Maybe move this to delchan() ? */
+ if ((ci->c) && (ci->c->mode & CMODE_r)) {
+ ci->c->mode &= ~CMODE_r;
+ send_mode(whosends(ci), ci->name, "-r");
+ }
+#endif
+
+ delchan(ci);
+ continue;
+ }
+ }
+
+ if (ci->successor == nc)
+ ci->successor = NULL;
+
+ for (ca = ci->access, j = ci->accesscount; j > 0; ca++, j--) {
+ if (ca->in_use && ca->nc == nc) {
+ ca->in_use = 0;
+ ca->nc = NULL;
+ }
+ }
+
+ for (akick = ci->akick, j = ci->akickcount; j > 0;
+ akick++, j--) {
+ if ((akick->flags & AK_USED) && (akick->flags & AK_ISNICK)
+ && akick->u.nc == nc) {
+ if (akick->creator) {
+ free(akick->creator);
+ akick->creator = NULL;
+ }
+ if (akick->reason) {
+ free(akick->reason);
+ akick->reason = NULL;
+ }
+ akick->flags = 0;
+ akick->u.nc = NULL;
+ }
+ }
+ }
+ }
+#ifdef USE_RDB
+ if (rdb_open()) {
+ rdb_cs_deluser(nc->display);
+ rdb_close();
+ }
+#endif
+}
+
+/*************************************************************************/
+
+/* Removes any reference to a bot */
+
+void cs_remove_bot(const BotInfo * bi)
+{
+ int i;
+ ChannelInfo *ci;
+
+ for (i = 0; i < 256; i++)
+ for (ci = chanlists[i]; ci; ci = ci->next)
+ if (ci->bi == bi)
+ ci->bi = NULL;
+}
+
+/*************************************************************************/
+
+/* Return the ChannelInfo structure for the given channel, or NULL if the
+ * channel isn't registered. */
+
+ChannelInfo *cs_findchan(const char *chan)
+{
+ ChannelInfo *ci;
+
+ for (ci = chanlists[tolower(chan[1])]; ci; ci = ci->next) {
+ if (stricmp(ci->name, chan) == 0)
+ return ci;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* 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). */
+
+int check_access(User * user, ChannelInfo * ci, int what)
+{
+ int level = get_access(user, ci);
+ int limit = ci->levels[what];
+
+ /* Resetting the last used time */
+ if (level > 0)
+ ci->last_used = time(NULL);
+
+ if (level == ACCESS_FOUNDER)
+ return (what == CA_AUTODEOP || what == CA_NOJOIN) ? 0 : 1;
+ /* Hacks to make flags work */
+ if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS) && level == 0)
+ return 1;
+ if (limit == ACCESS_INVALID)
+ return 0;
+ if (what == CA_AUTODEOP || what == CA_NOJOIN)
+ return level <= ci->levels[what];
+ else
+ return level >= ci->levels[what];
+}
+
+/*************************************************************************/
+/*********************** ChanServ private routines ***********************/
+/*************************************************************************/
+
+/* Insert a channel alphabetically into the database. */
+
+void alpha_insert_chan(ChannelInfo * ci)
+{
+ ChannelInfo *ptr, *prev;
+ char *chan = ci->name;
+
+ for (prev = NULL, ptr = chanlists[tolower(chan[1])];
+ ptr != NULL && stricmp(ptr->name, chan) < 0;
+ prev = ptr, ptr = ptr->next);
+ ci->prev = prev;
+ ci->next = ptr;
+ if (!prev)
+ chanlists[tolower(chan[1])] = ci;
+ else
+ prev->next = ci;
+ if (ptr)
+ ptr->prev = ci;
+}
+
+/*************************************************************************/
+
+/* 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. */
+
+static ChannelInfo *makechan(const char *chan)
+{
+ int i;
+ ChannelInfo *ci;
+
+ ci = scalloc(sizeof(ChannelInfo), 1);
+ strscpy(ci->name, chan, CHANMAX);
+ ci->time_registered = time(NULL);
+ reset_levels(ci);
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (i = 0; i < TTB_SIZE; i++)
+ ci->ttb[i] = 0;
+ alpha_insert_chan(ci);
+ return ci;
+}
+
+/*************************************************************************/
+
+/* Remove a channel from the ChanServ database. Return 1 on success, 0
+ * otherwise. */
+
+int delchan(ChannelInfo * ci)
+{
+ int i;
+ NickCore *nc = ci->founder;
+
+ if (ci->bi) {
+ ci->bi->chancount--;
+ }
+ if (ci->c) {
+ if (ci->bi && ci->c->usercount >= BSMinUsers) {
+ send_cmd(ci->bi->nick, "PART %s", ci->c->name);
+ }
+ ci->c->ci = NULL;
+ }
+#ifdef USE_RDB
+ if (rdb_open()) {
+ rdb_cs_delchan(ci);
+ rdb_close();
+ }
+#endif
+ if (ci->next)
+ ci->next->prev = ci->prev;
+ if (ci->prev)
+ ci->prev->next = ci->next;
+ else
+ chanlists[tolower(ci->name[1])] = ci->next;
+ if (ci->desc)
+ free(ci->desc);
+ if (ci->mlock_key)
+ free(ci->mlock_key);
+#ifdef HAS_FMODE
+ if (ci->mlock_flood)
+ free(ci->mlock_flood);
+#endif
+#ifdef HAS_LMODE
+ if (ci->mlock_redirect)
+ free(ci->mlock_redirect);
+#endif
+ if (ci->last_topic)
+ free(ci->last_topic);
+ if (ci->forbidby)
+ free(ci->forbidby);
+ if (ci->forbidreason)
+ free(ci->forbidreason);
+ if (ci->access)
+ free(ci->access);
+ for (i = 0; i < ci->akickcount; i++) {
+ if (!(ci->akick[i].flags & AK_ISNICK) && ci->akick[i].u.mask)
+ free(ci->akick[i].u.mask);
+ if (ci->akick[i].reason)
+ free(ci->akick[i].reason);
+ if (ci->akick[i].creator)
+ free(ci->akick[i].creator);
+ }
+ if (ci->akick)
+ free(ci->akick);
+ if (ci->levels)
+ free(ci->levels);
+ if (ci->memos.memos) {
+ for (i = 0; i < ci->memos.memocount; i++) {
+ if (ci->memos.memos[i].text)
+ free(ci->memos.memos[i].text);
+ moduleCleanStruct(ci->memos.memos[i].moduleData);
+ }
+ free(ci->memos.memos);
+ }
+ if (ci->ttb)
+ free(ci->ttb);
+ for (i = 0; i < ci->bwcount; i++) {
+ if (ci->badwords[i].word)
+ free(ci->badwords[i].word);
+ }
+ if (ci->badwords)
+ free(ci->badwords);
+
+ moduleCleanStruct(ci->moduleData);
+
+ free(ci);
+ if (nc)
+ nc->channelcount--;
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Reset channel access level values to their default state. */
+
+void reset_levels(ChannelInfo * ci)
+{
+ int i;
+
+ if (ci->levels)
+ free(ci->levels);
+ ci->levels = scalloc(CA_SIZE * sizeof(*ci->levels), 1);
+ for (i = 0; def_levels[i][0] >= 0; i++)
+ ci->levels[def_levels[i][0]] = def_levels[i][1];
+}
+
+/*************************************************************************/
+
+/* Does the given user have founder access to the channel? */
+
+int is_founder(User * user, ChannelInfo * ci)
+{
+ if (user->isSuperAdmin) {
+ return 1;
+ }
+
+ if (user->na && user->na->nc == ci->founder) {
+ if ((nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
+ return 1;
+ }
+ if (is_identified(user, ci))
+ return 1;
+ return 0;
+}
+
+/*************************************************************************/
+
+static int is_real_founder(User * user, ChannelInfo * ci)
+{
+ if (user->isSuperAdmin) {
+ return 1;
+ }
+
+ if (user->na && user->na->nc == ci->founder) {
+ if ((nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
+ return 1;
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Has the given user password-identified as founder for the channel? */
+
+static int is_identified(User * user, ChannelInfo * ci)
+{
+ struct u_chaninfolist *c;
+
+ for (c = user->founder_chans; c; c = c->next) {
+ if (c->chan == ci)
+ return 1;
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Returns the ChanAccess entry for an user */
+
+ChanAccess *get_access_entry(NickCore * nc, ChannelInfo * ci)
+{
+ ChanAccess *access;
+ int i;
+
+ for (access = ci->access, i = 0; i < ci->accesscount; access++, i++)
+ if (access->in_use && access->nc == nc)
+ return access;
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* 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. */
+
+int get_access(User * user, ChannelInfo * ci)
+{
+ ChanAccess *access;
+
+ if (!ci)
+ return 0;
+
+ if (is_founder(user, ci))
+ return ACCESS_FOUNDER;
+
+ if (!user->na)
+ return 0;
+
+ if (nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
+ if ((access = get_access_entry(user->na->nc, ci)))
+ return access->level;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+void update_cs_lastseen(User * user, ChannelInfo * ci)
+{
+ ChanAccess *access;
+
+ if (!ci || !user->na)
+ return;
+
+ if (is_founder(user, ci) || nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
+ if ((access = get_access_entry(user->na->nc, ci)))
+ access->last_seen = time(NULL);
+}
+
+/*************************************************************************/
+
+/* Returns the best ban possible for an user depending of the bantype
+ value. */
+
+int get_idealban(ChannelInfo * ci, User * u, char *ret, int retlen)
+{
+ char *mask;
+
+ if (!ci || !u || !ret || retlen == 0)
+ return 0;
+
+ switch (ci->bantype) {
+ case 0:
+ snprintf(ret, retlen, "*!%s@%s", GetIdent(u), GetHost(u));
+ return 1;
+ case 1:
+ snprintf(ret, retlen, "*!%s%s@%s",
+ (strlen(GetIdent(u)) <
+ (*(GetIdent(u)) ==
+ '~' ? USERMAX + 1 : USERMAX) ? "*" : ""),
+ (*(GetIdent(u)) == '~' ? GetIdent(u) + 1 : GetIdent(u)),
+ GetHost(u));
+ return 1;
+ case 2:
+ snprintf(ret, retlen, "*!*@%s", GetHost(u));
+ return 1;
+ case 3:
+ mask = create_mask(u);
+ snprintf(ret, retlen, "*!%s", mask);
+ free(mask);
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+/*************************************************************************/
+
+static void make_unidentified(User * u, ChannelInfo * ci)
+{
+ struct u_chaninfolist *uci;
+
+ if (!u || !ci)
+ return;
+
+ for (uci = u->founder_chans; uci; uci = uci->next) {
+ if (uci->chan == ci) {
+ if (uci->next)
+ uci->next->prev = uci->prev;
+ if (uci->prev)
+ uci->prev->next = uci->next;
+ else
+ u->founder_chans = uci->next;
+ free(uci);
+ break;
+ }
+ }
+}
+
+/*************************************************************************/
+
+#ifdef HAS_FMODE
+
+char *cs_get_flood(ChannelInfo * ci)
+{
+ return ci->mlock_flood;
+}
+
+#endif
+
+/*************************************************************************/
+
+char *cs_get_key(ChannelInfo * ci)
+{
+ return ci->mlock_key;
+}
+
+/*************************************************************************/
+
+char *cs_get_limit(ChannelInfo * ci)
+{
+ static char limit[16];
+
+ if (ci->mlock_limit == 0)
+ return NULL;
+
+ snprintf(limit, sizeof(limit), "%lu", ci->mlock_limit);
+ return limit;
+}
+
+/*************************************************************************/
+
+#ifdef HAS_LMODE
+
+char *cs_get_redirect(ChannelInfo * ci)
+{
+ return ci->mlock_redirect;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef HAS_FMODE
+
+void cs_set_flood(ChannelInfo * ci, char *value)
+{
+ char *dp, *end;
+
+ if (ci->mlock_flood)
+ free(ci->mlock_flood);
+
+ /* This looks ugly, but it works ;) */
+ if (value && *value != ':'
+ && (strtoul((*value == '*' ? value + 1 : value), &dp, 10) > 0)
+ && (*dp == ':') && (*(++dp) != 0) && (strtoul(dp, &end, 10) > 0)
+ && (*end == 0)) {
+ ci->mlock_flood = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~CMODE_f;
+ ci->mlock_flood = NULL;
+ }
+}
+
+#endif
+
+/*************************************************************************/
+
+void cs_set_key(ChannelInfo * ci, char *value)
+{
+ if (ci->mlock_key)
+ free(ci->mlock_key);
+
+ /* Don't allow keys with a coma */
+ if (value && *value != ':' && !strchr(value, ',')) {
+ ci->mlock_key = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~CMODE_k;
+ ci->mlock_key = NULL;
+ }
+}
+
+/*************************************************************************/
+
+void cs_set_limit(ChannelInfo * ci, char *value)
+{
+ ci->mlock_limit = value ? strtoul(value, NULL, 10) : 0;
+
+ if (ci->mlock_limit <= 0)
+ ci->mlock_on &= ~CMODE_l;
+}
+
+/*************************************************************************/
+
+#ifdef HAS_LMODE
+
+void cs_set_redirect(ChannelInfo * ci, char *value)
+{
+ if (ci->mlock_redirect)
+ free(ci->mlock_redirect);
+
+ /* Don't allow keys with a coma */
+ if (value && *value == '#') {
+ ci->mlock_redirect = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~CMODE_L;
+ ci->mlock_redirect = NULL;
+ }
+}
+
+#endif
+
+int get_access_level(ChannelInfo * ci, NickAlias * na)
+{
+ ChanAccess *access;
+ int num;
+
+ if (na->nc == ci->founder) {
+ return ACCESS_FOUNDER;
+ }
+
+ for (num = 0; num < ci->accesscount; num++) {
+
+ access = &ci->access[num];
+
+ if (!access->in_use)
+ return 0;
+
+ if (access->nc == na->nc) {
+ return access->level;
+ }
+
+ }
+
+ return 0;
+
+}
+
+char *get_xop_level(int level)
+{
+
+ if (level < ACCESS_VOP) {
+ return "Err";
+#ifdef HAS_HALFOP
+ } else if (level < ACCESS_HOP) {
+ return "VOP";
+ } else if (level < ACCESS_AOP) {
+ return "HOP";
+#else
+ } else if (level < ACCESS_AOP) {
+ return "VOP";
+#endif
+ } else if (level < ACCESS_SOP) {
+ return "AOP";
+ } else if (level < ACCESS_FOUNDER) {
+ return "SOP";
+ } else {
+ return "Founder";
+ }
+
+}
+
+/*************************************************************************/
+/*********************** ChanServ command routines ***********************/
+/*************************************************************************/
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_ChanServ, u, CHAN_HELP);
+#ifdef IRC_UNREAL
+ notice_help(s_ChanServ, u, CHAN_HELP_UNREAL);
+#endif
+#ifdef IRC_VIAGRA
+ notice_help(s_ChanServ, u, CHAN_HELP_UNREAL);
+#endif
+#ifdef IRC_ULTIMATE
+ notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE);
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE3);
+#endif
+ if (CSExpire >= 86400)
+ notice_help(s_ChanServ, u, CHAN_HELP_EXPIRES,
+ CSExpire / 86400);
+ if (is_services_oper(u))
+ notice_help(s_ChanServ, u, CHAN_SERVADMIN_HELP);
+ moduleDisplayHelp(2, u);
+ } else if (stricmp(cmd, "LEVELS DESC") == 0) {
+ int i;
+ notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC);
+ if (!levelinfo_maxwidth) {
+ 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++) {
+ notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC_FORMAT,
+ levelinfo_maxwidth, levelinfo[i].name,
+ getstring(u->na, levelinfo[i].desc));
+ }
+ } else {
+ mod_help_cmd(s_ChanServ, u, CHANSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_register(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ char *desc = strtok(NULL, "");
+ NickCore *nc;
+ Channel *c;
+ ChannelInfo *ci;
+ struct u_chaninfolist *uc;
+ int is_servadmin = is_services_admin(u);
+#ifdef USE_ENCRYPTION
+ char founderpass[PASSMAX + 1];
+#endif
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_REGISTER_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (checkDefCon(DEFCON_NO_NEW_CHANNELS)) {
+ notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!desc) {
+ syntax_error(s_ChanServ, u, "REGISTER", CHAN_REGISTER_SYNTAX);
+ } else if (*chan == '&') {
+ notice_lang(s_ChanServ, u, CHAN_REGISTER_NOT_LOCAL);
+ } else if (!u->na || !(nc = u->na->nc)) {
+ notice_lang(s_ChanServ, u, CHAN_MUST_REGISTER_NICK, s_NickServ);
+ } else if (!nick_recognized(u)) {
+ notice_lang(s_ChanServ, u, CHAN_MUST_IDENTIFY_NICK, s_NickServ,
+ s_NickServ);
+ } else if ((ci = cs_findchan(chan)) != NULL) {
+ if (ci->flags & CI_VERBOTEN) {
+ alog("%s: Attempt to register FORBIDden channel %s by %s!%s@%s", s_ChanServ, ci->name, u->nick, u->username, GetHost(u));
+ notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ALREADY_REGISTERED, chan);
+ }
+ } else if (!stricmp(chan, "#")) {
+ notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
+ } else if (!(c = findchan(chan))
+ || !chan_has_user_status(c, u, CUS_OP)) {
+ notice_lang(s_ChanServ, u, CHAN_MUST_BE_CHANOP);
+
+ } else if (!is_servadmin && nc->channelmax > 0
+ && nc->channelcount >= nc->channelmax) {
+ notice_lang(s_ChanServ, u,
+ nc->channelcount >
+ nc->
+ channelmax ? CHAN_EXCEEDED_CHANNEL_LIMIT :
+ CHAN_REACHED_CHANNEL_LIMIT, nc->channelmax);
+
+ } else if (!(ci = makechan(chan))) {
+ alog("%s: makechan() failed for REGISTER %s", s_ChanServ, chan);
+ notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
+
+#ifdef USE_ENCRYPTION
+ } else if (strscpy(founderpass, pass, PASSMAX + 1),
+ encrypt_in_place(founderpass, PASSMAX) < 0) {
+ alog("%s: Couldn't encrypt password for %s (REGISTER)",
+ s_ChanServ, chan);
+ notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
+ delchan(ci);
+#endif
+
+ } else {
+ c->ci = ci;
+ ci->c = c;
+ ci->bantype = CSDefBantype;
+ ci->flags = CSDefFlags;
+#ifdef IRC_HYBRID
+ ci->mlock_on = CMODE_n | CMODE_t;
+#else
+ ci->mlock_on = CMODE_n | CMODE_t | CMODE_r;
+#endif
+ ci->memos.memomax = MSMaxMemos;
+ ci->last_used = ci->time_registered;
+ ci->founder = nc;
+#ifdef USE_ENCRYPTION
+ if (strlen(pass) > PASSMAX)
+ notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
+ memset(pass, 0, strlen(pass));
+ memcpy(ci->founderpass, founderpass, PASSMAX);
+ ci->flags |= CI_ENCRYPTEDPW;
+#else
+ if (strlen(pass) > PASSMAX - 1) /* -1 for null byte */
+ notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
+ strscpy(ci->founderpass, pass, PASSMAX);
+#endif
+ ci->desc = sstrdup(desc);
+ if (c->topic) {
+ ci->last_topic = sstrdup(c->topic);
+ strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
+ ci->last_topic_time = c->topic_time;
+ }
+ ci->bi = NULL;
+ ci->botflags = BSDefFlags;
+ ci->founder->channelcount++;
+ alog("%s: Channel '%s' registered by %s!%s@%s", s_ChanServ, chan,
+ u->nick, u->username, GetHost(u));
+ notice_lang(s_ChanServ, u, CHAN_REGISTERED, chan, u->nick);
+#ifndef USE_ENCRYPTION
+ notice_lang(s_ChanServ, u, CHAN_PASSWORD_IS, ci->founderpass);
+#endif
+ uc = scalloc(sizeof(*uc), 1);
+ uc->next = u->founder_chans;
+ uc->prev = NULL;
+ if (u->founder_chans)
+ u->founder_chans->prev = uc;
+ u->founder_chans = uc;
+ uc->chan = ci;
+ /* Implement new mode lock */
+ check_modes(c);
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ send_mode(s_ChanServ, chan, "+a %s", u->nick);
+#endif
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_identify(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ ChannelInfo *ci;
+ struct u_chaninfolist *uc;
+
+ if (!pass) {
+ syntax_error(s_ChanServ, u, "IDENTIFY", CHAN_IDENTIFY_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!nick_identified(u)) {
+ notice_lang(s_ChanServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else if (is_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, NICK_ALREADY_IDENTIFIED);
+ } else {
+ int res;
+
+ if ((res = check_password(pass, ci->founderpass)) == 1) {
+ if (!is_identified(u, ci)) {
+ uc = scalloc(sizeof(*uc), 1);
+ uc->next = u->founder_chans;
+ if (u->founder_chans)
+ u->founder_chans->prev = uc;
+ u->founder_chans = uc;
+ uc->chan = ci;
+ alog("%s: %s!%s@%s identified for %s", s_ChanServ, u->nick,
+ u->username, GetHost(u), ci->name);
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_IDENTIFY_SUCCEEDED, chan);
+ } else if (res < 0) {
+ alog("%s: check_password failed for %s", s_ChanServ, ci->name);
+ notice_lang(s_ChanServ, u, CHAN_IDENTIFY_FAILED);
+ } else {
+ alog("%s: Failed IDENTIFY for %s by %s!%s@%s",
+ s_ChanServ, ci->name, u->nick, u->username, GetHost(u));
+ notice_lang(s_ChanServ, u, PASSWORD_INCORRECT);
+ bad_password(u);
+ }
+
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_logout(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ ChannelInfo *ci;
+ User *u2 = NULL;
+ int is_servadmin = is_services_admin(u);
+
+ if (!chan || (!nick && !is_servadmin)) {
+ syntax_error(s_ChanServ, u, "LOGOUT",
+ (!is_servadmin ? CHAN_LOGOUT_SYNTAX :
+ CHAN_LOGOUT_SERVADMIN_SYNTAX));
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (!is_servadmin & (ci->flags & CI_VERBOTEN)) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (nick && !(u2 = finduser(nick))) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (!is_servadmin && u2 != u && !is_real_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ if (u2) {
+ make_unidentified(u2, ci);
+ notice_lang(s_ChanServ, u, CHAN_LOGOUT_SUCCEEDED, nick, chan);
+ } else {
+ int i;
+ for (i = 0; i < 1024; i++)
+ for (u2 = userlist[i]; u2; u2 = u2->next)
+ make_unidentified(u2, ci);
+ notice_lang(s_ChanServ, u, CHAN_LOGOUT_ALL_SUCCEEDED, chan);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_drop(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ ChannelInfo *ci;
+ int is_servadmin = is_services_admin(u);
+
+ if (readonly && !is_servadmin) {
+ notice_lang(s_ChanServ, u, CHAN_DROP_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "DROP", CHAN_DROP_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (!is_servadmin && (ci->flags & CI_VERBOTEN)) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!is_servadmin && (ci->flags & CI_SUSPENDED)) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!is_servadmin
+ && (ci->
+ flags & CI_SECUREFOUNDER ? !is_real_founder(u,
+ ci) :
+ !is_founder(u, ci))) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ int level = get_access(u, ci);
+
+ if (readonly) /* in this case we know they're a Services admin */
+ notice_lang(s_ChanServ, u, READ_ONLY_MODE);
+#ifndef IRC_HYBRID
+ if (ci->c) {
+ ci->c->mode &= ~CMODE_r;
+ send_mode(whosends(ci), ci->name, "-r");
+ }
+#endif
+ alog("%s: Channel %s dropped by %s!%s@%s (founder: %s)",
+ s_ChanServ, ci->name, u->nick, u->username, GetHost(u),
+ (ci->founder ? ci->founder->display : "(none)"));
+
+ delchan(ci);
+
+ /* We must make sure that the Services admin has not normally the right to
+ * drop the channel before issuing the wallops.
+ */
+ if (WallDrop && is_servadmin && level < ACCESS_FOUNDER)
+ wallops(s_ChanServ, "\2%s\2 used DROP on channel \2%s\2",
+ u->nick, chan);
+
+ notice_lang(s_ChanServ, u, CHAN_DROPPED, chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* 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).
+ */
+static int do_set(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *param;
+ ChannelInfo *ci;
+ int is_servadmin = is_services_admin(u);
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (cmd) {
+ if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "ENTRYMSG") == 0)
+ param = strtok(NULL, "");
+ else
+ param = strtok(NULL, " ");
+ } else {
+ param = NULL;
+ }
+
+ if (!param && (!cmd || (stricmp(cmd, "SUCCESSOR") != 0 &&
+ stricmp(cmd, "URL") != 0 &&
+ stricmp(cmd, "EMAIL") != 0 &&
+ stricmp(cmd, "ENTRYMSG") != 0))) {
+ syntax_error(s_ChanServ, u, "SET", CHAN_SET_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!is_servadmin && !check_access(u, ci, CA_SET)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (stricmp(cmd, "FOUNDER") == 0) {
+ if (!is_servadmin
+ && (ci->
+ flags & CI_SECUREFOUNDER ? !is_real_founder(u,
+ ci) :
+ !is_founder(u, ci))) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ do_set_founder(u, ci, param);
+ }
+ } else if (stricmp(cmd, "SUCCESSOR") == 0) {
+ if (!is_servadmin
+ && (ci->
+ flags & CI_SECUREFOUNDER ? !is_real_founder(u,
+ ci) :
+ !is_founder(u, ci))) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ do_set_successor(u, ci, param);
+ }
+ } else if (stricmp(cmd, "PASSWORD") == 0) {
+ if (!is_servadmin
+ && (ci->
+ flags & CI_SECUREFOUNDER ? !is_real_founder(u,
+ ci) :
+ !is_founder(u, ci))) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ do_set_password(u, ci, param);
+ }
+ } else if (stricmp(cmd, "DESC") == 0) {
+ do_set_desc(u, ci, param);
+ } else if (stricmp(cmd, "URL") == 0) {
+ do_set_url(u, ci, param);
+ } else if (stricmp(cmd, "EMAIL") == 0) {
+ do_set_email(u, ci, param);
+ } else if (stricmp(cmd, "ENTRYMSG") == 0) {
+ do_set_entrymsg(u, ci, param);
+ } else if (stricmp(cmd, "TOPIC") == 0) {
+ notice_lang(s_ChanServ, u, OBSOLETE_COMMAND, "TOPIC");
+ } else if (stricmp(cmd, "BANTYPE") == 0) {
+ do_set_bantype(u, ci, param);
+ } else if (stricmp(cmd, "MLOCK") == 0) {
+ do_set_mlock(u, ci, param);
+ } else if (stricmp(cmd, "KEEPTOPIC") == 0) {
+ do_set_keeptopic(u, ci, param);
+ } else if (stricmp(cmd, "TOPICLOCK") == 0) {
+ do_set_topiclock(u, ci, param);
+ } else if (stricmp(cmd, "PRIVATE") == 0) {
+ do_set_private(u, ci, param);
+ } else if (stricmp(cmd, "SECUREOPS") == 0) {
+ do_set_secureops(u, ci, param);
+ } else if (stricmp(cmd, "SECUREFOUNDER") == 0) {
+ if (!is_servadmin
+ && (ci->
+ flags & CI_SECUREFOUNDER ? !is_real_founder(u,
+ ci) :
+ !is_founder(u, ci))) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ do_set_securefounder(u, ci, param);
+ }
+ } else if (stricmp(cmd, "RESTRICTED") == 0) {
+ do_set_restricted(u, ci, param);
+ } else if (stricmp(cmd, "SECURE") == 0) {
+ do_set_secure(u, ci, param);
+ } else if (stricmp(cmd, "SIGNKICK") == 0) {
+ do_set_signkick(u, ci, param);
+ } else if (stricmp(cmd, "OPNOTICE") == 0) {
+ do_set_opnotice(u, ci, param);
+ } else if (stricmp(cmd, "XOP") == 0) {
+ do_set_xop(u, ci, param);
+ } else if (stricmp(cmd, "PEACE") == 0) {
+ do_set_peace(u, ci, param);
+ } else if (stricmp(cmd, "NOEXPIRE") == 0) {
+ do_set_noexpire(u, ci, param);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, cmd);
+ notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET");
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_founder(User * u, ChannelInfo * ci, char *param)
+{
+ NickAlias *na = findnick(param);
+ NickCore *nc, *nc0 = ci->founder;
+
+ if (!na) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
+ return MOD_CONT;
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
+ return MOD_CONT;
+ }
+
+ nc = na->nc;
+ if (nc->channelmax > 0 && nc->channelcount >= nc->channelmax
+ && !is_services_admin(u)) {
+ notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param);
+ return MOD_CONT;
+ }
+
+ alog("%s: Changing founder of %s from %s to %s by %s!%s@%s",
+ s_ChanServ, ci->name, ci->founder->display, nc->display, u->nick,
+ u->username, GetHost(u));
+
+ /* Founder and successor must not be the same group */
+ if (nc == ci->successor)
+ ci->successor = NULL;
+
+ nc0->channelcount--;
+ ci->founder = nc;
+ nc->channelcount++;
+
+ notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_successor(User * u, ChannelInfo * ci, char *param)
+{
+ NickAlias *na;
+ NickCore *nc;
+
+ if (param) {
+ na = findnick(param);
+
+ if (!na) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
+ return MOD_CONT;
+ }
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
+ return MOD_CONT;
+ }
+ if (na->nc == ci->founder) {
+ notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER, param,
+ ci->name);
+ return MOD_CONT;
+ }
+ nc = na->nc;
+
+ } else {
+ nc = NULL;
+ }
+
+ alog("%s: Changing successor of %s from %s to %s by %s!%s@%s",
+ s_ChanServ, ci->name,
+ (ci->successor ? ci->successor->display : "none"),
+ (nc ? nc->display : "none"), u->nick, u->username, GetHost(u));
+
+ ci->successor = nc;
+
+ if (nc)
+ notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name,
+ param);
+ else
+ notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_password(User * u, ChannelInfo * ci, char *param)
+{
+#ifdef USE_ENCRYPTION
+ int len = strlen(param);
+
+ if (len > PASSMAX) {
+ len = PASSMAX;
+ param[len] = 0;
+ notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
+ }
+
+ if (encrypt(param, len, ci->founderpass, PASSMAX) < 0) {
+ memset(param, 0, strlen(param));
+ alog("%s: Failed to encrypt password for %s (set)", s_ChanServ,
+ ci->name);
+ notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED);
+ return MOD_CONT;
+ }
+
+ memset(param, 0, strlen(param));
+ notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED, ci->name);
+
+#else /* !USE_ENCRYPTION */
+ if (strlen(param) > PASSMAX - 1) /* -1 for null byte */
+ notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
+ strscpy(ci->founderpass, param, PASSMAX);
+ notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED_TO, ci->name,
+ ci->founderpass);
+#endif /* USE_ENCRYPTION */
+
+ if (get_access(u, ci) < ACCESS_FOUNDER) {
+ alog("%s: %s!%s@%s set password as Services admin for %s",
+ s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
+ if (WallSetpass)
+ wallops(s_ChanServ,
+ "\2%s\2 set password as Services admin for channel \2%s\2",
+ u->nick, ci->name);
+ } else {
+ alog("%s: %s!%s@%s changed password of %s (founder: %s)",
+ s_ChanServ, u->nick, u->username, GetHost(u),
+ ci->name, ci->founder->display);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_desc(User * u, ChannelInfo * ci, char *param)
+{
+ if (ci->desc)
+ free(ci->desc);
+ ci->desc = sstrdup(param);
+ notice_lang(s_ChanServ, u, CHAN_DESC_CHANGED, ci->name, param);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_url(User * u, ChannelInfo * ci, char *param)
+{
+ if (ci->url)
+ 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);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_email(User * u, ChannelInfo * ci, char *param)
+{
+ if (ci->email)
+ 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);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_entrymsg(User * u, ChannelInfo * ci, char *param)
+{
+ if (ci->entry_message)
+ free(ci->entry_message);
+ if (param) {
+ ci->entry_message = sstrdup(param);
+ notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_CHANGED, ci->name,
+ param);
+ } else {
+ ci->entry_message = NULL;
+ notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_UNSET, ci->name);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_mlock(User * u, ChannelInfo * ci, char *param)
+{
+ int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */
+ unsigned char mode;
+ CBMode *cbm;
+
+ if (checkDefCon(DEFCON_NO_MLOCK_CHANGE)) {
+ notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ }
+
+ /* Reinitialize everything */
+#ifdef IRC_HYBRID
+ ci->mlock_on = 0;
+#else
+ ci->mlock_on = CMODE_r;
+#endif
+ ci->mlock_off = ci->mlock_limit = 0;
+ ci->mlock_key = NULL;
+#ifdef HAS_FMODE
+ ci->mlock_flood = NULL;
+#endif
+#ifdef HAS_LMODE
+ ci->mlock_redirect = NULL;
+#endif
+
+ while ((mode = *param++)) {
+ switch (mode) {
+ case '+':
+ add = 1;
+ continue;
+ case '-':
+ add = 0;
+ continue;
+ default:
+ if (add < 0)
+ continue;
+ }
+
+ if ((int) mode < 128 && (cbm = &cbmodes[(int) mode])->flag != 0) {
+ if ((cbm->flags & CBM_NO_MLOCK)
+ || ((cbm->flags & CBM_NO_USER_MLOCK) && !is_oper(u))) {
+ notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_IMPOSSIBLE_CHAR,
+ mode);
+ } else if (add) {
+ ci->mlock_on |= cbm->flag;
+ ci->mlock_off &= ~cbm->flag;
+ if (cbm->cssetvalue)
+ cbm->cssetvalue(ci, strtok(NULL, " "));
+ } else {
+ ci->mlock_off |= cbm->flag;
+ if (ci->mlock_on & cbm->flag) {
+ ci->mlock_on &= ~cbm->flag;
+ if (cbm->cssetvalue)
+ cbm->cssetvalue(ci, NULL);
+ }
+ }
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, mode);
+ }
+ } /* while (*param) */
+
+#ifdef HAS_LMODE
+ /* We can't mlock +L if +l is not mlocked as well. */
+ if ((ci->mlock_on & CMODE_L) && !(ci->mlock_on & CMODE_l)) {
+ ci->mlock_on &= ~CMODE_L;
+ free(ci->mlock_redirect);
+ notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_L_REQUIRED);
+ }
+#endif
+
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
+ /* We can't mlock +K if +i is not mlocked as well. */
+ if ((ci->mlock_on & CMODE_K) && !(ci->mlock_on & CMODE_i)) {
+ ci->mlock_on &= ~CMODE_K;
+ notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_K_REQUIRED);
+ }
+#endif
+
+ /* Since we always enforce mode r there is no way to have no
+ * mode lock at all.
+ */
+#if defined(IRC_HYBRID)
+ /* James: Hybrid doesn't HAVE mode r, so now you have to check :P */
+ if (get_mlock_modes(ci, 0))
+#endif
+ notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name,
+ get_mlock_modes(ci, 0));
+
+
+ /* Implement the new lock. */
+ if (ci->c)
+ check_modes(ci->c);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_bantype(User * u, ChannelInfo * ci, char *param)
+{
+ char *endptr;
+
+ int16 bantype = strtol(param, &endptr, 10);
+
+ if (*endptr != 0 || bantype < 0 || bantype > 3) {
+ notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_INVALID, param);
+ } else {
+ ci->bantype = bantype;
+ notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_CHANGED, ci->name,
+ ci->bantype);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_KEEPTOPIC;
+ notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_KEEPTOPIC;
+ notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET KEEPTOPIC",
+ CHAN_SET_KEEPTOPIC_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_topiclock(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_TOPICLOCK;
+ notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_TOPICLOCK;
+ notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET TOPICLOCK",
+ CHAN_SET_TOPICLOCK_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_private(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_PRIVATE;
+ notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_PRIVATE;
+ notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET PRIVATE",
+ CHAN_SET_PRIVATE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_secureops(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_SECUREOPS;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_SECUREOPS;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET SECUREOPS",
+ CHAN_SET_SECUREOPS_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_securefounder(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_SECUREFOUNDER;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_SECUREFOUNDER;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET SECUREFOUNDER",
+ CHAN_SET_SECUREFOUNDER_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_restricted(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_RESTRICTED;
+ if (ci->levels[CA_NOJOIN] < 0)
+ ci->levels[CA_NOJOIN] = 0;
+ notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_RESTRICTED;
+ if (ci->levels[CA_NOJOIN] >= 0)
+ ci->levels[CA_NOJOIN] = -2;
+ notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET RESTRICTED",
+ CHAN_SET_RESTRICTED_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_secure(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_SECURE;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECURE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_SECURE;
+ notice_lang(s_ChanServ, u, CHAN_SET_SECURE_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET SECURE", CHAN_SET_SECURE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_signkick(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_SIGNKICK;
+ ci->flags &= ~CI_SIGNKICK_LEVEL;
+ notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_ON);
+ } else if (stricmp(param, "LEVEL") == 0) {
+ ci->flags |= CI_SIGNKICK_LEVEL;
+ ci->flags &= ~CI_SIGNKICK;
+ notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_LEVEL);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~(CI_SIGNKICK | CI_SIGNKICK_LEVEL);
+ notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET SIGNKICK",
+ CHAN_SET_SIGNKICK_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_opnotice(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_OPNOTICE;
+ notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_OPNOTICE;
+ notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET OPNOTICE",
+ CHAN_SET_OPNOTICE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#define CHECKLEV(lev) ((ci->levels[(lev)] != ACCESS_INVALID) && (access->level >= ci->levels[(lev)]))
+
+static int do_set_xop(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ if (!(ci->flags & CI_XOP)) {
+ int i;
+ ChanAccess *access;
+
+ for (access = ci->access, i = 0; i < ci->accesscount;
+ access++, i++) {
+ if (!access->in_use)
+ continue;
+ /* This will probably cause wrong levels to be set, but hey,
+ * it's better than losing it altogether.
+ */
+ if (CHECKLEV(CA_AKICK) || CHECKLEV(CA_SET)) {
+ access->level = ACCESS_SOP;
+ } else if (CHECKLEV(CA_AUTOOP) || CHECKLEV(CA_OPDEOP)
+ || CHECKLEV(CA_OPDEOPME)) {
+ access->level = ACCESS_AOP;
+#ifdef HAS_HALFOP
+ } else if (CHECKLEV(CA_AUTOHALFOP) || CHECKLEV(CA_HALFOP)
+ || CHECKLEV(CA_HALFOPME)) {
+ access->level = ACCESS_HOP;
+#endif
+ } else if (CHECKLEV(CA_AUTOVOICE) || CHECKLEV(CA_VOICE)
+ || CHECKLEV(CA_VOICEME)) {
+ access->level = ACCESS_VOP;
+ } else {
+ access->in_use = 0;
+ access->nc = NULL;
+ }
+ }
+
+ reset_levels(ci);
+ ci->flags |= CI_XOP;
+ }
+
+ alog("%s: %s!%s@%s enabled XOP for %s", s_ChanServ, u->nick,
+ u->username, GetHost(u), ci->name);
+ notice_lang(s_ChanServ, u, CHAN_SET_XOP_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_XOP;
+
+ alog("%s: %s!%s@%s disabled XOP for %s", s_ChanServ, u->nick,
+ u->username, GetHost(u), ci->name);
+ notice_lang(s_ChanServ, u, CHAN_SET_XOP_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET XOP", CHAN_SET_XOP_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+#undef CHECKLEV
+
+/*************************************************************************/
+
+static int do_set_peace(User * u, ChannelInfo * ci, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_PEACE;
+ notice_lang(s_ChanServ, u, CHAN_SET_PEACE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_PEACE;
+ notice_lang(s_ChanServ, u, CHAN_SET_PEACE_OFF);
+ } else {
+ syntax_error(s_ChanServ, u, "SET PEACE", CHAN_SET_PEACE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_noexpire(User * u, ChannelInfo * ci, char *param)
+{
+ if (!is_services_admin(u)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ if (stricmp(param, "ON") == 0) {
+ ci->flags |= CI_NO_EXPIRE;
+ notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_ON, ci->name);
+ } else if (stricmp(param, "OFF") == 0) {
+ ci->flags &= ~CI_NO_EXPIRE;
+ notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_OFF, ci->name);
+ } else {
+ syntax_error(s_ChanServ, u, "SET NOEXPIRE",
+ CHAN_SET_NOEXPIRE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* `last' is set to the last index this routine was called with
+ * `perm' is incremented whenever a permission-denied error occurs
+ */
+
+static int xop_del(User * u, ChanAccess * access, int *perm, int uacc,
+ int xlev)
+{
+ if (!access->in_use || access->level != xlev)
+ return 0;
+ if (!is_services_admin(u) && uacc <= access->level) {
+ (*perm)++;
+ return 0;
+ }
+ access->nc = NULL;
+ access->in_use = 0;
+ return 1;
+}
+
+static int xop_del_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *last = va_arg(args, int *);
+ int *perm = va_arg(args, int *);
+ int uacc = va_arg(args, int);
+ int xlev = va_arg(args, int);
+
+ if (num < 1 || num > ci->accesscount)
+ return 0;
+ *last = num;
+
+ return xop_del(u, &ci->access[num - 1], perm, uacc, xlev);
+}
+
+
+static int xop_list(User * u, int index, ChannelInfo * ci,
+ int *sent_header, int xlev, int xmsg)
+{
+ ChanAccess *access = &ci->access[index];
+
+ if (!access->in_use || access->level != xlev)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_ChanServ, u, xmsg, ci->name);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_XOP_LIST_FORMAT, index + 1,
+ access->nc->display);
+ return 1;
+}
+
+static int xop_list_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *sent_header = va_arg(args, int *);
+ int xlev = va_arg(args, int);
+ int xmsg = va_arg(args, int);
+
+ if (num < 1 || num > ci->accesscount)
+ return 0;
+
+ return xop_list(u, num - 1, ci, sent_header, xlev, xmsg);
+}
+
+
+static int do_xop(User * u, char *xname, int xlev, int *xmsgs)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+
+ ChannelInfo *ci;
+ NickAlias *na;
+ NickCore *nc;
+
+ int i;
+ int change = 0;
+ short ulev;
+ int is_list = (cmd && stricmp(cmd, "LIST") == 0);
+ int is_servadmin = is_services_admin(u);
+ ChanAccess *access;
+
+ /* If CLEAR, we don't need any parameters.
+ * If LIST, we don't *require* any parameters, but we can take any.
+ * If DEL or ADD we require a nick. */
+ if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 : !nick)) {
+ syntax_error(s_ChanServ, u, xname, xmsgs[0]);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!(ci->flags & CI_XOP)) {
+ notice_lang(s_ChanServ, u, CHAN_XOP_ACCESS, s_ChanServ);
+ } else if (stricmp(cmd, "ADD") == 0) {
+ if (readonly) {
+ notice_lang(s_ChanServ, u, xmsgs[1]);
+ return MOD_CONT;
+ }
+
+ ulev = get_access(u, ci);
+
+ if (xlev >= ulev || ulev < ACCESS_AOP) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ na = findnick(nick);
+ if (!na) {
+ notice_lang(s_ChanServ, u, xmsgs[2]);
+ return MOD_CONT;
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, na->nick);
+ return MOD_CONT;
+ }
+
+ nc = na->nc;
+ for (access = ci->access, i = 0; i < ci->accesscount;
+ access++, i++) {
+ if (access->nc == nc) {
+ /**
+ * Patch provided by PopCorn to prevert AOP's reducing SOP's levels
+ **/
+ if ((access->level >= ulev) && (!u->isSuperAdmin)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ change++;
+ break;
+ }
+ }
+
+ if (!change) {
+ for (i = 0; i < ci->accesscount; i++)
+ if (!ci->access[i].in_use)
+ break;
+
+ if (i == ci->accesscount) {
+ if (i < CSAccessMax) {
+ ci->accesscount++;
+ ci->access =
+ srealloc(ci->access,
+ sizeof(ChanAccess) * ci->accesscount);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT,
+ CSAccessMax);
+ return MOD_CONT;
+ }
+ }
+
+ access = &ci->access[i];
+ access->nc = nc;
+ }
+
+ access->in_use = 1;
+ access->level = xlev;
+ access->last_seen = 0;
+
+ alog("%s: %s!%s@%s (level %d) %s access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, change ? "changed" : "set", access->level, na->nick, nc->display, ci->name);
+
+ if (!change) {
+ notice_lang(s_ChanServ, u, xmsgs[3], access->nc->display,
+ ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, xmsgs[4], access->nc->display,
+ ci->name);
+ }
+
+ } else if (stricmp(cmd, "DEL") == 0) {
+ if (readonly) {
+ notice_lang(s_ChanServ, u, xmsgs[1]);
+ return MOD_CONT;
+ }
+
+ if (ci->accesscount == 0) {
+ notice_lang(s_ChanServ, u, xmsgs[11], chan);
+ return MOD_CONT;
+ }
+
+ ulev = get_access(u, ci);
+
+ /* Special case: is it a number/list? Only do search if it isn't. */
+ if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
+ int count, deleted, last = -1, perm = 0;
+ deleted =
+ process_numlist(nick, &count, xop_del_callback, u, ci,
+ &last, &perm, ulev, xlev);
+ if (!deleted) {
+ if (perm) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else if (count == 1) {
+ notice_lang(s_ChanServ, u, xmsgs[5], last, ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, xmsgs[7], ci->name);
+ }
+ } else if (deleted == 1) {
+ notice_lang(s_ChanServ, u, xmsgs[9], ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, xmsgs[10], deleted, ci->name);
+ }
+ } else {
+ na = findnick(nick);
+ if (!na) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+ nc = na->nc;
+
+ for (i = 0; i < ci->accesscount; i++)
+ if (ci->access[i].nc == nc && ci->access[i].level == xlev)
+ break;
+
+ if (i == ci->accesscount) {
+ notice_lang(s_ChanServ, u, xmsgs[6], nick, chan);
+ return MOD_CONT;
+ }
+
+ access = &ci->access[i];
+ if (!is_servadmin && ulev <= access->level) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+ notice_lang(s_ChanServ, u, xmsgs[8], access->nc->display,
+ ci->name);
+ access->nc = NULL;
+ access->in_use = 0;
+ }
+ }
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int sent_header = 0;
+
+ ulev = get_access(u, ci);
+
+ if (!is_servadmin && ulev < ACCESS_AOP) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+
+ if (ci->accesscount == 0) {
+ notice_lang(s_ChanServ, u, xmsgs[11], ci->name);
+ return MOD_CONT;
+ }
+
+ if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
+ process_numlist(nick, NULL, xop_list_callback, u, ci,
+ &sent_header, xlev, xmsgs[12]);
+ } else {
+ for (i = 0; i < ci->accesscount; i++) {
+ if (nick && ci->access[i].nc
+ && !match_wild_nocase(nick, ci->access[i].nc->display))
+ continue;
+ xop_list(u, i, ci, &sent_header, xlev, xmsgs[12]);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_ChanServ, u, xmsgs[7], chan);
+ } else if (stricmp(cmd, "CLEAR") == 0) {
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (ci->accesscount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+
+ if (!is_servadmin && !is_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ for (i = 0; i < ci->accesscount; i++) {
+ if (ci->access[i].in_use && ci->access[i].level == xlev) {
+ ci->access[i].nc = NULL;
+ ci->access[i].in_use = 0;
+ }
+ }
+
+ notice_lang(s_ChanServ, u, xmsgs[13]);
+ } else {
+ syntax_error(s_ChanServ, u, xname, xmsgs[0]);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_aop(User * u)
+{
+ return do_xop(u, "AOP", ACCESS_AOP, xop_msgs[0]);
+}
+
+/*************************************************************************/
+
+#ifdef HAS_HALFOP
+
+static int do_hop(User * u)
+{
+ return do_xop(u, "HOP", ACCESS_HOP, xop_msgs[3]);
+}
+
+#endif
+
+/*************************************************************************/
+
+static int do_sop(User * u)
+{
+ return do_xop(u, "SOP", ACCESS_SOP, xop_msgs[1]);
+}
+
+/*************************************************************************/
+
+static int do_vop(User * u)
+{
+ return do_xop(u, "VOP", ACCESS_VOP, xop_msgs[2]);
+}
+
+/*************************************************************************/
+
+/* `last' is set to the last index this routine was called with
+ * `perm' is incremented whenever a permission-denied error occurs
+ */
+
+static int access_del(User * u, ChanAccess * access, int *perm, int uacc)
+{
+ if (!access->in_use)
+ return 0;
+ if (!is_services_admin(u) && uacc <= access->level) {
+ (*perm)++;
+ return 0;
+ }
+ access->nc = NULL;
+ access->in_use = 0;
+ return 1;
+}
+
+static int access_del_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *last = va_arg(args, int *);
+ int *perm = va_arg(args, int *);
+ int uacc = va_arg(args, int);
+ if (num < 1 || num > ci->accesscount)
+ return 0;
+ *last = num;
+ return access_del(u, &ci->access[num - 1], perm, uacc);
+}
+
+
+static int access_list(User * u, int index, ChannelInfo * ci,
+ int *sent_header)
+{
+ ChanAccess *access = &ci->access[index];
+ char *xop;
+
+ if (!access->in_use)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name);
+ *sent_header = 1;
+ }
+
+ if (ci->flags & CI_XOP) {
+ xop = get_xop_level(access->level);
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_XOP_FORMAT, index + 1,
+ xop, access->nc->display);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_AXS_FORMAT, index + 1,
+ access->level, access->nc->display);
+ }
+ return 1;
+}
+
+static int access_list_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *sent_header = va_arg(args, int *);
+ if (num < 1 || num > ci->accesscount)
+ return 0;
+ return access_list(u, num - 1, ci, sent_header);
+}
+
+
+static int do_access(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ char *s = strtok(NULL, " ");
+
+ ChannelInfo *ci;
+ NickAlias *na;
+ NickCore *nc;
+ ChanAccess *access;
+
+ int i;
+ short level = 0, ulev;
+ int is_list = (cmd && stricmp(cmd, "LIST") == 0);
+ int is_servadmin = is_services_admin(u);
+
+ /* If LIST, we don't *require* any parameters, but we can take any.
+ * If DEL, we require a nick and no level.
+ * Else (ADD), we require a level (which implies a nick). */
+ if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 :
+ (stricmp(cmd, "DEL") == 0) ? (!nick || s) : !s)) {
+ syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ /* We still allow LIST in xOP mode, but not others */
+ } else if ((ci->flags & CI_XOP) && !is_list) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP, s_ChanServ);
+ } else if (((is_list && !check_access(u, ci, CA_ACCESS_LIST))
+ || (!is_list && !check_access(u, ci, CA_ACCESS_CHANGE)))
+ && !is_servadmin) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (stricmp(cmd, "ADD") == 0) {
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
+ return MOD_CONT;
+ }
+
+ level = atoi(s);
+ ulev = get_access(u, ci);
+
+ if (!is_servadmin && level >= ulev) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (level == 0) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO);
+ return MOD_CONT;
+ } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE,
+ ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
+ return MOD_CONT;
+ }
+
+ na = findnick(nick);
+ if (!na) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY);
+ return MOD_CONT;
+ }
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
+ return MOD_CONT;
+ }
+
+ nc = na->nc;
+ for (access = ci->access, i = 0; i < ci->accesscount;
+ access++, i++) {
+ if (access->nc == nc) {
+ /* Don't allow lowering from a level >= ulev */
+ if (!is_servadmin && access->level >= ulev) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ if (access->level == level) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED,
+ access->nc->display, chan, level);
+ return MOD_CONT;
+ }
+ access->level = level;
+ alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name);
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED,
+ access->nc->display, chan, level);
+ return MOD_CONT;
+ }
+ }
+
+ for (i = 0; i < ci->accesscount; i++) {
+ if (!ci->access[i].in_use)
+ break;
+ }
+ if (i == ci->accesscount) {
+ if (i < CSAccessMax) {
+ ci->accesscount++;
+ ci->access =
+ srealloc(ci->access,
+ sizeof(ChanAccess) * ci->accesscount);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT,
+ CSAccessMax);
+ return MOD_CONT;
+ }
+ }
+
+ access = &ci->access[i];
+ access->nc = nc;
+ access->in_use = 1;
+ access->level = level;
+ access->last_seen = 0;
+
+ alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name);
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nc->display,
+ ci->name, access->level);
+ } else if (stricmp(cmd, "DEL") == 0) {
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (ci->accesscount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+
+ /* Special case: is it a number/list? Only do search if it isn't. */
+ if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
+ int count, deleted, last = -1, perm = 0;
+ deleted = process_numlist(nick, &count, access_del_callback, u,
+ ci, &last, &perm, get_access(u, ci));
+ if (!deleted) {
+ if (perm) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else if (count == 1) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_SUCH_ENTRY,
+ last, ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH,
+ ci->name);
+ }
+ } else if (deleted == 1) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_ONE,
+ ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_SEVERAL,
+ deleted, ci->name);
+ }
+ } else {
+ na = findnick(nick);
+ if (!na) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+ nc = na->nc;
+ for (i = 0; i < ci->accesscount; i++) {
+ if (ci->access[i].nc == nc)
+ break;
+ }
+ if (i == ci->accesscount) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick,
+ chan);
+ return MOD_CONT;
+ }
+ access = &ci->access[i];
+ if (!is_servadmin && get_access(u, ci) <= access->level) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED,
+ access->nc->display, ci->name);
+ alog("%s: %s!%s@%s (level %d) deleted access of %s (group %s) on %s", s_ChanServ, u->nick, u->username, GetHost(u), get_access(u, ci), na->nick, access->nc->display, chan);
+ access->nc = NULL;
+ access->in_use = 0;
+ }
+ }
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int sent_header = 0;
+
+ if (ci->accesscount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+ if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
+ process_numlist(nick, NULL, access_list_callback, u, ci,
+ &sent_header);
+ } else {
+ for (i = 0; i < ci->accesscount; i++) {
+ if (nick && ci->access[i].nc
+ && !match_wild_nocase(nick, ci->access[i].nc->display))
+ continue;
+ access_list(u, i, ci, &sent_header);
+ }
+ }
+ if (!sent_header) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, chan);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FOOTER, ci->name);
+ }
+ } else if (stricmp(cmd, "CLEAR") == 0) {
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!is_servadmin && !is_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ free(ci->access);
+ ci->access = NULL;
+ ci->accesscount = 0;
+
+ notice_lang(s_ChanServ, u, CHAN_ACCESS_CLEAR);
+ alog("%s: %s!%s@%s (level %d) cleared access list on %s",
+ s_ChanServ, u->nick, u->username, GetHost(u), get_access(u,
+ ci),
+ chan);
+
+ } else {
+ syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Is the mask stuck? */
+
+AutoKick *is_stuck(ChannelInfo * ci, char *mask)
+{
+ int i;
+ AutoKick *akick;
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
+ || !(akick->flags & AK_STUCK))
+ continue;
+ /* Example: mask = *!*@*.org and akick->u.mask = *!*@*.epona.org */
+ if (match_wild_nocase(mask, akick->u.mask))
+ return akick;
+#ifdef IRC_DREAMFORGE
+ /* Example: mask = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
+ if (match_wild_nocase(akick->u.mask, mask))
+ return akick;
+#endif
+ }
+
+ return NULL;
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_mask(ChannelInfo * ci, AutoKick * akick)
+{
+ int i;
+ char *av[2];
+
+ for (i = 0; i < ci->c->bancount; i++) {
+ /* If akick is already covered by a wider ban.
+ Example: c->bans[i] = *!*@*.org and akick->u.mask = *!*@*.epona.org */
+ if (match_wild_nocase(ci->c->bans[i], akick->u.mask))
+ return;
+
+#ifdef IRC_DREAMFORGE
+ /* If akick is wider than a ban already in place.
+ Example: c->bans[i] = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
+ if (match_wild_nocase(akick->u.mask, ci->c->bans[i]))
+ return;
+#endif
+ }
+
+ /* Falling there means set the ban */
+
+ av[0] = sstrdup("+b");
+ av[1] = akick->u.mask;
+ send_mode(whosends(ci), ci->c->name, "+b %s", akick->u.mask);
+ chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
+ free(av[0]);
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_all(ChannelInfo * ci)
+{
+ int i;
+ char *av[2];
+ AutoKick *akick;
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
+ || !(akick->flags & AK_STUCK))
+ continue;
+
+ av[0] = sstrdup("+b");
+ av[1] = akick->u.mask;
+ send_mode(whosends(ci), ci->c->name, "+b %s", akick->u.mask);
+ chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
+ free(av[0]);
+ }
+}
+
+/* `last' is set to the last index this routine was called with */
+static int akick_del(User * u, AutoKick * akick)
+{
+ if (!(akick->flags & AK_USED))
+ return 0;
+ if (akick->flags & AK_ISNICK) {
+ akick->u.nc = NULL;
+ } else {
+ free(akick->u.mask);
+ akick->u.mask = NULL;
+ }
+ if (akick->reason) {
+ free(akick->reason);
+ akick->reason = NULL;
+ }
+ if (akick->creator) {
+ free(akick->creator);
+ akick->creator = NULL;
+ }
+ akick->addtime = 0;
+ akick->flags = 0;
+ return 1;
+}
+
+static int akick_del_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *last = va_arg(args, int *);
+ if (num < 1 || num > ci->akickcount)
+ return 0;
+ *last = num;
+ return akick_del(u, &ci->akick[num - 1]);
+}
+
+
+static int akick_list(User * u, int index, ChannelInfo * ci,
+ int *sent_header)
+{
+ AutoKick *akick = &ci->akick[index];
+
+ if (!(akick->flags & AK_USED))
+ return 0;
+ if (!*sent_header) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_FORMAT, index + 1,
+ ((akick->flags & AK_ISNICK) ? akick->u.nc->
+ display : akick->u.mask),
+ (akick->reason ? akick->
+ reason : getstring(u->na, NO_REASON)));
+ return 1;
+}
+
+static int akick_list_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *sent_header = va_arg(args, int *);
+ if (num < 1 || num > ci->akickcount)
+ return 0;
+ return akick_list(u, num - 1, ci, sent_header);
+}
+
+static int akick_view(User * u, int index, ChannelInfo * ci,
+ int *sent_header)
+{
+ AutoKick *akick = &ci->akick[index];
+ char timebuf[64];
+ struct tm tm;
+
+ if (!(akick->flags & AK_USED))
+ return 0;
+ if (!*sent_header) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
+ *sent_header = 1;
+ }
+
+ if (akick->addtime) {
+ tm = *localtime(&akick->addtime);
+ strftime_lang(timebuf, sizeof(timebuf), u,
+ STRFTIME_SHORT_DATE_FORMAT, &tm);
+ } else {
+ snprintf(timebuf, sizeof(timebuf), getstring(u->na, UNKNOWN));
+ }
+
+ notice_lang(s_ChanServ, u,
+ ((akick->
+ flags & AK_STUCK) ? CHAN_AKICK_VIEW_FORMAT_STUCK :
+ CHAN_AKICK_VIEW_FORMAT), index + 1,
+ ((akick->flags & AK_ISNICK) ? akick->u.nc->
+ display : akick->u.mask),
+ akick->creator ? akick->creator : getstring(u->na,
+ UNKNOWN),
+ timebuf,
+ (akick->reason ? akick->
+ reason : getstring(u->na, NO_REASON)));
+ return 1;
+}
+
+static int akick_view_callback(User * u, int num, va_list args)
+{
+ ChannelInfo *ci = va_arg(args, ChannelInfo *);
+ int *sent_header = va_arg(args, int *);
+ if (num < 1 || num > ci->akickcount)
+ return 0;
+ return akick_view(u, num - 1, ci, sent_header);
+}
+
+static int do_akick(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *mask = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+ ChannelInfo *ci;
+ AutoKick *akick;
+ int i;
+ Channel *c;
+ struct c_userlist *cu = NULL;
+ struct c_userlist *next;
+ char *argv[3];
+ int count = 0;
+
+ if (!cmd || (!mask && (!stricmp(cmd, "ADD") || !stricmp(cmd, "STICK")
+ || !stricmp(cmd, "UNSTICK")
+ || !stricmp(cmd, "DEL")))) {
+
+ syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!check_access(u, ci, CA_AKICK) && !is_services_admin(u)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (stricmp(cmd, "ADD") == 0) {
+
+ NickAlias *na = findnick(mask);
+ NickCore *nc = NULL;
+ char *nick, *user, *host;
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!na) {
+ split_usermask(mask, &nick, &user, &host);
+ mask =
+ scalloc(strlen(nick) + strlen(user) + strlen(host) + 3, 1);
+ sprintf(mask, "%s!%s@%s", nick, user, host);
+ free(nick);
+ free(user);
+ free(host);
+ } else {
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, mask);
+ return MOD_CONT;
+ }
+ nc = na->nc;
+ }
+
+ /* Check excepts BEFORE we get this far */
+#ifdef HAS_EXCEPT
+ if (is_excepted_mask(ci, mask) == 1) {
+ notice_lang(s_ChanServ, u, CHAN_EXCEPTED, mask, chan);
+ return MOD_CONT;
+ }
+#endif
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if ((akick->flags & AK_ISNICK) ? akick->u.nc == nc
+ : stricmp(akick->u.mask, mask) == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_ALREADY_EXISTS,
+ (akick->flags & AK_ISNICK) ? akick->u.nc->
+ display : akick->u.mask, chan);
+ return MOD_CONT;
+ }
+ }
+
+ for (i = 0; i < ci->akickcount; i++) {
+ if (!(ci->akick[i].flags & AK_USED))
+ break;
+ }
+ if (i == ci->akickcount) {
+ if (ci->akickcount >= CSAutokickMax) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_REACHED_LIMIT,
+ CSAutokickMax);
+ return MOD_CONT;
+ }
+ ci->akickcount++;
+ ci->akick =
+ srealloc(ci->akick, sizeof(AutoKick) * ci->akickcount);
+ }
+ akick = &ci->akick[i];
+ akick->flags = AK_USED;
+ if (nc) {
+ akick->flags |= AK_ISNICK;
+ akick->u.nc = nc;
+ } else {
+ akick->u.mask = mask;
+ }
+ akick->creator = sstrdup(u->nick);
+ akick->addtime = time(NULL);
+ if (reason) {
+ if (strlen(reason) > 200)
+ reason[200] = '\0';
+ akick->reason = sstrdup(reason);
+ } else {
+ akick->reason = NULL;
+ }
+
+ /* Auto ENFORCE #63 */
+ c = findchan(ci->name);
+ if (c) {
+ cu = c->users;
+ while (cu) {
+ next = cu->next;
+ if (check_kick(cu->user, c->name)) {
+ argv[0] = sstrdup(c->name);
+ argv[1] = sstrdup(cu->user->nick);
+ if (akick->reason)
+ argv[2] = sstrdup(akick->reason);
+ else
+ argv[2] = sstrdup("none");
+
+ do_kick(s_ChanServ, 3, argv);
+
+ free(argv[2]);
+ free(argv[1]);
+ free(argv[0]);
+ count++;
+
+ }
+ cu = next;
+ }
+ }
+ notice_lang(s_ChanServ, u, CHAN_AKICK_ADDED, mask, chan);
+
+ if (count)
+ notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan,
+ count);
+
+ } else if (stricmp(cmd, "STICK") == 0) {
+ NickAlias *na;
+ NickCore *nc;
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (ci->akickcount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name);
+ return MOD_CONT;
+ }
+
+ na = findnick(mask);
+ nc = (na ? na->nc : NULL);
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK))
+ continue;
+ if (!stricmp(akick->u.mask, mask))
+ break;
+ }
+
+ if (i == ci->akickcount) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
+ ci->name);
+ return MOD_CONT;
+ }
+
+ akick->flags |= AK_STUCK;
+ notice_lang(s_ChanServ, u, CHAN_AKICK_STUCK, akick->u.mask,
+ ci->name);
+
+ if (ci->c)
+ stick_mask(ci, akick);
+ } else if (stricmp(cmd, "UNSTICK") == 0) {
+ NickAlias *na;
+ NickCore *nc;
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (ci->akickcount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name);
+ return MOD_CONT;
+ }
+
+ na = findnick(mask);
+ nc = (na ? na->nc : NULL);
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK))
+ continue;
+ if (!stricmp(akick->u.mask, mask))
+ break;
+ }
+
+ if (i == ci->akickcount) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
+ ci->name);
+ return MOD_CONT;
+ }
+
+ akick->flags &= ~AK_STUCK;
+ notice_lang(s_ChanServ, u, CHAN_AKICK_UNSTUCK, akick->u.mask,
+ ci->name);
+
+ } else if (stricmp(cmd, "DEL") == 0) {
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (ci->akickcount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+
+ /* Special case: is it a number/list? Only do search if it isn't. */
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ int count, deleted, last = -1;
+ deleted = process_numlist(mask, &count, akick_del_callback, u,
+ ci, &last);
+ if (!deleted) {
+ if (count == 1) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NO_SUCH_ENTRY,
+ last, ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH,
+ ci->name);
+ }
+ } else if (deleted == 1) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_ONE,
+ ci->name);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_SEVERAL,
+ deleted, ci->name);
+ }
+ } else {
+ NickAlias *na = findnick(mask);
+ NickCore *nc = (na ? na->nc : NULL);
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount;
+ akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if (((akick->flags & AK_ISNICK) && akick->u.nc == nc)
+ || (!(akick->flags & AK_ISNICK)
+ && stricmp(akick->u.mask, mask) == 0))
+ break;
+ }
+ if (i == ci->akickcount) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
+ chan);
+ return MOD_CONT;
+ }
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED, mask, chan);
+ akick_del(u, akick);
+ }
+
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int sent_header = 0;
+
+ if (ci->akickcount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+ if (mask && isdigit(*mask) &&
+ strspn(mask, "1234567890,-") == strlen(mask)) {
+ process_numlist(mask, NULL, akick_list_callback, u, ci,
+ &sent_header);
+ } else {
+ for (akick = ci->akick, i = 0; i < ci->akickcount;
+ akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if (mask) {
+ if (!(akick->flags & AK_ISNICK)
+ && !match_wild_nocase(mask, akick->u.mask))
+ continue;
+ if ((akick->flags & AK_ISNICK)
+ && !match_wild_nocase(mask, akick->u.nc->display))
+ continue;
+ }
+ akick_list(u, i, ci, &sent_header);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);
+
+ } else if (stricmp(cmd, "VIEW") == 0) {
+ int sent_header = 0;
+
+ if (ci->akickcount == 0) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
+ return MOD_CONT;
+ }
+ if (mask && isdigit(*mask) &&
+ strspn(mask, "1234567890,-") == strlen(mask)) {
+ process_numlist(mask, NULL, akick_view_callback, u, ci,
+ &sent_header);
+ } else {
+ for (akick = ci->akick, i = 0; i < ci->akickcount;
+ akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if (mask) {
+ if (!(akick->flags & AK_ISNICK)
+ && !match_wild_nocase(mask, akick->u.mask))
+ continue;
+ if ((akick->flags & AK_ISNICK)
+ && !match_wild_nocase(mask, akick->u.nc->display))
+ continue;
+ }
+ akick_view(u, i, ci, &sent_header);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);
+
+ } else if (stricmp(cmd, "ENFORCE") == 0) {
+ Channel *c = findchan(ci->name);
+ struct c_userlist *cu = NULL;
+ struct c_userlist *next;
+ char *argv[3];
+ int count = 0;
+
+ if (!c) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, ci->name);
+ return MOD_CONT;
+ }
+
+ cu = c->users;
+
+ while (cu) {
+ next = cu->next;
+ if (check_kick(cu->user, c->name)) {
+ argv[0] = sstrdup(c->name);
+ argv[1] = sstrdup(cu->user->nick);
+ argv[2] = sstrdup(CSAutokickReason);
+
+ do_kick(s_ChanServ, 3, argv);
+
+ free(argv[2]);
+ free(argv[1]);
+ free(argv[0]);
+
+ count++;
+ }
+ cu = next;
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan, count);
+
+ } else if (stricmp(cmd, "CLEAR") == 0) {
+
+ if (readonly) {
+ notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
+ return MOD_CONT;
+ }
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ akick_del(u, akick);
+ }
+
+ free(ci->akick);
+ ci->akick = NULL;
+ ci->akickcount = 0;
+
+ notice_lang(s_ChanServ, u, CHAN_AKICK_CLEAR);
+
+ } else {
+ syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_levels(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *cmd = strtok(NULL, " ");
+ char *what = strtok(NULL, " ");
+ char *s = strtok(NULL, " ");
+ char *error;
+
+ ChannelInfo *ci;
+ short level;
+ int i;
+
+ /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only
+ * one; else, we want none.
+ */
+ if (!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 = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (ci->flags & CI_XOP) {
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_XOP);
+ } else if (!is_founder(u, ci) && !is_services_admin(u)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (stricmp(cmd, "SET") == 0) {
+ level = strtol(s, &error, 10);
+
+ if (*error != '\0') {
+ syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE,
+ ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
+ return MOD_CONT;
+ }
+
+ for (i = 0; levelinfo[i].what >= 0; i++) {
+ if (stricmp(levelinfo[i].name, what) == 0) {
+ ci->levels[levelinfo[i].what] = level;
+
+ alog("%s: %s!%s@%s set level %s on channel %s to %d",
+ s_ChanServ, u->nick, u->username, GetHost(u),
+ levelinfo[i].name, ci->name, level);
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED,
+ levelinfo[i].name, chan, level);
+ return MOD_CONT;
+ }
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
+
+ } else if (stricmp(cmd, "DIS") == 0 || stricmp(cmd, "DISABLE") == 0) {
+ for (i = 0; levelinfo[i].what >= 0; i++) {
+ if (stricmp(levelinfo[i].name, what) == 0) {
+ ci->levels[levelinfo[i].what] = ACCESS_INVALID;
+
+ alog("%s: %s!%s@%s disabled level %s on channel %s",
+ s_ChanServ, u->nick, u->username, GetHost(u),
+ levelinfo[i].name, ci->name);
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED,
+ levelinfo[i].name, chan);
+ return MOD_CONT;
+ }
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int i;
+
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_HEADER, chan);
+
+ if (!levelinfo_maxwidth) {
+ 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 j = ci->levels[levelinfo[i].what];
+
+ if (j == ACCESS_INVALID) {
+ j = levelinfo[i].what;
+
+ if (j == CA_AUTOOP || j == CA_AUTODEOP || j == CA_AUTOVOICE
+ || j == CA_NOJOIN) {
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
+ levelinfo_maxwidth, levelinfo[i].name);
+ } else {
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
+ levelinfo_maxwidth, levelinfo[i].name);
+ }
+ } else if (j == ACCESS_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, j);
+ }
+ }
+
+ } else if (stricmp(cmd, "RESET") == 0) {
+ reset_levels(ci);
+
+ alog("%s: %s!%s@%s reset levels definitions on channel %s",
+ s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
+ notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan);
+ } else {
+ syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* SADMINS and users, who have identified for a channel, can now cause it's
+ * Enstry Message and Successor to be displayed by supplying the ALL parameter.
+ * Syntax: INFO channel [ALL]
+ * -TheShadow (29 Mar 1999)
+ */
+
+static int do_info(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *param = strtok(NULL, " ");
+ ChannelInfo *ci;
+ char buf[BUFSIZE], *end;
+ struct tm *tm;
+ int need_comma = 0;
+ const char *commastr = getstring(u->na, COMMA_SPACE);
+ int is_servadmin = is_services_admin(u);
+ int show_all = 0;
+
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "INFO", CHAN_INFO_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ if (is_oper(u) && ci->forbidby)
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN_OPER, chan,
+ ci->forbidby,
+ (ci->forbidreason ? ci->
+ forbidreason : getstring(u->na, NO_REASON)));
+ else
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!ci->founder) {
+ /* Paranoia... this shouldn't be able to happen */
+ delchan(ci);
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else {
+
+ /* Should we show all fields? Only for sadmins and identified users */
+ if (param && stricmp(param, "ALL") == 0 &&
+ (check_access(u, ci, CA_INFO) || is_servadmin))
+ show_all = 1;
+
+ notice_lang(s_ChanServ, u, CHAN_INFO_HEADER, chan);
+ notice_lang(s_ChanServ, u, CHAN_INFO_NO_FOUNDER,
+ ci->founder->display);
+
+ if (show_all && ci->successor)
+ notice_lang(s_ChanServ, u, CHAN_INFO_NO_SUCCESSOR,
+ ci->successor->display);
+
+ notice_lang(s_ChanServ, u, CHAN_INFO_DESCRIPTION, ci->desc);
+ tm = localtime(&ci->time_registered);
+ strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
+ notice_lang(s_ChanServ, u, CHAN_INFO_TIME_REGGED, buf);
+ tm = localtime(&ci->last_used);
+ strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
+ notice_lang(s_ChanServ, u, CHAN_INFO_LAST_USED, buf);
+ if (ci->last_topic && (show_all || (!(ci->mlock_on & CMODE_s)
+ && (!ci->c
+ || !(ci->c->
+ mode & CMODE_s))))) {
+ notice_lang(s_ChanServ, u, CHAN_INFO_LAST_TOPIC,
+ ci->last_topic);
+ notice_lang(s_ChanServ, u, CHAN_INFO_TOPIC_SET_BY,
+ ci->last_topic_setter);
+ }
+
+ if (ci->entry_message && show_all)
+ notice_lang(s_ChanServ, u, CHAN_INFO_ENTRYMSG,
+ ci->entry_message);
+ if (ci->url)
+ notice_lang(s_ChanServ, u, CHAN_INFO_URL, ci->url);
+ if (ci->email)
+ notice_lang(s_ChanServ, u, CHAN_INFO_EMAIL, ci->email);
+
+ if (show_all) {
+ notice_lang(s_ChanServ, u, CHAN_INFO_BANTYPE, ci->bantype);
+
+ end = buf;
+ *end = 0;
+ if (ci->flags & CI_KEEPTOPIC) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s",
+ getstring(u->na, CHAN_INFO_OPT_KEEPTOPIC));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_OPNOTICE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_OPNOTICE));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_PEACE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_PEACE));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_PRIVATE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_PRIVATE));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_RESTRICTED) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na,
+ CHAN_INFO_OPT_RESTRICTED));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECURE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_SECURE));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECUREOPS) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_SECUREOPS));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECUREFOUNDER) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na,
+ CHAN_INFO_OPT_SECUREFOUNDER));
+ need_comma = 1;
+ }
+ if ((ci->flags & CI_SIGNKICK)
+ || (ci->flags & CI_SIGNKICK_LEVEL)) {
+ end +=
+ snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "", getstring(u->na,
+ CHAN_INFO_OPT_SIGNKICK));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_TOPICLOCK) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_TOPICLOCK));
+ need_comma = 1;
+ }
+ if (ci->flags & CI_XOP) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, CHAN_INFO_OPT_XOP));
+ need_comma = 1;
+ }
+ notice_lang(s_ChanServ, u, CHAN_INFO_OPTIONS,
+ *buf ? buf : getstring(u->na, CHAN_INFO_OPT_NONE));
+ notice_lang(s_ChanServ, u, CHAN_INFO_MODE_LOCK,
+ get_mlock_modes(ci, 1));
+
+ }
+
+ if ((ci->flags & CI_NO_EXPIRE) && show_all)
+ notice_lang(s_ChanServ, u, CHAN_INFO_NO_EXPIRE);
+ if (ci->flags & CI_SUSPENDED) {
+ notice_lang(s_ChanServ, u, CHAN_X_SUSPENDED, ci->forbidby,
+ (ci->forbidreason ? ci->
+ forbidreason : getstring(u->na, NO_REASON)));
+ }
+
+ if (!show_all && (check_access(u, ci, CA_INFO) || is_servadmin))
+ notice_lang(s_ChanServ, u, NICK_INFO_FOR_MORE, s_ChanServ,
+ ci->name);
+
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_list(User * u)
+{
+ char *pattern = strtok(NULL, " ");
+ ChannelInfo *ci;
+ int nchans, i;
+ char buf[BUFSIZE];
+ int is_servadmin = is_services_admin(u);
+ int count = 0, from = 0, to = 0;
+ char *tmp = NULL;
+ char *s = NULL;
+ char *keyword;
+ int32 matchflags = 0;
+
+
+ if (CSListOpersOnly && (!u || !is_oper(u))) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!pattern) {
+ syntax_error(s_ChanServ, u, "LIST",
+ is_servadmin ? CHAN_LIST_SERVADMIN_SYNTAX :
+ CHAN_LIST_SYNTAX);
+ } else {
+
+ if (pattern) {
+ if (pattern[0] == '#') {
+ tmp = myStrGetOnlyToken((pattern + 1), '-', 0); /* Read FROM out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ from = atoi(tmp);
+ tmp = myStrGetTokenRemainder(pattern, '-', 1); /* Read TO out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ to = atoi(tmp);
+ pattern = sstrdup("*");
+ }
+ }
+
+ nchans = 0;
+
+ while (is_servadmin && (keyword = strtok(NULL, " "))) {
+ if (stricmp(keyword, "FORBIDDEN") == 0)
+ matchflags |= CI_VERBOTEN;
+ if (stricmp(keyword, "SUSPENDED") == 0)
+ matchflags |= CI_SUSPENDED;
+ if (stricmp(keyword, "NOEXPIRE") == 0)
+ matchflags |= CI_NO_EXPIRE;
+ }
+
+ notice_lang(s_ChanServ, u, CHAN_LIST_HEADER, pattern);
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ if (!is_servadmin && ((ci->flags & CI_PRIVATE)
+ || (ci->flags & CI_VERBOTEN)))
+ continue;
+ if ((matchflags != 0) && !(ci->flags & matchflags))
+ continue;
+
+ if (stricmp(pattern, ci->name) == 0
+ || match_wild_nocase(pattern, ci->name)) {
+ if ((((count + 1 >= from) && (count + 1 <= to))
+ || ((from == 0) && (to == 0)))
+ && (++nchans <= CSListMax)) {
+ char noexpire_char = ' ';
+ if (is_servadmin && (ci->flags & CI_NO_EXPIRE))
+ noexpire_char = '!';
+
+ if (ci->flags & CI_VERBOTEN) {
+ snprintf(buf, sizeof(buf),
+ "%-20s [Forbidden]", ci->name);
+ } else if (ci->flags & CI_SUSPENDED) {
+ snprintf(buf, sizeof(buf),
+ "%-20s [Suspended]", ci->name);
+ } else {
+ snprintf(buf, sizeof(buf), "%-20s %s",
+ ci->name, ci->desc ? ci->desc : "");
+ }
+
+ notice_user(s_ChanServ, u, " %c%s",
+ noexpire_char, buf);
+ }
+ count++;
+ }
+ }
+ }
+ notice_lang(s_ChanServ, u, CHAN_LIST_END,
+ nchans > CSListMax ? CSListMax : nchans, nchans);
+ }
+ return MOD_CONT;
+
+}
+
+/*************************************************************************/
+
+static int do_invite(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "INVITE", CHAN_INVITE_SYNTAX);
+ } else if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!u || !check_access(u, ci, CA_INVITE)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+ send_cmd(whosends(ci), "INVITE %s %s", u->nick, chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* do_util: not a command, but does the job of other */
+
+static int do_util(User * u, CSModeUtil * util)
+{
+ char *av[2];
+ char *chan = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+
+ Channel *c;
+ ChannelInfo *ci;
+ User *u2;
+
+ int is_same;
+
+ if (!chan) {
+ struct u_chanlist *uc;
+
+ av[0] = util->mode;
+ av[1] = u->nick;
+
+ /* Sets the mode to the user on every channels he is on. */
+
+ for (uc = u->chans; uc; uc = uc->next) {
+ if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
+ && check_access(u, ci, util->levelself)) {
+ send_mode(whosends(ci), uc->chan->name, "%s %s",
+ util->mode, u->nick);
+ chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
+
+ if (util->notice && ci->flags & util->notice)
+ notice(whosends(ci), chan,
+ "%s command used for %s by %s", util->name,
+ u->nick, u->nick);
+ }
+ }
+
+ return MOD_CONT;
+ } else if (!nick) {
+ nick = u->nick;
+ }
+
+ is_same = (nick == u->nick) ? 1 : (stricmp(nick, u->nick) == 0);
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
+ } else if (is_same ? !(u2 = u) : !(u2 = finduser(nick))) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (!is_on_chan(c, u2)) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name);
+ } else if (is_same ? !check_access(u, ci, util->levelself) :
+ !check_access(u, ci, util->level)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (*util->mode == '-' && !is_same && (ci->flags & CI_PEACE)
+ && (get_access(u2, ci) >= get_access(u, ci))) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3)
+ } else if (*util->mode == '-' && is_protected(u2)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+#endif
+ } else {
+ send_mode(whosends(ci), c->name, "%s %s", util->mode, u2->nick);
+
+ av[0] = util->mode;
+ av[1] = u2->nick;
+ chan_set_modes(s_ChanServ, c, 2, av, 1);
+
+ if (util->notice && ci->flags & util->notice)
+ notice(whosends(ci), c->name, "%s command used for %s by %s",
+ util->name, u2->nick, u->nick);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_op(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_OP]);
+}
+
+/*************************************************************************/
+
+static int do_deop(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_DEOP]);
+}
+
+/*************************************************************************/
+
+static int do_voice(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_VOICE]);
+}
+
+/*************************************************************************/
+
+static int do_devoice(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_DEVOICE]);
+}
+
+/*************************************************************************/
+
+#ifdef HAS_HALFOP
+
+static int do_halfop(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_HALFOP]);
+}
+
+/*************************************************************************/
+
+static int do_dehalfop(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_DEHALFOP]);
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_RAGE2) || defined(IRC_PTLINK)
+
+static int do_protect(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_PROTECT]);
+}
+
+/*************************************************************************/
+
+static int do_deprotect(User * u)
+{
+ return do_util(u, &csmodeutils[MUT_DEPROTECT]);
+}
+
+/*************************************************************************/
+
+#endif
+
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+
+static int do_owner(User * u)
+{
+ char *av[2];
+ char *chan = strtok(NULL, " ");
+
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!chan) {
+ struct u_chanlist *uc;
+
+ av[0] = sstrdup("+q");
+ av[1] = u->nick;
+
+ /* Sets the mode to the user on every channels he is on. */
+
+ for (uc = u->chans; uc; uc = uc->next) {
+ if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
+ && is_founder(u, ci)) {
+ send_mode(whosends(ci), uc->chan->name, "%s %s",
+ av[0], u->nick);
+ chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
+ }
+ }
+
+ free(av[0]);
+ return MOD_CONT;
+ }
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
+ } else if (!is_on_chan(c, u)) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name);
+ } else if (!is_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ send_mode(whosends(ci), c->name, "+q %s", u->nick);
+
+ av[0] = sstrdup("+q");
+ av[1] = u->nick;
+ chan_set_modes(s_ChanServ, c, 2, av, 1);
+ free(av[0]);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_deowner(User * u)
+{
+ char *av[2];
+ char *chan = strtok(NULL, " ");
+
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!chan) {
+ struct u_chanlist *uc;
+
+ av[0] = sstrdup("-q");
+ av[1] = u->nick;
+
+ /* Sets the mode to the user on every channels he is on. */
+
+ for (uc = u->chans; uc; uc = uc->next) {
+ if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
+ && is_founder(u, ci)) {
+ send_mode(whosends(ci), uc->chan->name, "%s %s",
+ av[0], u->nick);
+ chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
+ }
+ }
+
+ free(av[0]);
+ return MOD_CONT;
+ }
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
+ } else if (!is_on_chan(c, u)) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name);
+ } else if (!is_founder(u, ci)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else {
+ send_mode(whosends(ci), c->name, "-q %s", u->nick);
+
+ av[0] = sstrdup("-q");
+ av[1] = u->nick;
+ chan_set_modes(s_ChanServ, c, 2, av, 1);
+ free(av[0]);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+static int do_cs_kick(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *params = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ Channel *c;
+ ChannelInfo *ci;
+ User *u2;
+
+ int is_same;
+
+ if (!reason) {
+ reason = "Requested";
+ } else {
+ if (strlen(reason) > 200)
+ reason[200] = '\0';
+ }
+
+ if (!chan) {
+ struct u_chanlist *uc, *next;
+
+ /* Kicks the user on every channels he is on. */
+
+ for (uc = u->chans; uc; uc = next) {
+ next = uc->next;
+ if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
+ && check_access(u, ci, CA_KICKME)) {
+ char *av[3];
+
+ if ((ci->flags & CI_SIGNKICK)
+ || ((ci->flags & CI_SIGNKICK_LEVEL)
+ && !check_access(u, ci, CA_SIGNKICK)))
+ send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name,
+ u->nick, reason, u->nick);
+ else
+ send_cmd(whosends(ci), "KICK %s %s :%s", ci->name,
+ u->nick, reason);
+ av[0] = ci->name;
+ av[1] = u->nick;
+ av[2] = reason;
+ do_kick(s_ChanServ, 3, av);
+ }
+ }
+
+ return MOD_CONT;
+ } else if (!params) {
+ params = u->nick;
+ }
+
+ is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0);
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (is_same ? !(u2 = u) : !(u2 = finduser(params))) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params);
+ } else if (!is_on_chan(c, u2)) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name);
+ } else if (!is_same ? !check_access(u, ci, CA_KICK) :
+ !check_access(u, ci, CA_KICKME)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (!is_same && (ci->flags & CI_PEACE)
+ && (get_access(u2, ci) >= get_access(u, ci))) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3)
+ } else if (is_protected(u2)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+#endif
+ } else {
+ char *av[3];
+
+ if ((ci->flags & CI_SIGNKICK)
+ || ((ci->flags & CI_SIGNKICK_LEVEL)
+ && !check_access(u, ci, CA_SIGNKICK)))
+ send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params,
+ reason, u->nick);
+ else
+ send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params,
+ reason);
+ av[0] = ci->name;
+ av[1] = params;
+ av[2] = reason;
+ do_kick(s_ChanServ, 3, av);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_ban(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *params = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ Channel *c;
+ ChannelInfo *ci;
+ User *u2;
+
+ int is_same;
+
+ if (!reason) {
+ reason = "Requested";
+ } else {
+ if (strlen(reason) > 200)
+ reason[200] = '\0';
+ }
+
+ if (!chan) {
+ struct u_chanlist *uc, *next;
+
+ /* Bans the user on every channels he is on. */
+
+ for (uc = u->chans; uc; uc = next) {
+ next = uc->next;
+ if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
+ && check_access(u, ci, CA_BANME)) {
+ char *av[3];
+ char mask[BUFSIZE];
+
+#ifdef HAS_EXCEPT
+ /*
+ * Dont ban/kick the user on channels where he is excepted
+ * to prevent services <-> server wars.
+ */
+ if (is_excepted(ci, u))
+ notice_lang(s_ChanServ, u, CHAN_EXCEPTED,
+ u->nick, ci->name);
+ continue;
+#endif
+ av[0] = sstrdup("+b");
+ get_idealban(ci, u, mask, sizeof(mask));
+ av[1] = mask;
+ send_mode(whosends(ci), uc->chan->name, "+b %s", av[1]);
+ chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
+ free(av[0]);
+
+ if ((ci->flags & CI_SIGNKICK)
+ || ((ci->flags & CI_SIGNKICK_LEVEL)
+ && !check_access(u, ci, CA_SIGNKICK)))
+ send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name,
+ u->nick, reason, u->nick);
+ else
+ send_cmd(whosends(ci), "KICK %s %s :%s", ci->name,
+ u->nick, reason);
+ av[0] = ci->name;
+ av[1] = u->nick;
+ av[2] = reason;
+ do_kick(s_ChanServ, 3, av);
+ }
+ }
+
+ return MOD_CONT;
+ } else if (!params) {
+ params = u->nick;
+ }
+
+ is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0);
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (is_same ? !(u2 = u) : !(u2 = finduser(params))) {
+ notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params);
+ } else if (!is_same ? !check_access(u, ci, CA_BAN) :
+ !check_access(u, ci, CA_BANME)) {
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ } else if (!is_same && (ci->flags & CI_PEACE)
+ && (get_access(u2, ci) >= get_access(u, ci))) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+#ifdef HAS_EXCEPT
+ /*
+ * Dont ban/kick the user on channels where he is excepted
+ * to prevent services <-> server wars.
+ */
+ } else if (is_excepted(ci, u2)) {
+ notice_lang(s_ChanServ, u, CHAN_EXCEPTED, u2->nick, ci->name);
+#endif
+ } else {
+ char *av[3];
+ char mask[BUFSIZE];
+
+ av[0] = sstrdup("+b");
+ get_idealban(ci, u2, mask, sizeof(mask));
+ av[1] = mask;
+ send_mode(whosends(ci), c->name, "+b %s", av[1]);
+ chan_set_modes(s_ChanServ, c, 2, av, 1);
+ free(av[0]);
+
+ /* We still allow host banning while not allowing to kick */
+ if (!is_on_chan(c, u2))
+ return MOD_CONT;
+
+ if ((ci->flags & CI_SIGNKICK)
+ || ((ci->flags & CI_SIGNKICK_LEVEL)
+ && !check_access(u, ci, CA_SIGNKICK)))
+ send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params,
+ reason, u->nick);
+ else
+ send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params,
+ reason);
+ av[0] = ci->name;
+ av[1] = params;
+ av[2] = reason;
+ do_kick(s_ChanServ, 3, av);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_cs_topic(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *topic = strtok(NULL, "");
+
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "TOPIC", CHAN_TOPIC_SYNTAX);
+ } else if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
+ } else if (!is_services_admin(u) && !check_access(u, ci, CA_TOPIC)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+ if (ci->last_topic)
+ free(ci->last_topic);
+ ci->last_topic = topic ? sstrdup(topic) : NULL;
+ strscpy(ci->last_topic_setter, u->nick, NICKMAX);
+ ci->last_topic_time = time(NULL);
+
+ if (c->topic)
+ free(c->topic);
+ c->topic = topic ? sstrdup(topic) : NULL;
+ strscpy(c->topic_setter, u->nick, NICKMAX);
+#if defined(IRC_DREAMFORGE) && !defined(IRC_ULTIMATE) && !defined(IRC_UNREAL)
+ c->topic_time = c->topic_time - 1;
+#else
+ c->topic_time = ci->last_topic_time;
+#endif
+
+ if (is_services_admin(u))
+ alog("%s: %s!%s@%s changed topic of %s as services admin.",
+ s_ChanServ, u->nick, u->username, u->host, c->name);
+#ifdef IRC_HYBRID
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name,
+ s_ChanServ);
+ send_mode(NULL, c->name, "+o %s", s_ChanServ);
+ }
+ send_cmd(whosends(ci), "TOPIC %s :%s", c->name,
+ c->topic ? c->topic : "");
+ if (whosends(ci) == s_ChanServ) {
+ send_cmd(s_ChanServ, "PART %s", c->name);
+ }
+#else
+ send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, u->nick,
+ c->topic_time, topic ? topic : "");
+#endif
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_unban(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "UNBAN", CHAN_UNBAN_SYNTAX);
+ } else if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!check_access(u, ci, CA_UNBAN)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+#ifndef IRC_BAHAMUT
+ int i;
+ char *av[3];
+ /* Save original ban info */
+ int count = c->bancount;
+ char **bans = scalloc(sizeof(char *) * count, 1);
+ memcpy(bans, c->bans, sizeof(char *) * count);
+
+ av[0] = chan;
+ av[1] = sstrdup("-b");
+ for (i = 0; i < count; i++) {
+ if (match_usermask(bans[i], u)) {
+ send_mode(whosends(ci), chan, "-b %s", bans[i]);
+ av[2] = sstrdup(bans[i]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ }
+ }
+ free(av[1]);
+ free(bans);
+#else
+ send_cmd(ServerName, "SVSMODE %s -b %s", chan, u->nick);
+#endif
+ notice_lang(s_ChanServ, u, CHAN_UNBANNED, chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_clear(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ char *what = strtok(NULL, " ");
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (!what) {
+ syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
+ } else if (!(c = findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (!(ci = c->ci)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (!u || !check_access(u, ci, CA_CLEAR)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else if (stricmp(what, "bans") == 0) {
+ char *av[3];
+ int i;
+
+ /* Save original ban info */
+ int count = c->bancount;
+ char **bans = scalloc(sizeof(char *) * count, 1);
+ for (i = 0; i < count; i++)
+ bans[i] = sstrdup(c->bans[i]);
+
+ for (i = 0; i < count; i++) {
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup("-b");
+ av[2] = bans[i];
+ send_mode(whosends(ci), av[0], "%s :%s", av[1], av[2]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_BANS, chan);
+ free(bans);
+#ifdef HAS_EXCEPT
+ } else if (stricmp(what, "excepts") == 0) {
+ char *av[3];
+ int i;
+
+ /* Save original except info */
+ int count = c->exceptcount;
+ char **excepts = scalloc(sizeof(char *) * count, 1);
+ for (i = 0; i < count; i++)
+ excepts[i] = sstrdup(c->excepts[i]);
+
+ for (i = 0; i < count; i++) {
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup("-e");
+ av[2] = excepts[i];
+ send_mode(whosends(ci), av[0], "%s :%s", av[1], av[2]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTS, chan);
+ free(excepts);
+#endif
+ } else if (stricmp(what, "modes") == 0) {
+ char buf[BUFSIZE], *end = buf;
+ char *argv[2];
+
+ if (c->mode) {
+ /* Clear modes */
+ send_mode(s_ChanServ, c->name, "%s %s", MODESTOREMOVE,
+ c->key ? c->key : "");
+ argv[0] = sstrdup(MODESTOREMOVE);
+ argv[1] = c->key ? c->key : NULL;
+ chan_set_modes(s_OperServ, c, c->key ? 2 : 1, argv, 0);
+ free(argv[0]);
+ check_modes(c);
+ }
+
+ /* TODO: decide if the above implementation is better than this one. */
+
+ if (0) {
+ CBModeInfo *cbmi = cbmodeinfos;
+ CBMode *cbm;
+
+ do {
+ if (c->mode & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0);
+
+ cbmi = cbmodeinfos;
+
+ do {
+ if (cbmi->getvalue && (c->mode & cbmi->flag)
+ && !(cbmi->flags & CBM_MINUS_NO_ARG)) {
+ char *value = cbmi->getvalue(c);
+
+ if (value) {
+ *end++ = ' ';
+ while (*value)
+ *end++ = *value++;
+
+ /* Free the value */
+ cbm = &cbmodes[(int) cbmi->mode];
+ cbm->setvalue(c, NULL);
+ }
+ }
+ } while ((++cbmi)->flag != 0);
+
+ *end = 0;
+
+ send_mode(whosends(ci), c->name, "-%s", buf);
+ c->mode = 0;
+ check_modes(c);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_MODES, chan);
+ } else if (stricmp(what, "ops") == 0) {
+ char *av[3];
+ struct c_userlist *cu, *next;
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ if (!chan_has_user_status(c, cu->user, CUS_OP))
+ continue;
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup("-o");
+ av[2] = sstrdup(cu->user->nick);
+ send_mode(whosends(ci), av[0], "%s :%s", av[1], av[2]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_OPS, chan);
+#ifdef HAS_HALFOP
+ } else if (stricmp(what, "hops") == 0) {
+ char *av[3];
+ struct c_userlist *cu, *next;
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ if (!chan_has_user_status(c, cu->user, CUS_HALFOP))
+ continue;
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup("-h");
+ av[2] = sstrdup(cu->user->nick);
+ send_mode(whosends(ci), av[0], "%s :%s", av[1], av[2]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_HOPS, chan);
+#endif
+ } else if (stricmp(what, "voices") == 0) {
+ char *av[3];
+ struct c_userlist *cu, *next;
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ if (!chan_has_user_status(c, cu->user, CUS_VOICE))
+ continue;
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup("-v");
+ av[2] = sstrdup(cu->user->nick);
+ send_mode(whosends(ci), av[0], "%s :%s", av[1], av[2]);
+ do_cmode(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_VOICES, chan);
+ } else if (stricmp(what, "users") == 0) {
+ char *av[3];
+ struct c_userlist *cu, *next;
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "CLEAR USERS command from %s", u->nick);
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ av[0] = sstrdup(chan);
+ av[1] = sstrdup(cu->user->nick);
+ av[2] = sstrdup(buf);
+ send_cmd(whosends(ci), "KICK %s %s :%s", av[0], av[1], av[2]);
+ do_kick(s_ChanServ, 3, av);
+ free(av[2]);
+ free(av[1]);
+ free(av[0]);
+ }
+ notice_lang(s_ChanServ, u, CHAN_CLEARED_USERS, chan);
+ } else {
+ syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_getkey(User * u)
+{
+ char *chan = strtok(NULL, " ");
+ ChannelInfo *ci;
+
+ if (chan && (ci = cs_findchan(chan)) && !(ci->flags & CI_VERBOTEN)
+ && check_access(u, ci, CA_GETKEY)) {
+ notice_user(s_ChanServ, u, "KEY %s %s", ci->name,
+ (ci->
+ c ? (ci->c->key ? ci->c->key : "NO KEY") : "NO KEY"));
+ } else {
+ notice_user(s_ChanServ, u, "KEY %s ERROR", chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_getpass(User * u)
+{
+#ifndef USE_ENCRYPTION
+ char *chan = strtok(NULL, " ");
+ ChannelInfo *ci;
+#endif
+
+ /* Assumes that permission checking has already been done. */
+#ifdef USE_ENCRYPTION
+ notice_lang(s_ChanServ, u, CHAN_GETPASS_UNAVAILABLE);
+#else
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "GETPASS", CHAN_GETPASS_SYNTAX);
+ } else if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else if (CSRestrictGetPass && !is_services_root(u)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else {
+ alog("%s: %s!%s@%s used GETPASS on %s",
+ s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
+ if (WallGetpass) {
+ wallops(s_ChanServ, "\2%s\2 used GETPASS on channel \2%s\2",
+ u->nick, chan);
+ }
+ notice_lang(s_ChanServ, u, CHAN_GETPASS_PASSWORD_IS,
+ chan, ci->founderpass);
+ }
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_sendpass(User * u)
+{
+#ifndef USE_ENCRYPTION
+ char *chan = strtok(NULL, " ");
+ ChannelInfo *ci;
+ NickCore *founder;
+#endif
+
+#ifdef USE_ENCRYPTION
+ notice_lang(s_ChanServ, u, CHAN_SENDPASS_UNAVAILABLE);
+#else
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "SENDPASS", CHAN_SENDPASS_SYNTAX);
+ } else if (RestrictMail && !is_oper(u)) {
+ notice_lang(s_ChanServ, u, PERMISSION_DENIED);
+ } else if (!(ci = cs_findchan(chan)) || !(founder = ci->founder)) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
+ } else {
+ char buf[BUFSIZE];
+ MailInfo *mail;
+
+ snprintf(buf, sizeof(buf),
+ getstring2(founder, CHAN_SENDPASS_SUBJECT), ci->name);
+ mail = MailBegin(u, founder, buf, s_ChanServ);
+ if (!mail)
+ return MOD_CONT;
+
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_HEAD));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_1),
+ ci->name);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_2),
+ ci->founderpass);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_3));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_4));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_5),
+ NetworkName);
+ fprintf(mail->pipe, "\n.\n");
+
+ MailEnd(mail);
+
+ alog("%s: %s!%s@%s used SENDPASS on %s", s_ChanServ, u->nick,
+ u->username, GetHost(u), chan);
+ notice_lang(s_ChanServ, u, CHAN_SENDPASS_OK, chan);
+ }
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_forbid(User * u)
+{
+ ChannelInfo *ci;
+ char *chan = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ Channel *c;
+
+ /* Assumes that permission checking has already been done. */
+ if (!chan || (ForceForbidReason && !reason)) {
+ syntax_error(s_ChanServ, u, "FORBID",
+ (ForceForbidReason ? CHAN_FORBID_SYNTAX_REASON :
+ CHAN_FORBID_SYNTAX));
+ return MOD_CONT;
+ }
+ if (readonly)
+ notice_lang(s_ChanServ, u, READ_ONLY_MODE);
+ if ((ci = cs_findchan(chan)) != NULL)
+ delchan(ci);
+ ci = makechan(chan);
+ if (ci) {
+ ci->flags |= CI_VERBOTEN;
+ ci->forbidby = sstrdup(u->nick);
+ if (reason)
+ ci->forbidreason = sstrdup(reason);
+
+ if ((c = findchan(ci->name))) {
+ struct c_userlist *cu, *next;
+ char *av[3];
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+
+ if (is_oper(cu->user))
+ continue;
+
+ av[0] = c->name;
+ av[1] = cu->user->nick;
+ av[2] = reason ? reason : "CHAN_FORBID_REASON";
+ send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1],
+ av[2]);
+ do_kick(s_ChanServ, 3, av);
+ }
+ }
+
+ if (WallForbid)
+ wallops(s_ChanServ, "\2%s\2 used FORBID on channel \2%s\2",
+ u->nick, ci->name);
+
+ alog("%s: %s set FORBID for channel %s", s_ChanServ, u->nick,
+ ci->name);
+ notice_lang(s_ChanServ, u, CHAN_FORBID_SUCCEEDED, chan);
+ } else {
+ alog("%s: Valid FORBID for %s by %s failed", s_ChanServ, ci->name,
+ u->nick);
+ notice_lang(s_ChanServ, u, CHAN_FORBID_FAILED, chan);
+ }
+ return MOD_CONT;
+}
+
+ /*************************************************************************/
+
+static int do_suspend(User * u)
+{
+ ChannelInfo *ci;
+ char *chan = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ Channel *c;
+
+ /* Assumes that permission checking has already been done. */
+ if (!chan || (ForceForbidReason && !reason)) {
+ syntax_error(s_ChanServ, u, "SUSPEND",
+ (ForceForbidReason ? CHAN_SUSPEND_SYNTAX_REASON :
+ CHAN_SUSPEND_SYNTAX));
+ return MOD_CONT;
+ }
+
+ /* Only SUSPEND existing channels, otherwise use FORBID (bug #54) */
+ if ((ci = cs_findchan(chan)) == NULL) {
+ notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
+ return MOD_CONT;
+ }
+
+ /* You should not SUSPEND a FORBIDEN channel */
+ if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
+ return MOD_CONT;
+ }
+
+ if (readonly)
+ notice_lang(s_ChanServ, u, READ_ONLY_MODE);
+
+ if (ci) {
+ ci->flags |= CI_SUSPENDED;
+ ci->forbidby = sstrdup(u->nick);
+ if (reason)
+ ci->forbidreason = sstrdup(reason);
+
+ if ((c = findchan(ci->name))) {
+ struct c_userlist *cu, *next;
+ char *av[3];
+
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+
+ if (is_oper(cu->user))
+ continue;
+
+ av[0] = c->name;
+ av[1] = cu->user->nick;
+ av[2] = reason ? reason : "CHAN_SUSPEND_REASON";
+ send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1],
+ av[2]);
+ do_kick(s_ChanServ, 3, av);
+ }
+ }
+
+ if (WallForbid)
+ wallops(s_ChanServ, "\2%s\2 used SUSPEND on channel \2%s\2",
+ u->nick, ci->name);
+
+ alog("%s: %s set SUSPEND for channel %s", s_ChanServ, u->nick,
+ ci->name);
+ notice_lang(s_ChanServ, u, CHAN_SUSPEND_SUCCEEDED, chan);
+ } else {
+ alog("%s: Valid SUSPEND for %s by %s failed", s_ChanServ, ci->name,
+ u->nick);
+ notice_lang(s_ChanServ, u, CHAN_SUSPEND_FAILED, chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_unsuspend(User * u)
+{
+ ChannelInfo *ci;
+ char *chan = strtok(NULL, " ");
+
+ /* Assumes that permission checking has already been done. */
+ if (!chan) {
+ syntax_error(s_ChanServ, u, "UNSUSPEND", CHAN_UNSUSPEND_SYNTAX);
+ return MOD_CONT;
+ }
+ if (chan[0] != '#') {
+ notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_ERROR);
+ return MOD_CONT;
+ }
+ if (readonly)
+ notice_lang(s_ChanServ, u, READ_ONLY_MODE);
+
+ ci = cs_findchan(chan);
+
+ if (ci) {
+ ci->flags &= ~CI_SUSPENDED;
+ ci->forbidreason = NULL;
+ ci->forbidby = NULL;
+
+ if (WallForbid)
+ wallops(s_ChanServ, "\2%s\2 used UNSUSPEND on channel \2%s\2",
+ u->nick, ci->name);
+
+ alog("%s: %s set UNSUSPEND for channel %s", s_ChanServ, u->nick,
+ ci->name);
+ notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_SUCCEEDED, chan);
+ } else {
+ alog("%s: Valid UNSUSPEND for %s by %s failed", s_ChanServ,
+ chan, u->nick);
+ notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_FAILED, chan);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_status(User * u)
+{
+ ChannelInfo *ci;
+ User *u2;
+ char *nick, *chan;
+
+ chan = strtok(NULL, " ");
+ nick = strtok(NULL, " ");
+ if (!nick || strtok(NULL, " ")) {
+ notice_user(s_ChanServ, u, "STATUS ERROR Syntax error");
+ return MOD_CONT;
+ }
+ if (!(ci = cs_findchan(chan))) {
+ char *temp = chan;
+ chan = nick;
+ nick = temp;
+ ci = cs_findchan(chan);
+ }
+ if (!ci) {
+ notice_user(s_ChanServ, u,
+ "STATUS ERROR Channel %s not registered", chan);
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_user(s_ChanServ, u, "STATUS ERROR Channel %s forbidden",
+ chan);
+ return MOD_CONT;
+ } else if ((u2 = finduser(nick)) != NULL) {
+ notice_user(s_ChanServ, u, "STATUS %s %s %d", chan, nick,
+ get_access(u2, ci));
+ } else { /* !u2 */
+ notice_user(s_ChanServ, u, "STATUS ERROR Nick %s not online",
+ nick);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
diff --git a/src/commands.c b/src/commands.c
new file mode 100644
index 000000000..2863d6ca3
--- /dev/null
+++ b/src/commands.c
@@ -0,0 +1,178 @@
+/* Routines for looking up commands in a *Serv command list.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "commands.h"
+#include "language.h"
+
+/*************************************************************************/
+
+/* Return the Command corresponding to the given name, or NULL if no such
+ * command exists.
+ */
+
+Command *lookup_cmd(Command * list, const char *cmd)
+{
+ Command *c;
+
+ for (c = list; c->name; c++) {
+ if (stricmp(c->name, cmd) == 0)
+ return c;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Run the routine for the given command, if it exists and the user has
+ * privilege to do so; if not, print an appropriate error message.
+ */
+
+void run_cmd(const char *service, User * u, Command * list,
+ const char *cmd)
+{
+ Command *c = lookup_cmd(list, cmd);
+ do_run_cmd(service, u, c, cmd);
+}
+
+void mod_run_cmd(const char *service, User * u, CommandHash * cmdTable[],
+ const char *cmd)
+{
+ Command *c = findCommand(cmdTable, cmd);
+ do_run_cmd(service, u, c, cmd);
+}
+
+void do_run_cmd(const char *service, User * u, Command * c,
+ const char *cmd)
+{
+ int retVal = 0;
+ Command *current;
+
+ if (c && c->routine) {
+ if ((checkDefCon(DEFCON_OPER_ONLY)
+ || checkDefCon(DEFCON_SILENT_OPER_ONLY)) && !is_oper(u)) {
+ if (!checkDefCon(DEFCON_SILENT_OPER_ONLY)) {
+ notice_lang(service, u, OPER_DEFCON_DENIED);
+ }
+ } else {
+ if ((c->has_priv == NULL) || c->has_priv(u)) {
+ mod_current_module_name = c->mod_name;
+ retVal = c->routine(u);
+ mod_current_module_name = NULL;
+ if (retVal == MOD_CONT) {
+ current = c->next;
+ while (current && retVal == MOD_CONT) {
+ mod_current_module_name = current->mod_name;
+ retVal = current->routine(u);
+ mod_current_module_name = NULL;
+ current = current->next;
+ }
+ }
+ }
+
+ else {
+ notice_lang(service, u, ACCESS_DENIED);
+ alog("Access denied for %s with service %s and command %s",
+ u->nick, service, cmd);
+ }
+ }
+ } else {
+ if ((!checkDefCon(DEFCON_SILENT_OPER_ONLY)) || is_oper(u))
+ notice_lang(service, u, UNKNOWN_COMMAND_HELP, cmd, service);
+ }
+}
+
+/*************************************************************************/
+
+/* Print a help message for the given command. */
+
+void do_help_cmd(const char *service, User * u, Command * c,
+ const char *cmd)
+{
+ Command *current;
+ int has_had_help = 0;
+ int cont = MOD_CONT;
+ const char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL;
+
+ for (current = c; (current) && (cont == MOD_CONT);
+ current = current->next) {
+ p1 = current->help_param1;
+ p2 = current->help_param2;
+ p3 = current->help_param3;
+ p4 = current->help_param4;
+ if (current->helpmsg_all >= 0) {
+ notice_help(service, u, current->helpmsg_all, p1, p2, p3, p4);
+ has_had_help = 1;
+ } else if (current->all_help) {
+ cont = current->all_help(u);
+ has_had_help = 1;
+ }
+ if (is_services_root(u)) {
+ if (current->helpmsg_root >= 0) {
+ notice_help(service, u, current->helpmsg_root, p1, p2, p3,
+ p4);
+ has_had_help = 1;
+ } else if (current->root_help) {
+ cont = current->root_help(u);
+ has_had_help = 1;
+ }
+ } else if (is_services_admin(u)) {
+ if (current->helpmsg_admin >= 0) {
+ notice_help(service, u, current->helpmsg_admin, p1, p2, p3,
+ p4);
+ has_had_help = 1;
+ } else if (current->admin_help) {
+ cont = current->admin_help(u);
+ has_had_help = 1;
+ }
+ } else if (is_services_oper(u)) {
+ if (current->helpmsg_oper >= 0) {
+ notice_help(service, u, current->helpmsg_oper, p1, p2, p3,
+ p4);
+ has_had_help = 1;
+ } else if (current->oper_help) {
+ cont = current->oper_help(u);
+ has_had_help = 1;
+ }
+ } else {
+ if (current->helpmsg_reg >= 0) {
+ notice_help(service, u, current->helpmsg_reg, p1, p2, p3,
+ p4);
+ has_had_help = 1;
+ } else if (current->regular_help) {
+ cont = current->regular_help(u);
+ has_had_help = 1;
+ }
+ }
+ }
+ if (has_had_help == 0) {
+ notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
+ }
+}
+
+void help_cmd(const char *service, User * u, Command * list,
+ const char *cmd)
+{
+ Command *c = lookup_cmd(list, cmd);
+ do_help_cmd(service, u, c, cmd);
+}
+
+void mod_help_cmd(const char *service, User * u, CommandHash * cmdTable[],
+ const char *cmd)
+{
+ Command *c = findCommand(cmdTable, cmd);
+ do_help_cmd(service, u, c, cmd);
+}
+
+/*************************************************************************/
diff --git a/src/compat.c b/src/compat.c
new file mode 100644
index 000000000..15e99529b
--- /dev/null
+++ b/src/compat.c
@@ -0,0 +1,212 @@
+/* Compatibility routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+
+#if !HAVE_SNPRINTF
+
+/* [v]snprintf: Like [v]sprintf, but don't write more than len bytes
+ * (including null terminator). Return the number of bytes
+ * written.
+ */
+
+#if BAD_SNPRINTF
+int vsnprintf(char *buf, size_t len, const char *fmt, va_list args)
+{
+ if (len <= 0)
+ return 0;
+ *buf = 0;
+#undef vsnprintf
+ vsnprintf(buf, len, fmt, args);
+#define vsnprintf my_vsnprintf
+ buf[len - 1] = 0;
+ return strlen(buf);
+}
+#endif /* BAD_SNPRINTF */
+
+int snprintf(char *buf, size_t len, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ return vsnprintf(buf, len, fmt, args);
+}
+
+#endif /* !HAVE_SNPRINTF */
+
+/*************************************************************************/
+
+#if !HAVE_STRICMP && !HAVE_STRCASECMP
+
+/* stricmp, strnicmp: Case-insensitive versions of strcmp() and
+ * strncmp().
+ */
+
+int stricmp(const char *s1, const char *s2)
+{
+ register int c;
+
+ while ((c = tolower(*s1)) == tolower(*s2)) {
+ if (c == 0)
+ return 0;
+ s1++;
+ s2++;
+ }
+ if (c < tolower(*s2))
+ return -1;
+ return 1;
+}
+
+int strnicmp(const char *s1, const char *s2, size_t len)
+{
+ register int c;
+
+ if (!len)
+ return 0;
+ while ((c = tolower(*s1)) == tolower(*s2) && len > 0) {
+ if (c == 0 || --len == 0)
+ return 0;
+ s1++;
+ s2++;
+ }
+ if (c < tolower(*s2))
+ return -1;
+ return 1;
+}
+#endif
+
+/*************************************************************************/
+
+#if !HAVE_STRDUP
+char *strdup(const char *s)
+{
+ char *new = calloc(strlen(s) + 1, 1);
+ if (new)
+ strcpy(new, s);
+ return new;
+}
+#endif
+
+/*************************************************************************/
+
+#if !HAVE_STRSPN
+size_t strspn(const char *s, const char *accept)
+{
+ size_t i = 0;
+
+ while (*s && strchr(accept, *s))
+ ++i, ++s;
+ return i;
+}
+#endif
+
+/*************************************************************************/
+
+#if !HAVE_STRERROR
+# if HAVE_SYS_ERRLIST
+extern char *sys_errlist[];
+# endif
+
+char *strerror(int errnum)
+{
+# if HAVE_SYS_ERRLIST
+ return sys_errlist[errnum];
+# else
+ static char buf[20];
+ snprintf(buf, sizeof(buf), "Error %d", errnum);
+ return buf;
+# endif
+}
+#endif
+
+/*************************************************************************/
+
+#if !HAVE_STRSIGNAL
+char *strsignal(int signum)
+{
+ static char buf[32];
+ switch (signum) {
+ case SIGHUP:
+ strscpy(buf, "Hangup", sizeof(buf));
+ break;
+ case SIGINT:
+ strscpy(buf, "Interrupt", sizeof(buf));
+ break;
+ case SIGQUIT:
+ strscpy(buf, "Quit", sizeof(buf));
+ break;
+#ifdef SIGILL
+ case SIGILL:
+ strscpy(buf, "Illegal instruction", sizeof(buf));
+ break;
+#endif
+#ifdef SIGABRT
+ case SIGABRT:
+ strscpy(buf, "Abort", sizeof(buf));
+ break;
+#endif
+#if defined(SIGIOT) && (!defined(SIGABRT) || SIGIOT != SIGABRT)
+ case SIGIOT:
+ strscpy(buf, "IOT trap", sizeof(buf));
+ break;
+#endif
+#ifdef SIGBUS
+ case SIGBUS:
+ strscpy(buf, "Bus error", sizeof(buf));
+ break;
+#endif
+ case SIGFPE:
+ strscpy(buf, "Floating point exception", sizeof(buf));
+ break;
+ case SIGKILL:
+ strscpy(buf, "Killed", sizeof(buf));
+ break;
+ case SIGUSR1:
+ strscpy(buf, "User signal 1", sizeof(buf));
+ break;
+ case SIGSEGV:
+ strscpy(buf, "Segmentation fault", sizeof(buf));
+ break;
+ case SIGUSR2:
+ strscpy(buf, "User signal 2", sizeof(buf));
+ break;
+ case SIGPIPE:
+ strscpy(buf, "Broken pipe", sizeof(buf));
+ break;
+ case SIGALRM:
+ strscpy(buf, "Alarm clock", sizeof(buf));
+ break;
+ case SIGTERM:
+ strscpy(buf, "Terminated", sizeof(buf));
+ break;
+ case SIGSTOP:
+ strscpy(buf, "Suspended (signal)", sizeof(buf));
+ break;
+ case SIGTSTP:
+ strscpy(buf, "Suspended", sizeof(buf));
+ break;
+ case SIGIO:
+ strscpy(buf, "I/O error", sizeof(buf));
+ break;
+ default:
+ snprintf(buf, sizeof(buf), "Signal %d\n", signum);
+ break;
+ }
+ return buf;
+}
+#endif
+
+/*************************************************************************/
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 000000000..e2d09f9e3
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,1333 @@
+/* Configuration file handling.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+
+/* Configurable variables: */
+
+char *RemoteServer;
+int RemotePort;
+char *RemotePassword;
+
+char *RemoteServer2;
+int RemotePort2;
+char *RemotePassword2;
+
+char *RemoteServer3;
+int RemotePort3;
+char *RemotePassword3;
+
+char *LocalHost;
+int LocalPort;
+
+char *ServerName;
+char *ServerDesc;
+char *ServiceUser;
+char *ServiceHost;
+static char *temp_userhost;
+
+char *HelpChannel;
+char *LogChannel;
+char *NetworkDomain;
+char **NetworkDomains;
+int DomainNumber;
+char *NetworkName;
+
+char *s_NickServ;
+char *s_ChanServ;
+char *s_MemoServ;
+char *s_BotServ;
+char *s_HelpServ;
+char *s_OperServ;
+char *s_GlobalNoticer;
+char *s_DevNull;
+char *desc_NickServ;
+char *desc_ChanServ;
+char *desc_MemoServ;
+char *desc_BotServ;
+char *desc_HelpServ;
+char *desc_OperServ;
+char *desc_GlobalNoticer;
+char *desc_DevNull;
+
+char *HostDBName; /* Name of HostServ DB File */
+char *s_HostServ; /* HostServ Name */
+char *desc_HostServ; /* HostServ Description */
+
+char *s_NickServAlias;
+char *s_ChanServAlias;
+char *s_MemoServAlias;
+char *s_BotServAlias;
+char *s_HelpServAlias;
+char *s_OperServAlias;
+char *s_GlobalNoticerAlias;
+char *s_DevNullAlias;
+char *s_HostServAlias;
+char *desc_NickServAlias;
+char *desc_ChanServAlias;
+char *desc_MemoServAlias;
+char *desc_BotServAlias;
+char *desc_HelpServAlias;
+char *desc_OperServAlias;
+char *desc_GlobalNoticerAlias;
+char *desc_DevNullAlias;
+char *desc_HostServAlias;
+
+char *PIDFilename;
+char *MOTDFilename;
+char *NickDBName;
+char *PreNickDBName;
+char *ChanDBName;
+char *BotDBName;
+char *OperDBName;
+char *AutokillDBName;
+char *NewsDBName;
+
+char *HostSetter;
+char **HostSetters;
+int HostNumber = 0; /* needs to be set to 0 */
+
+int NoBackupOkay;
+int StrictPasswords;
+int BadPassLimit;
+int BadPassTimeout;
+int UpdateTimeout;
+int ExpireTimeout;
+int ReadTimeout;
+int WarningTimeout;
+int TimeoutCheck;
+int KeepLogs;
+int KeepBackups;
+int ForceForbidReason;
+int UsePrivmsg;
+int DumpCore;
+int LogUsers;
+int NickRegDelay;
+int UseSVSHOLD;
+
+int UseMail;
+char *SendMailPath;
+char *SendFrom;
+int RestrictMail;
+int MailDelay;
+int DontQuoteAddresses;
+
+int ProxyDetect;
+int ProxyThreads;
+char *ProxyMessage[8];
+int ProxyCheckWingate;
+int ProxyCheckSocks4;
+int ProxyCheckSocks5;
+int ProxyCheckHTTP1;
+int ProxyCheckHTTP2;
+int ProxyCheckHTTP3;
+int ProxyTimeout;
+char *ProxyTestServer;
+int ProxyTestPort;
+int ProxyExpire;
+int ProxyCacheExpire;
+char *ProxyAkillReason;
+int WallProxy;
+int ProxyMax;
+
+static int NSDefNone;
+char *NSGuestNickPrefix;
+int NSAllowKillImmed;
+int NSNoGroupChange;
+int NSDefKill;
+int NSDefKillQuick;
+int NSDefSecure;
+int NSDefPrivate;
+int NSDefMsg;
+int NSDefHideEmail;
+int NSDefHideUsermask;
+int NSDefHideQuit;
+int NSDefMemoSignon;
+int NSDefMemoReceive;
+int NSDefFlags;
+int NSDefLanguage;
+int NSRegDelay;
+int NSExpire;
+int NSRExpire;
+int NSForceEmail;
+int NSMaxAliases;
+int NSAccessMax;
+char *NSEnforcerUser;
+char *NSEnforcerHost;
+static char *temp_nsuserhost;
+int NSReleaseTimeout;
+int NSListOpersOnly;
+int NSListMax;
+int NSSecureAdmins;
+int NSStrictPrivileges;
+int NSEmailReg;
+int NSModeOnID;
+int NSRestrictGetPass;
+int NSNickTracking;
+
+int CSDefNone;
+int CSDefKeepTopic;
+int CSDefOpNotice;
+int CSDefPeace;
+int CSDefPrivate;
+int CSDefRestricted;
+int CSDefSecure;
+int CSDefSecureOps;
+int CSDefSecureFounder;
+int CSDefSignKick;
+int CSDefSignKickLevel;
+int CSDefTopicLock;
+int CSDefXOP;
+int CSDefFlags;
+int CSMaxReg;
+int CSExpire;
+int CSDefBantype;
+int CSAccessMax;
+int CSAutokickMax;
+char *CSAutokickReason;
+int CSInhabit;
+int CSListOpersOnly;
+int CSListMax;
+int CSRestrictGetPass;
+int CSOpersOnly;
+
+int MSMaxMemos;
+int MSSendDelay;
+int MSNotifyAll;
+int MSMemoReceipt;
+
+int BSDefDontKickOps;
+int BSDefDontKickVoices;
+int BSDefFantasy;
+int BSDefGreet;
+int BSDefSymbiosis;
+int BSDefFlags;
+int BSKeepData;
+int BSMinUsers;
+int BSBadWordsMax;
+int BSSmartJoin;
+int BSGentleBWReason;
+int BSCaseSensitive;
+
+int HideStatsO;
+int GlobalOnCycle;
+int AnonymousGlobal;
+char *GlobalOnCycleMessage;
+char *GlobalOnCycleUP;
+char *ServicesRoot;
+char **ServicesRoots;
+int RootNumber;
+int SuperAdmin;
+int LogBot;
+int LogMaxUsers;
+int DisableRaw;
+int AutokillExpiry;
+int ChankillExpiry;
+int SGLineExpiry;
+int SQLineExpiry;
+int SZLineExpiry;
+int AkillOnAdd;
+int WallOper;
+int WallBadOS;
+int WallOSGlobal;
+int WallOSMode;
+int WallOSClearmodes;
+int WallOSKick;
+int WallOSAkill;
+int WallOSSGLine;
+int WallOSSQLine;
+int WallOSSZLine;
+int WallOSNoOp;
+int WallOSJupe;
+int WallOSRaw;
+int WallAkillExpire;
+int WallSGLineExpire;
+int WallSQLineExpire;
+int WallSZLineExpire;
+int WallExceptionExpire;
+int WallDrop;
+int WallForbid;
+int WallGetpass;
+int WallSetpass;
+int CheckClones;
+int CloneMinUsers;
+int CloneMaxDelay;
+int CloneWarningDelay;
+int KillClones;
+int AddAkiller;
+
+int KillClonesAkillExpire;
+
+int LimitSessions;
+int DefSessionLimit;
+int ExceptionExpiry;
+int MaxSessionKill;
+int MaxSessionLimit;
+int SessionAutoKillExpiry;
+char *ExceptionDBName;
+char *SessionLimitExceeded;
+char *SessionLimitDetailsLoc;
+
+char *Modules;
+char *ModulesDelayed;
+char **ModulesAutoload;
+int ModulesNumber;
+int ModulesDelayedNumber;
+char **ModulesDelayedAutoload;
+
+char *MysqlHost;
+char *MysqlUser;
+char *MysqlPass;
+char *MysqlName;
+int MysqlPort;
+char *MysqlSecure;
+char *MysqlSock;
+int MysqlRetries = 0;
+int MysqlRetryGap = 0;
+int UseRDB = 0;
+
+int DefConLevel;
+int DefCon1;
+int DefCon2;
+int DefCon3;
+int DefCon4;
+int DefCon5;
+int DefCon[6];
+char *DefConTimeOut;
+int DefConSessionLimit;
+char *DefConAKILL;
+char *DefConChanModes;
+int GlobalOnDefcon;
+int GlobalOnDefconMore;
+char *DefConOffMessage;
+char *DefconMessage;
+char *DefConAkillReason;
+
+/*************************************************************************/
+
+/* Deprecated directive (dep_) and value checking (chk_) functions: */
+
+static void dep_ListOpersOnly(void)
+{
+ NSListOpersOnly = 1;
+ CSListOpersOnly = 1;
+}
+
+/*************************************************************************/
+
+#define MAXPARAMS 8
+
+/* Configuration directives */
+
+typedef struct {
+ char *name;
+ struct {
+ int type; /* PARAM_* below */
+ int flags; /* Same */
+ void *ptr; /* Pointer to where to store the value */
+ } params[MAXPARAMS];
+} Directive;
+
+#define PARAM_NONE 0
+#define PARAM_INT 1
+#define PARAM_POSINT 2 /* Positive integer only */
+#define PARAM_PORT 3 /* 1..65535 only */
+#define PARAM_STRING 4
+#define PARAM_TIME 5
+#define PARAM_STRING_ARRAY 6 /* Array of string */
+#define PARAM_SET -1 /* Not a real parameter; just set the
+ * given integer variable to 1 */
+#define PARAM_DEPRECATED -2 /* Set for deprecated directives; `ptr'
+ * is a function pointer to call */
+
+/* Flags: */
+#define PARAM_OPTIONAL 0x01
+#define PARAM_FULLONLY 0x02 /* Directive only allowed if !STREAMLINED */
+#define PARAM_RELOAD 0x04 /* Directive is reloadable */
+
+Directive directives[] = {
+ {"AkillOnAdd", {{PARAM_SET, PARAM_RELOAD, &AkillOnAdd}}},
+ {"AutokillDB", {{PARAM_STRING, PARAM_RELOAD, &AutokillDBName}}},
+ {"AutokillExpiry", {{PARAM_TIME, PARAM_RELOAD, &AutokillExpiry}}},
+ {"ChankillExpiry", {{PARAM_TIME, PARAM_RELOAD, &ChankillExpiry}}},
+ {"BadPassLimit", {{PARAM_POSINT, PARAM_RELOAD, &BadPassLimit}}},
+ {"BadPassTimeout", {{PARAM_TIME, PARAM_RELOAD, &BadPassTimeout}}},
+ {"BotServDB", {{PARAM_STRING, PARAM_RELOAD, &BotDBName}}},
+ {"BotServName", {{PARAM_STRING, 0, &s_BotServ},
+ {PARAM_STRING, 0, &desc_BotServ}}},
+ {"BotServAlias", {{PARAM_STRING, 0, &s_BotServAlias},
+ {PARAM_STRING, 0, &desc_BotServAlias}}},
+ {"BSBadWordsMax", {{PARAM_POSINT, PARAM_RELOAD, &BSBadWordsMax}}},
+ {"BSDefDontKickOps", {{PARAM_SET, PARAM_RELOAD, &BSDefDontKickOps}}},
+ {"BSDefDontKickVoices",
+ {{PARAM_SET, PARAM_RELOAD, &BSDefDontKickVoices}}},
+ {"BSDefGreet", {{PARAM_SET, PARAM_RELOAD, &BSDefGreet}}},
+ {"BSDefFantasy", {{PARAM_SET, PARAM_RELOAD, &BSDefFantasy}}},
+ {"BSDefSymbiosis", {{PARAM_SET, PARAM_RELOAD, &BSDefSymbiosis}}},
+ {"BSCaseSensitive", {{PARAM_SET, PARAM_RELOAD, &BSCaseSensitive}}},
+ {"BSGentleBWReason", {{PARAM_SET, PARAM_RELOAD, &BSGentleBWReason}}},
+ {"BSKeepData", {{PARAM_TIME, PARAM_RELOAD, &BSKeepData}}},
+ {"BSMinUsers", {{PARAM_POSINT, PARAM_RELOAD, &BSMinUsers}}},
+ {"BSSmartJoin", {{PARAM_SET, PARAM_RELOAD, &BSSmartJoin}}},
+ {"HostServDB", {{PARAM_STRING, PARAM_RELOAD, &HostDBName}}},
+ {"HostServName", {{PARAM_STRING, 0, &s_HostServ},
+ {PARAM_STRING, 0, &desc_HostServ}}},
+ {"ChanServDB", {{PARAM_STRING, PARAM_RELOAD, &ChanDBName}}},
+ {"ChanServName", {{PARAM_STRING, 0, &s_ChanServ},
+ {PARAM_STRING, 0, &desc_ChanServ}}},
+ {"ChanServAlias", {{PARAM_STRING, 0, &s_ChanServAlias},
+ {PARAM_STRING, 0, &desc_ChanServAlias}}},
+ {"CheckClones", {{PARAM_SET, PARAM_FULLONLY, &CheckClones},
+ {PARAM_POSINT, 0, &CloneMinUsers},
+ {PARAM_TIME, 0, &CloneMaxDelay},
+ {PARAM_TIME, 0, &CloneWarningDelay}}},
+ {"CSAccessMax", {{PARAM_POSINT, PARAM_RELOAD, &CSAccessMax}}},
+ {"CSAutokickMax", {{PARAM_POSINT, PARAM_RELOAD, &CSAutokickMax}}},
+ {"CSAutokickReason",
+ {{PARAM_STRING, PARAM_RELOAD, &CSAutokickReason}}},
+ {"CSDefBantype", {{PARAM_POSINT, PARAM_RELOAD, &CSDefBantype}}},
+ {"CSDefNone", {{PARAM_SET, PARAM_RELOAD, &CSDefNone}}},
+ {"CSDefKeepTopic", {{PARAM_SET, PARAM_RELOAD, &CSDefKeepTopic}}},
+ {"CSDefOpNotice", {{PARAM_SET, PARAM_RELOAD, &CSDefOpNotice}}},
+ {"CSDefPeace", {{PARAM_SET, PARAM_RELOAD, &CSDefPeace}}},
+ {"CSDefPrivate", {{PARAM_SET, PARAM_RELOAD, &CSDefPrivate}}},
+ {"CSDefRestricted", {{PARAM_SET, PARAM_RELOAD, &CSDefRestricted}}},
+ {"CSDefSecure", {{PARAM_SET, PARAM_RELOAD, &CSDefSecure}}},
+ {"CSDefSecureOps", {{PARAM_SET, PARAM_RELOAD, &CSDefSecureOps}}},
+ {"CSDefSecureFounder",
+ {{PARAM_SET, PARAM_RELOAD, &CSDefSecureFounder}}},
+ {"CSDefSignKick", {{PARAM_SET, PARAM_RELOAD, &CSDefSignKick}}},
+ {"CSDefSignKickLevel",
+ {{PARAM_SET, PARAM_RELOAD, &CSDefSignKickLevel}}},
+ {"CSDefTopicLock", {{PARAM_SET, PARAM_RELOAD, &CSDefTopicLock}}},
+ {"CSDefXOP", {{PARAM_SET, PARAM_RELOAD, &CSDefXOP}}},
+ {"CSExpire", {{PARAM_TIME, PARAM_RELOAD, &CSExpire}}},
+ {"CSInhabit", {{PARAM_TIME, PARAM_RELOAD, &CSInhabit}}},
+ {"CSListMax", {{PARAM_POSINT, PARAM_RELOAD, &CSListMax}}},
+ {"CSListOpersOnly", {{PARAM_SET, PARAM_RELOAD, &CSListOpersOnly}}},
+ {"CSMaxReg", {{PARAM_POSINT, 0, &CSMaxReg}}},
+ {"CSRestrictGetPass", {{PARAM_SET, PARAM_RELOAD, &CSRestrictGetPass}}},
+ {"CSOpersOnly", {{PARAM_SET, PARAM_RELOAD, &CSOpersOnly}}},
+ {"DefSessionLimit", {{PARAM_POSINT, 0, &DefSessionLimit}}},
+ {"DevNullName", {{PARAM_STRING, 0, &s_DevNull},
+ {PARAM_STRING, 0, &desc_DevNull}}},
+ {"DevNullAlias", {{PARAM_STRING, 0, &s_DevNullAlias},
+ {PARAM_STRING, 0, &desc_DevNullAlias}}},
+ {"DisableRaw", {{PARAM_SET, PARAM_RELOAD, &DisableRaw}}},
+ {"DontQuoteAddresses",
+ {{PARAM_SET, PARAM_RELOAD, &DontQuoteAddresses}}},
+ {"DumpCore", {{PARAM_SET, 0, &DumpCore}}},
+ {"DefConLevel", {{PARAM_INT, PARAM_RELOAD, &DefConLevel}}},
+ {"DefCon1", {{PARAM_INT, PARAM_RELOAD, &DefCon1}}},
+ {"DefCon2", {{PARAM_INT, PARAM_RELOAD, &DefCon2}}},
+ {"DefCon3", {{PARAM_INT, PARAM_RELOAD, &DefCon3}}},
+ {"DefCon4", {{PARAM_INT, PARAM_RELOAD, &DefCon4}}},
+ {"DefConSessionLimit",
+ {{PARAM_INT, PARAM_RELOAD, &DefConSessionLimit}}},
+ {"DefConAkillExpire", {{PARAM_STRING, PARAM_RELOAD, &DefConAKILL}}},
+ {"DefConChanModes", {{PARAM_STRING, PARAM_RELOAD, &DefConChanModes}}},
+ {"DefConTimeOut", {{PARAM_STRING, PARAM_RELOAD, &DefConTimeOut}}},
+ {"DefConAkillReason",
+ {{PARAM_STRING, PARAM_RELOAD, &DefConAkillReason}}},
+ {"DefConOffMessage",
+ {{PARAM_STRING, PARAM_RELOAD, &DefConOffMessage}}},
+ {"ExceptionDB", {{PARAM_STRING, PARAM_RELOAD, &ExceptionDBName}}},
+ {"ExceptionExpiry", {{PARAM_TIME, PARAM_RELOAD, &ExceptionExpiry}}},
+ {"ExpireTimeout", {{PARAM_TIME, PARAM_RELOAD, &ExpireTimeout}}},
+ {"ForceForbidReason", {{PARAM_SET, PARAM_RELOAD, &ForceForbidReason}}},
+ {"GlobalName", {{PARAM_STRING, 0, &s_GlobalNoticer},
+ {PARAM_STRING, 0, &desc_GlobalNoticer}}},
+ {"GlobalAlias", {{PARAM_STRING, 0, &s_GlobalNoticerAlias},
+ {PARAM_STRING, 0, &desc_GlobalNoticerAlias}}},
+ {"HelpChannel", {{PARAM_STRING, PARAM_RELOAD, &HelpChannel}}},
+ {"HostServAlias", {{PARAM_STRING, 0, &s_HostServAlias},
+ {PARAM_STRING, 0, &desc_HostServAlias}}},
+ {"HostSetters", {{PARAM_STRING, PARAM_RELOAD, &HostSetter}}},
+ {"LogChannel", {{PARAM_STRING, PARAM_RELOAD, &LogChannel}}},
+ {"LogBot", {{PARAM_SET, PARAM_RELOAD, &LogBot}}},
+ {"HelpServName", {{PARAM_STRING, 0, &s_HelpServ},
+ {PARAM_STRING, 0, &desc_HelpServ}}},
+ {"HelpServAlias", {{PARAM_STRING, 0, &s_HelpServAlias},
+ {PARAM_STRING, 0, &desc_HelpServAlias}}},
+ {"KeepBackups", {{PARAM_POSINT, PARAM_RELOAD, &KeepBackups}}},
+ {"KeepLogs", {{PARAM_POSINT, PARAM_RELOAD, &KeepLogs}}},
+ {"KillClones", {{PARAM_SET, PARAM_FULLONLY, &KillClones}}},
+ {"AddAkiller", {{PARAM_SET, PARAM_RELOAD, &AddAkiller}}},
+ {"KillClonesAkillExpire",
+ {{PARAM_TIME, PARAM_RELOAD, &KillClonesAkillExpire}}},
+ {"LimitSessions", {{PARAM_SET, PARAM_FULLONLY, &LimitSessions}}},
+ {"ListOpersOnly",
+ {{PARAM_DEPRECATED, PARAM_RELOAD, dep_ListOpersOnly}}},
+ {"LocalAddress", {{PARAM_STRING, 0, &LocalHost},
+ {PARAM_PORT, PARAM_OPTIONAL, &LocalPort}}},
+ {"LogUsers", {{PARAM_SET, PARAM_RELOAD, &LogUsers}}},
+ {"SuperAdmin", {{PARAM_SET, PARAM_RELOAD, &SuperAdmin}}},
+ {"LogMaxUsers", {{PARAM_SET, PARAM_RELOAD, &LogMaxUsers}}},
+ {"MailDelay", {{PARAM_TIME, PARAM_RELOAD, &MailDelay}}},
+ {"MaxSessionKill", {{PARAM_POSINT, PARAM_RELOAD, &MaxSessionKill}}},
+ {"MaxSessionLimit", {{PARAM_POSINT, PARAM_RELOAD, &MaxSessionLimit}}},
+ {"MemoServName", {{PARAM_STRING, 0, &s_MemoServ},
+ {PARAM_STRING, 0, &desc_MemoServ}}},
+ {"MemoServAlias", {{PARAM_STRING, 0, &s_MemoServAlias},
+ {PARAM_STRING, 0, &desc_MemoServAlias}}},
+ {"MysqlHost", {{PARAM_STRING, PARAM_RELOAD, &MysqlHost}}},
+ {"MysqlUser", {{PARAM_STRING, PARAM_RELOAD, &MysqlUser}}},
+ {"MysqlPass", {{PARAM_STRING, PARAM_RELOAD, &MysqlPass}}},
+ {"MysqlName", {{PARAM_STRING, PARAM_RELOAD, &MysqlName}}},
+ {"MysqlPort", {{PARAM_PORT, PARAM_RELOAD, &MysqlPort}}},
+ {"MysqlSecure", {{PARAM_STRING, PARAM_RELOAD, &MysqlSecure}}},
+ {"MysqlSock", {{PARAM_STRING, PARAM_RELOAD, &MysqlSock}}},
+ {"MysqlRetries", {{PARAM_POSINT, PARAM_RELOAD, &MysqlRetries}}},
+ {"MysqlRetryGap", {{PARAM_POSINT, PARAM_RELOAD, &MysqlRetryGap}}},
+ {"UseRDB", {{PARAM_SET, PARAM_RELOAD, &UseRDB}}},
+ {"ModuleAutoload", {{PARAM_STRING, PARAM_RELOAD, &Modules}}},
+ {"ModuleDelayedAutoload",
+ {{PARAM_STRING, PARAM_RELOAD, &ModulesDelayed}}},
+ {"MOTDFile", {{PARAM_STRING, PARAM_RELOAD, &MOTDFilename}}},
+ {"MSMaxMemos", {{PARAM_POSINT, PARAM_RELOAD, &MSMaxMemos}}},
+ {"MSNotifyAll", {{PARAM_SET, PARAM_RELOAD, &MSNotifyAll}}},
+ {"MSSendDelay", {{PARAM_TIME, PARAM_RELOAD, &MSSendDelay}}},
+ {"MSMemoReceipt", {{PARAM_POSINT, PARAM_RELOAD, &MSMemoReceipt}}},
+ {"NetworkDomain", {{PARAM_STRING, PARAM_RELOAD, &NetworkDomain}}},
+ {"NetworkName", {{PARAM_STRING, PARAM_RELOAD, &NetworkName}}},
+ {"NewsDB", {{PARAM_STRING, PARAM_RELOAD, &NewsDBName}}},
+ {"NickservDB", {{PARAM_STRING, PARAM_RELOAD, &NickDBName}}},
+ {"PreNickServDB", {{PARAM_STRING, PARAM_RELOAD, &PreNickDBName}}},
+ {"NSEmailReg", {{PARAM_SET, PARAM_RELOAD, &NSEmailReg}}},
+ {"NickRegDelay", {{PARAM_INT, PARAM_RELOAD, &NickRegDelay}}},
+ {"NickServName", {{PARAM_STRING, 0, &s_NickServ},
+ {PARAM_STRING, 0, &desc_NickServ}}},
+ {"NickServAlias", {{PARAM_STRING, 0, &s_NickServAlias},
+ {PARAM_STRING, 0, &desc_NickServAlias}}},
+ {"NoBackupOkay", {{PARAM_SET, PARAM_RELOAD, &NoBackupOkay}}},
+ {"NSAccessMax", {{PARAM_POSINT, PARAM_RELOAD, &NSAccessMax}}},
+ {"NSAllowKillImmed", {{PARAM_SET, 0, &NSAllowKillImmed}}},
+ {"NSDefHideEmail", {{PARAM_SET, PARAM_RELOAD, &NSDefHideEmail}}},
+ {"NSDefHideQuit", {{PARAM_SET, PARAM_RELOAD, &NSDefHideQuit}}},
+ {"NSDefHideUsermask", {{PARAM_SET, PARAM_RELOAD, &NSDefHideUsermask}}},
+ {"NSDefKill", {{PARAM_SET, PARAM_RELOAD, &NSDefKill}}},
+ {"NSDefKillQuick", {{PARAM_SET, PARAM_RELOAD, &NSDefKillQuick}}},
+ {"NSDefLanguage", {{PARAM_POSINT, PARAM_RELOAD, &NSDefLanguage}}},
+ {"NSDefMemoReceive", {{PARAM_SET, PARAM_RELOAD, &NSDefMemoReceive}}},
+ {"NSDefMemoSignon", {{PARAM_SET, PARAM_RELOAD, &NSDefMemoSignon}}},
+ {"NSDefMsg", {{PARAM_SET, PARAM_RELOAD, &NSDefMsg}}},
+ {"NSDefNone", {{PARAM_SET, PARAM_RELOAD, &NSDefNone}}},
+ {"NSDefPrivate", {{PARAM_SET, PARAM_RELOAD, &NSDefPrivate}}},
+ {"NSDefSecure", {{PARAM_SET, PARAM_RELOAD, &NSDefSecure}}},
+ {"NSEnforcerUser", {{PARAM_STRING, PARAM_RELOAD, &temp_nsuserhost}}},
+ {"NSExpire", {{PARAM_TIME, PARAM_RELOAD, &NSExpire}}},
+ {"NSRExpire", {{PARAM_TIME, PARAM_RELOAD, &NSRExpire}}},
+ {"NSModeOnID", {{PARAM_SET, PARAM_RELOAD, &NSModeOnID}}},
+ {"NSForceEmail", {{PARAM_SET, PARAM_RELOAD, &NSForceEmail}}},
+ {"NSGuestNickPrefix",
+ {{PARAM_STRING, PARAM_RELOAD, &NSGuestNickPrefix}}},
+ {"NSListMax", {{PARAM_POSINT, PARAM_RELOAD, &NSListMax}}},
+ {"NSListOpersOnly", {{PARAM_SET, PARAM_RELOAD, &NSListOpersOnly}}},
+ {"NSMaxAliases", {{PARAM_POSINT, PARAM_RELOAD, &NSMaxAliases}}},
+ {"NSNoGroupChange", {{PARAM_SET, PARAM_RELOAD, &NSNoGroupChange}}},
+ {"NSRegDelay", {{PARAM_TIME, PARAM_RELOAD, &NSRegDelay}}},
+ {"NSReleaseTimeout", {{PARAM_TIME, PARAM_RELOAD, &NSReleaseTimeout}}},
+ {"NSSecureAdmins", {{PARAM_SET, PARAM_RELOAD, &NSSecureAdmins}}},
+ {"NSStrictPrivileges",
+ {{PARAM_SET, PARAM_RELOAD, &NSStrictPrivileges}}},
+ {"NSRestrictGetPass", {{PARAM_SET, PARAM_RELOAD, &NSRestrictGetPass}}},
+ {"NSNickTracking", {{PARAM_SET, PARAM_RELOAD, &NSNickTracking}}},
+ {"OperServDB", {{PARAM_STRING, PARAM_RELOAD, &OperDBName}}},
+ {"OperServName", {{PARAM_STRING, 0, &s_OperServ},
+ {PARAM_STRING, 0, &desc_OperServ}}},
+ {"OperServAlias", {{PARAM_STRING, 0, &s_OperServAlias},
+ {PARAM_STRING, 0, &desc_OperServAlias}}},
+ {"PIDFile", {{PARAM_STRING, 0, &PIDFilename}}},
+ {"ProxyAkillReason",
+ {{PARAM_STRING, PARAM_RELOAD, &ProxyAkillReason}}},
+ {"ProxyCacheExpire", {{PARAM_TIME, PARAM_RELOAD, &ProxyCacheExpire}}},
+ {"ProxyCheckWingate", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckWingate}}},
+ {"ProxyCheckSocks4", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckSocks4}}},
+ {"ProxyCheckSocks5", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckSocks5}}},
+ {"ProxyCheckHTTP1", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckHTTP1}}},
+ {"ProxyCheckHTTP2", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckHTTP2}}},
+ {"ProxyCheckHTTP3", {{PARAM_SET, PARAM_RELOAD, &ProxyCheckHTTP3}}},
+ {"ProxyDetect", {{PARAM_SET, 0, &ProxyDetect}}},
+ {"ProxyExpire", {{PARAM_TIME, PARAM_RELOAD, &ProxyExpire}}},
+ {"ProxyMax", {{PARAM_POSINT, PARAM_RELOAD, &ProxyMax}}},
+ {"ProxyMessage1", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[0]}}},
+ {"ProxyMessage2", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[1]}}},
+ {"ProxyMessage3", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[2]}}},
+ {"ProxyMessage4", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[3]}}},
+ {"ProxyMessage5", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[4]}}},
+ {"ProxyMessage6", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[5]}}},
+ {"ProxyMessage7", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[6]}}},
+ {"ProxyMessage8", {{PARAM_STRING, PARAM_RELOAD, &ProxyMessage[7]}}},
+ {"ProxyTestServer", {{PARAM_STRING, PARAM_RELOAD, &ProxyTestServer},
+ {PARAM_PORT, PARAM_RELOAD, &ProxyTestPort}}},
+ {"ProxyThreads", {{PARAM_POSINT, 0, &ProxyThreads}}},
+ {"ProxyTimeout", {{PARAM_TIME, PARAM_RELOAD, &ProxyTimeout}}},
+ {"ReadTimeout", {{PARAM_TIME, PARAM_RELOAD, &ReadTimeout}}},
+ {"RemoteServer", {{PARAM_STRING, 0, &RemoteServer},
+ {PARAM_PORT, 0, &RemotePort},
+ {PARAM_STRING, 0, &RemotePassword}}},
+ {"RemoteServer2", {{PARAM_STRING, 0, &RemoteServer2},
+ {PARAM_PORT, 0, &RemotePort2},
+ {PARAM_STRING, 0, &RemotePassword2}}},
+ {"RemoteServer3", {{PARAM_STRING, 0, &RemoteServer3},
+ {PARAM_PORT, 0, &RemotePort3},
+ {PARAM_STRING, 0, &RemotePassword3}}},
+ {"RestrictMail", {{PARAM_SET, PARAM_RELOAD, &RestrictMail}}},
+ {"SendMailPath", {{PARAM_STRING, PARAM_RELOAD, &SendMailPath}}},
+ {"SendFrom", {{PARAM_STRING, PARAM_RELOAD, &SendFrom}}},
+ {"ServerDesc", {{PARAM_STRING, 0, &ServerDesc}}},
+ {"ServerName", {{PARAM_STRING, 0, &ServerName}}},
+ {"ServicesRoot", {{PARAM_STRING, PARAM_RELOAD, &ServicesRoot}}},
+ {"ServiceUser", {{PARAM_STRING, 0, &temp_userhost}}},
+ {"SessionLimitDetailsLoc",
+ {{PARAM_STRING, PARAM_RELOAD, &SessionLimitDetailsLoc}}},
+ {"SessionLimitExceeded",
+ {{PARAM_STRING, PARAM_RELOAD, &SessionLimitExceeded}}},
+ {"SessionAutoKillExpiry",
+ {{PARAM_TIME, PARAM_RELOAD, &SessionAutoKillExpiry}}},
+ {"SGLineExpiry", {{PARAM_TIME, PARAM_RELOAD, &SGLineExpiry}}},
+ {"SQLineExpiry", {{PARAM_TIME, PARAM_RELOAD, &SQLineExpiry}}},
+ {"SZLineExpiry", {{PARAM_TIME, PARAM_RELOAD, &SZLineExpiry}}},
+ {"HideStatsO", {{PARAM_SET, PARAM_RELOAD, &HideStatsO}}},
+ {"GlobalOnCycle", {{PARAM_SET, PARAM_RELOAD, &GlobalOnCycle}}},
+ {"AnonymousGlobal", {{PARAM_SET, PARAM_RELOAD, &AnonymousGlobal}}},
+ {"GlobalOnCycleMessage",
+ {{PARAM_STRING, PARAM_RELOAD, &GlobalOnCycleMessage}}},
+ {"GlobalOnCycleUP", {{PARAM_STRING, PARAM_RELOAD, &GlobalOnCycleUP}}},
+ {"StrictPasswords", {{PARAM_SET, PARAM_RELOAD, &StrictPasswords}}},
+ {"TimeoutCheck", {{PARAM_TIME, PARAM_RELOAD, &TimeoutCheck}}},
+ {"UpdateTimeout", {{PARAM_TIME, PARAM_RELOAD, &UpdateTimeout}}},
+ {"UseMail", {{PARAM_SET, PARAM_RELOAD, &UseMail}}},
+ {"UsePrivmsg", {{PARAM_SET, PARAM_RELOAD, &UsePrivmsg}}},
+ {"UseSVSHOLD", {{PARAM_SET, PARAM_RELOAD, &UseSVSHOLD}}},
+ {"WallAkillExpire", {{PARAM_SET, PARAM_RELOAD, &WallAkillExpire}}},
+ {"WallBadOS", {{PARAM_SET, PARAM_RELOAD, &WallBadOS}}},
+ {"WallDrop", {{PARAM_SET, PARAM_RELOAD, &WallDrop}}},
+ {"WallExceptionExpire",
+ {{PARAM_SET, PARAM_RELOAD, &WallExceptionExpire}}},
+ {"WallForbid", {{PARAM_SET, PARAM_RELOAD, &WallForbid}}},
+ {"WallGetpass", {{PARAM_SET, PARAM_RELOAD, &WallGetpass}}},
+ {"WallOper", {{PARAM_SET, PARAM_RELOAD, &WallOper}}},
+ {"WallOSAkill", {{PARAM_SET, PARAM_RELOAD, &WallOSAkill}}},
+ {"WallOSClearmodes", {{PARAM_SET, PARAM_RELOAD, &WallOSClearmodes}}},
+ {"WallOSGlobal", {{PARAM_SET, PARAM_RELOAD, &WallOSGlobal}}},
+ {"WallOSKick", {{PARAM_SET, PARAM_RELOAD, &WallOSKick}}},
+ {"WallOSJupe", {{PARAM_SET, PARAM_RELOAD, &WallOSJupe}}},
+ {"WallOSMode", {{PARAM_SET, PARAM_RELOAD, &WallOSMode}}},
+ {"WallOSNoOp", {{PARAM_SET, PARAM_RELOAD, &WallOSNoOp}}},
+ {"WallOSRaw", {{PARAM_SET, PARAM_RELOAD, &WallOSRaw}}},
+ {"WallOSSGLine", {{PARAM_SET, PARAM_RELOAD, &WallOSSGLine}}},
+ {"WallOSSQLine", {{PARAM_SET, PARAM_RELOAD, &WallOSSQLine}}},
+ {"WallOSSZLine", {{PARAM_SET, PARAM_RELOAD, &WallOSSZLine}}},
+ {"WallProxy", {{PARAM_SET, PARAM_RELOAD, &WallProxy}}},
+ {"WallSetpass", {{PARAM_SET, PARAM_RELOAD, &WallSetpass}}},
+ {"WallSGLineExpire", {{PARAM_SET, PARAM_RELOAD, &WallSGLineExpire}}},
+ {"WallSQLineExpire", {{PARAM_SET, PARAM_RELOAD, &WallSQLineExpire}}},
+ {"WallSZLineExpire", {{PARAM_SET, PARAM_RELOAD, &WallSZLineExpire}}},
+ {"WarningTimeout", {{PARAM_TIME, PARAM_RELOAD, &WarningTimeout}}},
+ {"GlobalOnDefcon", {{PARAM_SET, PARAM_RELOAD, &GlobalOnDefcon}}},
+ {"GlobalOnDefconMore",
+ {{PARAM_SET, PARAM_RELOAD, &GlobalOnDefconMore}}},
+ {"DefconMessage", {{PARAM_STRING, PARAM_RELOAD, &DefconMessage}}},
+};
+
+/*************************************************************************/
+
+/* Print an error message to the log (and the console, if open). */
+
+void error(int linenum, char *message, ...)
+{
+ char buf[4096];
+ va_list args;
+
+ va_start(args, message);
+ vsnprintf(buf, sizeof(buf), message, args);
+#ifndef NOT_MAIN
+ if (linenum)
+ alog("%s:%d: %s", SERVICES_CONF, linenum, buf);
+ else
+ alog("%s: %s", SERVICES_CONF, buf);
+ if (!nofork && isatty(2)) {
+#endif
+ if (linenum)
+ fprintf(stderr, "%s:%d: %s\n", SERVICES_CONF, linenum, buf);
+ else
+ fprintf(stderr, "%s: %s\n", SERVICES_CONF, buf);
+#ifndef NOT_MAIN
+ }
+#endif
+}
+
+/*************************************************************************/
+
+/* Parse a configuration line. Return 1 on success; otherwise, print an
+ * appropriate error message and return 0. Destroys the buffer by side
+ * effect.
+ */
+
+int parse(char *buf, int linenum, int reload)
+{
+ char *s, *t, *dir;
+ int i, n, optind, val;
+ int retval = 1;
+ int ac = 0;
+ char *av[MAXPARAMS];
+
+ dir = strtok(buf, " \t\r\n");
+ s = strtok(NULL, "");
+ if (s) {
+ while (isspace(*s))
+ s++;
+ while (*s) {
+ if (ac >= MAXPARAMS) {
+ error(linenum, "Warning: too many parameters (%d max)",
+ MAXPARAMS);
+ break;
+ }
+ t = s;
+ if (*s == '"') {
+ t++;
+ s++;
+ while (*s && *s != '"') {
+ if (*s == '\\' && s[1] != 0)
+ s++;
+ s++;
+ }
+ if (!*s)
+ error(linenum,
+ "Warning: unterminated double-quoted string");
+ else
+ *s++ = 0;
+ } else {
+ s += strcspn(s, " \t\r\n");
+ if (*s)
+ *s++ = 0;
+ }
+ av[ac++] = t;
+ while (isspace(*s))
+ s++;
+ }
+ }
+
+ if (!dir)
+ return 1;
+
+ for (n = 0; n < lenof(directives); n++) {
+ Directive *d = &directives[n];
+ if (stricmp(dir, d->name) != 0)
+ continue;
+ optind = 0;
+ for (i = 0; i < MAXPARAMS && d->params[i].type != PARAM_NONE; i++) {
+ if (reload && !(d->params[i].flags & PARAM_RELOAD))
+ continue;
+
+ if (d->params[i].type == PARAM_SET) {
+ *(int *) d->params[i].ptr = 1;
+ continue;
+ }
+#ifdef STREAMLINED
+ if (d->params[i].flags & PARAM_FULLONLY) {
+ error(linenum,
+ "Directive `%s' not available in STREAMLINED mode",
+ d->name);
+ break;
+ }
+#endif
+
+ if (d->params[i].type == PARAM_DEPRECATED) {
+ void (*func) (void);
+ error(linenum, "Deprecated directive `%s' used", d->name);
+ func = (void (*)(void)) (d->params[i].ptr);
+ func(); /* For clarity */
+ continue;
+ }
+ if (optind >= ac) {
+ if (!(d->params[i].flags & PARAM_OPTIONAL)) {
+ error(linenum, "Not enough parameters for `%s'",
+ d->name);
+ retval = 0;
+ }
+ break;
+ }
+ switch (d->params[i].type) {
+ case PARAM_INT:
+ val = strtol(av[optind++], &s, 0);
+ if (*s) {
+ error(linenum,
+ "%s: Expected an integer for parameter %d",
+ d->name, optind);
+ retval = 0;
+ break;
+ }
+ *(int *) d->params[i].ptr = val;
+ break;
+ case PARAM_POSINT:
+ val = strtol(av[optind++], &s, 0);
+ if (*s || val <= 0) {
+ error(linenum,
+ "%s: Expected a positive integer for parameter %d",
+ d->name, optind);
+ retval = 0;
+ break;
+ }
+ *(int *) d->params[i].ptr = val;
+ break;
+ case PARAM_PORT:
+ val = strtol(av[optind++], &s, 0);
+ if (*s) {
+ error(linenum,
+ "%s: Expected a port number for parameter %d",
+ d->name, optind);
+ retval = 0;
+ break;
+ }
+ if (val < 1 || val > 65535) {
+ error(linenum,
+ "Port numbers must be in the range 1..65535");
+ retval = 0;
+ break;
+ }
+ *(int *) d->params[i].ptr = val;
+ break;
+ case PARAM_STRING:
+/* if (reload && *(char **)d->params[i].ptr)
+ free(*(char **)d->params[i].ptr); */
+ *(char **) d->params[i].ptr = strdup(av[optind++]);
+ if (!d->params[i].ptr) {
+ error(linenum, "%s: Out of memory", d->name);
+ return 0;
+ }
+ break;
+ case PARAM_TIME:
+ val = dotime(av[optind++]);
+ if (val < 0) {
+ error(linenum,
+ "%s: Expected a time value for parameter %d",
+ d->name, optind);
+ retval = 0;
+ break;
+ }
+ *(int *) d->params[i].ptr = val;
+ break;
+ default:
+ error(linenum, "%s: Unknown type %d for param %d",
+ d->name, d->params[i].type, i + 1);
+ return 0; /* don't bother continuing--something's bizarre */
+ }
+ }
+ break; /* because we found a match */
+ }
+
+ if (n == lenof(directives)) {
+ error(linenum, "Unknown directive `%s'", dir);
+ return 1; /* don't cause abort */
+ }
+
+ return retval;
+}
+
+/*************************************************************************/
+
+#define CHECK(v) do { \
+ if (!v) { \
+ error(0, #v " missing"); \
+ retval = 0; \
+ } \
+} while (0)
+
+#define CHEK2(v,n) do { \
+ if (!v) { \
+ error(0, #n " missing"); \
+ retval = 0; \
+ } \
+} while (0)
+
+/* Read the entire configuration file. If an error occurs while reading
+ * the file or a required directive is not found, print and log an
+ * appropriate error message and return 0; otherwise, return 1.
+ *
+ * If reload is 1, will reload the configuration file.
+ * --lara
+ *
+ */
+
+int read_config(int reload)
+{
+ FILE *config;
+ int linenum = 0, retval = 1;
+ char buf[1024], *s;
+ int defconCount = 0;
+
+ if (reload) {
+ int i, n;
+
+ /* Reset all the reloadable settings */
+
+ for (n = 0; n < lenof(directives); n++) {
+ Directive *d = &directives[n];
+
+ for (i = 0; i < MAXPARAMS && d->params[i].type != PARAM_NONE;
+ i++) {
+ if (!(d->params[i].flags & PARAM_RELOAD))
+ continue;
+
+ if (d->params[i].type == PARAM_SET
+ || d->params[i].type == PARAM_INT
+ || d->params[i].type == PARAM_POSINT
+ || d->params[i].type == PARAM_TIME) {
+ *(int *) d->params[i].ptr = 0;
+ } else if (d->params[i].type == PARAM_STRING) {
+ if (*(char **) d->params[i].ptr)
+ free(*(char **) d->params[i].ptr);
+ (*(char **) d->params[i].ptr) = NULL;
+ }
+ }
+ }
+ }
+
+ config = fopen(SERVICES_CONF, "r");
+ if (!config) {
+#ifndef NOT_MAIN
+ log_perror("Can't open " SERVICES_CONF);
+ if (!nofork && isatty(2)) {
+#endif
+ if (!reload)
+ perror("Can't open " SERVICES_CONF);
+ else
+ alog("Can't open %s", SERVICES_CONF);
+ }
+ return 0;
+ }
+ while (fgets(buf, sizeof(buf), config)) {
+ linenum++;
+ if (*buf == '#' || *buf == '\r' || *buf == '\n')
+ continue;
+ if (!parse(buf, linenum, reload))
+ retval = 0;
+ }
+ fclose(config);
+
+ if (!reload) {
+ CHECK(RemoteServer);
+ CHECK(ServerName);
+ CHECK(ServerDesc);
+ }
+ if (!reload) {
+ if (RemoteServer3)
+ CHECK(RemoteServer2);
+ }
+ if (!reload) {
+ if (LocalHost) {
+ if ((!stricmp(LocalHost, RemoteServer))
+ && LocalPort == RemotePort) {
+ printf
+ ("\n*** LocalAddress and RemoteServer are set to use the same IP address\n"
+ "*** (%s) and port (%d). This would have resulted in errors.\n"
+ "*** Change the LocalAddress to bind to another port.\n",
+ RemoteServer, LocalPort);
+ retval = 0;
+ }
+ }
+ }
+
+
+ CHECK(NetworkName);
+ if (!reload) {
+ CHEK2(temp_userhost, ServiceUser);
+ CHEK2(s_NickServ, NickServName);
+ CHEK2(s_ChanServ, ChanServName);
+ CHEK2(s_MemoServ, MemoServName);
+ CHEK2(s_HelpServ, HelpServName);
+ CHEK2(s_OperServ, OperServName);
+ CHEK2(s_GlobalNoticer, GlobalName);
+ CHEK2(PIDFilename, PIDFile);
+ }
+ CHEK2(MOTDFilename, MOTDFile);
+ if (!reload) {
+ CHEK2(NickDBName, NickServDB);
+ CHEK2(ChanDBName, ChanServDB);
+ CHEK2(OperDBName, OperServDB);
+ CHEK2(NewsDBName, NewsDB);
+ CHEK2(ExceptionDBName, ExceptionDB);
+ }
+ CHECK(UpdateTimeout);
+ CHECK(ExpireTimeout);
+ CHECK(ReadTimeout);
+ CHECK(WarningTimeout);
+ CHECK(TimeoutCheck);
+ CHECK(NSAccessMax);
+ CHEK2(temp_nsuserhost, NSEnforcerUser);
+ CHECK(NSReleaseTimeout);
+ CHECK(NSListMax);
+ CHECK(CSAccessMax);
+ CHECK(CSAutokickMax);
+ CHECK(CSAutokickReason);
+ CHECK(CSInhabit);
+ CHECK(CSListMax);
+ CHECK(ServicesRoot);
+ CHECK(AutokillExpiry);
+ CHECK(ChankillExpiry);
+ CHECK(SGLineExpiry);
+ CHECK(SQLineExpiry);
+ CHECK(SZLineExpiry);
+
+ if (!reload) {
+
+ if (temp_userhost) {
+ if (!(s = strchr(temp_userhost, '@'))) {
+ error(0, "Missing `@' for ServiceUser");
+ } else {
+ *s++ = 0;
+ ServiceUser = temp_userhost;
+ ServiceHost = s;
+ }
+ }
+
+ }
+
+ if (temp_nsuserhost) {
+ if (!(s = strchr(temp_nsuserhost, '@'))) {
+ NSEnforcerUser = temp_nsuserhost;
+ NSEnforcerHost = ServiceHost;
+ } else {
+ *s++ = 0;
+ NSEnforcerUser = temp_userhost;
+ NSEnforcerHost = s;
+ }
+ }
+
+ if (!NSDefNone &&
+ !NSDefKill &&
+ !NSDefKillQuick &&
+ !NSDefSecure &&
+ !NSDefPrivate &&
+ !NSDefHideEmail &&
+ !NSDefHideUsermask &&
+ !NSDefHideQuit && !NSDefMemoSignon && !NSDefMemoReceive) {
+ NSDefSecure = 1;
+ NSDefMemoSignon = 1;
+ NSDefMemoReceive = 1;
+ }
+
+ NSDefFlags = 0;
+ if (!NSDefNone) {
+ if (NSDefKill)
+ NSDefFlags |= NI_KILLPROTECT;
+ if (NSDefKillQuick)
+ NSDefFlags |= NI_KILL_QUICK;
+ if (NSDefSecure)
+ NSDefFlags |= NI_SECURE;
+ if (NSDefPrivate)
+ NSDefFlags |= NI_PRIVATE;
+ if (NSDefMsg)
+ NSDefFlags |= NI_MSG;
+ if (NSDefHideEmail)
+ NSDefFlags |= NI_HIDE_EMAIL;
+ if (NSDefHideUsermask)
+ NSDefFlags |= NI_HIDE_MASK;
+ if (NSDefHideQuit)
+ NSDefFlags |= NI_HIDE_QUIT;
+ if (NSDefMemoSignon)
+ NSDefFlags |= NI_MEMO_SIGNON;
+ if (NSDefMemoReceive)
+ NSDefFlags |= NI_MEMO_RECEIVE;
+ }
+
+ if (!ServicesRoot) {
+ error(0,
+ "You must define the 'ServicesRoot' configuration directive");
+ error(0,
+ "in your services.conf file. This is a required setting that");
+ error(0,
+ "defines the main Administrative nick(s) Anope will obey.");
+ retval = 0;
+ }
+
+ CHECK(NSGuestNickPrefix); /* Add safety check */
+ if (NSGuestNickPrefix && (strlen(NSGuestNickPrefix) > 21)) {
+ error(0, "Value of NSGuestNickPrefix must be between 1 and 21");
+ retval = 0;
+ }
+
+ CHECK(NSDefLanguage);
+ if (NSDefLanguage) {
+ NSDefLanguage--;
+ if (NSDefLanguage < 0 || NSDefLanguage >= NUM_LANGS) {
+ error(0, "Value of NSDefLanguage must be between 1 and %d",
+ USED_LANGS);
+ retval = 0;
+ }
+ }
+
+ if (reload) {
+ if ((NSDefLanguage = langlist[NSDefLanguage]) < 0)
+ NSDefLanguage = DEF_LANGUAGE;
+ }
+
+ if (CSDefBantype < 0 || CSDefBantype > 3) {
+ error(0, "Value of CSDefBantype must be between 0 and 3 included");
+ retval = 0;
+ }
+
+ if (!MysqlRetries || !MysqlRetryGap) {
+ MysqlRetries = 5;
+ MysqlRetryGap = 1;
+ } else if (((MysqlRetries * MysqlRetryGap) > 60)
+ || ((MysqlRetries * MysqlRetryGap) < 1)) {
+ error(0,
+ "MysqlRetries * MysqlRetryGap must be between 1 and 60, using standard values.");
+ MysqlRetries = 5;
+ MysqlRetryGap = 1;
+ }
+
+ if (!CSDefNone &&
+ !CSDefKeepTopic &&
+ !CSDefTopicLock &&
+ !CSDefPrivate &&
+ !CSDefRestricted &&
+ !CSDefSecure &&
+ !CSDefSecureOps &&
+ !CSDefSecureFounder &&
+ !CSDefSignKick && !CSDefSignKickLevel && !CSDefOpNotice) {
+ CSDefKeepTopic = 1;
+ CSDefSecure = 1;
+ CSDefSecureFounder = 1;
+ CSDefSignKick = 1;
+ }
+
+ CSDefFlags = 0;
+ if (!CSDefNone) {
+ if (CSDefKeepTopic)
+ CSDefFlags |= CI_KEEPTOPIC;
+ if (CSDefTopicLock)
+ CSDefFlags |= CI_TOPICLOCK;
+ if (CSDefPrivate)
+ CSDefFlags |= CI_PRIVATE;
+ if (CSDefRestricted)
+ CSDefFlags |= CI_RESTRICTED;
+ if (CSDefSecure)
+ CSDefFlags |= CI_SECURE;
+ if (CSDefSecureOps)
+ CSDefFlags |= CI_SECUREOPS;
+ if (CSDefSecureFounder)
+ CSDefFlags |= CI_SECUREFOUNDER;
+ if (CSDefSignKick)
+ CSDefFlags |= CI_SIGNKICK;
+ if (CSDefSignKickLevel)
+ CSDefFlags |= CI_SIGNKICK_LEVEL;
+ if (CSDefOpNotice)
+ CSDefFlags |= CI_OPNOTICE;
+ if (CSDefXOP)
+ CSDefFlags |= CI_XOP;
+ if (CSDefPeace)
+ CSDefFlags |= CI_PEACE;
+ }
+
+ BSDefFlags = 0;
+ if (BSDefDontKickOps)
+ BSDefFlags |= BS_DONTKICKOPS;
+ if (BSDefDontKickVoices)
+ BSDefFlags |= BS_DONTKICKVOICES;
+ if (BSDefGreet)
+ BSDefFlags |= BS_GREET;
+ if (BSDefFantasy)
+ BSDefFlags |= BS_FANTASY;
+ if (BSDefSymbiosis)
+ BSDefFlags |= BS_SYMBIOSIS;
+
+ /* Services Root building */
+
+ if (ServicesRoot && !reload) { /* Check to prevent segmentation fault if it's missing */
+ RootNumber = 0;
+
+ s = strtok(ServicesRoot, " ");
+ do {
+ RootNumber++;
+ ServicesRoots =
+ realloc(ServicesRoots, sizeof(char *) * RootNumber);
+ ServicesRoots[RootNumber - 1] = strdup(s);
+ } while ((s = strtok(NULL, " ")));
+ }
+
+ /* Host Setters building... :P */
+ HostNumber = 0; /* always zero it, even if we have no setters */
+ if (HostSetter) {
+ s = strtok(HostSetter, " ");
+ do {
+ if (s) {
+ HostNumber++;
+ HostSetters =
+ realloc(HostSetters, sizeof(char *) * HostNumber);
+ HostSetters[HostNumber - 1] = strdup(s);
+ }
+ } while ((s = strtok(NULL, " ")));
+ }
+
+ /* Modules Autoload building... :P */
+ ModulesNumber = 0; /* always zero it, even if we have no setters */
+ if (Modules) {
+ s = strtok(Modules, " ");
+ do {
+ if (s) {
+ ModulesNumber++;
+ ModulesAutoload =
+ realloc(ModulesAutoload,
+ sizeof(char *) * ModulesNumber);
+ ModulesAutoload[ModulesNumber - 1] = strdup(s);
+ }
+ } while ((s = strtok(NULL, " ")));
+ }
+
+ ModulesDelayedNumber = 0; /* always zero it, even if we have no setters */
+ if (ModulesDelayed) {
+ s = strtok(ModulesDelayed, " ");
+ do {
+ if (s) {
+ ModulesDelayedNumber++;
+ ModulesDelayedAutoload =
+ realloc(ModulesDelayedAutoload,
+ sizeof(char *) * ModulesDelayedNumber);
+ ModulesDelayedAutoload[ModulesDelayedNumber - 1] =
+ strdup(s);
+ }
+ } while ((s = strtok(NULL, " ")));
+ }
+
+ if (LimitSessions) {
+ CHECK(DefSessionLimit);
+ CHECK(MaxSessionLimit);
+ CHECK(ExceptionExpiry);
+
+ if (MaxSessionKill && !SessionAutoKillExpiry)
+ SessionAutoKillExpiry = 30 * 60; /* 30 minutes */
+
+ if (!reload && CheckClones) {
+ printf
+ ("Warning: You have enabled both session limiting (config "
+ "option: LimitSessions)\nand clone detection (config option: "
+ "CheckClones). These two features do not\nfunction correctly "
+ "when running together. Session limiting is preferred.\n\n");
+#ifndef NOT_MAIN
+ alog("*** Warning: Both LimitSessions and CheckClones are enabled " "- this is bad! Check your config.");
+#endif
+ }
+ }
+
+ if (s_BotServ) {
+ CHEK2(BotDBName, BotServDB);
+ CHECK(BSBadWordsMax);
+ CHECK(BSMinUsers);
+ CHECK(BSKeepData);
+ }
+
+ if (s_HostServ) {
+ CHEK2(s_HostServ, HostServName);
+ CHEK2(HostDBName, HostServDB);
+ }
+
+ if (UseMail) {
+ CHECK(SendMailPath);
+ CHECK(SendFrom);
+ }
+
+ if (ProxyDetect) {
+ CHECK(ProxyThreads);
+ CHECK(ProxyTimeout);
+ CHECK(ProxyTestServer);
+ CHECK(ProxyCacheExpire);
+ CHECK(ProxyAkillReason);
+ CHECK(ProxyMax);
+ }
+
+ if (GlobalOnCycle) {
+ if (!GlobalOnCycleMessage && !GlobalOnCycleUP) {
+ alog("GlobalOnCycleMessage and GlobalOnCycleUP are not defined disabling GlobalOnCycle");
+ GlobalOnCycle = 0;
+ }
+ }
+
+ /**
+ * Check all DEFCON dependiencies...
+ **/
+ if (DefConLevel) {
+ CHECK(DefCon1);
+ CHECK(DefCon2);
+ CHECK(DefCon3);
+ CHECK(DefCon4);
+ DefCon5 = 0; /* ALWAYS have defcon 5 as normal operation */
+ /* Build DefCon's */
+ DefCon[0] = 0;
+ DefCon[1] = DefCon1;
+ DefCon[2] = DefCon2;
+ DefCon[3] = DefCon3;
+ DefCon[4] = DefCon4;
+ DefCon[5] = DefCon5;
+ for (defconCount = 1; defconCount <= 5; defconCount++) { /* Check any defcon needed settings */
+ if (DefCon[defconCount] & DEFCON_REDUCE_SESSION) {
+ CHECK(DefConSessionLimit);
+ }
+ if (DefCon[defconCount] & DEFCON_AKILL_NEW_CLIENTS) {
+ CHECK(DefConAKILL);
+ CHECK(DefConAkillReason);
+ }
+ if (DefCon[defconCount] & DEFCON_FORCE_CHAN_MODES) {
+ CHECK(DefConChanModes);
+ }
+ }
+ if (GlobalOnDefconMore)
+ CHECK(DefconMessage);
+ }
+
+ /**
+ * If they try to enable any email registration option,
+ * make sure they have everything else they need too...
+ *
+ * rob
+ **/
+ if (NSEmailReg) {
+ CHEK2(PreNickDBName, PreNickServDB);
+ CHECK(NSEmailReg);
+ CHECK(NSRExpire);
+ CHECK(UseMail);
+ CHECK(NSForceEmail);
+ } else {
+ PreNickDBName = NULL;
+ NSRExpire = 0;
+ }
+
+ /* Network Domain building */
+ DomainNumber = 0;
+ if (NetworkDomain) {
+ s = strtok(NetworkDomain, " ");
+ if (s) {
+ do {
+ DomainNumber++;
+ NetworkDomains =
+ realloc(NetworkDomains, sizeof(char *) * DomainNumber);
+ NetworkDomains[DomainNumber - 1] = strdup(s);
+ } while ((s = strtok(NULL, " ")));
+ }
+ }
+
+ if (!retval) {
+ printf
+ ("\n*** Support resources: Read through the services.conf self-contained \n*** documentation. Read the documentation files found in the 'docs' \n*** folder. Visit our portal located at http://www.anope.org/. Join \n*** our support channel on /server irc.anope.org channel #anope.\n\n");
+ }
+
+ return retval;
+}
+
+/*************************************************************************/
diff --git a/src/converter.c b/src/converter.c
new file mode 100644
index 000000000..1db434a6c
--- /dev/null
+++ b/src/converter.c
@@ -0,0 +1,398 @@
+/* Database converters.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "datafiles.h"
+
+/* Each converter will try to convert the databases to the format used
+ * by ircservices-4.3 (that is compatible with Epona).
+ */
+
+#ifdef USE_CONVERTER
+
+/*************************************************************************/
+
+#define IS43_VERSION 7
+
+#define SAFER(x) do { \
+ if ((x) < 0) { \
+ fatal("Read error on %s", rdb); \
+ return 0; \
+ } \
+} while (0)
+
+#define SAFEW(x) do { \
+ if ((x) < 0) { \
+ fatal("Write error on %s", wdb); \
+ return 0; \
+ } \
+} while (0)
+
+/*************************************************************************/
+/*****************************ircservices-4.4*****************************/
+/*************************************************************************/
+
+#ifdef IS44_CONVERTER
+
+#define IS44_VERSION 8
+#define IS44_CA_SIZE 13
+
+int convert_ircservices_44(void)
+{
+ dbFILE *f, *g; /* f for reading, g for writing */
+ int c, i, j;
+ char *rdb, *wdb;
+
+ char *tmp;
+ int16 tmp16;
+ int32 tmp32;
+
+ char nick[NICKMAX];
+ char pass[PASSMAX];
+ char chan[CHANMAX];
+
+ /* NickServ database */
+
+ if (rename("nick.db", "nick.db.old") == -1) {
+ fatal("Converter: unable to rename nick.db to nick.db.old: %s",
+ strerror(errno));
+ return 0;
+ }
+
+ if (!(f = open_db(s_NickServ, "nick.db.old", "r", IS44_VERSION))) {
+ fatal("Converter: unable to open nick.db.old in read access: %s",
+ strerror(errno));
+ return 0;
+ }
+
+ if (!(g = open_db(s_NickServ, NickDBName, "w", IS43_VERSION))) {
+ fatal("Converter: unable to open %s in write access: %s",
+ NickDBName, strerror(errno));
+ return 0;
+ }
+
+ rdb = sstrdup("nick.db.old");
+ wdb = sstrdup(NickDBName);
+
+ get_file_version(f);
+
+ for (i = 0; i < 256; i++) {
+ while ((c = getc_db(f)) == 1) {
+ if (c != 1)
+ fatal("Invalid format in %s", wdb);
+
+ SAFEW(write_int8(1, g));
+
+ SAFER(read_buffer(nick, f));
+ SAFEW(write_buffer(nick, g));
+ SAFER(read_buffer(pass, f));
+ SAFEW(write_buffer(pass, g));
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+
+ if (tmp) {
+ if (tmp)
+ free(tmp);
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ } else {
+ int32 flags;
+ int16 memocount, accesscount;
+
+ SAFER(read_int32(&flags, f));
+ tmp32 = (flags & ~0x10000000);
+ SAFEW(write_int32(tmp32, g));
+
+ /* Suspend stuff that we don't convert */
+ if (flags & 0x10000000) {
+ SAFER(read_buffer(nick, f));
+ SAFER(read_string(&tmp, f));
+ if (tmp)
+ free(tmp);
+ SAFER(read_int32(&tmp32, f));
+ SAFER(read_int32(&tmp32, f));
+ }
+
+ SAFER(read_int16(&tmp16, f));
+ accesscount = tmp16;
+ SAFEW(write_int16(tmp16, g));
+
+ if (accesscount) {
+ for (j = 0; j < accesscount; j++) {
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ }
+ }
+
+ SAFER(read_int16(&memocount, f));
+ SAFEW(write_int16(memocount, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+
+ if (memocount) {
+ for (j = 0; j < memocount; j++) {
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_buffer(nick, f));
+ SAFEW(write_buffer(nick, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ }
+ }
+
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ }
+ } /* while (getc_db(f) != 0) */
+ SAFEW(write_int8(0, g));
+ } /* for (i) */
+
+ close_db(f);
+ close_db(g);
+
+ free(rdb);
+ free(wdb);
+
+ /* ChanServ */
+
+ rdb = sstrdup("chan.db.old");
+ wdb = sstrdup(ChanDBName);
+
+ if (rename("chan.db", rdb) == -1) {
+ fatal("Converter: unable to rename chan.db to %s: %s", rdb,
+ strerror(errno));
+ return 0;
+ }
+
+ if (!(f = open_db(s_ChanServ, rdb, "r", IS44_VERSION))) {
+ fatal("Converter: unable to open %s in read access: %s", rdb,
+ strerror(errno));
+ return 0;
+ }
+
+ if (!(g = open_db(s_ChanServ, wdb, "w", IS43_VERSION))) {
+ fatal("Converter: unable to open %s in write access: %s", wdb,
+ strerror(errno));
+ return 0;
+ }
+
+ get_file_version(f);
+
+ for (i = 0; i < 256; i++) {
+ while ((c = getc_db(f)) != 0) {
+ int16 n_levels, accesscount, akickcount, memocount;
+
+ if (c != 1)
+ fatal("Invalid format in %s", wdb);
+
+ SAFEW(write_int8(1, g));
+
+ SAFER(read_buffer(chan, f));
+ SAFEW(write_buffer(chan, g));
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+
+ SAFER(read_buffer(pass, f));
+ SAFEW(write_buffer(pass, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_buffer(nick, f));
+ SAFEW(write_buffer(nick, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+
+ SAFER(read_int16(&n_levels, f));
+ SAFEW(write_int16(n_levels, g));
+
+ for (j = 0; j < n_levels; j++) {
+ SAFER(read_int16(&tmp16, f));
+ if (j < IS44_CA_SIZE)
+ SAFEW(write_int16(tmp16, g));
+ }
+
+ SAFER(read_int16(&accesscount, f));
+ SAFEW(write_int16(accesscount, g));
+
+ if (accesscount) {
+ for (j = 0; j < accesscount; j++) {
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ if (tmp16) {
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ }
+ }
+ }
+
+ SAFER(read_int16(&akickcount, f));
+ SAFEW(write_int16(akickcount, g));
+
+ if (akickcount) {
+ for (j = 0; j < akickcount; j++) {
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ if (tmp16) {
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ SAFER(read_buffer(nick, f));
+ }
+ }
+ }
+
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+
+ SAFER(read_int16(&memocount, f));
+ SAFEW(write_int16(memocount, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+
+ if (memocount) {
+ for (j = 0; j < memocount; j++) {
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_int16(&tmp16, f));
+ SAFEW(write_int16(tmp16, g));
+ SAFER(read_int32(&tmp32, f));
+ SAFEW(write_int32(tmp32, g));
+ SAFER(read_buffer(nick, f));
+ SAFER(write_buffer(nick, g));
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ }
+ }
+
+ SAFER(read_string(&tmp, f));
+ SAFEW(write_string(tmp, g));
+ if (tmp)
+ free(tmp);
+ } /* while (getc_db(f) != 0) */
+ SAFEW(write_int8(0, g));
+ } /* for (i) */
+
+ close_db(f);
+ close_db(g);
+
+ free(rdb);
+ free(wdb);
+
+ return 1;
+}
+
+#endif
+
+/*************************************************************************/
+
+#endif
+
+/*************************************************************************/
diff --git a/src/datafiles.c b/src/datafiles.c
new file mode 100644
index 000000000..5a5512e80
--- /dev/null
+++ b/src/datafiles.c
@@ -0,0 +1,510 @@
+/* Database file handling routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "datafiles.h"
+#include <fcntl.h>
+
+static int curday = 0;
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Return the version number on the file. Return 0 if there is no version
+ * number or the number doesn't make sense (i.e. less than 1 or greater
+ * than FILE_VERSION).
+ */
+
+int 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 NOT_MAIN
+ log_perror("Error reading version number on %s", f->filename);
+#endif
+ return 0;
+ } else if (feof(fp)) {
+#ifndef NOT_MAIN
+ alog("Error reading version number on %s: End of file detected",
+ f->filename);
+#endif
+ return 0;
+ } else if (version < 1) {
+#ifndef NOT_MAIN
+ alog("Invalid version number (%d) on %s", version, f->filename);
+#endif
+ return 0;
+ }
+ return version;
+}
+
+/*************************************************************************/
+
+/* Write the current version number to the file. Return 0 on error, 1 on
+ * success.
+ */
+
+int write_file_version(dbFILE * f, uint32 version)
+{
+ FILE *fp = f->fp;
+ if (fputc(version >> 24 & 0xFF, fp) < 0 ||
+ fputc(version >> 16 & 0xFF, fp) < 0 ||
+ fputc(version >> 8 & 0xFF, fp) < 0 ||
+ fputc(version & 0xFF, fp) < 0) {
+#ifndef NOT_MAIN
+ log_perror("Error writing version number on %s", f->filename);
+#endif
+ return 0;
+ }
+ return 1;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+static dbFILE *open_db_read(const char *service, const char *filename)
+{
+ dbFILE *f;
+ FILE *fp;
+
+ f = scalloc(sizeof(*f), 1);
+ if (!f) {
+#ifndef NOT_MAIN
+ log_perror("Can't read %s database %s", service, filename);
+#endif
+ return NULL;
+ }
+ strscpy(f->filename, filename, sizeof(f->filename));
+ f->mode = 'r';
+ fp = fopen(f->filename, "rb");
+ if (!fp) {
+ int errno_save = errno;
+#ifndef NOT_MAIN
+ if (errno != ENOENT)
+ log_perror("Can't read %s database %s", service, f->filename);
+#endif
+ free(f);
+ errno = errno_save;
+ return NULL;
+ }
+ f->fp = fp;
+ f->backupfp = NULL;
+ return f;
+}
+
+/*************************************************************************/
+
+static dbFILE *open_db_write(const char *service, const char *filename,
+ uint32 version)
+{
+ dbFILE *f;
+ int fd;
+
+ f = scalloc(sizeof(*f), 1);
+ if (!f) {
+#ifndef NOT_MAIN
+ log_perror("Can't read %s database %s", service, filename);
+#endif
+ return NULL;
+ }
+ strscpy(f->filename, filename, sizeof(f->filename));
+ filename = f->filename;
+ f->mode = 'w';
+
+ *f->backupname = 0;
+ snprintf(f->backupname, sizeof(f->backupname), "%s.save", filename);
+ if (!*f->backupname || strcmp(f->backupname, filename) == 0) {
+ int errno_save = errno;
+#ifndef NOT_MAIN
+ alog("Opening %s database %s for write: Filename too long",
+ service, filename);
+#endif
+ free(f);
+ errno = errno_save;
+ return NULL;
+ }
+ unlink(f->backupname);
+ f->backupfp = fopen(filename, "rb");
+ if (rename(filename, f->backupname) < 0 && errno != ENOENT) {
+ int errno_save = errno;
+#ifndef NOT_MAIN
+ static int walloped = 0;
+ if (!walloped) {
+ walloped++;
+ wallops(NULL, "Can't back up %s database %s", service,
+ filename);
+ }
+ errno = errno_save;
+ log_perror("Can't back up %s database %s", service, filename);
+ if (!NoBackupOkay) {
+#endif
+ if (f->backupfp)
+ fclose(f->backupfp);
+ free(f);
+ errno = errno_save;
+ return NULL;
+#ifndef NOT_MAIN
+ }
+#endif
+ *f->backupname = 0;
+ }
+ unlink(filename);
+ /* Use open() to avoid people sneaking a new file in under us */
+ fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ f->fp = fdopen(fd, "wb"); /* will fail and return NULL if fd < 0 */
+ if (!f->fp || !write_file_version(f, version)) {
+ int errno_save = errno;
+#ifndef NOT_MAIN
+ static int walloped = 0;
+ if (!walloped) {
+ walloped++;
+ wallops(NULL, "Can't write to %s database %s", service,
+ filename);
+ }
+ errno = errno_save;
+ log_perror("Can't write to %s database %s", service, filename);
+#endif
+ if (f->fp) {
+ fclose(f->fp);
+ unlink(filename);
+ }
+ if (*f->backupname && rename(f->backupname, filename) < 0)
+#ifndef NOT_MAIN
+ log_perror("Cannot restore backup copy of %s", filename);
+#else
+ ;
+#endif
+ /* Then the Lord said unto Moses, thou shalt free what thou hast malloced
+ * -- codemastr */
+ free(f);
+ 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, it
+ * is an error for rename() to return an error (when backing up the original
+ * file) other than ENOENT, if NO_BACKUP_OKAY is not defined; it is an error
+ * if the version number cannot be written to the file; and it is a fatal
+ * error if opening the file for write fails and the backup was successfully
+ * made but cannot be restored.
+ */
+
+dbFILE *open_db(const char *service, const char *filename,
+ const char *mode, uint32 version)
+{
+ if (*mode == 'r') {
+ return open_db_read(service, filename);
+ } else if (*mode == 'w') {
+ return open_db_write(service, filename, version);
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+}
+
+/*************************************************************************/
+
+/* 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 first attempt to restore any backup file before
+ * closing files.
+ */
+
+void restore_db(dbFILE * f)
+{
+ int errno_save = errno;
+
+ if (f->mode == 'w') {
+ int ok = 0; /* Did we manage to restore the old file? */
+ errno = errno_save = 0;
+ if (*f->backupname && strcmp(f->backupname, f->filename) != 0) {
+ if (rename(f->backupname, f->filename) == 0)
+ ok = 1;
+ }
+ if (!ok && f->backupfp) {
+ char buf[1024];
+ int i;
+ ok = 1;
+ if (fseek(f->fp, 0, SEEK_SET) < 0)
+ ok = 0;
+ while (ok && (i = fread(buf, 1, sizeof(buf), f->backupfp)) > 0) {
+ if (fwrite(buf, 1, i, f->fp) != i)
+ ok = 0;
+ }
+ if (ok) {
+ fflush(f->fp);
+ ftruncate(fileno(f->fp), ftell(f->fp));
+ }
+ }
+#ifndef NOT_MAIN
+ if (!ok && errno > 0)
+ log_perror("Unable to restore backup of %s", f->filename);
+#endif
+ errno_save = errno;
+ if (f->backupfp)
+ fclose(f->backupfp);
+ if (*f->backupname)
+ unlink(f->backupname);
+ }
+ fclose(f->fp);
+ if (!errno_save)
+ errno_save = errno;
+ free(f);
+ errno = errno_save;
+}
+
+/*************************************************************************/
+
+/* Close a database file. If the file was opened for write, remove the
+ * backup we (may have) created earlier.
+ */
+
+void close_db(dbFILE * f)
+{
+ if (f->mode == 'w' && *f->backupname
+ && strcmp(f->backupname, f->filename) != 0) {
+ if (f->backupfp)
+ fclose(f->backupfp);
+ unlink(f->backupname);
+ }
+ fclose(f->fp);
+ free(f);
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* 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_int16(uint16 * ret, dbFILE * f)
+{
+ int c1, c2;
+
+ c1 = fgetc(f->fp);
+ c2 = fgetc(f->fp);
+ if (c1 == EOF || c2 == EOF)
+ return -1;
+ *ret = c1 << 8 | c2;
+ return 0;
+}
+
+int write_int16(uint16 val, dbFILE * f)
+{
+ if (fputc((val >> 8) & 0xFF, f->fp) == EOF
+ || fputc(val & 0xFF, f->fp) == EOF)
+ return -1;
+ return 0;
+}
+
+
+int read_int32(uint32 * 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 (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF)
+ return -1;
+ *ret = c1 << 24 | c2 << 16 | c3 << 8 | c4;
+ return 0;
+}
+
+int write_int32(uint32 val, dbFILE * f)
+{
+ if (fputc((val >> 24) & 0xFF, f->fp) == EOF)
+ return -1;
+ if (fputc((val >> 16) & 0xFF, f->fp) == EOF)
+ return -1;
+ if (fputc((val >> 8) & 0xFF, f->fp) == EOF)
+ return -1;
+ if (fputc((val) & 0xFF, f->fp) == EOF)
+ 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_int16(&len, f) < 0)
+ return -1;
+ if (len == 0) {
+ *ret = NULL;
+ return 0;
+ }
+ s = scalloc(len, 1);
+ 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 (len > 0 && fwrite(s, 1, len, f->fp) != len)
+ return -1;
+ if (fputc(0, f->fp) == EOF)
+ return -1;
+ return 0;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Renames a database */
+
+static void rename_database(char *name, char *ext)
+{
+
+ char destpath[PATH_MAX];
+
+ snprintf(destpath, sizeof(destpath), "backups/%s.%s", name, ext);
+ if (rename(name, destpath) != 0) {
+ alog("Backup of %s failed.", name);
+ wallops(s_OperServ, "WARNING! Backup of %s failed.", name);
+ }
+}
+
+/*************************************************************************/
+
+/* Removes old databases */
+
+static void remove_backups(void)
+{
+
+ char ext[9];
+ char path[PATH_MAX];
+
+ time_t t;
+ struct tm tm;
+
+ time(&t);
+ t -= (60 * 60 * 24 * KeepBackups);
+ tm = *localtime(&t);
+ strftime(ext, sizeof(ext), "%Y%m%d", &tm);
+
+ snprintf(path, sizeof(path), "backups/%s.%s", NickDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", BotDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", ChanDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", OperDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", NewsDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", ExceptionDBName, ext);
+ unlink(path);
+ snprintf(path, sizeof(path), "backups/%s.%s", HostDBName, ext);
+ unlink(path);
+}
+
+/*************************************************************************/
+
+/* Handles database backups. */
+
+void backup_databases(void)
+{
+
+ time_t t;
+ struct tm tm;
+
+ if (!KeepBackups)
+ return;
+
+ time(&t);
+ tm = *localtime(&t);
+
+ if (!curday) {
+ curday = tm.tm_yday;
+ return;
+ }
+
+ if (curday != tm.tm_yday) {
+
+ char ext[9];
+
+ alog("Backing up databases");
+
+ remove_backups();
+
+ curday = tm.tm_yday;
+ strftime(ext, sizeof(ext), "%Y%m%d", &tm);
+
+ if (!skeleton) {
+ rename_database(NickDBName, ext);
+ if (s_BotServ)
+ rename_database(BotDBName, ext);
+ rename_database(ChanDBName, ext);
+ if (s_HostServ)
+ rename_database(HostDBName, ext);
+ }
+
+ rename_database(OperDBName, ext);
+ rename_database(NewsDBName, ext);
+ rename_database(ExceptionDBName, ext);
+ }
+}
diff --git a/src/encrypt.c b/src/encrypt.c
new file mode 100644
index 000000000..39d6c6588
--- /dev/null
+++ b/src/encrypt.c
@@ -0,0 +1,434 @@
+/* Include file for high-level encryption routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "encrypt.h"
+
+#ifdef USE_ENCRYPTION
+
+/*************************************************************************/
+
+/******** Code specific to the type of encryption. ********/
+
+#ifdef /********/ ENCRYPT_MD5 /********/
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#include <string.h>
+
+typedef unsigned int UINT4;
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+typedef void *POINTER;
+
+/* Constants for MD5Transform routine.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform(UINT4[4], unsigned char[64]);
+static void Encode(unsigned char *, UINT4 *, unsigned int);
+static void Decode(UINT4 *, unsigned char *, unsigned int);
+
+static unsigned char PADDING[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
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+static void MD5Init(context)
+MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.
+ */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+static void MD5Update(context, input, inputLen)
+MD5_CTX *context; /* context */
+unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int) ((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4) inputLen << 3))
+ < ((UINT4) inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4) inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.
+ */
+ if (inputLen >= partLen) {
+ memcpy
+ ((POINTER) & context->buffer[index], (POINTER) input, partLen);
+ MD5Transform(context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform(context->state, &input[i]);
+
+ index = 0;
+ } else
+ i = 0;
+
+ /* Buffer remaining input */
+ memcpy
+ ((POINTER) & context->buffer[index], (POINTER) & input[i],
+ inputLen - i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+static void MD5Final(digest, context)
+unsigned char digest[16]; /* message digest */
+MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+ */
+ index = (unsigned int) ((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ MD5Update(context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update(context, bits, 8);
+ /* Store state in digest */
+ Encode(digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+ */
+ memset((POINTER) context, 0, sizeof(*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform(state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode(x, block, 64);
+
+ /* Round 1 */
+ FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+ */
+ memset((POINTER) x, 0, sizeof(x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode(output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char) (input[i] & 0xff);
+ output[j + 1] = (unsigned char) ((input[i] >> 8) & 0xff);
+ output[j + 2] = (unsigned char) ((input[i] >> 16) & 0xff);
+ output[j + 3] = (unsigned char) ((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4.
+ */
+static void Decode(output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4) input[j]) | (((UINT4) input[j + 1]) << 8) |
+ (((UINT4) input[j + 2]) << 16) | (((UINT4) input[j + 3]) <<
+ 24);
+}
+
+#endif /******** ENCRYPT_MD5 ********/
+
+/*************************************************************************/
+
+/******** Our own high-level routines. ********/
+
+
+#define XTOI(c) ((c)>9 ? (c)-'A'+10 : (c)-'0')
+
+/* Encrypt `src' of length `len' and store the result in `dest'. If the
+ * resulting string would be longer than `size', return -1 and leave `dest'
+ * unchanged; else return 0.
+ */
+int encrypt(const char *src, int len, char *dest, int size)
+{
+
+#ifdef ENCRYPT_MD5
+
+ MD5_CTX context;
+ char digest[33];
+ int i;
+
+ if (size < 16)
+ return -1;
+
+ memset(&context, 0, sizeof(context));
+ memset(&digest, 0, sizeof(digest));
+
+ MD5Init(&context);
+ MD5Update(&context, src, len);
+ MD5Final(digest, &context);
+ for (i = 0; i < 32; i += 2)
+ dest[i / 2] = XTOI(digest[i]) << 4 | XTOI(digest[i + 1]);
+ return 0;
+
+#endif
+
+ return -1; /* unknown encryption algorithm */
+
+}
+
+
+/* Shortcut for encrypting a null-terminated string in place. */
+int encrypt_in_place(char *buf, int size)
+{
+ return encrypt(buf, strlen(buf), buf, size);
+}
+
+
+/* Compare a plaintext string against an encrypted password. Return 1 if
+ * they match, 0 if not, and -1 if something went wrong. */
+
+int check_password(const char *plaintext, const char *password)
+{
+ char buf[BUFSIZE];
+
+ if (encrypt(plaintext, strlen(plaintext), buf, sizeof(buf)) < 0)
+ return -1;
+#ifdef ENCRYPT_MD5
+ if (memcmp(buf, password, 16) == 0)
+#else
+ if (0)
+#endif
+ return 1;
+ else
+ return 0;
+}
+
+/*************************************************************************/
+
+#else /* !USE_ENCRYPTION */
+
+int encrypt(const char *src, int len, char *dest, int size)
+{
+ if (size < len)
+ return -1;
+ memcpy(dest, src, len);
+ return 0;
+}
+
+int encrypt_in_place(char *buf, int size)
+{
+ return 0;
+}
+
+int check_password(const char *plaintext, const char *password)
+{
+ if (strcmp(plaintext, password) == 0)
+ return 1;
+ else
+ return 0;
+}
+
+#endif /* USE_ENCRYPTION */
+
+/*************************************************************************/
diff --git a/src/helpserv.c b/src/helpserv.c
new file mode 100644
index 000000000..dfded27fc
--- /dev/null
+++ b/src/helpserv.c
@@ -0,0 +1,83 @@
+/* HelpServ functions
+ *
+ * (C) 2003 Anope Team / GeniusDex
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+/*************************************************************************/
+#include "services.h"
+#include "pseudo.h"
+
+#define HELP_VERSION 1
+
+void helpserv_init(void);
+static int do_help(User * u);
+void moduleAddHelpServCmds(void);
+
+/*************************************************************************/
+void moduleAddHelpServCmds(void)
+{
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1, -1, -1, -1);
+ addCoreCommand(HELPSERV, c);
+}
+
+/*************************************************************************/
+
+/*************************************************************************/
+/* HelpServ initialization. */
+void helpserv_init(void)
+{
+ moduleAddHelpServCmds();
+}
+
+/*************************************************************************/
+/* Main HelpServ routine. */
+void helpserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_HelpServ, u->nick, "\1PING %s", s);
+ } else {
+ mod_run_cmd(s_HelpServ, u, HELPSERV, cmd);
+ }
+}
+
+/*************************************************************************/
+/* Display the HelpServ help. */
+/* This core function has been embed in the source for a long time, but */
+/* it moved into it's own file so we now all can enjoy the joy of */
+/* modules for HelpServ. */
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_HelpServ, u, HELP_HELP, s_NickServ, s_ChanServ,
+ s_MemoServ);
+ if (s_BotServ)
+ notice_help(s_HelpServ, u, HELP_HELP_BOT, s_BotServ);
+ if (s_HostServ)
+ notice_help(s_HelpServ, u, HELP_HELP_HOST, s_HostServ);
+ moduleDisplayHelp(7, u);
+ } else {
+ mod_help_cmd(s_HelpServ, u, HELPSERV, cmd);
+ }
+ return MOD_CONT;
+}
diff --git a/src/hostserv.c b/src/hostserv.c
new file mode 100644
index 000000000..7c2945683
--- /dev/null
+++ b/src/hostserv.c
@@ -0,0 +1,1107 @@
+/* HostServ functions
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+/*************************************************************************/
+#include "services.h"
+#include "pseudo.h"
+
+#define HOST_VERSION 3
+#define HASH(nick) ((tolower((nick)[0])&31)<<5 | (tolower((nick)[1])&31))
+
+void load_hs_dbase_v1(dbFILE * f);
+void load_hs_dbase_v2(dbFILE * f);
+void load_hs_dbase_v3(dbFILE * f);
+
+HostCore *head = NULL; /* head of the HostCore list */
+HostCore *createHostCorelist(HostCore * next, char *nick, char *vIdent,
+ char *vHost, char *creator, int32 tmp_time);
+HostCore *findHostCore(HostCore * head, char *nick, boolean * found);
+HostCore *insertHostCore(HostCore * head, HostCore * prev, char *nick,
+ char *vIdent, char *vHost, char *creator,
+ int32 tmp_time);
+HostCore *deleteHostCore(HostCore * head, HostCore * prev);
+void delHostCore(char *nick);
+
+static void send_on(char *nick, char *vIdent, char *vhost);
+static void send_off(User * u);
+
+char *getvIdent(char *nick);
+char *getvHost(char *nick);
+
+int is_host_setter(User * u);
+int is_host_remover(User * u);
+
+static int do_help(User * u);
+static int do_set(User * u);
+static int do_on(User * u);
+int do_on_id(User * u);
+static void set_lastmask(User * u);
+static int do_off(User * u);
+static int do_del(User * u);
+static int do_group(User * u);
+static int listOut(User * u);
+int do_hs_sync(NickCore * nc, char *vIdent, char *hostmask, char *creator,
+ time_t time);
+int do_setall(User * u);
+int do_delall(User * u);
+void moduleAddHostServCmds(void);
+/*************************************************************************/
+void moduleAddHostServCmds(void)
+{
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("SET", do_set, is_host_setter, HOST_HELP_SET, -1, -1,
+ -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("GROUP", do_group, NULL, HOST_HELP_GROUP, -1, -1, -1,
+ -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("SETALL", do_setall, is_host_setter,
+ HOST_HELP_SETALL, -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("DELALL", do_delall, is_host_remover,
+ HOST_HELP_DELALL, -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("ON", do_on, NULL, HOST_HELP_ON, -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("OFF", do_off, NULL, HOST_HELP_OFF, -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("DEL", do_del, is_host_remover, HOST_HELP_DEL, -1,
+ -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+ c = createCommand("LIST", listOut, is_services_oper, HOST_HELP_LIST,
+ -1, -1, -1, -1);
+ addCoreCommand(HOSTSERV, c);
+}
+
+/*************************************************************************/
+
+/*************************************************************************/
+/* HostServ initialization. */
+void hostserv_init(void)
+{
+ moduleAddHostServCmds();
+}
+
+/*************************************************************************/
+/* Main HostServ routine. */
+void hostserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_HostServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_HostServ, u, SERVICE_OFFLINE, s_HostServ);
+ } else {
+#ifdef HAS_VHOST
+ mod_run_cmd(s_HostServ, u, HOSTSERV, cmd);
+#else
+ notice_lang(s_HostServ, u, SERVICE_OFFLINE, s_HostServ);
+#endif
+ }
+}
+
+/*************************************************************************/
+/* Start of Linked List routines */
+/*************************************************************************/
+HostCore *createHostCorelist(HostCore * next, char *nick, char *vIdent,
+ char *vHost, char *creator, int32 tmp_time)
+{
+
+ next = malloc(sizeof(HostCore));
+ if (next == NULL) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to create the vHost LL, problems i sense..");
+ } else {
+ next->nick = malloc(sizeof(char) * strlen(nick) + 1);
+ next->vHost = malloc(sizeof(char) * strlen(vHost) + 1);
+ next->creator = malloc(sizeof(char) * strlen(creator) + 1);
+ if (vIdent)
+ next->vIdent = malloc(sizeof(char) * strlen(vIdent) + 1);
+ if ((next->nick == NULL) || (next->vHost == NULL)
+ || (next->creator == NULL)) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to create the vHost LL, problems i sense..");
+ return NULL;
+ }
+ strcpy(next->nick, nick);
+ strcpy(next->vHost, vHost);
+ strcpy(next->creator, creator);
+ if (vIdent) {
+ if ((next->vIdent == NULL)) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to create the vHost LL, problems i sense..");
+ return NULL;
+ }
+ strcpy(next->vIdent, vIdent);
+ } else {
+ next->vIdent = NULL;
+ }
+ next->time = tmp_time;
+ next->next = NULL;
+ return next;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+/**
+ * Returns either NULL for the head, or the location of the *PREVIOUS*
+ * record, this is where we need to insert etc..
+ *
+ * -rob
+ **/
+HostCore *findHostCore(HostCore * head, char *nick, boolean * found)
+{
+
+ HostCore *previous, *current;
+
+ *found = false;
+ current = head;
+ previous = current;
+
+ while (current != NULL) {
+ if (stricmp(nick, current->nick) == 0) {
+ *found = true;
+ break;
+ } else if (stricmp(nick, current->nick) < 0) {
+ /* we know were not gonna find it now.... */
+ break;
+ } else {
+ previous = current;
+ current = current->next;
+ }
+ }
+ if (current == head) {
+ return NULL;
+ } else {
+ return previous;
+ }
+}
+
+/*************************************************************************/
+HostCore *insertHostCore(HostCore * head, HostCore * prev, char *nick,
+ char *vIdent, char *vHost, char *creator,
+ int32 tmp_time)
+{
+
+ HostCore *newCore, *tmp;
+
+ newCore = malloc(sizeof(HostCore));
+ if (newCore == NULL) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to insert into the vHost LL, problems i sense..");
+ return NULL;
+ } else {
+ newCore->nick = malloc(sizeof(char) * strlen(nick) + 1);
+ newCore->vHost = malloc(sizeof(char) * strlen(vHost) + 1);
+ newCore->creator = malloc(sizeof(char) * strlen(creator) + 1);
+ if (vIdent)
+ newCore->vIdent = malloc(sizeof(char) * strlen(vIdent) + 1);
+ if ((newCore->nick == NULL) || (newCore->vHost == NULL)
+ || (newCore->creator == NULL)) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to create the vHost LL, problems i sense..");
+ return NULL;
+ }
+ strcpy(newCore->nick, nick);
+ strcpy(newCore->vHost, vHost);
+ strcpy(newCore->creator, creator);
+ if (vIdent) {
+ if ((newCore->vIdent == NULL)) {
+ wallops(s_HostServ,
+ "Unable to allocate memory to create the vHost LL, problems i sense..");
+ return NULL;
+ }
+ strcpy(newCore->vIdent, vIdent);
+ } else {
+ newCore->vIdent = NULL;
+ }
+ newCore->time = tmp_time;
+ if (prev == NULL) {
+ tmp = head;
+ head = newCore;
+ newCore->next = tmp;
+ } else {
+ tmp = prev->next;
+ prev->next = newCore;
+ newCore->next = tmp;
+ }
+ }
+ return head;
+}
+
+/*************************************************************************/
+HostCore *deleteHostCore(HostCore * head, HostCore * prev)
+{
+
+ HostCore *tmp;
+
+ if (prev == NULL) {
+ tmp = head;
+ head = head->next;
+ } else {
+ tmp = prev->next;
+ prev->next = tmp->next;
+ }
+ free(tmp->vHost);
+ free(tmp->nick);
+ free(tmp->creator);
+ if (tmp->vIdent) {
+ free(tmp->vIdent);
+ }
+ free(tmp);
+ return head;
+}
+
+/*************************************************************************/
+void addHostCore(char *nick, char *vIdent, char *vhost, char *creator,
+ int32 tmp_time)
+{
+ HostCore *tmp;
+ boolean found = false;
+
+ if (head == NULL) {
+ head =
+ createHostCorelist(head, nick, vIdent, vhost, creator,
+ tmp_time);
+ } else {
+ tmp = findHostCore(head, nick, &found);
+ if (!found) {
+ head =
+ insertHostCore(head, tmp, nick, vIdent, vhost, creator,
+ tmp_time);
+ } else {
+ head = deleteHostCore(head, tmp); /* delete the old entry */
+ addHostCore(nick, vIdent, vhost, creator, tmp_time); /* recursive call to add new entry */
+ }
+ }
+}
+
+/*************************************************************************/
+char *getvHost(char *nick)
+{
+ HostCore *tmp;
+ boolean found = false;
+ tmp = findHostCore(head, nick, &found);
+ if (found) {
+ if (tmp == NULL)
+ return head->vHost;
+ else
+ return tmp->next->vHost;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+char *getvIdent(char *nick)
+{
+ HostCore *tmp;
+ boolean found = false;
+ tmp = findHostCore(head, nick, &found);
+ if (found) {
+ if (tmp == NULL)
+ return head->vIdent;
+ else
+ return tmp->next->vIdent;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+int listOut(User * u)
+{
+ char *key = strtok(NULL, "");
+ struct tm *tm;
+ char buf[BUFSIZE];
+ int counter = 1;
+ int from = 0, to = 0;
+ char *tmp = NULL;
+ char *s = NULL;
+ int display_counter = 0;
+
+ HostCore *current;
+
+ current = head;
+ if (current == NULL)
+ notice_lang(s_HostServ, u, HOST_EMPTY);
+ else {
+ /**
+ * Do a check for a range here, then in the next loop
+ * we'll only display what has been requested..
+ **/
+ if (key) {
+ if (key[0] == '#') {
+ tmp = myStrGetOnlyToken((key + 1), '-', 0); /* Read FROM out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ from = atoi(tmp);
+ tmp = myStrGetTokenRemainder(key, '-', 1); /* Read TO out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ to = atoi(tmp);
+ key = NULL;
+ }
+ }
+
+ while (current != NULL) {
+ if (key) {
+ if (((match_wild_nocase(key, current->nick))
+ || (match_wild_nocase(key, current->vHost)))
+ && (display_counter < NSListMax)) {
+ display_counter++;
+ tm = localtime(&current->time);
+ strftime(buf, sizeof(buf),
+ getstring(NULL,
+ STRFTIME_DATE_TIME_FORMAT), tm);
+ if (current->vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_ENTRY,
+ counter, current->nick,
+ current->vIdent, current->vHost,
+ current->creator, buf);
+ } else {
+ notice_lang(s_HostServ, u, HOST_ENTRY, counter,
+ current->nick, current->vHost,
+ current->creator, buf);
+ }
+ }
+ } else {
+ /**
+ * List the host if its in the display range, and not more
+ * than NSListMax records have been displayed...
+ **/
+ if ((((counter >= from) && (counter <= to))
+ || ((from == 0) && (to == 0)))
+ && (display_counter < NSListMax)) {
+ display_counter++;
+ tm = localtime(&current->time);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT),
+ tm);
+ if (current->vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_ENTRY,
+ counter, current->nick,
+ current->vIdent, current->vHost,
+ current->creator, buf);
+ } else {
+ notice_lang(s_HostServ, u, HOST_ENTRY, counter,
+ current->nick, current->vHost,
+ current->creator, buf);
+ }
+ }
+ }
+ counter++;
+ current = current->next;
+ }
+ if (key) {
+ notice_lang(s_HostServ, u, HOST_LIST_KEY_FOOTER, key,
+ display_counter);
+ } else {
+ if (from != 0) {
+ notice_lang(s_HostServ, u, HOST_LIST_RANGE_FOOTER, from,
+ to);
+ } else {
+ notice_lang(s_HostServ, u, HOST_LIST_FOOTER,
+ display_counter);
+ }
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+void delHostCore(char *nick)
+{
+#ifdef USE_RDB
+ static char clause[128];
+#endif
+ HostCore *tmp;
+ boolean found = false;
+ tmp = findHostCore(head, nick, &found);
+ if (found) {
+ head = deleteHostCore(head, tmp);
+
+#ifdef USE_RDB
+ /* Reflect this change in the database right away. */
+ if (rdb_open()) {
+
+ snprintf(clause, sizeof(clause), "nick='%s'", nick);
+ rdb_scrub_table("anope_hs_core", clause);
+ rdb_close();
+ }
+#endif
+
+ }
+
+}
+
+/*************************************************************************/
+/* End of Linked List routines */
+/*************************************************************************/
+/*************************************************************************/
+/* Start of Load/Save routines */
+/*************************************************************************/
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", HostDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+void load_hs_dbase(void)
+{
+ dbFILE *f;
+ int ver;
+
+ if (!(f = open_db(s_HostServ, HostDBName, "r", HOST_VERSION))) {
+ return;
+ }
+ ver = get_file_version(f);
+
+ if (ver == 1) {
+ load_hs_dbase_v1(f);
+ } else if (ver == 2) {
+ load_hs_dbase_v2(f);
+ } else if (ver == 3) {
+ load_hs_dbase_v3(f);
+ }
+ close_db(f);
+}
+
+void load_hs_dbase_v1(dbFILE * f)
+{
+ int c;
+ int failed = 0;
+ int32 tmp;
+
+ char *nick;
+ char *vHost;
+
+ tmp = time(NULL);
+
+ while (!failed && (c = getc_db(f)) == 1) {
+
+ if (c == 1) {
+ SAFE(read_string(&nick, f));
+ SAFE(read_string(&vHost, f));
+ addHostCore(nick, NULL, vHost, "Unknown", tmp); /* could get a speed increase by not searching the list */
+ free(nick); /* as we know the db is in alphabetical order... */
+ free(vHost);
+ } else {
+ fatal("Invalid format in %s %d", HostDBName, c);
+ }
+ }
+}
+
+void load_hs_dbase_v2(dbFILE * f)
+{
+ int c;
+ int failed = 0;
+
+ char *nick;
+ char *vHost;
+ char *creator;
+ int32 time;
+
+ while (!failed && (c = getc_db(f)) == 1) {
+
+ if (c == 1) {
+ SAFE(read_string(&nick, f));
+ SAFE(read_string(&vHost, f));
+ SAFE(read_string(&creator, f));
+ SAFE(read_int32(&time, f));
+ addHostCore(nick, NULL, vHost, creator, time); /* could get a speed increase by not searching the list */
+ free(nick); /* as we know the db is in alphabetical order... */
+ free(vHost);
+ free(creator);
+ } else {
+ fatal("Invalid format in %s %d", HostDBName, c);
+ }
+ }
+}
+
+void load_hs_dbase_v3(dbFILE * f)
+{
+ int c;
+ int failed = 0;
+
+ char *nick;
+ char *vHost;
+ char *creator;
+ char *vIdent;
+ int32 time;
+
+ while (!failed && (c = getc_db(f)) == 1) {
+ if (c == 1) {
+ SAFE(read_string(&nick, f));
+ SAFE(read_string(&vIdent, f));
+ SAFE(read_string(&vHost, f));
+ SAFE(read_string(&creator, f));
+ SAFE(read_int32(&time, f));
+ addHostCore(nick, vIdent, vHost, creator, time); /* could get a speed increase by not searching the list */
+ free(nick); /* as we know the db is in alphabetical order... */
+ free(vHost);
+ free(creator);
+ free(vIdent);
+ } else {
+ fatal("Invalid format in %s %d", HostDBName, c);
+ }
+ }
+}
+
+#undef SAFE
+/*************************************************************************/
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", HostDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", HostDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_hs_dbase(void)
+{
+ dbFILE *f;
+ static time_t lastwarn = 0;
+ HostCore *current;
+
+ if (!(f = open_db(s_HostServ, HostDBName, "w", HOST_VERSION)))
+ return;
+
+ current = head;
+ while (current != NULL) {
+ SAFE(write_int8(1, f));
+ SAFE(write_string(current->nick, f));
+ SAFE(write_string(current->vIdent, f));
+ SAFE(write_string(current->vHost, f));
+ SAFE(write_string(current->creator, f));
+ SAFE(write_int32(current->time, f));
+ current = current->next;
+ }
+ SAFE(write_int8(0, f));
+ close_db(f);
+
+}
+
+#undef SAFE
+
+void save_hs_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ HostCore *current;
+
+ if (!rdb_open())
+ return;
+
+ rdb_clear_table("anope_hs_core");
+
+ current = head;
+ while (current != NULL) {
+ rdb_save_hs_core(current);
+ current = current->next;
+ }
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+/* End of Load/Save Functions */
+/*************************************************************************/
+/*************************************************************************/
+/* Start of Generic Functions */
+/*************************************************************************/
+int do_setall(User * u)
+{
+
+ char *nick = strtok(NULL, " ");
+ char *rawhostmask = strtok(NULL, " ");
+ char *hostmask = smalloc(HOSTMAX);
+
+ NickAlias *na;
+ int32 tmp_time;
+ char *s;
+
+ char *vIdent = NULL;
+
+ if (!nick || !rawhostmask) {
+ notice_lang(s_HostServ, u, HOST_SETALL_SYNTAX, s_HostServ);
+ return MOD_CONT;
+ }
+
+ vIdent = myStrGetOnlyToken(rawhostmask, '@', 0); /* Get the first substring, @ as delimiter */
+ if (vIdent) {
+ rawhostmask = myStrGetTokenRemainder(rawhostmask, '@', 1); /* get the remaining string */
+ if (!rawhostmask) {
+ notice_lang(s_HostServ, u, HOST_SETALL_SYNTAX, s_HostServ);
+ return MOD_CONT;
+ }
+ if (strlen(vIdent) > USERMAX - 1) {
+ notice_lang(s_HostServ, u, HOST_SET_IDENTTOOLONG, USERMAX);
+ return MOD_CONT;
+ } else {
+ for (s = vIdent; *s; s++) {
+ if (!isvalidchar(*s)) {
+ notice_lang(s_HostServ, u, HOST_SET_IDENT_ERROR);
+ return MOD_CONT;
+ }
+ }
+ }
+#ifndef HAS_VIDENT
+ notice_lang(s_HostServ, u, HOST_NO_VIDENT);
+ return MOD_CONT;
+#endif
+ }
+
+ if (strlen(rawhostmask) < HOSTMAX - 1)
+ snprintf(hostmask, HOSTMAX - 1, "%s", rawhostmask);
+ else {
+ notice_lang(s_HostServ, u, HOST_SET_TOOLONG, HOSTMAX);
+ return MOD_CONT;
+ }
+
+ if (!isValidHost(hostmask, 3)) {
+ notice_lang(s_HostServ, u, HOST_SET_ERROR);
+ return MOD_CONT;
+ }
+
+ tmp_time = time(NULL);
+
+ if ((na = findnick(nick))) {
+ alog("vHost for all nicks in group \002%s\002 set to \002%s\002 by oper \002%s\002", nick, hostmask, u->nick);
+ do_hs_sync(na->nc, vIdent, hostmask, u->nick, tmp_time);
+ if (vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_SETALL, nick, vIdent,
+ hostmask);
+ } else {
+ notice_lang(s_HostServ, u, HOST_SETALL, nick, hostmask);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOREG, nick);
+ }
+
+ free(hostmask);
+ return MOD_CONT;
+}
+
+int do_delall(User * u)
+{
+ int i;
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+ NickCore *nc;
+ if (!nick) {
+ notice_lang(s_HostServ, u, HOST_DELALL_SYNTAX, s_HostServ);
+ return MOD_CONT;
+ }
+ if ((na = findnick(nick))) {
+ nc = na->nc;
+ for (i = 0; i < nc->aliases.count; i++) {
+ na = nc->aliases.list[i];
+ delHostCore(na->nick);
+ }
+ alog("vHosts for all nicks in group \002%s\002 deleted by oper \002%s\002", nc->display, u->nick);
+ notice_lang(s_HostServ, u, HOST_DELALL, nc->display);
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOREG, nick);
+ }
+ return MOD_CONT;
+}
+
+int do_hs_sync(NickCore * nc, char *vIdent, char *hostmask, char *creator,
+ time_t time)
+{
+ int i;
+ NickAlias *na;
+
+ for (i = 0; i < nc->aliases.count; i++) {
+ na = nc->aliases.list[i];
+ addHostCore(na->nick, vIdent, hostmask, creator, time);
+ }
+ return MOD_CONT;
+}
+
+static int do_group(User * u)
+{
+ NickAlias *na;
+ HostCore *tmp;
+ char *vHost = NULL;
+ char *vIdent = NULL;
+ char *creator = NULL;
+ time_t time;
+ boolean found = false;
+
+ if ((na = findnick(u->nick))) {
+ if (na->status & NS_IDENTIFIED) {
+ tmp = findHostCore(head, u->nick, &found);
+ if (found) {
+ if (tmp == NULL) {
+ tmp = head; /* incase first in list */
+ } else if (tmp->next) { /* we dont want the previous entry were not inserting! */
+ tmp = tmp->next; /* jump to the next */
+ }
+
+ vHost = sstrdup(tmp->vHost);
+ if (tmp->vIdent)
+ vIdent = sstrdup(tmp->vIdent);
+ creator = sstrdup(tmp->creator);
+ time = tmp->time;
+
+ do_hs_sync(na->nc, vIdent, vHost, creator, time);
+ if (tmp->vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_GROUP,
+ na->nc->display, vIdent, vHost);
+ } else {
+ notice_lang(s_HostServ, u, HOST_GROUP, na->nc->display,
+ vHost);
+ }
+ free(vHost);
+ if (vIdent)
+ free(vIdent);
+ free(creator);
+
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOT_ASSIGNED);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_ID);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOT_REGED);
+ }
+ return MOD_CONT;
+}
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_HostServ, u, HOST_HELP, s_HostServ);
+ if ((is_services_oper(u)) || (is_host_setter(u)))
+ notice_help(s_HostServ, u, HOST_OPER_HELP);
+ /* Removed as atm, there is no admin only help */
+/* if (is_services_admin(u))
+ notice_help(s_HostServ, u, HOST_ADMIN_HELP);*/
+ moduleDisplayHelp(6, u);
+ } else {
+ mod_help_cmd(s_HostServ, u, HOSTSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+int do_set(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *rawhostmask = strtok(NULL, " ");
+ char *hostmask = smalloc(HOSTMAX);
+
+ NickAlias *na;
+ int32 tmp_time;
+ char *s;
+
+ char *vIdent = NULL;
+
+ if (!nick || !rawhostmask) {
+ notice_lang(s_HostServ, u, HOST_SET_SYNTAX, s_HostServ);
+ return MOD_CONT;
+ }
+
+ vIdent = myStrGetOnlyToken(rawhostmask, '@', 0); /* Get the first substring, @ as delimiter */
+ if (vIdent) {
+ rawhostmask = myStrGetTokenRemainder(rawhostmask, '@', 1); /* get the remaining string */
+ if (!rawhostmask) {
+ notice_lang(s_HostServ, u, HOST_SET_SYNTAX, s_HostServ);
+ return MOD_CONT;
+ }
+ if (strlen(vIdent) > USERMAX - 1) {
+ notice_lang(s_HostServ, u, HOST_SET_IDENTTOOLONG, USERMAX);
+ return MOD_CONT;
+ } else {
+ for (s = vIdent; *s; s++) {
+ if (!isvalidchar(*s)) {
+ notice_lang(s_HostServ, u, HOST_SET_IDENT_ERROR);
+ return MOD_CONT;
+ }
+ }
+ }
+#ifndef HAS_VIDENT
+ notice_lang(s_HostServ, u, HOST_NO_VIDENT);
+ return MOD_CONT;
+#endif
+ }
+ if (strlen(rawhostmask) < HOSTMAX - 1)
+ snprintf(hostmask, HOSTMAX - 1, "%s", rawhostmask);
+ else {
+ notice_lang(s_HostServ, u, HOST_SET_TOOLONG, HOSTMAX);
+ return MOD_CONT;
+ }
+
+ if (!isValidHost(hostmask, 3)) {
+ notice_lang(s_HostServ, u, HOST_SET_ERROR);
+ return MOD_CONT;
+ }
+
+
+ tmp_time = time(NULL);
+
+ if ((na = findnick(nick))) {
+ alog("vHost for user \002%s\002 set to \002%s\002 by oper \002%s\002", nick, hostmask, u->nick);
+ addHostCore(nick, vIdent, hostmask, u->nick, tmp_time);
+ if (vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_SET, nick, vIdent,
+ hostmask);
+ } else {
+ notice_lang(s_HostServ, u, HOST_SET, nick, hostmask);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOREG, nick);
+ }
+ free(hostmask);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+int do_on(User * u)
+{
+ NickAlias *na;
+ char *vHost;
+ char *vIdent = NULL;
+ if ((na = findnick(u->nick))) {
+ if (na->status & NS_IDENTIFIED) {
+ vHost = getvHost(u->nick);
+ vIdent = getvIdent(u->nick);
+ if (vHost == NULL) {
+ notice_lang(s_HostServ, u, HOST_NOT_ASSIGNED);
+ } else {
+ if (vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_ACTIVATED,
+ vIdent, vHost);
+ } else {
+ notice_lang(s_HostServ, u, HOST_ACTIVATED, vHost);
+ }
+ send_on(u->nick, vIdent, vHost);
+#ifdef HAS_VHOST
+ u->vhost = sstrdup(vHost);
+#endif
+#ifdef HAS_VIDENT
+ if (vIdent)
+ u->vident = sstrdup(vIdent);
+#endif
+ set_lastmask(u);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_ID);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOT_REGED);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+int do_on_id(User * u)
+{ /* we've assumed that the user exists etc.. */
+ char *vHost; /* as were only gonna call this from nsid routine */
+ char *vIdent;
+ vHost = getvHost(u->nick);
+ vIdent = getvIdent(u->nick);
+ if (vHost != NULL) {
+ if (vIdent) {
+ notice_lang(s_HostServ, u, HOST_IDENT_ACTIVATED, vIdent,
+ vHost);
+ } else {
+ notice_lang(s_HostServ, u, HOST_ACTIVATED, vHost);
+ }
+ send_on(u->nick, vIdent, vHost);
+#ifdef HAS_VHOST
+ u->vhost = sstrdup(vHost);
+#endif
+#ifdef HAS_VIDENT
+ if (vIdent)
+ u->vident = sstrdup(vIdent);
+#endif
+ set_lastmask(u);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+int do_del(User * u)
+{
+ NickAlias *na;
+ char *nick = strtok(NULL, " ");
+ if (nick) {
+ if ((na = findnick(nick))) {
+ alog("vHost for user \002%s\002 deleted by oper \002%s\002",
+ nick, u->nick);
+ delHostCore(nick);
+ notice_lang(s_HostServ, u, HOST_DEL, nick);
+ } else {
+ notice_lang(s_HostServ, u, HOST_NOREG, nick);
+ }
+ } else {
+ notice_lang(s_HostServ, u, HOST_DEL_SYNTAX, s_HostServ);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+int do_off(User * u)
+{
+ /* put any generic code here... :) */
+ send_off(u);
+ return MOD_CONT;
+}
+
+int is_host_setter(User * u)
+{
+ int i, j;
+ NickAlias *na;
+
+ if (is_services_oper(u)) {
+ return 1;
+ }
+ if (!nick_identified(u)) {
+ return 0;
+ }
+
+ /* Look through all user's aliases (0000412) */
+ for (i = 0; i < u->na->nc->aliases.count; i++) {
+ na = u->na->nc->aliases.list[i];
+ for (j = 0; j < HostNumber; j++) {
+ if (stricmp(HostSetters[j], na->nick) == 0) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int is_host_remover(User * u)
+{
+ return is_host_setter(u); /* only here incase we want to split them up later */
+}
+
+/*
+ * Sets the last_usermak properly. Using virtual ident and/or host
+ */
+void set_lastmask(User * u)
+{
+ if (u->na->last_usermask)
+ free(u->na->last_usermask);
+
+ u->na->last_usermask =
+ smalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2);
+ sprintf(u->na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
+
+}
+
+/*************************************************************************/
+/* End of Generic Functions */
+/*************************************************************************/
+/*************************************************************************/
+/* Start of Server Functions */
+/*************************************************************************/
+void send_on(char *nick, char *vIdent, char *vhost)
+{
+#ifdef IRC_UNREAL
+ if (vIdent) {
+ send_cmd(ServerName, "CHGIDENT %s %s", nick, vIdent);
+ }
+ send_cmd(ServerName, "CHGHOST %s %s", nick, vhost);
+#endif
+#ifdef IRC_VIAGRA
+ if (vIdent) {
+ send_cmd(NULL, "CHGIDENT %s %s", nick, vIdent);
+ }
+ send_cmd(NULL, "SVSMODE %s +x", nick);
+ send_cmd(NULL, "SVSCHGHOST %s %s", nick, vhost);
+#endif
+#ifdef IRC_ULTIMATE
+ if (vIdent) {
+ send_cmd(ServerName, "CHGIDENT %s %s", nick, vIdent);
+ }
+ send_cmd(s_HostServ, "SVSMODE %s +x", nick);
+ send_cmd(ServerName, "CHGHOST %s %s", nick, vhost);
+#endif
+#ifdef IRC_ULTIMATE3
+ send_cmd(s_HostServ, "SVSMODE %s +x", nick);
+ send_cmd(ServerName, "SETHOST %s %s", nick, vhost);
+#endif
+#ifdef IRC_RAGE2
+ send_cmd(s_HostServ, "SVSMODE %s +z", nick);
+ send_cmd(ServerName, "VHOST %s %s", nick, vhost);
+#endif
+}
+
+/*************************************************************************/
+void send_off(User * u)
+{
+#ifdef IRC_UNREAL
+ send_cmd(s_HostServ, "SVSMODE %s -xt", u->nick);
+ notice_lang(s_HostServ, u, HOST_OFF_UNREAL, u->nick);
+ /*
+ * tell them to type /mode nick +x to get their original
+ * host cloaking back
+ */
+#endif
+#ifdef IRC_VIAGRA
+ send_cmd(NULL, "SVSMODE %s -x", u->nick);
+ notice_lang(s_HostServ, u, HOST_OFF_UNREAL, u->nick);
+#endif
+#ifdef IRC_ULTIMATE
+ /* UltimateIRCd 2.x does not allow users to control +x */
+#endif
+#ifdef IRC_ULTIMATE3
+ send_cmd(s_HostServ, "SVSMODE %s -x", u->nick);
+ notice_lang(s_HostServ, u, HOST_OFF_UNREAL, u->nick);
+#endif
+#ifdef IRC_RAGE2
+ send_cmd(s_HostServ, "SVSMODE %s -z", u->nick);
+ notice_lang(s_HostServ, u, HOST_OFF_UNREAL, u->nick);
+#endif
+}
+
+/*************************************************************************/
+/* End of Server Functions */
+/*************************************************************************/
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 000000000..c6daf5918
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,849 @@
+/* Initalization and related routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+int servernum = 0;
+
+extern void moduleAddMsgs(void);
+/*************************************************************************/
+
+/* Send a NICK command for the given pseudo-client. If `user' is NULL,
+ * send NICK commands for all the pseudo-clients.
+ *
+ * Now also sends MODE and SQLINE */
+#if defined(IRC_HYBRID)
+# define NICK(nick,name,modes) \
+ do { \
+ kill_user(NULL, (nick), "Nick used by Services"); \
+ send_cmd(NULL, "NICK %s 1 %ld %s %s %s %s :%s", (nick), time(NULL), (modes), \
+ ServiceUser, ServiceHost, ServerName, (name)); \
+ } while (0)
+#elif defined(IRC_ULTIMATE3)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "CLIENT %s 1 %ld %s + %s %s * %s 0 0 :%s", (nick), time(NULL), (modes), \
+ ServiceUser, ServiceHost, ServerName, (name)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#elif defined(IRC_RAGE2)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "SNICK %s %ld 1 %s %s 0 * %s 0 %s :%s", (nick), time(NULL), ServiceUser, \
+ ServiceHost, ServerName, (modes), (name)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#elif defined(IRC_BAHAMUT)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "NICK %s 1 %ld %s %s %s %s 0 0 :%s", (nick), time(NULL), (modes), \
+ ServiceUser, ServiceHost, ServerName, (name)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#elif defined(IRC_UNREAL)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "NICK %s 1 %ld %s %s %s 0 %s * :%s", (nick), time(NULL), \
+ ServiceUser, ServiceHost, ServerName, (modes), (name)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#elif defined(IRC_DREAMFORGE)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "NICK %s 1 %ld %s %s %s 0 :%s", (nick), time(NULL), \
+ ServiceUser, ServiceHost, ServerName, (name)); \
+ if (strcmp(modes, "+")) send_mode((nick), (nick), "%s", (modes)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#elif defined(IRC_PTLINK)
+# define NICK(nick,name,modes) \
+ do { \
+ send_cmd(NULL, "NICK %s 1 %lu %s %s %s %s %s :%s", (nick), time(NULL), \
+ (modes), ServiceUser, ServiceHost, ServiceHost, ServerName, (name)); \
+ send_cmd(NULL, "SQLINE %s :Reserved for services", (nick)); \
+ } while (0)
+#endif
+
+void introduce_user(const char *user)
+{
+
+ /* Watch out for infinite loops... */
+#define LTSIZE 20
+ static int lasttimes[LTSIZE];
+ if (lasttimes[0] >= time(NULL) - 3)
+ fatal("introduce_user() loop detected");
+ memmove(lasttimes, lasttimes + 1, sizeof(lasttimes) - sizeof(int));
+ lasttimes[LTSIZE - 1] = time(NULL);
+#undef LTSIZE
+
+ if (!user || stricmp(user, s_NickServ) == 0) {
+ EnforceQlinedNick(s_NickServ, NULL);
+ NICK(s_NickServ, desc_NickServ, NICKSERV_MODE);
+ }
+ if (!user || stricmp(user, s_ChanServ) == 0) {
+ EnforceQlinedNick(s_ChanServ, NULL);
+ NICK(s_ChanServ, desc_ChanServ, CHANSERV_MODE);
+ }
+#ifdef HAS_VHOST
+ if (s_HostServ && (!user || stricmp(user, s_HostServ) == 0)) {
+ EnforceQlinedNick(s_HostServ, NULL);
+ NICK(s_HostServ, desc_HostServ, HOSTSERV_MODE);
+ }
+#endif
+
+ if (!user || stricmp(user, s_MemoServ) == 0) {
+ EnforceQlinedNick(s_MemoServ, NULL);
+ NICK(s_MemoServ, desc_MemoServ, MEMOSERV_MODE);
+ }
+
+ if (s_BotServ && (!user || stricmp(user, s_BotServ) == 0)) {
+ EnforceQlinedNick(s_BotServ, NULL);
+ NICK(s_BotServ, desc_BotServ, BOTSERV_MODE);
+ }
+
+ if (!user || stricmp(user, s_HelpServ) == 0) {
+ EnforceQlinedNick(s_HelpServ, NULL);
+ NICK(s_HelpServ, desc_HelpServ, HELPSERV_MODE);
+ }
+
+ if (!user || stricmp(user, s_OperServ) == 0) {
+ EnforceQlinedNick(s_OperServ, NULL);
+ NICK(s_OperServ, desc_OperServ, OPERSERV_MODE);
+ }
+
+ if (s_DevNull && (!user || stricmp(user, s_DevNull) == 0)) {
+ EnforceQlinedNick(s_DevNull, NULL);
+ NICK(s_DevNull, desc_DevNull, DEVNULL_MODE);
+ }
+
+ if (!user || stricmp(user, s_GlobalNoticer) == 0) {
+ EnforceQlinedNick(s_GlobalNoticer, NULL);
+ NICK(s_GlobalNoticer, desc_GlobalNoticer, GLOBAL_MODE);
+ }
+
+ /* We make aliases go online */
+ if (s_NickServAlias && (!user || stricmp(user, s_NickServAlias) == 0)) {
+ EnforceQlinedNick(s_NickServAlias, NULL);
+ NICK(s_NickServAlias, desc_NickServAlias, NICKSERV_ALIAS_MODE);
+ }
+
+ if (s_ChanServAlias && (!user || stricmp(user, s_ChanServAlias) == 0)) {
+ EnforceQlinedNick(s_ChanServAlias, NULL);
+ NICK(s_ChanServAlias, desc_ChanServAlias, CHANSERV_ALIAS_MODE);
+ }
+
+ if (s_MemoServAlias && (!user || stricmp(user, s_MemoServAlias) == 0)) {
+ EnforceQlinedNick(s_MemoServAlias, NULL);
+ NICK(s_MemoServAlias, desc_MemoServAlias, MEMOSERV_ALIAS_MODE);
+ }
+
+ if (s_BotServAlias && (!user || stricmp(user, s_BotServAlias) == 0)) {
+ EnforceQlinedNick(s_BotServAlias, NULL);
+ NICK(s_BotServAlias, desc_BotServAlias, BOTSERV_ALIAS_MODE);
+ }
+
+ if (s_HelpServAlias && (!user || stricmp(user, s_HelpServAlias) == 0)) {
+ EnforceQlinedNick(s_HelpServAlias, NULL);
+ NICK(s_HelpServAlias, desc_HelpServAlias, HELPSERV_ALIAS_MODE);
+ }
+
+ if (s_OperServAlias && (!user || stricmp(user, s_OperServAlias) == 0)) {
+ EnforceQlinedNick(s_OperServAlias, NULL);
+ NICK(s_OperServAlias, desc_OperServAlias, OPERSERV_ALIAS_MODE);
+ }
+
+ if (s_DevNullAlias && (!user || stricmp(user, s_DevNullAlias) == 0)) {
+ EnforceQlinedNick(s_DevNullAlias, NULL);
+ NICK(s_DevNullAlias, desc_DevNullAlias, DEVNULL_ALIAS_MODE);
+ }
+#ifdef HAS_VHOST
+ if (s_HostServAlias && (!user || stricmp(user, s_HostServAlias) == 0)) {
+ EnforceQlinedNick(s_HostServAlias, NULL);
+ NICK(s_HostServAlias, desc_HostServAlias, HOSTSERV_ALIAS_MODE);
+ }
+#endif
+
+ if (s_GlobalNoticerAlias
+ && (!user || stricmp(user, s_GlobalNoticerAlias) == 0)) {
+ EnforceQlinedNick(s_GlobalNoticerAlias, NULL);
+ NICK(s_GlobalNoticerAlias, desc_GlobalNoticerAlias,
+ GLOBAL_ALIAS_MODE);
+ }
+
+ /* We make the bots go online */
+ if (s_BotServ) {
+ BotInfo *bi;
+ int i;
+
+ for (i = 0; i < 256; i++)
+ for (bi = botlists[i]; bi; bi = bi->next) {
+
+ EnforceQlinedNick(bi->nick, s_BotServ);
+
+ if (!user || !stricmp(user, bi->nick))
+ NEWNICK(bi->nick, bi->user, bi->host, bi->real,
+ BOTSERV_BOTS_MODE, 1);
+ }
+ }
+}
+
+#undef NICK
+
+/*************************************************************************/
+
+/* Set GID if necessary. Return 0 if successful (or if RUNGROUP not
+ * defined), else print an error message to logfile and return -1.
+ */
+
+static int set_group(void)
+{
+#if defined(RUNGROUP) && defined(HAVE_SETGRENT)
+ struct group *gr;
+
+ setgrent();
+ while ((gr = getgrent()) != NULL) {
+ if (strcmp(gr->gr_name, RUNGROUP) == 0)
+ break;
+ }
+ endgrent();
+ if (gr) {
+ setgid(gr->gr_gid);
+ return 0;
+ } else {
+ alog("Unknown group `%s'\n", RUNGROUP);
+ return -1;
+ }
+#else
+ return 0;
+#endif
+}
+
+/*************************************************************************/
+
+/* Parse command-line options for the "-dir" option only. Return 0 if all
+ * went well or -1 for a syntax error.
+ */
+
+/* XXX this could fail if we have "-some-option-taking-an-argument -dir" */
+
+static int parse_dir_options(int ac, char **av)
+{
+ int i;
+ char *s;
+
+ for (i = 1; i < ac; i++) {
+ s = av[i];
+ if (*s == '-') {
+ s++;
+ if (strcmp(s, "dir") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-dir requires a parameter\n");
+ return -1;
+ }
+ services_dir = av[i];
+ } else if (strcmp(s, "log") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-log requires a parameter\n");
+ return -1;
+ }
+ log_filename = av[i];
+ }
+ }
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Parse command-line options. Return 0 if all went well, -1 for an error
+ * with an option, or 1 for -help.
+ */
+
+static int parse_options(int ac, char **av)
+{
+ int i;
+ char *s, *t;
+
+ for (i = 1; i < ac; i++) {
+ s = av[i];
+ if (*s == '-') {
+ s++;
+ if (strcmp(s, "remote") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-remote requires hostname[:port]\n");
+ return -1;
+ }
+ s = av[i];
+ t = strchr(s, ':');
+ if (t) {
+ *t++ = 0;
+ if (atoi(t) > 0)
+ RemotePort = atoi(t);
+ else {
+ fprintf(stderr,
+ "-remote: port number must be a positive integer. Using default.");
+ return -1;
+ }
+ }
+ RemoteServer = s;
+ } else if (strcmp(s, "local") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr,
+ "-local requires hostname or [hostname]:[port]\n");
+ return -1;
+ }
+ s = av[i];
+ t = strchr(s, ':');
+ if (t) {
+ *t++ = 0;
+ if (atoi(t) >= 0)
+ LocalPort = atoi(t);
+ else {
+ fprintf(stderr,
+ "-local: port number must be a positive integer or 0. Using default.");
+ return -1;
+ }
+ }
+ LocalHost = s;
+ } else if (strcmp(s, "name") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-name requires a parameter\n");
+ return -1;
+ }
+ ServerName = av[i];
+ } else if (strcmp(s, "desc") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-desc requires a parameter\n");
+ return -1;
+ }
+ ServerDesc = av[i];
+ } else if (strcmp(s, "user") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-user requires a parameter\n");
+ return -1;
+ }
+ ServiceUser = av[i];
+ } else if (strcmp(s, "host") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-host requires a parameter\n");
+ return -1;
+ }
+ ServiceHost = av[i];
+ } else if (strcmp(s, "dir") == 0) {
+ /* Handled by parse_dir_options() */
+ i++; /* Skip parameter */
+ } else if (strcmp(s, "log") == 0) {
+ /* Handled by parse_dir_options(), too */
+ i++; /* Skip parameter */
+ } else if (strcmp(s, "update") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-update requires a parameter\n");
+ return -1;
+ }
+ s = av[i];
+ if (atoi(s) <= 0) {
+ fprintf(stderr,
+ "-update: number of seconds must be positive");
+ return -1;
+ } else
+ UpdateTimeout = atol(s);
+ } else if (strcmp(s, "expire") == 0) {
+ if (++i >= ac) {
+ fprintf(stderr, "-expire requires a parameter\n");
+ return -1;
+ }
+ s = av[i];
+ if (atoi(s) <= 0) {
+ fprintf(stderr,
+ "-expire: number of seconds must be positive");
+ return -1;
+ } else
+ ExpireTimeout = atol(s);
+ } else if (strcmp(s, "debug") == 0) {
+ debug++;
+ } else if (strcmp(s, "readonly") == 0) {
+ readonly = 1;
+ skeleton = 0;
+ } else if (strcmp(s, "skeleton") == 0) {
+ readonly = 0;
+ skeleton = 1;
+ } else if (strcmp(s, "nofork") == 0) {
+ nofork = 1;
+ } else if (strcmp(s, "logchan") == 0) {
+ logchan = 1;
+ } else if (strcmp(s, "forceload") == 0) {
+ forceload = 1;
+ } else if (!strcmp(s, "noexpire")) {
+ noexpire = 1;
+#ifdef IS44_CONVERTER
+ } else if (!strcmp(s, "is44")) {
+ is44 = 1;
+#endif
+ } else if (!strcmp(s, "version")) {
+ fprintf(stdout, "Anope-%s %s -- %s\n", version_number,
+ version_flags, version_build);
+ exit(EXIT_SUCCESS);
+ } else if (!strcmp(s, "help")) {
+ fprintf(stdout, "Anope-%s %s -- %s\n", version_number,
+ version_flags, version_build);
+ fprintf(stdout,
+ "Anope IRC Services (http://www.anope.org)\n");
+ fprintf(stdout, "Usage ./services [options] ...\n");
+ fprintf(stdout,
+ "-remote -remote hostname[:port]\n");
+ fprintf(stdout, "-local -local hostname[:port]\n");
+ fprintf(stdout, "-name -name servername\n");
+ fprintf(stdout, "-desc -desc serverdesc\n");
+ fprintf(stdout, "-user -user serviceuser\n");
+ fprintf(stdout, "-host -host servicehost\n");
+ fprintf(stdout,
+ "-update -update updatetime(secs)\n");
+ fprintf(stdout,
+ "-expire -expire expiretime(secs)\n");
+ fprintf(stdout, "-debug -debug\n");
+ fprintf(stdout, "-nofork -nofork\n");
+ fprintf(stdout, "-logchan -logchan channelname\n");
+ fprintf(stdout, "-skeleton -skeleton\n");
+ fprintf(stdout, "-forceload -forceload\n");
+ fprintf(stdout, "-readonly -readonly\n");
+ fprintf(stdout, "-noexpire -noexpire\n");
+ fprintf(stdout, "-is44 -is44\n");
+ fprintf(stdout, "-version -version\n");
+ fprintf(stdout, "-help -help\n");
+ fprintf(stdout, "-log -log logfilename\n");
+ fprintf(stdout,
+ "-dir -dir servicesdirectory\n\n");
+ fprintf(stdout,
+ "Further support is available from http://www.anope.org\n");
+ fprintf(stdout,
+ "Or visit US on IRC at irc.anope.org #anope\n");
+ exit(EXIT_SUCCESS);
+ } else {
+ fprintf(stderr, "Unknown option -%s\n", s);
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Non-option arguments not allowed\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Remove our PID file. Done at exit. */
+
+static void remove_pidfile(void)
+{
+ remove(PIDFilename);
+}
+
+/*************************************************************************/
+
+/* Create our PID file and write the PID to it. */
+
+static void write_pidfile(void)
+{
+ FILE *pidfile;
+
+ pidfile = fopen(PIDFilename, "w");
+ if (pidfile) {
+ fprintf(pidfile, "%d\n", (int) getpid());
+ fclose(pidfile);
+ atexit(remove_pidfile);
+ } else {
+ log_perror("Warning: cannot write to PID file %s", PIDFilename);
+ }
+}
+
+/*************************************************************************/
+
+/* Overall initialization routine. Returns 0 on success, -1 on failure. */
+
+int init(int ac, char **av)
+{
+ int i;
+ int openlog_failed = 0, openlog_errno = 0;
+ int started_from_term = isatty(0) && isatty(1) && isatty(2);
+
+ /* Imported from main.c */
+ extern void sighandler(int signum);
+
+
+ /* Set file creation mask and group ID. */
+#if defined(DEFUMASK) && HAVE_UMASK
+ umask(DEFUMASK);
+#endif
+ if (set_group() < 0)
+ return -1;
+
+ /* Parse command line for -dir option. */
+ parse_dir_options(ac, av);
+
+ /* Chdir to Services data directory. */
+ if (chdir(services_dir) < 0) {
+ fprintf(stderr, "chdir(%s): %s\n", services_dir, strerror(errno));
+ return -1;
+ }
+
+ /* Open logfile, and complain if we didn't. */
+ if (open_log() < 0) {
+ openlog_errno = errno;
+ if (started_from_term) {
+ fprintf(stderr, "Warning: unable to open log file %s: %s\n",
+ log_filename, strerror(errno));
+ } else {
+ openlog_failed = 1;
+ }
+ }
+
+ /* Read configuration file; exit if there are problems. */
+ if (!read_config(0))
+ return -1;
+
+ /* Add Core MSG handles */
+ moduleAddMsgs();
+
+ /* Parse all remaining command-line options. */
+ parse_options(ac, av);
+
+ /* Detach ourselves if requested. */
+ if (!nofork) {
+ if ((i = fork()) < 0) {
+ perror("fork()");
+ return -1;
+ } else if (i != 0) {
+ exit(0);
+ }
+ if (started_from_term) {
+ close(0);
+ close(1);
+ close(2);
+ }
+ if (setpgid(0, 0) < 0) {
+ perror("setpgid()");
+ return -1;
+ }
+ }
+
+ /* Write our PID to the PID file. */
+ write_pidfile();
+
+ /* Announce ourselves to the logfile. */
+ if (debug || readonly || skeleton) {
+ alog("Anope %s (compiled for %s) starting up (options:%s%s%s)",
+ version_number, version_protocol,
+ debug ? " debug" : "", readonly ? " readonly" : "",
+ skeleton ? " skeleton" : "");
+ } else {
+ alog("Anope %s (compiled for %s) starting up",
+ version_number, version_protocol);
+ }
+ start_time = time(NULL);
+
+ /* If in read-only mode, close the logfile again. */
+ if (readonly)
+ close_log();
+
+ /* Set signal handlers. Catch certain signals to let us do things or
+ * panic as necessary, and ignore all others.
+ */
+
+#if defined(NSIG) && !defined(LINUX20) && !defined(LINUX22)
+ for (i = 1; i <= NSIG - 1; i++) {
+#else
+ for (i = 1; i <= 31; i++) {
+#endif
+#if defined(USE_THREADS) && defined(LINUX20)
+ if (i != SIGUSR1)
+#endif
+ signal(i, SIG_IGN);
+ }
+
+#ifndef USE_THREADS
+ signal(SIGINT, sighandler);
+#else
+ signal(SIGINT, SIG_DFL);
+#endif
+ signal(SIGTERM, sighandler);
+ signal(SIGQUIT, sighandler);
+ if (!DumpCore) {
+ signal(SIGSEGV, sighandler);
+ signal(SIGBUS, sighandler);
+ signal(SIGILL, sighandler);
+ signal(SIGTRAP, sighandler);
+ } else {
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGBUS, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
+ signal(SIGTRAP, SIG_DFL);
+ }
+ signal(SIGQUIT, sighandler);
+ signal(SIGHUP, sighandler);
+ signal(SIGUSR2, sighandler);
+
+#ifdef SIGIOT
+ signal(SIGIOT, sighandler);
+#endif
+ signal(SIGFPE, sighandler);
+
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ signal(SIGUSR1, sighandler); /* This is our "out-of-memory" panic switch */
+#endif
+
+ /* Initialize multi-language support */
+ lang_init();
+ if (debug)
+ alog("debug: Loaded languages");
+
+ /* Initialize subservices */
+ ns_init();
+ cs_init();
+ ms_init();
+ bs_init();
+ os_init();
+ hostserv_init();
+ helpserv_init();
+
+#ifdef USE_RDB
+ rdb_init();
+#endif
+
+ /* Initialize proxy detection */
+#ifdef USE_THREADS
+ if (ProxyDetect && !proxy_init()) {
+ perror("proxy_init()");
+ return -1;
+ }
+#endif
+
+ /* load any custom modules */
+ modules_init();
+
+#ifdef USE_CONVERTER
+ /* Convert the databases NOW! */
+# ifdef IS44_CONVERTER
+ if (is44) {
+ convert_ircservices_44();
+ alog("debug: Databases converted");
+ }
+# endif
+#endif
+
+ /* Load up databases */
+#ifdef USE_RDB
+ if (UseRDB)
+ rdb_load_dbases();
+ /* Need a better way to handle this -dane */
+ if (!UseRDB) {
+#endif
+ if (!skeleton) {
+ load_ns_dbase();
+ if (debug)
+ alog("debug: Loaded %s database (1/9)", s_NickServ);
+ if (s_HostServ) {
+ load_hs_dbase();
+ if (debug)
+ alog("debug: Loaded %s database (2/9)", s_HostServ);
+ }
+ if (s_BotServ) {
+ load_bs_dbase();
+ if (debug)
+ alog("debug: Loaded %s database (3/9)", s_BotServ);
+ } else if (debug)
+ alog("debug: BotServ database (4/9) not loaded because BotServ is disabled");
+ load_cs_dbase();
+ if (debug)
+ alog("debug: Loaded %s database (5/9)", s_ChanServ);
+ }
+ load_os_dbase();
+ if (debug)
+ alog("debug: Loaded %s database (6/9)", s_OperServ);
+ load_news();
+ if (debug)
+ alog("debug: Loaded news database (7/9)");
+ load_exceptions();
+ if (debug)
+ alog("debug: Loaded exception database (8/9)");
+ if (PreNickDBName) {
+ load_ns_req_db();
+ if (debug)
+ alog("debug: Loaded PreNick database (9/9)");
+ }
+#ifdef USE_RDB
+ }
+#endif
+ alog("Databases loaded");
+
+ /* Save the databases back to file/mysql to reflect any changes */
+#ifdef USE_RDB
+ if (!UseRDB) { /* Only save if we are not using remote databases
+ * to avoid floods. As a side effects our nice
+ * FFF databases won't get overwritten if the
+ * mysql db is broken (empty etc.) */
+#endif
+ alog("Info: Reflecting database records.");
+ save_databases();
+#ifdef USE_RDB
+ } else {
+ alog("Info: Not reflecting database records.");
+ }
+#endif
+ /* Make myself known to myself in the serverlist */
+ me_server = new_server(NULL, ServerName, ServerDesc, SERVER_ISME);
+
+ /* Connect to the remote server */
+ servsock = conn(RemoteServer, RemotePort, LocalHost, LocalPort);
+ if (servsock < 0 && RemoteServer2) {
+ servsock = conn(RemoteServer2, RemotePort2, LocalHost, LocalPort);
+ if (servsock < 0 && RemoteServer3) {
+ servsock =
+ conn(RemoteServer3, RemotePort3, LocalHost, LocalPort);
+ if (servsock < 0) {
+ fatal_perror("Can't connect to server");
+ } else {
+ servernum = 3;
+ alog("Connected to Server %d (%s:%d)", servernum,
+ RemoteServer3, RemotePort3);
+ }
+ } else {
+ if (servsock < 0) {
+ fatal_perror("Can't connect to server");
+ }
+ servernum = 2;
+ alog("Connected to Server %d (%s:%d)", servernum,
+ RemoteServer2, RemotePort2);
+ }
+ } else {
+ if (servsock < 0) {
+ fatal_perror("Can't connect to server");
+ }
+ servernum = 1;
+ alog("Connected to Server %d (%s:%d)", servernum, RemoteServer,
+ RemotePort);
+ }
+
+#ifdef IRC_UNREAL
+ send_cmd(NULL, "PROTOCTL NICKv2 VHP");
+#endif
+#if defined(IRC_ULTIMATE3)
+ if (servernum == 1)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword);
+ else if (servernum == 2)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword2);
+ else if (servernum == 3)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword3);
+ send_cmd(NULL, "CAPAB NICKIP SSJ5 TS5 CLIENT");
+#elif defined(IRC_RAGE2)
+ if (servernum == 1)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword);
+ else if (servernum == 2)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword2);
+ else if (servernum == 3)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword3);
+ send_cmd(NULL, "CAPAB SSJ3 SN2 VHOST");
+#elif defined(IRC_BAHAMUT)
+ if (servernum == 1)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword);
+ else if (servernum == 2)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword2);
+ else if (servernum == 3)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword3);
+ send_cmd(NULL, "CAPAB NICKIP SSJOIN TS3 NOQUIT TSMODE UNCONNECT");
+#elif defined(IRC_HYBRID)
+ if (servernum == 1)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword);
+ else if (servernum == 2)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword2);
+ else if (servernum == 3)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword3);
+ send_cmd(NULL, "CAPAB TS5 EX IE HOPS HUB AOPS");
+#elif defined(IRC_PTLINK)
+ if (servernum == 1)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword);
+ else if (servernum == 2)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword2);
+ else if (servernum == 3)
+ send_cmd(NULL, "PASS %s :TS", RemotePassword3);
+#else
+ if (servernum == 1)
+ send_cmd(NULL, "PASS :%s", RemotePassword);
+ if (servernum == 2)
+ send_cmd(NULL, "PASS :%s", RemotePassword2);
+ if (servernum == 3)
+ send_cmd(NULL, "PASS :%s", RemotePassword3);
+#endif
+#ifdef IRC_PTLINK
+ send_cmd(NULL, "SERVER %s 1 Anope.Services%s :%s",
+ ServerName, version_number, ServerDesc);
+#else
+ send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
+#endif
+#ifdef IRC_RAGE2
+ send_cmd(NULL, "SVINFO 5 5 0 %ld bluemoon 0", time(NULL));
+#endif
+#if defined(IRC_BAHAMUT) && !defined(IRC_RAGE2)
+ send_cmd(NULL, "SVINFO 3 1 0 :%ld", time(NULL));
+#endif
+#ifdef IRC_HYBRID
+ send_cmd(NULL, "SVSINFO 5 5 0 :%ld", time(NULL));
+#endif
+#ifdef IRC_PTLINK
+ send_cmd(NULL, "SVINFO 3 6 %lu", time(NULL));
+ send_cmd(NULL, "SVSINFO %lu %d", time(NULL), maxusercnt);
+#endif
+ sgets2(inbuf, sizeof(inbuf), servsock);
+ if (strnicmp(inbuf, "ERROR", 5) == 0) {
+ /* Close server socket first to stop wallops, since the other
+ * server doesn't want to listen to us anyway */
+ disconn(servsock);
+ servsock = -1;
+ fatal("Remote server returned: %s", inbuf);
+ }
+
+ /* Announce a logfile error if there was one */
+ if (openlog_failed) {
+ wallops(NULL, "Warning: couldn't open logfile: %s",
+ strerror(openlog_errno));
+ }
+
+ /* Bring in our pseudo-clients */
+ introduce_user(NULL);
+
+ /* And hybrid needs Global joined in the logchan */
+#ifdef IRC_HYBRID
+ if (logchan) {
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), LogChannel,
+ s_GlobalNoticer);
+ }
+#endif
+
+ /**
+ * Load our delayed modeles - modules that are planing on making clients need to wait till now
+ * where as modules wanting to modify our ircd connection messages need to load eariler :|
+ **/
+ modules_delayed_init();
+
+ /* Write the StartGlobal */
+ if (GlobalOnCycle) {
+ if (GlobalOnCycleUP)
+ oper_global(NULL, "%s", GlobalOnCycleUP);
+ }
+
+ /* Success! */
+ return 0;
+}
+
+/*************************************************************************/
diff --git a/src/language.c b/src/language.c
new file mode 100644
index 000000000..ce49e4be4
--- /dev/null
+++ b/src/language.c
@@ -0,0 +1,265 @@
+/* Multi-language support.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "language.h"
+
+/*************************************************************************/
+
+/* The list of lists of messages. */
+char **langtexts[NUM_LANGS];
+
+/* The list of names of languages. */
+char *langnames[NUM_LANGS];
+
+/* Indexes of available languages: */
+int langlist[NUM_LANGS];
+
+/* Order in which languages should be displayed: (alphabetical) */
+static int langorder[NUM_LANGS] = {
+ LANG_EN_US, /* English (US) */
+ LANG_FR, /* French */
+ LANG_DE, /* German */
+ LANG_IT, /* Italian */
+ LANG_JA_JIS, /* Japanese (JIS encoding) */
+ LANG_JA_EUC, /* Japanese (EUC encoding) */
+ LANG_JA_SJIS, /* Japanese (SJIS encoding) */
+ LANG_PT, /* Portugese */
+ LANG_ES, /* Spanish */
+ LANG_TR, /* Turkish */
+ LANG_CAT, /* Catalan */
+ LANG_GR, /* Greek */
+ LANG_NL, /* Dutch */
+ LANG_RU, /* Russian */
+};
+
+/*************************************************************************/
+
+/* Load a language file. */
+
+static int read_int32(int32 * ptr, FILE * f)
+{
+ int a = fgetc(f);
+ int b = fgetc(f);
+ int c = fgetc(f);
+ int d = fgetc(f);
+ if (a == EOF || b == EOF || c == EOF || d == EOF)
+ return -1;
+ *ptr = a << 24 | b << 16 | c << 8 | d;
+ return 0;
+}
+
+static void load_lang(int index, const char *filename)
+{
+ char buf[256];
+ FILE *f;
+ int num, i;
+
+ if (debug) {
+ alog("debug: Loading language %d from file `languages/%s'",
+ index, filename);
+ }
+ snprintf(buf, sizeof(buf), "languages/%s", filename);
+ if (!(f = fopen(buf, "r"))) {
+ log_perror("Failed to load language %d (%s)", index, filename);
+ return;
+ } else if (read_int32(&num, f) < 0) {
+ alog("Failed to read number of strings for language %d (%s)",
+ index, filename);
+ return;
+ } else if (num != NUM_STRINGS) {
+ alog("Warning: Bad number of strings (%d, wanted %d) "
+ "for language %d (%s)", num, NUM_STRINGS, index, filename);
+ }
+ langtexts[index] = scalloc(sizeof(char *), NUM_STRINGS);
+ if (num > NUM_STRINGS)
+ num = NUM_STRINGS;
+ for (i = 0; i < num; i++) {
+ int32 pos, len;
+ fseek(f, i * 8 + 4, SEEK_SET);
+ if (read_int32(&pos, f) < 0 || read_int32(&len, f) < 0) {
+ alog("Failed to read entry %d in language %d (%s) TOC",
+ i, index, filename);
+ while (--i >= 0) {
+ if (langtexts[index][i])
+ free(langtexts[index][i]);
+ }
+ free(langtexts[index]);
+ langtexts[index] = NULL;
+ return;
+ }
+ if (len == 0) {
+ langtexts[index][i] = NULL;
+ } else if (len >= 65536) {
+ alog("Entry %d in language %d (%s) is too long (over 64k)--"
+ "corrupt TOC?", i, index, filename);
+ while (--i >= 0) {
+ if (langtexts[index][i])
+ free(langtexts[index][i]);
+ }
+ free(langtexts[index]);
+ langtexts[index] = NULL;
+ return;
+ } else if (len < 0) {
+ alog("Entry %d in language %d (%s) has negative length--"
+ "corrupt TOC?", i, index, filename);
+ while (--i >= 0) {
+ if (langtexts[index][i])
+ free(langtexts[index][i]);
+ }
+ free(langtexts[index]);
+ langtexts[index] = NULL;
+ return;
+ } else {
+ langtexts[index][i] = scalloc(len + 1, 1);
+ fseek(f, pos, SEEK_SET);
+ if (fread(langtexts[index][i], 1, len, f) != len) {
+ alog("Failed to read string %d in language %d (%s)",
+ i, index, filename);
+ while (--i >= 0) {
+ if (langtexts[index][i])
+ free(langtexts[index][i]);
+ }
+ free(langtexts[index]);
+ langtexts[index] = NULL;
+ return;
+ }
+ langtexts[index][i][len] = 0;
+ }
+ }
+ fclose(f);
+}
+
+/*************************************************************************/
+
+/* Initialize list of lists. */
+
+void lang_init()
+{
+ int i, j, n = 0;
+
+ load_lang(LANG_CAT, "cat");
+ load_lang(LANG_DE, "de");
+ load_lang(LANG_EN_US, "en_us");
+ load_lang(LANG_ES, "es");
+ load_lang(LANG_FR, "fr");
+ load_lang(LANG_GR, "gr");
+ load_lang(LANG_PT, "pt");
+ load_lang(LANG_TR, "tr");
+ load_lang(LANG_IT, "it");
+ load_lang(LANG_NL, "nl");
+ load_lang(LANG_RU, "ru");
+
+ for (i = 0; i < NUM_LANGS; i++) {
+ if (langtexts[langorder[i]] != NULL) {
+ langnames[langorder[i]] = langtexts[langorder[i]][LANG_NAME];
+ langlist[n++] = langorder[i];
+ for (j = 0; j < NUM_STRINGS; j++) {
+ if (!langtexts[langorder[i]][j]) {
+ langtexts[langorder[i]][j] =
+ langtexts[DEF_LANGUAGE][j];
+ }
+ if (!langtexts[langorder[i]][j]) {
+ langtexts[langorder[i]][j] = langtexts[LANG_EN_US][j];
+ }
+ }
+ }
+ }
+ while (n < NUM_LANGS)
+ langlist[n++] = -1;
+
+ /* Not what I intended to do, but these services are so archaïc
+ * that it's difficult to do more. */
+ if ((NSDefLanguage = langlist[NSDefLanguage]) < 0)
+ NSDefLanguage = DEF_LANGUAGE;
+
+ if (!langtexts[DEF_LANGUAGE])
+ fatal("Unable to load default language");
+ for (i = 0; i < NUM_LANGS; i++) {
+ if (!langtexts[i])
+ langtexts[i] = langtexts[DEF_LANGUAGE];
+ }
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Format a string in a strftime()-like way, but heed the user's language
+ * setting for month and day names. The string stored in the buffer will
+ * always be null-terminated, even if the actual string was longer than the
+ * buffer size.
+ * Assumption: No month or day name has a length (including trailing null)
+ * greater than BUFSIZE.
+ */
+
+int strftime_lang(char *buf, int size, User * u, int format, struct tm *tm)
+{
+ int language = u && u->na ? u->na->nc->language : NSDefLanguage;
+ char tmpbuf[BUFSIZE], buf2[BUFSIZE];
+ char *s;
+ int i, ret;
+
+ strscpy(tmpbuf, langtexts[language][format], sizeof(tmpbuf));
+ if ((s = langtexts[language][STRFTIME_DAYS_SHORT]) != NULL) {
+ for (i = 0; i < tm->tm_wday; i++)
+ s += strcspn(s, "\n") + 1;
+ i = strcspn(s, "\n");
+ strncpy(buf2, s, i);
+ buf2[i] = 0;
+ strnrepl(tmpbuf, sizeof(tmpbuf), "%a", buf2);
+ }
+ if ((s = langtexts[language][STRFTIME_DAYS_LONG]) != NULL) {
+ for (i = 0; i < tm->tm_wday; i++)
+ s += strcspn(s, "\n") + 1;
+ i = strcspn(s, "\n");
+ strncpy(buf2, s, i);
+ buf2[i] = 0;
+ strnrepl(tmpbuf, sizeof(tmpbuf), "%A", buf2);
+ }
+ if ((s = langtexts[language][STRFTIME_MONTHS_SHORT]) != NULL) {
+ for (i = 0; i < tm->tm_mon; i++)
+ s += strcspn(s, "\n") + 1;
+ i = strcspn(s, "\n");
+ strncpy(buf2, s, i);
+ buf2[i] = 0;
+ strnrepl(tmpbuf, sizeof(tmpbuf), "%b", buf2);
+ }
+ if ((s = langtexts[language][STRFTIME_MONTHS_LONG]) != NULL) {
+ for (i = 0; i < tm->tm_mon; i++)
+ s += strcspn(s, "\n") + 1;
+ i = strcspn(s, "\n");
+ strncpy(buf2, s, i);
+ buf2[i] = 0;
+ strnrepl(tmpbuf, sizeof(tmpbuf), "%B", buf2);
+ }
+ ret = strftime(buf, size, tmpbuf, tm);
+ if (ret == size)
+ buf[size - 1] = 0;
+ return ret;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Send a syntax-error message to the user. */
+
+void syntax_error(const char *service, User * u, const char *command,
+ int msgnum)
+{
+ const char *str = getstring(u->na, msgnum);
+ notice_lang(service, u, SYNTAX_ERROR, str);
+ notice_lang(service, u, MORE_INFO, service, command);
+}
+
+/*************************************************************************/
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 000000000..da73f1ab7
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,178 @@
+/* Routines to handle `listnicks' and `listchans' invocations.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+
+void do_listnicks(int ac, char **av)
+{
+ int count = 0; /* Count only rather than display? */
+ int usage = 0; /* Display command usage? (>0 also indicates error) */
+ int i;
+
+ i = 1;
+ while (i < ac) {
+ if (av[i][0] == '-') {
+ switch (av[i][1]) {
+ case 'h':
+ usage = -1;
+ break;
+ case 'c':
+ if (i > 1)
+ usage = 1;
+ count = 1;
+ break;
+ case 'd':
+ if (av[i][2]) {
+ services_dir = av[i] + 2;
+ } else {
+ if (i >= ac - 1) {
+ usage = 1;
+ break;
+ }
+ ac--;
+ memmove(av + i, av + i + 1, sizeof(char *) * ac - i);
+ services_dir = av[i];
+ }
+ default:
+ usage = 1;
+ break;
+ } /* switch */
+ ac--;
+ if (i < ac)
+ memmove(av + i, av + i + 1, sizeof(char *) * ac - i);
+ } else {
+ if (count)
+ usage = 1;
+ i++;
+ }
+ }
+ if (usage) {
+ fprintf(stderr, "\
+\n\
+Usage: listnicks [-c] [-d data-dir] [nick [nick...]]\n\
+ -c: display only count of registered nicks\n\
+ (cannot be combined with nicks)\n\
+ nick: nickname(s) to display information for\n\
+\n\
+If no nicks are given, the entire nickname database is printed out in\n\
+compact format followed by the number of registered nicks (with -c, the\n\
+list is suppressed and only the count is printed). If one or more nicks\n\
+are given, detailed information about those nicks is displayed.\n\
+\n");
+ exit(usage > 0 ? 1 : 0);
+ }
+
+ if (chdir(services_dir) < 0) {
+ fprintf(stderr, "chdir(%s): %s\n", services_dir, strerror(errno));
+ exit(1);
+ }
+ if (!read_config(0))
+ exit(1);
+ load_ns_dbase();
+
+ lang_init();
+
+ if (ac > 1) {
+ for (i = 1; i < ac; i++)
+ listnicks(0, av[i]);
+ } else {
+ listnicks(count, NULL);
+ }
+ exit(0);
+}
+
+/*************************************************************************/
+
+void do_listchans(int ac, char **av)
+{
+ int count = 0; /* Count only rather than display? */
+ int usage = 0; /* Display command usage? (>0 also indicates error) */
+ int i;
+
+ i = 1;
+ while (i < ac) {
+ if (av[i][0] == '-') {
+ switch (av[i][1]) {
+ case 'h':
+ usage = -1;
+ break;
+ case 'c':
+ if (i > 1)
+ usage = 1;
+ count = 1;
+ break;
+ case 'd':
+ if (av[i][2]) {
+ services_dir = av[i] + 2;
+ } else {
+ if (i >= ac - 1) {
+ usage = 1;
+ break;
+ }
+ ac--;
+ memmove(av + i, av + i + 1, sizeof(char *) * ac - i);
+ services_dir = av[i];
+ }
+ default:
+ usage = 1;
+ break;
+ } /* switch */
+ ac--;
+ if (i < ac)
+ memmove(av + i, av + i + 1, sizeof(char *) * ac - i);
+ } else {
+ if (count)
+ usage = 1;
+ i++;
+ }
+ }
+ if (usage) {
+ fprintf(stderr, "\
+\n\
+Usage: listchans [-c] [-d data-dir] [channel [channel...]]\n\
+ -c: display only count of registered channels\n\
+ (cannot be combined with channels)\n\
+channel: channel(s) to display information for\n\
+\n\
+If no channels are given, the entire channel database is printed out in\n\
+compact format followed by the number of registered channels (with -c, the\n\
+list is suppressed and only the count is printed). If one or more channels\n\
+are given, detailed information about those channels is displayed.\n\
+\n");
+ exit(usage > 0 ? 1 : 0);
+ }
+
+ if (chdir(services_dir) < 0) {
+ fprintf(stderr, "chdir(%s): %s\n", services_dir, strerror(errno));
+ exit(1);
+ }
+ if (!read_config(0))
+ exit(1);
+ load_ns_dbase();
+ load_cs_dbase();
+
+ lang_init();
+
+ if (ac > 1) {
+ for (i = 1; i < ac; i++)
+ listchans(0, av[i]);
+ } else {
+ listchans(count, NULL);
+ }
+ exit(0);
+}
+
+/*************************************************************************/
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 000000000..a1b3c7622
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,298 @@
+/* Logging routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+
+static FILE *logfile;
+
+static int curday = 0;
+
+/*************************************************************************/
+
+static int get_logname(char *name, int count, struct tm *tm)
+{
+
+ char timestamp[32];
+
+ if (!tm) {
+ time_t t;
+
+ time(&t);
+ tm = localtime(&t);
+ }
+
+ strftime(timestamp, count, "%Y%m%d", tm);
+ snprintf(name, count, "logs/%s.%s", log_filename, timestamp);
+ curday = tm->tm_yday;
+
+ return 1;
+}
+
+/*************************************************************************/
+
+static void remove_log(void)
+{
+ time_t t;
+ struct tm tm;
+
+ char name[PATH_MAX];
+
+ if (!KeepLogs)
+ return;
+
+ time(&t);
+ t -= (60 * 60 * 24 * KeepLogs);
+ tm = *localtime(&t);
+
+ if (!get_logname(name, sizeof(name), &tm))
+ return;
+ unlink(name);
+}
+
+/*************************************************************************/
+
+static void checkday(void)
+{
+ time_t t;
+ struct tm tm;
+
+ time(&t);
+ tm = *localtime(&t);
+
+ if (curday != tm.tm_yday) {
+ close_log();
+ remove_log();
+ open_log();
+ }
+}
+
+/*************************************************************************/
+
+/* Open the log file. Return -1 if the log file could not be opened, else
+ * return 0. */
+
+int open_log(void)
+{
+ char name[PATH_MAX];
+
+ if (logfile)
+ return 0;
+
+ if (!get_logname(name, sizeof(name), NULL))
+ return 0;
+ logfile = fopen(name, "a");
+
+ if (logfile)
+ setbuf(logfile, NULL);
+ return logfile != NULL ? 0 : -1;
+}
+
+/* Close the log file. */
+
+void close_log(void)
+{
+ if (!logfile)
+ return;
+ fclose(logfile);
+ logfile = NULL;
+}
+
+/*************************************************************************/
+
+/* Log stuff to the log file with a datestamp. Note that errno is
+ * preserved by this routine and log_perror().
+ */
+
+void alog(const char *fmt, ...)
+{
+ va_list args;
+ time_t t;
+ struct tm tm;
+ char buf[256];
+ int errno_save = errno;
+
+ checkday();
+
+ va_start(args, fmt);
+ time(&t);
+ tm = *localtime(&t);
+#if HAVE_GETTIMEOFDAY
+ if (debug) {
+ char *s;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S", &tm);
+ s = buf + strlen(buf);
+ s += snprintf(s, sizeof(buf) - (s - buf), ".%06d", tv.tv_usec);
+ strftime(s, sizeof(buf) - (s - buf) - 1, " %Y] ", &tm);
+ } else {
+#endif
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S %Y] ", &tm);
+#if HAVE_GETTIMEOFDAY
+ }
+#endif
+ if (logfile) {
+ fputs(buf, logfile);
+ vfprintf(logfile, fmt, args);
+ fputc('\n', logfile);
+ }
+ if (nofork) {
+ fputs(buf, stderr);
+ vfprintf(stderr, fmt, args);
+ fputc('\n', stderr);
+ }
+
+ if (LogChannel && logchan && !debug && findchan(LogChannel)) {
+ char str[BUFSIZE];
+ vsnprintf(str, sizeof(str), fmt, args);
+ privmsg(s_GlobalNoticer, LogChannel, str);
+ }
+
+ errno = errno_save;
+}
+
+
+/* Like alog(), but tack a ": " and a system error message (as returned by
+ * strerror()) onto the end.
+ */
+
+void log_perror(const char *fmt, ...)
+{
+ va_list args;
+ time_t t;
+ struct tm tm;
+ char buf[256];
+ int errno_save = errno;
+
+ checkday();
+
+ va_start(args, fmt);
+ time(&t);
+ tm = *localtime(&t);
+#if HAVE_GETTIMEOFDAY
+ if (debug) {
+ char *s;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S", &tm);
+ s = buf + strlen(buf);
+ s += snprintf(s, sizeof(buf) - (s - buf), ".%06d", tv.tv_usec);
+ strftime(s, sizeof(buf) - (s - buf) - 1, " %Y] ", &tm);
+ } else {
+#endif
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S %Y] ", &tm);
+#if HAVE_GETTIMEOFDAY
+ }
+#endif
+ if (logfile) {
+ fputs(buf, logfile);
+ vfprintf(logfile, fmt, args);
+ fprintf(logfile, ": %s\n", strerror(errno_save));
+ }
+ if (nofork) {
+ fputs(buf, stderr);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, ": %s\n", strerror(errno_save));
+ }
+ errno = errno_save;
+}
+
+/*************************************************************************/
+
+/* We've hit something we can't recover from. Let people know what
+ * happened, then go down.
+ */
+
+void fatal(const char *fmt, ...)
+{
+ va_list args;
+ time_t t;
+ struct tm tm;
+ char buf[256], buf2[4096];
+
+ checkday();
+
+ va_start(args, fmt);
+ time(&t);
+ tm = *localtime(&t);
+#if HAVE_GETTIMEOFDAY
+ if (debug) {
+ char *s;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S", &tm);
+ s = buf + strlen(buf);
+ s += snprintf(s, sizeof(buf) - (s - buf), ".%06d", tv.tv_usec);
+ strftime(s, sizeof(buf) - (s - buf) - 1, " %Y] ", &tm);
+ } else {
+#endif
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S %Y] ", &tm);
+#if HAVE_GETTIMEOFDAY
+ }
+#endif
+ vsnprintf(buf2, sizeof(buf2), fmt, args);
+ if (logfile)
+ fprintf(logfile, "%sFATAL: %s\n", buf, buf2);
+ if (nofork)
+ fprintf(stderr, "%sFATAL: %s\n", buf, buf2);
+ if (servsock >= 0)
+ wallops(NULL, "FATAL ERROR! %s", buf2);
+ exit(1);
+}
+
+
+/* Same thing, but do it like perror(). */
+
+void fatal_perror(const char *fmt, ...)
+{
+ va_list args;
+ time_t t;
+ struct tm tm;
+ char buf[256], buf2[4096];
+ int errno_save = errno;
+
+ checkday();
+
+ va_start(args, fmt);
+ time(&t);
+ tm = *localtime(&t);
+#if HAVE_GETTIMEOFDAY
+ if (debug) {
+ char *s;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S", &tm);
+ s = buf + strlen(buf);
+ s += snprintf(s, sizeof(buf) - (s - buf), ".%06d", tv.tv_usec);
+ strftime(s, sizeof(buf) - (s - buf) - 1, " %Y] ", &tm);
+ } else {
+#endif
+ strftime(buf, sizeof(buf) - 1, "[%b %d %H:%M:%S %Y] ", &tm);
+#if HAVE_GETTIMEOFDAY
+ }
+#endif
+ vsnprintf(buf2, sizeof(buf2), fmt, args);
+ if (logfile)
+ fprintf(logfile, "%sFATAL: %s: %s\n", buf, buf2,
+ strerror(errno_save));
+ if (stderr)
+ fprintf(stderr, "%sFATAL: %s: %s\n", buf, buf2,
+ strerror(errno_save));
+ if (servsock >= 0)
+ wallops(NULL, "FATAL ERROR! %s: %s", buf2, strerror(errno_save));
+ exit(1);
+}
+
+/*************************************************************************/
diff --git a/src/mail.c b/src/mail.c
new file mode 100644
index 000000000..b74d30028
--- /dev/null
+++ b/src/mail.c
@@ -0,0 +1,246 @@
+/* Mail utility routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "language.h"
+
+/* Begins to send a mail. Must be followed by a MailEnd call.
+ * Returns NULL if the call failed. Error messages are
+ * automatically sent to the user.
+ */
+
+MailInfo *MailRegBegin(User * u, NickRequest * nr, char *subject,
+ char *service)
+{
+ if (!u || !nr || !subject)
+ return NULL;
+
+ if (!UseMail) {
+ notice_lang(service, u, MAIL_DISABLED);
+ } else if ((time(NULL) - u->lastmail < MailDelay)
+ || (time(NULL) - nr->lastmail < MailDelay)) {
+ notice_lang(service, u, MAIL_DELAYED, MailDelay);
+ } else if (!nr->email) {
+ notice_lang(service, u, MAIL_INVALID, nr->nick);
+ } else {
+ MailInfo *mail;
+
+ mail = scalloc(sizeof(MailInfo), 1);
+ mail->sender = u;
+ mail->recipient = NULL;
+ mail->recip = nr;
+
+ if (!(mail->pipe = popen(SendMailPath, "w"))) {
+ free(mail);
+ notice_lang(service, u, MAIL_LATER);
+ return NULL;
+ }
+
+ fprintf(mail->pipe, "From: %s\n", SendFrom);
+ if (DontQuoteAddresses) {
+ fprintf(mail->pipe, "To: %s <%s>\n", nr->nick, nr->email);
+ } else {
+ fprintf(mail->pipe, "To: \"%s\" <%s>\n", nr->nick, nr->email);
+ }
+ fprintf(mail->pipe, "Subject: %s\n", subject);
+ return mail;
+ }
+
+ return NULL;
+}
+
+/* Begins to send a mail. Must be followed by a MailEnd call.
+ * Returns NULL if the call failed. Error messages are
+ * automatically sent to the user.
+ */
+
+MailInfo *MailBegin(User * u, NickCore * nc, char *subject, char *service)
+{
+ if (!u || !nc || !subject)
+ return NULL;
+
+ if (!UseMail) {
+ notice_lang(service, u, MAIL_DISABLED);
+ } else if (((time(NULL) - u->lastmail < MailDelay)
+ || (time(NULL) - nc->lastmail < MailDelay))
+ && !is_services_root(u)) {
+ notice_lang(service, u, MAIL_DELAYED, MailDelay);
+ } else if (!nc->email) {
+ notice_lang(service, u, MAIL_INVALID, nc->display);
+ } else {
+ MailInfo *mail;
+
+ mail = scalloc(sizeof(MailInfo), 1);
+ mail->sender = u;
+ mail->recipient = nc;
+ mail->recip = NULL;
+
+ if (!(mail->pipe = popen(SendMailPath, "w"))) {
+ free(mail);
+ notice_lang(service, u, MAIL_LATER);
+ return NULL;
+ }
+
+ fprintf(mail->pipe, "From: %s\n", SendFrom);
+ if (DontQuoteAddresses) {
+ fprintf(mail->pipe, "To: %s <%s>\n", nc->display, nc->email);
+ } else {
+ fprintf(mail->pipe, "To: \"%s\" <%s>\n", nc->display,
+ nc->email);
+ }
+ fprintf(mail->pipe, "Subject: %s\n", subject);
+
+ return mail;
+ }
+
+ return NULL;
+}
+
+/* new function to send memo mails */
+
+MailInfo *MailMemoBegin(NickCore * nc)
+{
+
+ if (!nc)
+ return NULL;
+
+ if (!UseMail || !nc->email) {
+ return NULL;
+
+ } else {
+ MailInfo *mail;
+
+ mail = scalloc(sizeof(MailInfo), 1);
+ mail->sender = NULL;
+ mail->recipient = nc;
+ mail->recip = NULL;
+
+ if (!(mail->pipe = popen(SendMailPath, "w"))) {
+ free(mail);
+ return NULL;
+ }
+
+ fprintf(mail->pipe, "From: %s\n", SendFrom);
+ if (DontQuoteAddresses) {
+ fprintf(mail->pipe, "To: %s <%s>\n", nc->display, nc->email);
+ } else {
+ fprintf(mail->pipe, "To: \"%s\" <%s>\n", nc->display,
+ nc->email);
+ }
+ fprintf(mail->pipe, "Subject: %s\n",
+ getstring2(NULL, MEMO_MAIL_SUBJECT));
+ return mail;
+ }
+ return NULL;
+}
+
+/* Finish to send the mail. Cleanup everything. */
+
+/* - param checking modified because we don't
+ have an user sending this mail.
+ Certus, 02.04.2004 */
+
+void MailEnd(MailInfo * mail)
+{
+ if (!mail || !mail->pipe) /* removed sender check */
+ return;
+
+ if (!mail->recipient && !mail->recip)
+ return;
+
+ pclose(mail->pipe);
+
+ if (mail->sender) /* added sender check */
+ mail->sender->lastmail = time(NULL);
+
+ if (mail->recipient)
+ mail->recipient->lastmail = time(NULL);
+ else
+ mail->recip->lastmail = time(NULL);
+
+
+ free(mail);
+}
+
+/* Resets the MailDelay protection */
+
+void MailReset(User * u, NickCore * nc)
+{
+ if (u)
+ u->lastmail = 0;
+ if (nc)
+ nc->lastmail = 0;
+}
+
+/* Checks whether we have a valid, common e-mail address.
+ * This is NOT entirely RFC compliant, and won't be so, because I said
+ * *common* cases. ;) It is very unlikely that e-mail addresses that
+ * are really being used will fail the check.
+ *
+ * FIXME: rewrite this a bit cleaner.
+ */
+
+int MailValidate(const char *email)
+{
+ int i, j, has_period = 0, len;
+ char copy[BUFSIZE], *domain;
+
+ static char specials[] =
+ { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '\"', '[', ']',
+ ' '
+ };
+
+ if (!email)
+ return 0;
+ strcpy(copy, email);
+
+ domain = strchr(copy, '@');
+ if (!domain)
+ return 0;
+ *domain = '\0';
+ domain++;
+
+ /* Don't accept NULL copy or domain. */
+ if (*copy == 0 || *domain == 0)
+ return 0;
+
+ /* Check for forbidden characters in the name */
+ for (i = 0; i < strlen(copy); i++) {
+
+ if (copy[i] <= 31 || copy[i] >= 127)
+ return 0;
+ for (j = 0; j < 13; j++)
+ if (copy[i] == specials[j])
+ return 0;
+ }
+
+ /* Check for forbidden characters in the domain, and if it seems to be valid. */
+ for (i = 0; i < (len = strlen(domain)); i++) {
+ if (domain[i] <= 31 || domain[i] >= 127)
+ return 0;
+ for (j = 0; j < 13; j++)
+ if (domain[i] == specials[j])
+ return 0;
+ if (domain[i] == '.') {
+ if (i == 0 || i == len - 1)
+ return 0;
+ has_period = 1;
+ }
+ }
+
+ if (!has_period)
+ return 0;
+
+ return 1;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 000000000..cb9c3a009
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,537 @@
+/* Services -- main source file.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING); if not, write to the
+ * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "timeout.h"
+#include "version.h"
+#include "datafiles.h"
+
+/******** Global variables! ********/
+
+/* Command-line options: (note that configuration variables are in config.c) */
+char *services_dir = SERVICES_DIR; /* -dir dirname */
+char *log_filename = LOG_FILENAME; /* -log filename */
+int debug = 0; /* -debug */
+int readonly = 0; /* -readonly */
+int logchan = 0; /* -logchan */
+int skeleton = 0; /* -skeleton */
+int nofork = 0; /* -nofork */
+int forceload = 0; /* -forceload */
+int noexpire = 0; /* -noexpire */
+#ifdef IS44_CONVERTER
+int is44 = 0; /* -is44 */
+#endif
+
+#ifdef USE_RDB
+int do_mysql = 0; /* use mysql ? */
+#endif
+
+/* Set to 1 if we are to quit */
+int quitting = 0;
+
+/* Set to 1 if we are to quit after saving databases */
+int delayed_quit = 0;
+
+/* Contains a message as to why services is terminating */
+char *quitmsg = NULL;
+
+/* Input buffer - global, so we can dump it if something goes wrong */
+char inbuf[BUFSIZE];
+
+/* Socket for talking to server */
+int servsock = -1;
+
+/* Should we update the databases now? */
+int save_data = 0;
+
+/* At what time were we started? */
+time_t start_time;
+
+/* Parameters and environment */
+char **my_av, **my_envp;
+
+/******** Local variables! ********/
+
+/* Set to 1 if we are waiting for input */
+static int waiting = 0;
+
+/* Set to 1 after we've set everything up */
+static int started = 0;
+
+/*************************************************************************/
+
+/* Run expiration routines */
+
+static void expire_all(void)
+{
+ waiting = -3;
+ if (debug)
+ alog("debug: Running expire routines");
+ if (!skeleton) {
+ waiting = -21;
+ expire_nicks();
+ waiting = -22;
+ expire_chans();
+ waiting = -23;
+ expire_requests();
+ }
+ waiting = -25;
+ expire_akills();
+#ifdef IRC_BAHAMUT
+ waiting = -26;
+ expire_sglines();
+#endif
+ waiting = -28;
+ expire_sqlines();
+#ifdef IRC_BAHAMUT
+ waiting = -27;
+ expire_szlines();
+#endif
+#ifndef STREAMLINED
+ expire_exceptions();
+#endif
+#ifdef USE_THREADS
+ if (ProxyDetect)
+ proxy_expire();
+#endif
+}
+
+/*************************************************************************/
+
+void save_databases(void)
+{
+ waiting = -2;
+ if (debug)
+ alog("debug: Saving FFF databases");
+ waiting = -10;
+ backup_databases();
+ if (!skeleton) {
+ waiting = -11;
+ save_ns_dbase();
+ waiting = -12;
+ if (PreNickDBName) {
+ save_ns_req_dbase();
+ waiting = -13;
+ }
+ save_cs_dbase();
+ if (s_BotServ) {
+ waiting = -14;
+ save_bs_dbase();
+ }
+ if (s_HostServ) {
+ waiting = -15;
+ save_hs_dbase();
+ }
+ }
+ waiting = -16;
+ save_os_dbase();
+ waiting = -17;
+ save_news();
+ waiting = -18;
+ save_exceptions();
+
+#ifdef USE_RDB
+ if (do_mysql) {
+ if (debug)
+ alog("debug: Saving RDB databases");
+ waiting = -10;
+ if (!skeleton) {
+ waiting = -11;
+ save_ns_rdb_dbase();
+ waiting = -12;
+ save_cs_rdb_dbase();
+ if (PreNickDBName) {
+ save_ns_req_rdb_dbase();
+ waiting = -13;
+ }
+ /* Temporary fix to avoid unwanted timeouts... */
+ send_cmd(ServerName, "PONG %s", ServerName);
+ if (s_BotServ) {
+ waiting = -14;
+ save_bs_rdb_dbase();
+ }
+ if (s_HostServ) {
+ waiting = -15;
+ save_hs_rdb_dbase();
+ }
+ waiting = -16;
+ save_os_rdb_dbase();
+ waiting = -17;
+ save_rdb_news();
+ waiting = -18;
+ save_rdb_exceptions();
+ }
+ }
+#endif
+}
+
+/*************************************************************************/
+
+/* Restarts services */
+
+static void services_restart(void)
+{
+ alog("Restarting");
+ if (!quitmsg)
+ quitmsg = "Restarting";
+ send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg);
+ disconn(servsock);
+ close_log();
+#if defined(LINUX20) || defined(LINUX22)
+ pthread_kill_other_threads_np();
+#endif
+ execve(SERVICES_BIN, my_av, my_envp);
+ if (!readonly) {
+ open_log();
+ log_perror("Restart failed");
+ close_log();
+ }
+}
+
+/*************************************************************************/
+/**
+ * Added to allow do_restart from operserv access to the static functions without making them
+ * fair game to every other function - not exactly ideal :|
+ **/
+void do_restart_services(void)
+{
+ expire_all();
+ save_databases();
+ services_restart();
+ exit(1);
+}
+
+/*************************************************************************/
+
+/* Terminates services */
+
+static void services_shutdown(void)
+{
+ if (!quitmsg)
+ quitmsg = "Terminating, reason unknown";
+ alog("%s", quitmsg);
+ if (started)
+ send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg);
+ disconn(servsock);
+}
+
+/*************************************************************************/
+
+/* If we get a weird signal, come here. */
+
+void sighandler(int signum)
+{
+ if (started) {
+ if (signum == SIGHUP) { /* SIGHUP = save databases and restart */
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGUSR2, SIG_IGN);
+ alog("Received SIGHUP, restarting.");
+
+ expire_all();
+ save_databases();
+
+ if (!quitmsg)
+ quitmsg = "Restarting on SIGHUP";
+
+#ifdef SERVICES_BIN
+ services_restart();
+ exit(1);
+#else
+ quitmsg =
+ "Restart attempt failed--SERVICES_BIN not defined (rerun configure)";
+#endif
+
+ } else if (signum == SIGUSR2) {
+
+ alog("Received SIGUSR2: Saving Databases & Rehash Configuration");
+
+ expire_all();
+ save_databases();
+
+ if (!read_config(1)) {
+ sprintf(quitmsg,
+ "Error Reading Configuration File (Received SIGUSR2)");
+ quitting = 1;
+ }
+ return;
+
+ } else if (signum == SIGTERM) {
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ alog("Received SIGTERM, exiting.");
+
+ expire_all();
+ save_databases();
+ quitmsg = "Shutting down on SIGTERM";
+ services_shutdown();
+ exit(0);
+ } else if (signum == SIGINT || signum == SIGQUIT) {
+ /* nothing -- terminate below */
+ } else if (!waiting) {
+ alog("PANIC! buffer = %s", inbuf);
+ /* Cut off if this would make IRC command >510 characters. */
+ if (strlen(inbuf) > 448) {
+ inbuf[446] = '>';
+ inbuf[447] = '>';
+ inbuf[448] = 0;
+ }
+ wallops(NULL, "PANIC! buffer = %s\r\n", inbuf);
+ } else if (waiting < 0) {
+ /* This is static on the off-chance we run low on stack */
+ static char buf[BUFSIZE];
+ switch (waiting) {
+ case -1:
+ snprintf(buf, sizeof(buf), "in timed_update");
+ break;
+ case -10:
+ snprintf(buf, sizeof(buf), "backing up databases");
+ break;
+ case -11:
+ snprintf(buf, sizeof(buf), "saving %s", NickDBName);
+ break;
+ case -12:
+ snprintf(buf, sizeof(buf), "saving %s", ChanDBName);
+ break;
+ case -13:
+ snprintf(buf, sizeof(buf), "saving %s", PreNickDBName);
+ break;
+ case -14:
+ snprintf(buf, sizeof(buf), "saving %s", BotDBName);
+ break;
+ case -15:
+ snprintf(buf, sizeof(buf), "saving %s", HostDBName);
+ break;
+ case -16:
+ snprintf(buf, sizeof(buf), "saving %s", OperDBName);
+ break;
+ case -17:
+ snprintf(buf, sizeof(buf), "saving %s", NewsDBName);
+ break;
+ case -18:
+ snprintf(buf, sizeof(buf), "saving %s", ExceptionDBName);
+ break;
+ case -21:
+ snprintf(buf, sizeof(buf), "expiring nicknames");
+ break;
+ case -22:
+ snprintf(buf, sizeof(buf), "expiring channels");
+ break;
+ case -25:
+ snprintf(buf, sizeof(buf), "expiring autokills");
+ break;
+#ifdef IRC_BAHAMUT
+ case -26:
+ snprintf(buf, sizeof(buf), "expiring SGLINEs");
+ break;
+ case -27:
+ snprintf(buf, sizeof(buf), "expiring SZLINEs");
+ break;
+#endif
+ case -28:
+ snprintf(buf, sizeof(buf), "expiring SQLINEs");
+ break;
+ default:
+ snprintf(buf, sizeof(buf), "waiting=%d", waiting);
+ }
+ wallops(NULL, "PANIC! %s (%s)", buf, strsignal(signum));
+ alog("PANIC! %s (%s)", buf, strsignal(signum));
+ }
+ }
+
+ if (
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ signum == SIGUSR1 ||
+#endif
+ !(quitmsg = calloc(BUFSIZE, 1))) {
+ quitmsg = "Out of memory!";
+ } else {
+#if HAVE_STRSIGNAL
+ snprintf(quitmsg, BUFSIZE, "Services terminating: %s",
+ strsignal(signum));
+#else
+ snprintf(quitmsg, BUFSIZE, "Services terminating on signal %d",
+ signum);
+#endif
+ }
+ if (started) {
+ services_shutdown();
+ exit(0);
+ } else {
+ alog("%s", quitmsg);
+ if (isatty(2))
+ fprintf(stderr, "%s\n", quitmsg);
+ exit(1);
+ }
+}
+
+/*************************************************************************/
+
+/* Main routine. (What does it look like? :-) ) */
+
+int main(int ac, char **av, char **envp)
+{
+ volatile time_t last_update; /* When did we last update the databases? */
+ volatile time_t last_expire; /* When did we last expire nicks/channels? */
+ volatile time_t last_check; /* When did we last check timeouts? */
+ volatile time_t last_DefCon; /* When was DefCon last checked? */
+
+ int i;
+ char *progname;
+
+ my_av = av;
+ my_envp = envp;
+
+ /* Find program name. */
+ if ((progname = strrchr(av[0], '/')) != NULL)
+ progname++;
+ else
+ progname = av[0];
+
+ /* Were we run under "listnicks" or "listchans"? Do appropriate stuff
+ * if so. */
+ if (strcmp(progname, "listnicks") == 0) {
+ do_listnicks(ac, av);
+ return 0;
+ } else if (strcmp(progname, "listchans") == 0) {
+ do_listchans(ac, av);
+ return 0;
+ }
+
+
+ /* Initialization stuff. */
+ if ((i = init(ac, av)) != 0)
+ return i;
+
+
+ /* We have a line left over from earlier, so process it first. */
+ process();
+
+ /* Set up timers. */
+ last_update = time(NULL);
+ last_expire = time(NULL);
+ last_check = time(NULL);
+ last_DefCon = time(NULL);
+
+ started = 1;
+
+ /*** Main loop. ***/
+
+ while (!quitting) {
+ time_t t = time(NULL);
+
+ if (debug >= 2)
+ alog("debug: Top of main loop");
+
+ if (!noexpire && !readonly
+ && (save_data || t - last_expire >= ExpireTimeout)) {
+ expire_all();
+ last_expire = t;
+ }
+
+ if (!readonly && (save_data || t - last_update >= UpdateTimeout)) {
+ if (delayed_quit)
+ wallops(NULL,
+ "Updating databases on shutdown, please wait.");
+
+ save_databases();
+
+ if (save_data < 0)
+ break; /* out of main loop */
+
+ save_data = 0;
+ last_update = t;
+ }
+
+ if ((DefConTimeOut) && (t - last_DefCon >= dotime(DefConTimeOut))) {
+ resetDefCon(5);
+ last_DefCon = t;
+ }
+
+ if (delayed_quit)
+ break;
+
+ moduleCallBackRun();
+
+ waiting = -1;
+ if (t - last_check >= TimeoutCheck) {
+ check_timeouts();
+ last_check = t;
+ }
+
+ waiting = 1;
+ i = (int) (long) sgets2(inbuf, sizeof(inbuf), servsock);
+ waiting = 0;
+ if (i > 0) {
+ process();
+ } else if (i == 0) {
+ int errno_save = errno;
+ quitmsg = scalloc(BUFSIZE, 1);
+ if (quitmsg) {
+ snprintf(quitmsg, BUFSIZE, "Read error from server: %s",
+ strerror(errno_save));
+ } else {
+ quitmsg = "Read error from server";
+ }
+ quitting = 1;
+ }
+ waiting = -4;
+ }
+
+
+ /* Check for restart instead of exit */
+ if (save_data == -2) {
+#ifdef SERVICES_BIN
+ alog("Restarting");
+ if (!quitmsg)
+ quitmsg = "Restarting";
+ send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg);
+ disconn(servsock);
+ close_log();
+#if defined(LINUX20) || defined(LINUX22)
+ pthread_kill_other_threads_np();
+#endif
+ execve(SERVICES_BIN, av, envp);
+ if (!readonly) {
+ open_log();
+ log_perror("Restart failed");
+ close_log();
+ }
+ return 1;
+#else
+ quitmsg =
+ "Restart attempt failed--SERVICES_BIN not defined (rerun configure)";
+#endif
+ }
+
+ /* Disconnect and exit */
+ services_shutdown();
+ return 0;
+}
+
+/*************************************************************************/
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 000000000..25c9bd328
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,96 @@
+/* Memory management routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* smalloc, scalloc, srealloc, sstrdup:
+ * Versions of the memory allocation functions which will cause the
+ * program to terminate with an "Out of memory" error if the memory
+ * cannot be allocated. (Hence, the return value from these functions
+ * is never NULL.)
+ */
+
+void *smalloc(long size)
+{
+ void *buf;
+
+ if (!size) {
+ size = 1;
+ }
+ buf = malloc(size);
+ if (!buf)
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ raise(SIGUSR1);
+#else
+ abort();
+#endif
+ return buf;
+}
+
+void *scalloc(long elsize, long els)
+{
+ void *buf;
+
+ if (!elsize || !els) {
+ elsize = els = 1;
+ }
+ buf = calloc(elsize, els);
+ if (!buf)
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ raise(SIGUSR1);
+#else
+ abort();
+#endif
+ return buf;
+}
+
+void *srealloc(void *oldptr, long newsize)
+{
+ void *buf;
+
+ if (!newsize) {
+ newsize = 1;
+ }
+ buf = realloc(oldptr, newsize);
+ if (!buf)
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ raise(SIGUSR1);
+#else
+ abort();
+#endif
+ return buf;
+}
+
+char *sstrdup(const char *s)
+{
+ char *t = strdup(s);
+ if (!t)
+#if !defined(USE_THREADS) || !defined(LINUX20)
+ raise(SIGUSR1);
+#else
+ abort();
+#endif
+ return t;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* In the future: malloc() replacements that tell us if we're leaking and
+ * maybe do sanity checks too... */
+
+/*************************************************************************/
diff --git a/src/memoserv.c b/src/memoserv.c
new file mode 100644
index 000000000..e182f0535
--- /dev/null
+++ b/src/memoserv.c
@@ -0,0 +1,1382 @@
+/* MemoServ functions.
+*
+* (C) 2003 Anope Team
+* Contact us at info@anope.org
+*
+* Please read COPYING and README for furhter details.
+*
+* Based on the original code of Epona by Lara.
+* Based on the original code of Services by Andy Church.
+*
+* $Id$
+*
+*/
+
+#include "services.h"
+#include "pseudo.h"
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+
+NickCore *nclists[1024];
+static int delmemo(MemoInfo *mi, int num);
+static int do_help(User *u);
+static int do_send(User *u);
+void memo_send(User *u, char *name, char *text, int z);
+static int do_cancel(User *u);
+static int do_list(User *u);
+static int do_read(User *u);
+static int do_del(User *u);
+static int do_set(User *u);
+static int do_set_notify(User *u, MemoInfo *mi, char *param);
+static int do_set_limit(User *u, MemoInfo *mi, char *param);
+static int do_info(User *u);
+static int do_staff(User *u);
+static int do_sendall(User *u);
+void moduleAddMemoServCmds(void);
+static void new_memo_mail(NickCore *nc, Memo *m);
+static int do_rsend(User *u);
+static int do_memocheck(User *u);
+void rsend_notify(User *u, Memo *m, const char *chan);
+/*************************************************************************/
+
+void moduleAddMemoServCmds(void) {
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("SEND", do_send, NULL, MEMO_HELP_SEND, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("CANCEL", do_cancel, NULL, MEMO_HELP_CANCEL, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("LIST", do_list, NULL, MEMO_HELP_LIST, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("READ", do_read, NULL, MEMO_HELP_READ, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("DEL", do_del, NULL, MEMO_HELP_DEL, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("STAFF", do_staff, is_services_oper, MEMO_HELP_STAFF, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("SET", do_set, NULL, MEMO_HELP_SET, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("SET NOTIFY", NULL, NULL, MEMO_HELP_SET_NOTIFY, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("SET LIMIT", NULL, NULL, -1,MEMO_HELP_SET_LIMIT, MEMO_SERVADMIN_HELP_SET_LIMIT,MEMO_SERVADMIN_HELP_SET_LIMIT, MEMO_SERVADMIN_HELP_SET_LIMIT); addCoreCommand(MEMOSERV,c);
+ c = createCommand("INFO", do_info, NULL, -1,MEMO_HELP_INFO, MEMO_SERVADMIN_HELP_INFO,MEMO_SERVADMIN_HELP_INFO, MEMO_SERVADMIN_HELP_INFO); addCoreCommand(MEMOSERV,c);
+ c = createCommand("SENDALL", do_sendall, is_services_admin, MEMO_HELP_SENDALL, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("RSEND", do_rsend, NULL, MEMO_HELP_RSEND, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+ c = createCommand("CHECK", do_memocheck, NULL, MEMO_HELP_CHECK, -1,-1,-1,-1); addCoreCommand(MEMOSERV,c);
+}
+
+/*************************************************************************/
+/*************************************************************************/
+/* *INDENT-ON* */
+
+/* MemoServ initialization. */
+
+void ms_init(void)
+{
+ Command *cmd;
+ moduleAddMemoServCmds();
+ cmd = findCommand(MEMOSERV, "SET LIMIT");
+ if (cmd)
+ cmd->help_param1 = (char *) (long) MSMaxMemos;
+}
+
+/*************************************************************************/
+
+/* memoserv: Main MemoServ routine.
+ * Note that the User structure passed to the do_* routines will
+ * always be valid (non-NULL) and will always have a valid
+ * NickInfo pointer in the `ni' field.
+ */
+
+void memoserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_MemoServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_MemoServ, u, SERVICE_OFFLINE, s_MemoServ);
+ } else {
+ if (!u->na && stricmp(cmd, "HELP") != 0)
+ notice_lang(s_MemoServ, u, NICK_NOT_REGISTERED_HELP,
+ s_NickServ);
+ else
+ mod_run_cmd(s_MemoServ, u, MEMOSERV, cmd);
+ }
+}
+
+/*************************************************************************/
+
+/* check_memos: See if the given user has any unread memos, and send a
+ * NOTICE to that user if so (and if the appropriate flag is
+ * set).
+ */
+
+void check_memos(User * u)
+{
+ NickCore *nc;
+ int i, newcnt = 0;
+
+ if (!(nc = (u->na ? u->na->nc : NULL)) || !nick_recognized(u) ||
+ !(nc->flags & NI_MEMO_SIGNON))
+ return;
+
+ for (i = 0; i < nc->memos.memocount; i++) {
+ if (nc->memos.memos[i].flags & MF_UNREAD)
+ newcnt++;
+ }
+ if (newcnt > 0) {
+ notice_lang(s_MemoServ, u,
+ newcnt == 1 ? MEMO_HAVE_NEW_MEMO : MEMO_HAVE_NEW_MEMOS,
+ newcnt);
+ if (newcnt == 1 && (nc->memos.memos[i - 1].flags & MF_UNREAD)) {
+ notice_lang(s_MemoServ, u, MEMO_TYPE_READ_LAST, s_MemoServ);
+ } else if (newcnt == 1) {
+ for (i = 0; i < nc->memos.memocount; i++) {
+ if (nc->memos.memos[i].flags & MF_UNREAD)
+ break;
+ }
+ notice_lang(s_MemoServ, u, MEMO_TYPE_READ_NUM, s_MemoServ,
+ nc->memos.memos[i].number);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_TYPE_LIST_NEW, s_MemoServ);
+ }
+ }
+ if (nc->memos.memomax > 0 && nc->memos.memocount >= nc->memos.memomax) {
+ if (nc->memos.memocount > nc->memos.memomax)
+ notice_lang(s_MemoServ, u, MEMO_OVER_LIMIT, nc->memos.memomax);
+ else
+ notice_lang(s_MemoServ, u, MEMO_AT_LIMIT, nc->memos.memomax);
+ }
+}
+
+/*************************************************************************/
+/*********************** MemoServ private routines ***********************/
+/*************************************************************************/
+
+/* Return the MemoInfo corresponding to the given nick or channel name.
+ * Return in `ischan' 1 if the name was a channel name, else 0.
+ */
+
+static MemoInfo *getmemoinfo(const char *name, int *ischan)
+{
+ if (*name == '#') {
+ ChannelInfo *ci;
+ if (ischan)
+ *ischan = 1;
+ ci = cs_findchan(name);
+ if (ci && !(ci->flags & CI_VERBOTEN))
+ return &ci->memos;
+ else
+ return NULL;
+ } else {
+ NickAlias *na;
+ if (ischan)
+ *ischan = 0;
+ na = findnick(name);
+ if (na && !(na->status & NS_VERBOTEN))
+ return &na->nc->memos;
+ else
+ return NULL;
+ }
+}
+
+/*************************************************************************/
+
+/* Delete a memo by number. Return 1 if the memo was found, else 0. */
+
+static int delmemo(MemoInfo * mi, int num)
+{
+ int i;
+
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].number == num)
+ break;
+ }
+ if (i < mi->memocount) {
+ moduleCleanStruct(mi->memos[i].moduleData);
+ free(mi->memos[i].text); /* Deallocate memo text memory */
+ mi->memocount--; /* One less memo now */
+ if (i < mi->memocount) /* Move remaining memos down a slot */
+ memmove(mi->memos + i, mi->memos + i + 1,
+ sizeof(Memo) * (mi->memocount - i));
+ if (mi->memocount == 0) { /* If no more memos, free array */
+ free(mi->memos);
+ mi->memos = NULL;
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*************************************************************************/
+/*********************** MemoServ command routines ***********************/
+/*************************************************************************/
+
+/* Return a help message. */
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_MemoServ, u, MEMO_HELP);
+ if (is_services_oper(u)) {
+ notice_help(s_MemoServ, u, MEMO_HELP_OPER);
+ }
+ if (is_services_admin(u)) {
+ notice_help(s_MemoServ, u, MEMO_HELP_ADMIN);
+ }
+ moduleDisplayHelp(3, u);
+ notice_help(s_MemoServ, u, MEMO_HELP_FOOTER, s_ChanServ);
+ } else {
+ mod_help_cmd(s_MemoServ, u, MEMOSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Send a memo to a nick/channel. */
+
+static int do_send(User * u)
+{
+ char *name = strtok(NULL, " ");
+ char *text = strtok(NULL, "");
+ int z = 0;
+ memo_send(u, name, text, z);
+ return MOD_CONT;
+}
+
+/**
+ * Split from do_send, this way we can easily send a memo from any point :)
+ * u - sender User
+ * name - target name
+ * text - memo Text
+ * z - output level,
+ * 0 - reply to user
+ * 1 - silent
+ * 2 - silent with no delay timer
+ * 3 - reply to user and request read receipt
+ **/
+void memo_send(User * u, char *name, char *text, int z)
+{
+ int ischan;
+ Memo *m;
+ MemoInfo *mi;
+ time_t now = time(NULL);
+ char *source = u->na->nc->display;
+ int is_servoper = is_services_oper(u);
+ int j;
+
+ if (readonly) {
+ notice_lang(s_MemoServ, u, MEMO_SEND_DISABLED);
+ } else if (checkDefCon(DEFCON_NO_NEW_MEMOS)) {
+ notice_lang(s_MemoServ, u, OPER_DEFCON_DENIED);
+ return;
+ } else if (!text) {
+ if (z == 0)
+ syntax_error(s_MemoServ, u, "SEND", MEMO_SEND_SYNTAX);
+
+ if (z == 3)
+ syntax_error(s_MemoServ, u, "RSEND", MEMO_RSEND_SYNTAX);
+
+ } else if (!nick_recognized(u)) {
+ if (z == 0 || z == 3)
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+
+ } else if (!(mi = getmemoinfo(name, &ischan))) {
+ if (z == 0 || z == 3)
+ notice_lang(s_MemoServ, u,
+ ischan ? CHAN_X_NOT_REGISTERED :
+ NICK_X_NOT_REGISTERED, name);
+
+ } else if (z != 2 && MSSendDelay > 0 &&
+ u && u->lastmemosend + MSSendDelay > now && !is_servoper) {
+ u->lastmemosend = now;
+ if (z == 0)
+ notice_lang(s_MemoServ, u, MEMO_SEND_PLEASE_WAIT, MSSendDelay);
+
+ if (z == 3)
+ notice_lang(s_MemoServ, u, MEMO_RSEND_PLEASE_WAIT,
+ MSSendDelay);
+
+ } else if (mi->memomax == 0 && !is_servoper) {
+ if (z == 0 || z == 3)
+ notice_lang(s_MemoServ, u, MEMO_X_GETS_NO_MEMOS, name);
+
+ } else if (mi->memomax > 0 && mi->memocount >= mi->memomax
+ && !is_servoper) {
+ if (z == 0 || z == 3)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_TOO_MANY_MEMOS, name);
+
+ } else {
+ u->lastmemosend = now;
+ mi->memocount++;
+ mi->memos = srealloc(mi->memos, sizeof(Memo) * mi->memocount);
+ m = &mi->memos[mi->memocount - 1];
+ strscpy(m->sender, source, NICKMAX);
+ for (j = 0; j < MAX_CMD_HASH; j++)
+ m->moduleData[j] = NULL;
+ if (mi->memocount > 1) {
+ m->number = m[-1].number + 1;
+ if (m->number < 1) {
+ int i;
+ for (i = 0; i < mi->memocount; i++) {
+ mi->memos[i].number = i + 1;
+ }
+ }
+ } else {
+ m->number = 1;
+ }
+ m->time = time(NULL);
+ m->text = sstrdup(text);
+ m->flags = MF_UNREAD;
+ /* Set receipt request flag */
+ if (z == 3)
+ m->flags |= MF_RECEIPT;
+ if (z == 0 || z == 3)
+ notice_lang(s_MemoServ, u, MEMO_SENT, name);
+ if (!ischan) {
+ NickAlias *na;
+ NickCore *nc = (findnick(name))->nc;
+
+ if (MSNotifyAll) {
+ if ((nc->flags & NI_MEMO_RECEIVE)
+ && get_ignore(name) == NULL) {
+ int i;
+
+ for (i = 0; i < nc->aliases.count; i++) {
+ na = nc->aliases.list[i];
+ if (na->u && nick_identified(na->u))
+ notice_lang(s_MemoServ, na->u,
+ MEMO_NEW_MEMO_ARRIVED, source,
+ s_MemoServ, m->number);
+ }
+ } else {
+ if ((u = finduser(name)) && nick_identified(u)
+ && (nc->flags & NI_MEMO_RECEIVE))
+ notice_lang(s_MemoServ, u, MEMO_NEW_MEMO_ARRIVED,
+ source, s_MemoServ, m->number);
+ } /* if (flags & MEMO_RECEIVE) */
+ }
+ /* if (MSNotifyAll) */
+ /* let's get out the mail if set in the nickcore - certus */
+ if (nc->flags & NI_MEMO_MAIL)
+ new_memo_mail(nc, m);
+ } else {
+ struct c_userlist *cu, *next;
+ Channel *c;
+
+ if (MSNotifyAll && (c = findchan(name))) {
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ if (check_access(cu->user, c->ci, CA_MEMO)) {
+ if (cu->user->na
+ && (cu->user->na->nc->flags & NI_MEMO_RECEIVE)
+ && get_ignore(cu->user->nick) == NULL) {
+ notice_lang(s_MemoServ, cu->user,
+ MEMO_NEW_X_MEMO_ARRIVED,
+ c->ci->name, s_MemoServ,
+ c->ci->name, m->number);
+ }
+ }
+ }
+ } /* MSNotifyAll */
+ } /* if (!ischan) */
+ } /* if command is valid */
+}
+
+/*************************************************************************/
+
+static int do_cancel(User * u)
+{
+ int ischan;
+ char *name = strtok(NULL, " ");
+ MemoInfo *mi;
+
+ if (!name) {
+ syntax_error(s_MemoServ, u, "CANCEL", MEMO_CANCEL_SYNTAX);
+
+ } else if (!nick_recognized(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+
+ } else if (!(mi = getmemoinfo(name, &ischan))) {
+ notice_lang(s_MemoServ, u,
+ ischan ? CHAN_X_NOT_REGISTERED : NICK_X_NOT_REGISTERED,
+ name);
+ } else {
+ int i;
+
+ for (i = mi->memocount - 1; i >= 0; i--) {
+ if ((mi->memos[i].flags & MF_UNREAD)
+ && !stricmp(mi->memos[i].sender, u->na->nc->display)) {
+ delmemo(mi, mi->memos[i].number);
+ notice_lang(s_MemoServ, u, MEMO_CANCELLED, name);
+ return MOD_CONT;
+ }
+ }
+
+ notice_lang(s_MemoServ, u, MEMO_CANCEL_NONE);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Display a single memo entry, possibly printing the header first. */
+
+static int list_memo(User * u, int index, MemoInfo * mi, int *sent_header,
+ int new, const char *chan)
+{
+ Memo *m;
+ char timebuf[64];
+ struct tm tm;
+
+ if (index < 0 || index >= mi->memocount)
+ return 0;
+ if (!*sent_header) {
+ if (chan) {
+ notice_lang(s_MemoServ, u,
+ new ? MEMO_LIST_CHAN_NEW_MEMOS :
+ MEMO_LIST_CHAN_MEMOS, chan, s_MemoServ, chan);
+ } else {
+ notice_lang(s_MemoServ, u,
+ new ? MEMO_LIST_NEW_MEMOS : MEMO_LIST_MEMOS,
+ u->nick, s_MemoServ);
+ }
+ notice_lang(s_MemoServ, u, MEMO_LIST_HEADER);
+ *sent_header = 1;
+ }
+ m = &mi->memos[index];
+ tm = *localtime(&m->time);
+ strftime_lang(timebuf, sizeof(timebuf),
+ u, STRFTIME_DATE_TIME_FORMAT, &tm);
+ timebuf[sizeof(timebuf) - 1] = 0; /* just in case */
+ notice_lang(s_MemoServ, u, MEMO_LIST_FORMAT,
+ (m->flags & MF_UNREAD) ? '*' : ' ',
+ m->number, m->sender, timebuf);
+ return 1;
+}
+
+static int list_memo_callback(User * u, int num, va_list args)
+{
+ MemoInfo *mi = va_arg(args, MemoInfo *);
+ int *sent_header = va_arg(args, int *);
+ const char *chan = va_arg(args, const char *);
+ int i;
+
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].number == num)
+ break;
+ }
+ /* Range checking done by list_memo() */
+ return list_memo(u, i, mi, sent_header, 0, chan);
+}
+
+
+/* List the memos (if any) for the source nick or given channel. */
+
+static int do_list(User * u)
+{
+ char *param = strtok(NULL, " "), *chan = NULL;
+ ChannelInfo *ci;
+ MemoInfo *mi;
+ Memo *m;
+ int i;
+
+ if (param && *param == '#') {
+ chan = param;
+ param = strtok(NULL, " ");
+ if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_MemoServ, u, CHAN_X_NOT_REGISTERED, chan);
+ return MOD_CONT;
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_MemoServ, u, CHAN_X_FORBIDDEN, chan);
+ return MOD_CONT;
+ } else if (!check_access(u, ci, CA_MEMO)) {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+ mi = &ci->memos;
+ } else {
+ if (!nick_identified(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ }
+ mi = &u->na->nc->memos;
+ }
+ if (param && !isdigit(*param) && stricmp(param, "NEW") != 0) {
+ syntax_error(s_MemoServ, u, "LIST", MEMO_LIST_SYNTAX);
+ } else if (mi->memocount == 0) {
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_NO_MEMOS, chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HAVE_NO_MEMOS);
+ } else {
+ int sent_header = 0;
+ if (param && isdigit(*param)) {
+ process_numlist(param, NULL, list_memo_callback, u,
+ mi, &sent_header, chan);
+ } else {
+ if (param) {
+ for (i = 0, m = mi->memos; i < mi->memocount; i++, m++) {
+ if (m->flags & MF_UNREAD)
+ break;
+ }
+ if (i == mi->memocount) {
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_NO_NEW_MEMOS,
+ chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HAVE_NO_NEW_MEMOS);
+ return MOD_CONT;
+ }
+ }
+ for (i = 0, m = mi->memos; i < mi->memocount; i++, m++) {
+ if (param && !(m->flags & MF_UNREAD))
+ continue;
+ list_memo(u, i, mi, &sent_header, param != NULL, chan);
+ }
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Send a single memo to the given user. */
+
+static int read_memo(User * u, int index, MemoInfo * mi, const char *chan)
+{
+ Memo *m;
+ char timebuf[64];
+ struct tm tm;
+
+ if (index < 0 || index >= mi->memocount)
+ return 0;
+ m = &mi->memos[index];
+ tm = *localtime(&m->time);
+ strftime_lang(timebuf, sizeof(timebuf),
+ u, STRFTIME_DATE_TIME_FORMAT, &tm);
+ timebuf[sizeof(timebuf) - 1] = 0;
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_CHAN_HEADER, m->number,
+ m->sender, timebuf, s_MemoServ, chan, m->number);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HEADER, m->number,
+ m->sender, timebuf, s_MemoServ, m->number);
+ notice_lang(s_MemoServ, u, MEMO_TEXT, m->text);
+ m->flags &= ~MF_UNREAD;
+
+ /* Check if a receipt notification was requested */
+ if (m->flags && MF_RECEIPT) {
+ rsend_notify(u, m, chan);
+ }
+
+ return 1;
+}
+
+static int read_memo_callback(User * u, int num, va_list args)
+{
+ MemoInfo *mi = va_arg(args, MemoInfo *);
+ const char *chan = va_arg(args, const char *);
+ int i;
+
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].number == num)
+ break;
+ }
+ /* Range check done in read_memo */
+ return read_memo(u, i, mi, chan);
+}
+
+
+/* Read memos. */
+
+static int do_read(User * u)
+{
+ MemoInfo *mi;
+ ChannelInfo *ci;
+ char *numstr = strtok(NULL, " "), *chan = NULL;
+ int num, count;
+
+ if (numstr && *numstr == '#') {
+ chan = numstr;
+ numstr = strtok(NULL, " ");
+ if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_MemoServ, u, CHAN_X_NOT_REGISTERED, chan);
+ return MOD_CONT;
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_MemoServ, u, CHAN_X_FORBIDDEN, chan);
+ return MOD_CONT;
+ } else if (!check_access(u, ci, CA_MEMO)) {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+ mi = &ci->memos;
+ } else {
+ if (!nick_identified(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ }
+ mi = &u->na->nc->memos;
+ }
+ num = numstr ? atoi(numstr) : -1;
+ if (!numstr
+ || (stricmp(numstr, "LAST") != 0 && stricmp(numstr, "NEW") != 0
+ && num <= 0)) {
+ syntax_error(s_MemoServ, u, "READ", MEMO_READ_SYNTAX);
+
+ } else if (mi->memocount == 0) {
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_NO_MEMOS, chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HAVE_NO_MEMOS);
+
+ } else {
+ int i;
+
+ if (stricmp(numstr, "NEW") == 0) {
+ int readcount = 0;
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].flags & MF_UNREAD) {
+ read_memo(u, i, mi, chan);
+ readcount++;
+ }
+ }
+ if (!readcount) {
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_NO_NEW_MEMOS,
+ chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HAVE_NO_NEW_MEMOS);
+ }
+ } else if (stricmp(numstr, "LAST") == 0) {
+ for (i = 0; i < mi->memocount - 1; i++);
+ read_memo(u, i, mi, chan);
+ } else { /* number[s] */
+ if (!process_numlist(numstr, &count, read_memo_callback, u,
+ mi, chan)) {
+ if (count == 1)
+ notice_lang(s_MemoServ, u, MEMO_DOES_NOT_EXIST, num);
+ else
+ notice_lang(s_MemoServ, u, MEMO_LIST_NOT_FOUND,
+ numstr);
+ }
+ }
+
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Delete a single memo from a MemoInfo. */
+
+static int del_memo_callback(User * u, int num, va_list args)
+{
+ MemoInfo *mi = va_arg(args, MemoInfo *);
+ int *last = va_arg(args, int *);
+ int *last0 = va_arg(args, int *);
+ char **end = va_arg(args, char **);
+ int *left = va_arg(args, int *);
+
+ if (delmemo(mi, num)) {
+ if (num != (*last) + 1) {
+ if (*last != -1) {
+ int len;
+ if (*last0 != *last)
+ len = snprintf(*end, *left, ",%d-%d", *last0, *last);
+ else
+ len = snprintf(*end, *left, ",%d", *last);
+ *end += len;
+ *left -= len;
+ }
+ *last0 = num;
+ }
+ *last = num;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+/* Delete memos. */
+
+static int do_del(User * u)
+{
+ MemoInfo *mi;
+ ChannelInfo *ci;
+ char *numstr = strtok(NULL, ""), *chan = NULL;
+ int last, last0, i;
+ char buf[BUFSIZE], *end;
+ int delcount, count, left;
+
+ if (numstr && *numstr == '#') {
+ chan = strtok(numstr, " ");
+ numstr = strtok(NULL, "");
+ if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_MemoServ, u, CHAN_X_NOT_REGISTERED, chan);
+ return MOD_CONT;
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_MemoServ, u, CHAN_X_FORBIDDEN, chan);
+ return MOD_CONT;
+ } else if (!check_access(u, ci, CA_MEMO)) {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+ mi = &ci->memos;
+ } else {
+ if (!nick_identified(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ }
+ mi = &u->na->nc->memos;
+ }
+ if (!numstr
+ || (!isdigit(*numstr) && stricmp(numstr, "ALL") != 0
+ && stricmp(numstr, "LAST") != 0)) {
+ syntax_error(s_MemoServ, u, "DEL", MEMO_DEL_SYNTAX);
+ } else if (mi->memocount == 0) {
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_X_HAS_NO_MEMOS, chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_HAVE_NO_MEMOS);
+ } else {
+ if (isdigit(*numstr)) {
+ /* Delete a specific memo or memos. */
+ last = -1; /* Last memo deleted */
+ last0 = -1; /* Beginning of range of last memos deleted */
+ end = buf;
+ left = sizeof(buf);
+ delcount =
+ process_numlist(numstr, &count, del_memo_callback, u, mi,
+ &last, &last0, &end, &left);
+ if (last != -1) {
+ /* Some memos got deleted; tell them which ones. */
+ if (delcount > 1) {
+ if (last0 != last)
+ end += snprintf(end, sizeof(buf) - (end - buf),
+ ",%d-%d", last0, last);
+ else
+ end += snprintf(end, sizeof(buf) - (end - buf),
+ ",%d", last);
+ /* "buf+1" here because *buf == ',' */
+ notice_lang(s_MemoServ, u, MEMO_DELETED_SEVERAL,
+ buf + 1);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_DELETED_ONE, last);
+ }
+ } else {
+ /* No memos were deleted. Tell them so. */
+ if (count == 1)
+ notice_lang(s_MemoServ, u, MEMO_DOES_NOT_EXIST,
+ atoi(numstr));
+ else
+ notice_lang(s_MemoServ, u, MEMO_DELETED_NONE);
+ }
+ } else if (stricmp(numstr, "LAST") == 0) {
+ /* Delete last memo. */
+ for (i = 0; i < mi->memocount; i++)
+ last = mi->memos[i].number;
+ delmemo(mi, last);
+ notice_lang(s_MemoServ, u, MEMO_DELETED_ONE, last);
+ } else {
+ /* Delete all memos. */
+ for (i = 0; i < mi->memocount; i++) {
+ free(mi->memos[i].text);
+ moduleCleanStruct(mi->memos[i].moduleData);
+ }
+ free(mi->memos);
+ mi->memos = NULL;
+ mi->memocount = 0;
+ if (chan)
+ notice_lang(s_MemoServ, u, MEMO_CHAN_DELETED_ALL, chan);
+ else
+ notice_lang(s_MemoServ, u, MEMO_DELETED_ALL);
+ }
+
+ /* Reset the order */
+ for (i = 0; i < mi->memocount; i++)
+ mi->memos[i].number = i + 1;
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *param = strtok(NULL, "");
+ MemoInfo *mi = &u->na->nc->memos;
+
+ if (readonly) {
+ notice_lang(s_MemoServ, u, MEMO_SET_DISABLED);
+ return MOD_CONT;
+ }
+ if (!param) {
+ syntax_error(s_MemoServ, u, "SET", MEMO_SET_SYNTAX);
+ } else if (!nick_identified(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ } else if (stricmp(cmd, "NOTIFY") == 0) {
+ do_set_notify(u, mi, param);
+ } else if (stricmp(cmd, "LIMIT") == 0) {
+ do_set_limit(u, mi, param);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_SET_UNKNOWN_OPTION, cmd);
+ notice_lang(s_MemoServ, u, MORE_INFO, s_MemoServ, "SET");
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_notify(User * u, MemoInfo * mi, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ u->na->nc->flags |= NI_MEMO_SIGNON | NI_MEMO_RECEIVE;
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_ON, s_MemoServ);
+ } else if (stricmp(param, "LOGON") == 0) {
+ u->na->nc->flags |= NI_MEMO_SIGNON;
+ u->na->nc->flags &= ~NI_MEMO_RECEIVE;
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_LOGON, s_MemoServ);
+ } else if (stricmp(param, "NEW") == 0) {
+ u->na->nc->flags &= ~NI_MEMO_SIGNON;
+ u->na->nc->flags |= NI_MEMO_RECEIVE;
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_NEW, s_MemoServ);
+ } else if (stricmp(param, "MAIL") == 0) {
+ if (u->na->nc->email) {
+ u->na->nc->flags |= NI_MEMO_MAIL;
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_MAIL);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_INVALIDMAIL);
+ }
+ } else if (stricmp(param, "NOMAIL") == 0) {
+ u->na->nc->flags &= ~NI_MEMO_MAIL;
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_NOMAIL);
+ } else if (stricmp(param, "OFF") == 0) {
+ u->na->nc->flags &= ~(NI_MEMO_SIGNON | NI_MEMO_RECEIVE);
+ notice_lang(s_MemoServ, u, MEMO_SET_NOTIFY_OFF, s_MemoServ);
+ } else {
+ syntax_error(s_MemoServ, u, "SET NOTIFY", MEMO_SET_NOTIFY_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_limit(User * u, MemoInfo * mi, char *param)
+{
+ char *p1 = strtok(param, " ");
+ char *p2 = strtok(NULL, " ");
+ char *p3 = strtok(NULL, " ");
+ char *user = NULL, *chan = NULL;
+ int32 limit;
+ NickAlias *na = u->na;
+ ChannelInfo *ci = NULL;
+ int is_servadmin = is_services_admin(u);
+
+ if (p1 && *p1 == '#') {
+ chan = p1;
+ p1 = p2;
+ p2 = p3;
+ p3 = strtok(NULL, " ");
+ if (!(ci = cs_findchan(chan))) {
+ notice_lang(s_MemoServ, u, CHAN_X_NOT_REGISTERED, chan);
+ return MOD_CONT;
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_MemoServ, u, CHAN_X_FORBIDDEN, chan);
+ return MOD_CONT;
+ } else if (!is_servadmin && !check_access(u, ci, CA_MEMO)) {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+ mi = &ci->memos;
+ }
+ if (is_servadmin) {
+ if (p2 && stricmp(p2, "HARD") != 0 && !chan) {
+ if (!(na = findnick(p1))) {
+ notice_lang(s_MemoServ, u, NICK_X_NOT_REGISTERED, p1);
+ return MOD_CONT;
+ }
+ user = p1;
+ mi = &na->nc->memos;
+ p1 = p2;
+ p2 = p3;
+ } else if (!p1) {
+ syntax_error(s_MemoServ, u, "SET LIMIT",
+ MEMO_SET_LIMIT_SERVADMIN_SYNTAX);
+ return MOD_CONT;
+ }
+ if ((!isdigit(*p1) && stricmp(p1, "NONE") != 0) ||
+ (p2 && stricmp(p2, "HARD") != 0)) {
+ syntax_error(s_MemoServ, u, "SET LIMIT",
+ MEMO_SET_LIMIT_SERVADMIN_SYNTAX);
+ return MOD_CONT;
+ }
+ if (chan) {
+ if (p2)
+ ci->flags |= CI_MEMO_HARDMAX;
+ else
+ ci->flags &= ~CI_MEMO_HARDMAX;
+ } else {
+ if (p2)
+ na->nc->flags |= NI_MEMO_HARDMAX;
+ else
+ na->nc->flags &= ~NI_MEMO_HARDMAX;
+ }
+ limit = atoi(p1);
+ if (limit < 0 || limit > 32767) {
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT_OVERFLOW, 32767);
+ limit = 32767;
+ }
+ if (stricmp(p1, "NONE") == 0)
+ limit = -1;
+ } else {
+ if (!p1 || p2 || !isdigit(*p1)) {
+ syntax_error(s_MemoServ, u, "SET LIMIT",
+ MEMO_SET_LIMIT_SYNTAX);
+ return MOD_CONT;
+ }
+ if (chan && (ci->flags & CI_MEMO_HARDMAX)) {
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT_FORBIDDEN, chan);
+ return MOD_CONT;
+ } else if (!chan && (na->nc->flags & NI_MEMO_HARDMAX)) {
+ notice_lang(s_MemoServ, u, MEMO_SET_YOUR_LIMIT_FORBIDDEN);
+ return MOD_CONT;
+ }
+ limit = atoi(p1);
+ /* The first character is a digit, but we could still go negative
+ * from overflow... watch out! */
+ if (limit < 0 || (MSMaxMemos > 0 && limit > MSMaxMemos)) {
+ if (chan) {
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT_TOO_HIGH,
+ chan, MSMaxMemos);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_SET_YOUR_LIMIT_TOO_HIGH,
+ MSMaxMemos);
+ }
+ return MOD_CONT;
+ } else if (limit > 32767) {
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT_OVERFLOW, 32767);
+ limit = 32767;
+ }
+ }
+ mi->memomax = limit;
+ if (limit > 0) {
+ if (!chan && na->nc == u->na->nc)
+ notice_lang(s_MemoServ, u, MEMO_SET_YOUR_LIMIT, limit);
+ else
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT,
+ chan ? chan : user, limit);
+ } else if (limit == 0) {
+ if (!chan && na->nc == u->na->nc)
+ notice_lang(s_MemoServ, u, MEMO_SET_YOUR_LIMIT_ZERO);
+ else
+ notice_lang(s_MemoServ, u, MEMO_SET_LIMIT_ZERO,
+ chan ? chan : user);
+ } else {
+ if (!chan && na->nc == u->na->nc)
+ notice_lang(s_MemoServ, u, MEMO_UNSET_YOUR_LIMIT);
+ else
+ notice_lang(s_MemoServ, u, MEMO_UNSET_LIMIT,
+ chan ? chan : user);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_info(User * u)
+{
+ MemoInfo *mi;
+ NickAlias *na = NULL;
+ ChannelInfo *ci = NULL;
+ char *name = strtok(NULL, " ");
+ int is_servadmin = is_services_admin(u);
+ int hardmax = 0;
+
+ if (is_servadmin && name && *name != '#') {
+ na = findnick(name);
+ if (!na) {
+ notice_lang(s_MemoServ, u, NICK_X_NOT_REGISTERED, name);
+ return MOD_CONT;
+ }
+ mi = &na->nc->memos;
+ hardmax = na->nc->flags & NI_MEMO_HARDMAX ? 1 : 0;
+ } else if (name && *name == '#') {
+ ci = cs_findchan(name);
+ if (!ci) {
+ notice_lang(s_MemoServ, u, CHAN_X_NOT_REGISTERED, name);
+ return MOD_CONT;
+ } else if (ci->flags & CI_VERBOTEN) {
+ notice_lang(s_MemoServ, u, CHAN_X_FORBIDDEN, name);
+ return MOD_CONT;
+ } else if (!check_access(u, ci, CA_MEMO)) {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ }
+ mi = &ci->memos;
+ hardmax = ci->flags & CI_MEMO_HARDMAX ? 1 : 0;
+ } else if (name) { /* It's not a chan and we aren't services admin */
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ return MOD_CONT;
+ } else { /* !name */
+ if (!nick_identified(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ }
+ mi = &u->na->nc->memos;
+ hardmax = u->na->nc->flags & NI_MEMO_HARDMAX ? 1 : 0;
+ }
+
+ if (name && (ci || na->nc != u->na->nc)) {
+
+ if (!mi->memocount) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NO_MEMOS, name);
+ } else if (mi->memocount == 1) {
+ if (mi->memos[0].flags & MF_UNREAD)
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMO_UNREAD, name);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMO, name);
+ } else {
+ int count = 0, i;
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].flags & MF_UNREAD)
+ count++;
+ }
+ if (count == mi->memocount)
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMOS_ALL_UNREAD,
+ name, count);
+ else if (count == 0)
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMOS, name,
+ mi->memocount);
+ else if (count == 0)
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMOS_ONE_UNREAD,
+ name, mi->memocount);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_MEMOS_SOME_UNREAD,
+ name, mi->memocount, count);
+ }
+ if (mi->memomax >= 0) {
+ if (hardmax)
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_HARD_LIMIT, name,
+ mi->memomax);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_LIMIT, name,
+ mi->memomax);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NO_LIMIT, name);
+ }
+
+ /* I ripped this code out of ircservices 4.4.5, since I didn't want
+ to rewrite the whole thing (it pisses me off). */
+ if (na) {
+ if ((na->nc->flags & NI_MEMO_RECEIVE)
+ && (na->nc->flags & NI_MEMO_SIGNON)) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NOTIFY_ON, name);
+ } else if (na->nc->flags & NI_MEMO_RECEIVE) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NOTIFY_RECEIVE,
+ name);
+ } else if (na->nc->flags & NI_MEMO_SIGNON) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NOTIFY_SIGNON,
+ name);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_INFO_X_NOTIFY_OFF, name);
+ }
+ }
+
+ } else { /* !name || (!ci || na->nc == u->na->nc) */
+
+ if (!mi->memocount) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NO_MEMOS);
+ } else if (mi->memocount == 1) {
+ if (mi->memos[0].flags & MF_UNREAD)
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMO_UNREAD);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMO);
+ } else {
+ int count = 0, i;
+ for (i = 0; i < mi->memocount; i++) {
+ if (mi->memos[i].flags & MF_UNREAD)
+ count++;
+ }
+ if (count == mi->memocount)
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMOS_ALL_UNREAD,
+ count);
+ else if (count == 0)
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMOS, mi->memocount);
+ else if (count == 1)
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMOS_ONE_UNREAD,
+ mi->memocount);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_MEMOS_SOME_UNREAD,
+ mi->memocount, count);
+ }
+
+ if (mi->memomax == 0) {
+ if (!is_servadmin && hardmax)
+ notice_lang(s_MemoServ, u, MEMO_INFO_HARD_LIMIT_ZERO);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_LIMIT_ZERO);
+ } else if (mi->memomax > 0) {
+ if (!is_servadmin && hardmax)
+ notice_lang(s_MemoServ, u, MEMO_INFO_HARD_LIMIT,
+ mi->memomax);
+ else
+ notice_lang(s_MemoServ, u, MEMO_INFO_LIMIT, mi->memomax);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NO_LIMIT);
+ }
+
+ /* Ripped too. But differently because of a seg fault (loughs) */
+ if ((u->na->nc->flags & NI_MEMO_RECEIVE)
+ && (u->na->nc->flags & NI_MEMO_SIGNON)) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NOTIFY_ON);
+ } else if (u->na->nc->flags & NI_MEMO_RECEIVE) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NOTIFY_RECEIVE);
+ } else if (u->na->nc->flags & NI_MEMO_SIGNON) {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NOTIFY_SIGNON);
+ } else {
+ notice_lang(s_MemoServ, u, MEMO_INFO_NOTIFY_OFF);
+ }
+ }
+ return MOD_CONT; /* if (name && (ci || na->nc != u->na->nc)) */
+}
+
+/*************************************************************************/
+/**
+ * Allow the easy sending of memo's to all user's on the oper/admin/root lists
+ * - Rob
+ * Opers in several lists won't get the memo twice from now on
+ * - Certus
+ **/
+
+static int do_staff(User * u)
+{
+ NickCore *nc;
+ int i, z = 0;
+ char *text = strtok(NULL, "");
+
+ if (readonly) {
+ notice_lang(s_MemoServ, u, MEMO_SEND_DISABLED);
+ return MOD_CONT;
+ } else if (checkDefCon(DEFCON_NO_NEW_MEMOS)) {
+ notice_lang(s_MemoServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ } else if (text == NULL) {
+ syntax_error(s_MemoServ, u, "SEND", MEMO_SEND_SYNTAX);
+ return MOD_CONT;
+ }
+
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ if (nick_is_services_oper(nc))
+ memo_send(u, nc->display, text, z);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+/**
+ * Send a memo to all registered nicks
+ * - Certus - 06/06/2003
+ **/
+static int do_sendall(User * u)
+{
+ int i, z = 1;
+ NickCore *nc;
+ char *text = strtok(NULL, "");
+
+
+
+ if (readonly) {
+ notice_lang(s_MemoServ, u, MEMO_SEND_DISABLED);
+ return MOD_CONT;
+ } else if (checkDefCon(DEFCON_NO_NEW_MEMOS)) {
+ notice_lang(s_MemoServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ } else if (!text) {
+ syntax_error(s_MemoServ, u, "SENDALL", MEMO_SEND_SYNTAX);
+ return MOD_CONT;
+ }
+
+
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ if (stricmp(u->nick, nc->display) != 0)
+ memo_send(u, nc->display, text, z);
+ } /* /nc */
+ } /* /i */
+
+ notice_lang(s_MemoServ, u, MEMO_MASS_SENT);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static void new_memo_mail(NickCore * nc, Memo * m)
+{
+ MailInfo *mail = NULL;
+
+ if (!nc || !m)
+ return;
+
+ mail = MailMemoBegin(nc);
+ if (!mail) {
+ return;
+ }
+ fprintf(mail->pipe, getstring2(NULL, MEMO_MAIL_TEXT1), nc->display);
+ fprintf(mail->pipe, "\n");
+ fprintf(mail->pipe, getstring2(NULL, MEMO_MAIL_TEXT2), m->sender,
+ m->number);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, MEMO_MAIL_TEXT3));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, "%s", m->text);
+ fprintf(mail->pipe, "\n");
+ MailEnd(mail);
+ return;
+}
+
+/*************************************************************************/
+/* Send a memo to a nick/channel requesting a receipt. */
+
+static int do_rsend(User * u)
+{
+ char *name = strtok(NULL, " ");
+ char *text = strtok(NULL, "");
+ int z = 3;
+
+ if (MSMemoReceipt == 1) {
+ /* Services opers and above can use rsend */
+ if (is_services_oper(u)) {
+ memo_send(u, name, text, z);
+ } else {
+ notice_lang(s_MemoServ, u, ACCESS_DENIED);
+ }
+ } else if (MSMemoReceipt == 2) {
+ /* Everybody can use rsend */
+ memo_send(u, name, text, z);
+ } else {
+ /* rsend has been disabled */
+ notice_lang(s_MemoServ, u, MEMO_RSEND_DISABLED);
+ }
+
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+/* Send receipt notification to sender. */
+
+void rsend_notify(User * u, Memo * m, const char *chan)
+{
+ NickAlias *na;
+ NickCore *nc;
+ char text[256];
+ const char *fmt;
+
+ /* Only send receipt if memos are allowed */
+ if ((!readonly) && (!checkDefCon(DEFCON_NO_NEW_MEMOS))) {
+
+ /* Get nick alias for sender */
+ na = findnick(m->sender);
+
+ if (!na) {
+ return;
+ }
+
+ /* Get nick core for sender */
+ nc = na->nc;
+
+ if (!nc) {
+ return;
+ }
+
+ /* Text of the memo varies if the recepient was a
+ nick or channel */
+ if (chan) {
+ fmt = getstring(na, MEMO_RSEND_CHAN_MEMO_TEXT);
+ sprintf(text, fmt, chan);
+ } else {
+ fmt = getstring(na, MEMO_RSEND_NICK_MEMO_TEXT);
+ sprintf(text, fmt);
+ }
+
+ /* Send notification */
+ memo_send(u, m->sender, text, 2);
+
+ /* Notify recepient of the memo that a notification has
+ been sent to the sender */
+ notice_lang(s_MemoServ, u, MEMO_RSEND_USER_NOTIFICATION,
+ nc->display);
+ }
+
+ /* Remove receipt flag from the original memo */
+ m->flags &= ~MF_RECEIPT;
+
+ return;
+}
+
+
+
+/*************************************************************************/
+/* This function checks whether the last memo you sent to person X has been read
+ or not. Note that this function does only work with nicks, NOT with chans. */
+
+static int do_memocheck(User * u)
+{
+ NickAlias *na = NULL;
+ MemoInfo *mi = NULL;
+ int i, found = 0;
+ char *stime = NULL;
+ char *recipient = strtok(NULL, "");
+
+ if (!recipient) {
+ syntax_error(s_MemoServ, u, "CHECK", MEMO_CHECK_SYNTAX);
+ return MOD_CONT;
+ } else if (!nick_recognized(u)) {
+ notice_lang(s_MemoServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ return MOD_CONT;
+ } else if (!(na = findnick(recipient))) {
+ notice_lang(s_MemoServ, u, NICK_X_NOT_REGISTERED, recipient);
+ return MOD_CONT;
+ }
+
+ mi = &na->nc->memos;
+
+/* Okay, I know this looks strange but we wanna get the LAST memo, so we
+ have to loop backwards */
+
+ for (i = (mi->memocount - 1); i >= 0; i--) {
+ if (!stricmp(mi->memos[i].sender, u->nick)) {
+ found = 1; /* Yes, we've found the memo */
+
+ stime = strdup(ctime(&mi->memos[i].time));
+ *(stime + strlen(stime) - 1) = ' '; /* cut the f*cking \0 terminator and replace it with a single space */
+
+ if (mi->memos[i].flags & MF_UNREAD)
+ notice_lang(s_MemoServ, u, MEMO_CHECK_NOT_READ, na->nick,
+ stime);
+ else
+ notice_lang(s_MemoServ, u, MEMO_CHECK_READ, na->nick,
+ stime);
+ break;
+ }
+ }
+
+ if (!found)
+ notice_lang(s_MemoServ, u, MEMO_CHECK_NO_MEMO, na->nick);
+
+ if (stime)
+ free(stime);
+
+ return MOD_CONT;
+}
+
+
+/*************************************************************************/
diff --git a/src/messages.c b/src/messages.c
new file mode 100644
index 000000000..9e5d4466b
--- /dev/null
+++ b/src/messages.c
@@ -0,0 +1,1284 @@
+/* Definitions of IRC message functions and list of messages.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "messages.h"
+#include "language.h"
+
+static char *uplink;
+int servernum;
+/* List of messages is at the bottom of the file. */
+
+/*************************************************************************/
+/*************************************************************************/
+
+static int m_nickcoll(char *source, int ac, char **av)
+{
+ if (ac < 1)
+ return MOD_CONT;
+ if (!skeleton && !readonly)
+ introduce_user(av[0]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_ping(char *source, int ac, char **av)
+{
+ if (ac < 1)
+ return MOD_CONT;
+ send_cmd(ServerName, "PONG %s %s", ac > 1 ? av[1] : ServerName, av[0]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_away(char *source, int ac, char **av)
+{
+ User *u = finduser(source);
+
+ if (u && (ac == 0 || *av[0] == 0)) /* un-away */
+ check_memos(u);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+
+#ifdef IRC_BAHAMUT
+
+static int m_capab(char *source, int ac, char **av)
+{
+ int i;
+
+ for (i = 0; i < ac; i++) {
+ if (!stricmp(av[i], "NOQUIT"))
+ uplink_capab |= CAPAB_NOQUIT;
+ else if (!stricmp(av[i], "TSMODE"))
+ uplink_capab |= CAPAB_TSMODE;
+ else if (!stricmp(av[i], "UNCONNECT"))
+ uplink_capab |= CAPAB_UNCONNECT;
+ }
+
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+static int m_cs(char *source, int ac, char **av)
+{
+ User *u;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ if (ac < 1 || skeleton)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", s_ChanServ, source);
+ notice(s_ChanServ, source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ starttime = time(NULL);
+ if (!is_oper(u) && CSOpersOnly)
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ else
+ chanserv(u, av[0]);
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+static int m_hs(char *source, int ac, char **av)
+{
+ User *u;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ if (ac < 1 || skeleton)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", s_HelpServ, source);
+ notice(s_HelpServ, source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ starttime = time(NULL);
+
+ notice_help(s_HelpServ, u, HELP_HELP, s_NickServ, s_ChanServ,
+ s_MemoServ);
+ if (s_BotServ)
+ notice_help(s_HelpServ, u, HELP_HELP_BOT, s_BotServ);
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+static int m_join(char *source, int ac, char **av)
+{
+ if (ac != 1)
+ return MOD_CONT;
+ do_join(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_kick(char *source, int ac, char **av)
+{
+ if (ac != 3)
+ return MOD_CONT;
+ do_kick(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_kill(char *source, int ac, char **av)
+{
+ BotInfo *bi;
+
+ if (ac != 2)
+ return MOD_CONT;
+ /* Recover if someone kills us. */
+ if (stricmp(av[0], s_OperServ) == 0 ||
+ (s_OperServAlias && stricmp(av[0], s_OperServAlias) == 0) ||
+ stricmp(av[0], s_NickServ) == 0 ||
+ (s_NickServAlias && stricmp(av[0], s_NickServAlias) == 0) ||
+ stricmp(av[0], s_ChanServ) == 0 ||
+ (s_ChanServAlias && stricmp(av[0], s_ChanServAlias) == 0) ||
+ stricmp(av[0], s_MemoServ) == 0 ||
+ (s_MemoServAlias && stricmp(av[0], s_MemoServAlias) == 0) ||
+ (s_HostServ && stricmp(av[0], s_HostServ) == 0) ||
+ (s_HostServAlias && stricmp(av[0], s_HostServAlias) == 0) ||
+ (s_BotServ && stricmp(av[0], s_BotServ) == 0) ||
+ (s_BotServAlias && stricmp(av[0], s_BotServAlias) == 0) ||
+ stricmp(av[0], s_HelpServ) == 0 ||
+ (s_HelpServAlias && stricmp(av[0], s_HelpServAlias) == 0) ||
+ (s_DevNull && stricmp(av[0], s_DevNull) == 0) ||
+ (s_DevNullAlias && stricmp(av[0], s_DevNullAlias) == 0) ||
+ stricmp(av[0], s_GlobalNoticer) == 0 ||
+ (s_GlobalNoticerAlias && stricmp(av[0], s_GlobalNoticerAlias) == 0)
+ ) {
+ if (!readonly && !skeleton)
+ introduce_user(av[0]);
+ } else if (s_BotServ && (bi = findbot(av[0]))) {
+ if (!readonly && !skeleton) {
+ introduce_user(av[0]);
+ bot_rejoin_all(bi);
+ }
+ } else {
+ do_kill(source, ac, av);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_mode(char *source, int ac, char **av)
+{
+ if (*av[0] == '#' || *av[0] == '&') {
+ if (ac < 2)
+ return MOD_CONT;
+ do_cmode(source, ac, av);
+ } else {
+ if (ac != 2)
+ return MOD_CONT;
+ do_umode(source, ac, av);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_motd(char *source, int ac, char **av)
+{
+ FILE *f;
+ char buf[BUFSIZE];
+
+ f = fopen(MOTDFilename, "r");
+ send_cmd(ServerName, "375 %s :- %s Message of the Day",
+ source, ServerName);
+ if (f) {
+ while (fgets(buf, sizeof(buf), f)) {
+ buf[strlen(buf) - 1] = 0;
+ send_cmd(ServerName, "372 %s :- %s", source, buf);
+ }
+ fclose(f);
+ } else {
+ send_cmd(ServerName, "372 %s :- MOTD file not found! Please "
+ "contact your IRC administrator.", source);
+ }
+ send_cmd(ServerName, "376 %s :End of /MOTD command.", source);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+static int m_ms(char *source, int ac, char **av)
+{
+ User *u;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ if (ac < 1 || skeleton)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", s_MemoServ, source);
+ notice(s_MemoServ, source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ starttime = time(NULL);
+
+ memoserv(u, av[0]);
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+static int m_nick(char *source, int ac, char **av)
+{
+ if (ac != 2) {
+#if defined(IRC_HYBRID)
+ User *user = do_nick(source, av[0], av[4], av[5], av[6], av[7],
+ strtoul(av[2], NULL, 10), 0);
+ if (user)
+ set_umode(user, 1, &av[3]);
+#else
+#if defined(IRC_BAHAMUT)
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ User *user = do_nick(source, av[0], av[4], av[5], av[6], av[9],
+ strtoul(av[2], NULL, 10), strtoul(av[7], NULL,
+ 0),
+ strtoul(av[8], NULL, 0), "*");
+# else
+ User *user = do_nick(source, av[0], av[4], av[5], av[6], av[9],
+ strtoul(av[2], NULL, 10), strtoul(av[7], NULL,
+ 0),
+ strtoul(av[8], NULL, 0));
+# endif
+ if (user)
+ set_umode(user, 1, &av[3]);
+#elif defined(IRC_UNREAL)
+ if (ac == 7) {
+ /* For some reasons, Unreal sends this sometimes */
+ do_nick(source, av[0], av[3], av[4], av[5], av[6],
+ strtoul(av[2], NULL, 10), 0, "*");
+ } else {
+ User *user = do_nick(source, av[0], av[3], av[4], av[5], av[9],
+ strtoul(av[2], NULL, 10), strtoul(av[6],
+ NULL,
+ 0),
+ av[8]);
+
+ if (user)
+ set_umode(user, 1, &av[7]);
+ }
+#else
+# if defined(IRC_ULTIMATE)
+ if (ac == 7) {
+ do_nick(source, av[0], av[3], av[4], av[5], av[6],
+ strtoul(av[2], NULL, 10), 0);
+ } else {
+ do_nick(source, av[0], av[3], av[4], av[5], av[7],
+ strtoul(av[2], NULL, 10), strtoul(av[6], NULL, 0));
+ }
+/* PTlink IRCd - PTS4 */
+#elif defined(IRC_PTLINK)
+ User *user = do_nick(source, av[0], av[4], av[6], av[7], av[8],
+ strtoul(av[2], NULL, 10), 0, av[5]);
+ if (user)
+ set_umode(user, 1, &av[3]);
+#else
+ do_nick(source, av[0], av[3], av[4], av[5], av[7],
+ strtoul(av[2], NULL, 10), strtoul(av[6], NULL, 0));
+# endif
+#endif
+#endif
+ } else {
+ do_nick(source, av[0], NULL, NULL, NULL, NULL,
+ strtoul(av[1], NULL, 10), 0);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef IRC_ULTIMATE3
+
+static int m_client(char *source, int ac, char **av)
+{
+ if (ac != 2) {
+ User *user = do_nick(source, av[0], av[5], av[6], av[8], av[11],
+ strtoul(av[2], NULL, 10), strtoul(av[9], NULL,
+ 0),
+ strtoul(av[10], NULL, 0), av[7]);
+ if (user) {
+ set_umode(user, 1, &av[3]);
+ }
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_RAGE2
+
+static int m_snick(char *source, int ac, char **av)
+{
+ if (ac != 2) {
+ User *user = do_nick(source, av[0], av[3], av[4], av[8], av[10],
+ strtoul(av[1], NULL, 10), strtoul(av[7], NULL,
+ 0),
+ strtoul(av[5], NULL, 0), av[6]);
+ if (user) {
+ set_umode(user, 1, &av[9]);
+ }
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+static int m_ns(char *source, int ac, char **av)
+{
+ User *u;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ if (ac < 1 || skeleton)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", s_NickServ, source);
+ notice(s_NickServ, source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ starttime = time(NULL);
+
+ nickserv(u, av[0]);
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_PTLINK
+/*
+ * Note: This function has no validation whatsoever. Also, as of PTlink6.15.1
+ * when you /deoper you get to keep your vindent, but you lose your vhost. In
+ * that case serives will *NOT* modify it's internal record for the vhost. We
+ * need to address this in the future.
+ */
+static int m_newmask(char *source, int ac, char **av)
+{
+ User *u;
+ char *newhost = NULL, *newuser = NULL;
+
+ if (ac != 1)
+ return MOD_CONT;
+ u = finduser(source);
+
+ if (!u) {
+ alog("user: NEWMASK for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ newuser = myStrGetOnlyToken(av[0], '@', 0);
+ if (newuser) {
+ newhost = myStrGetTokenRemainder(av[0], '@', 1);
+ change_user_username(u, newuser);
+ } else {
+ newhost = av[0];
+ }
+
+ if (*newhost == '@')
+ newhost++;
+
+ if (newhost) {
+ change_user_host(u, newhost);
+ }
+
+ return MOD_CONT;
+}
+#endif
+
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+static int m_os(char *source, int ac, char **av)
+{
+ User *u;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ if (ac < 1)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", s_OperServ, source);
+ notice(s_OperServ, source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ starttime = time(NULL);
+
+ if (is_oper(u)) {
+ operserv(u, av[0]);
+ } else {
+ notice_lang(s_OperServ, u, ACCESS_DENIED);
+
+ if (WallBadOS)
+ wallops(s_OperServ,
+ "Denied access to %s from %s!%s@%s (non-oper)",
+ s_OperServ, u->nick, u->username, u->host);
+ }
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+static int m_part(char *source, int ac, char **av)
+{
+ if (ac < 1 || ac > 2)
+ return MOD_CONT;
+ do_part(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_privmsg(char *source, int ac, char **av)
+{
+ char *s;
+ time_t starttime, stoptime; /* When processing started and finished */
+
+ BotInfo *bi;
+ ChannelInfo *ci;
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(source);
+
+ if (!u) {
+ alog("%s: user record for %s not found", av[1], source);
+ notice(av[1], source, getstring(NULL, USER_RECORD_NOT_FOUND));
+ return MOD_CONT;
+ }
+
+ if (*av[0] == '#') {
+ if (s_BotServ && (ci = cs_findchan(av[0])))
+ if (!(ci->flags & CI_VERBOTEN) && ci->c && ci->bi) /* Some paranoia checks */
+ botchanmsgs(u, ci, av[1]);
+ } else {
+
+ /* Check if we should ignore. Operators always get through. */
+ if (allow_ignore && !is_oper(u)) {
+ IgnoreData *ign = get_ignore(source);
+ if (ign && ign->time > time(NULL)) {
+ alog("Ignored message from %s: \"%s\"", source, inbuf);
+ return MOD_CONT;
+ }
+ }
+
+ /* If a server is specified (nick@server format), make sure it matches
+ * us, and strip it off. */
+ s = strchr(av[0], '@');
+ if (s) {
+ *s++ = 0;
+ if (stricmp(s, ServerName) != 0)
+ return MOD_CONT;
+ }
+
+ starttime = time(NULL);
+
+ if ((stricmp(av[0], s_OperServ) == 0)
+ || (s_OperServAlias && (stricmp(av[0], s_OperServAlias) == 0))) {
+ if (is_oper(u)) {
+ operserv(u, av[1]);
+ } else {
+ notice_lang(s_OperServ, u, ACCESS_DENIED);
+
+ if (WallBadOS)
+ wallops(s_OperServ,
+ "Denied access to %s from %s!%s@%s (non-oper)",
+ s_OperServ, u->nick, u->username, u->host);
+ }
+ } else if ((stricmp(av[0], s_NickServ) == 0)
+ || (s_NickServAlias
+ && (stricmp(av[0], s_NickServAlias) == 0))) {
+ nickserv(u, av[1]);
+ } else if ((stricmp(av[0], s_ChanServ) == 0)
+ || (s_ChanServAlias
+ && (stricmp(av[0], s_ChanServAlias) == 0))) {
+ if (!is_oper(u) && CSOpersOnly)
+ notice_lang(s_ChanServ, u, ACCESS_DENIED);
+ else
+ chanserv(u, av[1]);
+ } else if ((stricmp(av[0], s_MemoServ) == 0)
+ || (s_MemoServAlias
+ && (stricmp(av[0], s_MemoServAlias) == 0))) {
+ memoserv(u, av[1]);
+ } else if (s_HostServ && ((stricmp(av[0], s_HostServ) == 0)
+ || (s_HostServAlias
+ && (stricmp(av[0], s_HostServAlias)
+ == 0)))) {
+ hostserv(u, av[1]);
+ } else if (s_HelpServ && ((stricmp(av[0], s_HelpServ) == 0)
+ || (s_HelpServAlias
+ && (stricmp(av[0], s_HelpServAlias)
+ == 0)))) {
+ helpserv(u, av[1]);
+ } else if (s_BotServ && ((stricmp(av[0], s_BotServ) == 0)
+ || (s_BotServAlias
+ && (stricmp(av[0], s_BotServAlias) ==
+ 0)))) {
+ botserv(u, av[1]);
+/* This HelpServ code is history since HelpServ is a REAL service */
+
+/* } else if ((stricmp(av[0], s_HelpServ) == 0)
+ || (s_HelpServAlias
+ && (stricmp(av[0], s_HelpServAlias) == 0))) {
+ notice_help(s_HelpServ, u, HELP_HELP, s_NickServ, s_ChanServ,
+ s_MemoServ);
+ if (s_BotServ)
+ notice_help(s_HelpServ, u, HELP_HELP_BOT, s_BotServ); */
+ } else if (s_BotServ && (bi = findbot(av[0]))) {
+ botmsgs(u, bi, av[1]);
+ }
+
+ /* Add to ignore list if the command took a significant amount of time. */
+ if (allow_ignore) {
+ stoptime = time(NULL);
+ if (stoptime > starttime && *source && !strchr(source, '.'))
+ add_ignore(source, stoptime - starttime);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_quit(char *source, int ac, char **av)
+{
+ if (ac != 1)
+ return MOD_CONT;
+ do_quit(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_squit(char *source, int ac, char **av)
+{
+ if (ac != 2)
+ return MOD_CONT;
+ do_squit(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_server(char *source, int ac, char **av)
+{
+ if (!stricmp(av[1], "1"))
+ uplink = sstrdup(av[0]);
+#ifdef IRC_PTLINK
+ if (ac != 4)
+#else
+ if (ac != 3)
+#endif
+ return MOD_CONT;
+ do_server(source, ac, av);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE3)
+
+static int m_sethost(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ if (debug)
+ alog("user: SETHOST for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_host(u, av[1]);
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#ifdef IRC_RAGE2
+
+static int m_vhost(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ if (debug)
+ alog("user: VHOST for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_host(u, av[1]);
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+
+static int m_chghost(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ alog("user: CHGHOST for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_host(u, av[1]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_sethost(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 1)
+ return MOD_CONT;
+
+ u = finduser(source);
+ if (!u) {
+ if (debug)
+ alog("user: SETHOST for nonexistent user %s", source);
+ return MOD_CONT;
+ }
+
+ change_user_host(u, av[0]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_chgident(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ alog("user: CHGIDENT for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_username(u, av[1]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_setident(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 1)
+ return MOD_CONT;
+
+ u = finduser(source);
+ if (!u) {
+ alog("user: SETIDENT for nonexistent user %s", source);
+ return MOD_CONT;
+ }
+
+ change_user_username(u, av[0]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_chgname(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ alog("user: CHGNAME for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_realname(u, av[1]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_setname(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 1)
+ return MOD_CONT;
+
+ u = finduser(source);
+ if (!u) {
+ alog("user: SETNAME for nonexistent user %s", source);
+ return MOD_CONT;
+ }
+
+ change_user_realname(u, av[0]);
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_BAHAMUT) || defined(IRC_HYBRID) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+
+static int m_sjoin(char *source, int ac, char **av)
+{
+ do_sjoin(source, ac, av);
+ return MOD_CONT;
+}
+
+#endif
+
+/*************************************************************************/
+
+static int m_stats(char *source, int ac, char **av)
+{
+ int i;
+ User *u;
+ NickCore *nc;
+
+ if (ac < 1)
+ return MOD_CONT;
+
+ switch (*av[0]) {
+ case 'l':
+ u = finduser(source);
+
+ if (u && is_oper(u)) {
+
+ if (servernum == 1) {
+ send_cmd(NULL,
+ "211 %s Server SendBuf SentBytes SentMsgs RecvBuf "
+ "RecvBytes RecvMsgs ConnTime", source);
+ send_cmd(NULL, "211 %s %s %d %d %d %d %d %d %ld", source,
+ RemoteServer, write_buffer_len(), total_written,
+ -1, read_buffer_len(), total_read, -1,
+ time(NULL) - start_time);
+ } else if (servernum == 2) {
+ send_cmd(NULL,
+ "211 %s Server SendBuf SentBytes SentMsgs RecvBuf "
+ "RecvBytes RecvMsgs ConnTime", source);
+ send_cmd(NULL, "211 %s %s %d %d %d %d %d %d %ld", source,
+ RemoteServer2, write_buffer_len(), total_written,
+ -1, read_buffer_len(), total_read, -1,
+ time(NULL) - start_time);
+ } else if (servernum == 3) {
+ send_cmd(NULL,
+ "211 %s Server SendBuf SentBytes SentMsgs RecvBuf "
+ "RecvBytes RecvMsgs ConnTime", source);
+ send_cmd(NULL, "211 %s %s %d %d %d %d %d %d %ld", source,
+ RemoteServer3, write_buffer_len(), total_written,
+ -1, read_buffer_len(), total_read, -1,
+ time(NULL) - start_time);
+ }
+ }
+
+ send_cmd(NULL, "219 %s l :End of /STATS report.", source);
+ break;
+ case 'o':
+ case 'O':
+/* Check whether the user is an operator */
+ u = finduser(source);
+ if (u && !is_oper(u) && HideStatsO) {
+ send_cmd(NULL, "219 %s %c :End of /STATS report.", source,
+ *av[0]);
+ } else {
+ for (i = 0; i < RootNumber; i++)
+ send_cmd(NULL, "243 %s O * * %s Root 0", source,
+ ServicesRoots[i]);
+ for (i = 0; i < servadmins.count && (nc = servadmins.list[i]);
+ i++)
+ send_cmd(NULL, "243 %s O * * %s Admin 0", source,
+ nc->display);
+ for (i = 0; i < servopers.count && (nc = servopers.list[i]);
+ i++)
+ send_cmd(NULL, "243 %s O * * %s Oper 0", source,
+ nc->display);
+
+ send_cmd(NULL, "219 %s %c :End of /STATS report.", source,
+ *av[0]);
+ }
+
+ break;
+
+ case 'u':{
+ int uptime = time(NULL) - start_time;
+ send_cmd(NULL, "242 %s :Services up %d day%s, %02d:%02d:%02d",
+ source, uptime / 86400,
+ (uptime / 86400 == 1) ? "" : "s",
+ (uptime / 3600) % 24, (uptime / 60) % 60,
+ uptime % 60);
+ send_cmd(NULL,
+ "250 %s :Current users: %d (%d ops); maximum %d",
+ source, usercnt, opcnt, maxusercnt);
+ send_cmd(NULL, "219 %s u :End of /STATS report.", source);
+ break;
+ } /* case 'u' */
+
+ default:
+ send_cmd(NULL, "219 %s %c :End of /STATS report.", source, *av[0]);
+ break;
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int m_time(char *source, int ac, char **av)
+{
+ time_t t;
+ struct tm *tm;
+ char buf[64];
+
+ time(&t);
+ tm = localtime(&t);
+ strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y %Z", tm);
+ send_cmd(NULL, "391 %s %s :%s", source, ServerName, buf);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+#ifdef IRC_HYBRID
+static int m_topic(char *source, int ac, char **av)
+{
+ if (ac == 4) {
+ do_topic(source, ac, av);
+ } else {
+ Channel *c = findchan(av[0]);
+ time_t topic_time = time(NULL);
+
+ if (!c) {
+ alog("channel: TOPIC %s for nonexistent channel %s",
+ merge_args(ac - 1, av + 1), av[0]);
+ return MOD_CONT;
+ }
+
+ if (check_topiclock(c, topic_time))
+ return MOD_CONT;
+
+ if (c->topic) {
+ free(c->topic);
+ c->topic = NULL;
+ }
+ if (ac > 1 && *av[1])
+ c->topic = sstrdup(av[1]);
+
+ strscpy(c->topic_setter, source, sizeof(c->topic_setter));
+ c->topic_time = topic_time;
+
+ record_topic(av[0]);
+ }
+ return MOD_CONT;
+}
+#else
+/*************************************************************************/
+static int m_topic(char *source, int ac, char **av)
+{
+ if (ac != 4)
+ return MOD_CONT;
+ do_topic(source, ac, av);
+ return MOD_CONT;
+}
+#endif
+/*************************************************************************/
+
+int m_version(char *source, int ac, char **av)
+{
+ if (source)
+ send_cmd(ServerName, "351 %s Anope-%s %s :%s -- %s",
+ source, version_number, ServerName, version_flags,
+ version_build);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+int m_whois(char *source, int ac, char **av)
+{
+ BotInfo *bi;
+ const char *clientdesc;
+
+ if (source && ac >= 1) {
+ if (stricmp(av[0], s_NickServ) == 0)
+ clientdesc = desc_NickServ;
+ else if (stricmp(av[0], s_ChanServ) == 0)
+ clientdesc = desc_ChanServ;
+ else if (stricmp(av[0], s_MemoServ) == 0)
+ clientdesc = desc_MemoServ;
+ else if (s_BotServ && stricmp(av[0], s_BotServ) == 0)
+ clientdesc = desc_BotServ;
+ else if (s_HostServ && stricmp(av[0], s_HostServ) == 0)
+ clientdesc = desc_HostServ;
+ else if (stricmp(av[0], s_HelpServ) == 0)
+ clientdesc = desc_HelpServ;
+ else if (stricmp(av[0], s_OperServ) == 0)
+ clientdesc = desc_OperServ;
+ else if (stricmp(av[0], s_GlobalNoticer) == 0)
+ clientdesc = desc_GlobalNoticer;
+ else if (s_DevNull && stricmp(av[0], s_DevNull) == 0)
+ clientdesc = desc_DevNull;
+ else if (s_BotServ && (bi = findbot(av[0]))) {
+ /* Bots are handled separately */
+ send_cmd(ServerName, "311 %s %s %s %s * :%s", source, bi->nick,
+ bi->user, bi->host, bi->real);
+ send_cmd(ServerName, "307 %s :%s is a registered nick", source,
+ bi->nick);
+ send_cmd(ServerName, "312 %s %s %s :%s", source, bi->nick,
+ ServerName, ServerDesc);
+ send_cmd(ServerName,
+ "317 %s %s %ld %ld :seconds idle, signon time",
+ source, bi->nick, time(NULL) - bi->lastmsg,
+ start_time);
+ send_cmd(ServerName, "318 %s %s :End of /WHOIS list.", source,
+ bi->nick);
+ return MOD_CONT;
+ } else {
+ send_cmd(ServerName, "401 %s %s :No such service.", source,
+ av[0]);
+ return MOD_CONT;
+ }
+ send_cmd(ServerName, "311 %s %s %s %s * :%s", source, av[0],
+ ServiceUser, ServiceHost, clientdesc);
+ send_cmd(ServerName, "312 %s %s %s :%s", source, av[0], ServerName,
+ ServerDesc);
+ send_cmd(ServerName,
+ "317 %s %s %ld %ld :seconds idle, signon time", source,
+ av[0], time(NULL) - start_time, start_time);
+ send_cmd(ServerName, "318 %s %s :End of /WHOIS list.", source,
+ av[0]);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef IRC_VIAGRA
+int m_vs(char *source, int ac, char **av)
+{
+ User *u;
+
+ if (ac != 2)
+ return MOD_CONT;
+
+ u = finduser(av[0]);
+ if (!u) {
+ alog("user: VS for nonexistent user %s", av[0]);
+ return MOD_CONT;
+ }
+
+ change_user_host(u, av[1]);
+ return MOD_CONT;
+
+}
+#endif
+
+/*************************************************************************/
+
+/* *INDENT-OFF* */
+void moduleAddMsgs(void) {
+ Message *m;
+ m = createMessage("401", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("436", m_nickcoll); addCoreMessage(IRCD,m);
+ m = createMessage("AWAY", m_away); addCoreMessage(IRCD,m);
+ m = createMessage("INVITE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("JOIN", m_join); addCoreMessage(IRCD,m);
+ m = createMessage("KICK", m_kick); addCoreMessage(IRCD,m);
+ m = createMessage("KILL", m_kill); addCoreMessage(IRCD,m);
+ m = createMessage("MODE", m_mode); addCoreMessage(IRCD,m);
+ m = createMessage("MOTD", m_motd); addCoreMessage(IRCD,m);
+ m = createMessage("NICK", m_nick); addCoreMessage(IRCD,m);
+ m = createMessage("NOTICE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("PART", m_part); addCoreMessage(IRCD,m);
+ m = createMessage("PASS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("PING", m_ping); addCoreMessage(IRCD,m);
+ m = createMessage("PRIVMSG", m_privmsg); addCoreMessage(IRCD,m);
+ m = createMessage("QUIT", m_quit); addCoreMessage(IRCD,m);
+ m = createMessage("SERVER", m_server); addCoreMessage(IRCD,m);
+ m = createMessage("SQUIT", m_squit); addCoreMessage(IRCD,m);
+ m = createMessage("STATS", m_stats); addCoreMessage(IRCD,m);
+ m = createMessage("TIME", m_time); addCoreMessage(IRCD,m);
+ m = createMessage("TOPIC", m_topic); addCoreMessage(IRCD,m);
+ m = createMessage("USER", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("VERSION", m_version); addCoreMessage(IRCD,m);
+ m = createMessage("WALLOPS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("WHOIS", m_whois); addCoreMessage(IRCD,m);
+
+ /* DALnet specific messages */
+ m = createMessage("AKILL", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("GLOBOPS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("GNOTICE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("GOPER", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("RAKILL", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SILENCE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVSKILL", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVSMODE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVSNICK", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVSNOOP", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SQLINE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("UNSQLINE", NULL); addCoreMessage(IRCD,m);
+
+ /* DreamForge specific messages */
+#ifdef IRC_DREAMFORGE
+ m = createMessage("PROTOCTL", NULL); addCoreMessage(IRCD,m);
+#endif
+
+ /* Bahamut specific messages */
+#ifdef IRC_BAHAMUT
+ m = createMessage("CAPAB", m_capab); addCoreMessage(IRCD,m);
+ m = createMessage("CS", m_cs); addCoreMessage(IRCD,m);
+ m = createMessage("HS", m_hs); addCoreMessage(IRCD,m);
+ m = createMessage("MS", m_ms); addCoreMessage(IRCD,m);
+ m = createMessage("NS", m_ns); addCoreMessage(IRCD,m);
+ m = createMessage("OS", m_os); addCoreMessage(IRCD,m);
+ m = createMessage("RS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SGLINE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SJOIN", m_sjoin); addCoreMessage(IRCD,m);
+ m = createMessage("SS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVINFO", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SZLINE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("UNSGLINE", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("UNSZLINE", NULL); addCoreMessage(IRCD,m);
+#endif
+ /* Hyb Messages */
+#ifdef IRC_HYBRID
+ m = createMessage("CAPAB", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SJOIN", m_sjoin); addCoreMessage(IRCD,m);
+ m = createMessage("SVINFO", NULL); addCoreMessage(IRCD,m);
+#endif
+
+
+#ifdef IRC_ULTIMATE
+ m = createMessage("CHGHOST", m_chghost); addCoreMessage(IRCD,m);
+ m = createMessage("CHGIDENT", m_chgident); addCoreMessage(IRCD,m);
+ m = createMessage("CHGNAME", m_chgname); addCoreMessage(IRCD,m);
+ m = createMessage("NETINFO", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SETHOST", m_sethost); addCoreMessage(IRCD,m);
+ m = createMessage("SETIDENT", m_setident); addCoreMessage(IRCD,m);
+ m = createMessage("SETNAME", m_setname); addCoreMessage(IRCD,m);
+ m = createMessage("VCTRL", NULL); addCoreMessage(IRCD,m);
+#endif
+
+#ifdef IRC_PTLINK
+ m = createMessage("NEWMASK" , m_newmask); addCoreMessage(IRCD,m);
+ m = createMessage("CAPAB" , NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVINFO" , NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SVSINFO" , NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SJOIN", m_sjoin); addCoreMessage(IRCD,m);
+#endif
+
+#ifdef IRC_UNREAL
+ m = createMessage("CHGHOST", m_chghost); addCoreMessage(IRCD,m);
+ m = createMessage("CHGIDENT", m_chgident); addCoreMessage(IRCD,m);
+ m = createMessage("CHGNAME", m_chgname); addCoreMessage(IRCD,m);
+ m = createMessage("NETINFO", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("SETHOST", m_sethost); addCoreMessage(IRCD,m);
+ m = createMessage("SETIDENT", m_setident); addCoreMessage(IRCD,m);
+ m = createMessage("SETNAME", m_setname); addCoreMessage(IRCD,m);
+#endif
+#ifdef IRC_VIAGRA
+ m = createMessage("CHGHOST", m_chghost); addCoreMessage(IRCD,m);
+ m = createMessage("CHGIDENT", m_chgident); addCoreMessage(IRCD,m);
+ m = createMessage("CHGNAME", m_chgname); addCoreMessage(IRCD,m);
+ m = createMessage("SETHOST", m_sethost); addCoreMessage(IRCD,m);
+ m = createMessage("SETIDENT", m_setident); addCoreMessage(IRCD,m);
+ m = createMessage("SETNAME", m_setname); addCoreMessage(IRCD,m);
+ m = createMessage("VS", m_vs); addCoreMessage(IRCD,m);
+#endif
+#ifdef IRC_ULTIMATE3
+ m = createMessage("SETHOST", m_sethost); addCoreMessage(IRCD,m);
+ m = createMessage("NETINFO", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("GCONNECT", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("NETGLOBAL", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("CHATOPS", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("NETCTRL", NULL); addCoreMessage(IRCD,m);
+ m = createMessage("CLIENT", m_client); addCoreMessage(IRCD,m);
+ m = createMessage("SMODE", NULL); addCoreMessage(IRCD,m);
+#endif
+#ifdef IRC_RAGE2
+ m = createMessage("SNICK", m_snick); addCoreMessage(IRCD,m);
+ m = createMessage("VHOST", m_vhost); addCoreMessage(IRCD,m);
+#endif
+}
+
+/* *INDENT-ON* */
+/*************************************************************************/
+
+Message *find_message(const char *name)
+{
+ Message *m;
+ m = findMessage(IRCD, name);
+ return m;
+}
+
+/*************************************************************************/
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 000000000..922f0e05d
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,711 @@
+/* Miscellaneous routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "language.h"
+
+/* Cheaper than isspace() or isblank() */
+#define issp(c) ((c) == 32)
+
+/*************************************************************************/
+
+/* toupper/tolower: Like the ANSI functions, but make sure we return an
+ * int instead of a (signed) char.
+ */
+
+int toupper(char c)
+{
+ if (islower(c))
+ return (unsigned char) c - ('a' - 'A');
+ else
+ return (unsigned char) c;
+}
+
+int tolower(char c)
+{
+ if (isupper(c))
+ return (unsigned char) c + ('a' - 'A');
+ else
+ return (unsigned char) c;
+}
+
+/*************************************************************************/
+
+/* strscpy: Copy at most len-1 characters from a string to a buffer, and
+ * add a null terminator after the last character copied.
+ */
+
+char *strscpy(char *d, const char *s, size_t len)
+{
+ char *d_orig = d;
+
+ if (!len)
+ return d;
+ while (--len && (*d++ = *s++));
+ *d = '\0';
+ return d_orig;
+}
+
+/*************************************************************************/
+
+/* stristr: Search case-insensitively for string s2 within string s1,
+ * returning the first occurrence of s2 or NULL if s2 was not
+ * found.
+ */
+
+char *stristr(char *s1, char *s2)
+{
+ register char *s = s1, *d = s2;
+
+ while (*s1) {
+ if (tolower(*s1) == tolower(*d)) {
+ s1++;
+ d++;
+ if (*d == 0)
+ return s;
+ } else {
+ s = ++s1;
+ d = s2;
+ }
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* strnrepl: Replace occurrences of `old' with `new' in string `s'. Stop
+ * replacing if a replacement would cause the string to exceed
+ * `size' bytes (including the null terminator). Return the
+ * string.
+ */
+
+char *strnrepl(char *s, int32 size, const char *old, const char *new)
+{
+ char *ptr = s;
+ int32 left = strlen(s);
+ int32 avail = size - (left + 1);
+ int32 oldlen = strlen(old);
+ int32 newlen = strlen(new);
+ int32 diff = newlen - oldlen;
+
+ while (left >= oldlen) {
+ if (strncmp(ptr, old, oldlen) != 0) {
+ left--;
+ ptr++;
+ continue;
+ }
+ if (diff > avail)
+ break;
+ if (diff != 0)
+ memmove(ptr + oldlen + diff, ptr + oldlen, left + 1);
+ strncpy(ptr, new, newlen);
+ ptr += newlen;
+ left -= oldlen;
+ }
+ return s;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* merge_args: Take an argument count and argument vector and merge them
+ * into a single string in which each argument is separated by
+ * a space.
+ */
+
+char *merge_args(int argc, char **argv)
+{
+ int i;
+ static char s[4096];
+ char *t;
+
+ t = s;
+ for (i = 0; i < argc; i++)
+ t += snprintf(t, sizeof(s) - (t - s), "%s%s", *argv++,
+ (i < argc - 1) ? " " : "");
+ return s;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* match_wild: Attempt to match a string to a pattern which might contain
+ * '*' or '?' wildcards. Return 1 if the string matches the
+ * pattern, 0 if not.
+ */
+
+static int do_match_wild(const char *pattern, const char *str, int docase)
+{
+ char c;
+ const char *s;
+
+ /* This WILL eventually terminate: either by *pattern == 0, or by a
+ * trailing '*'. */
+
+ for (;;) {
+ switch (c = *pattern++) {
+ case 0:
+ if (!*str)
+ return 1;
+ return 0;
+ case '?':
+ if (!*str)
+ return 0;
+ str++;
+ break;
+ case '*':
+ if (!*pattern)
+ return 1; /* trailing '*' matches everything else */
+ s = str;
+ while (*s) {
+ if ((docase ? (*s == *pattern)
+ : (tolower(*s) == tolower(*pattern)))
+ && do_match_wild(pattern, s, docase))
+ return 1;
+ s++;
+ }
+ break;
+ default:
+ if (docase ? (*str++ != c) : (tolower(*str++) != tolower(c)))
+ return 0;
+ break;
+ } /* switch */
+ }
+}
+
+
+int match_wild(const char *pattern, const char *str)
+{
+ return do_match_wild(pattern, str, 1);
+}
+
+int match_wild_nocase(const char *pattern, const char *str)
+{
+ return do_match_wild(pattern, str, 0);
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Process a string containing a number/range list in the form
+ * "n1[-n2][,n3[-n4]]...", calling a caller-specified routine for each
+ * number in the list. If the callback returns -1, stop immediately.
+ * Returns the sum of all nonnegative return values from the callback.
+ * If `count' is non-NULL, it will be set to the total number of times the
+ * callback was called.
+ *
+ * The callback should be of type range_callback_t, which is defined as:
+ * int (*range_callback_t)(User *u, int num, va_list args)
+ */
+
+int process_numlist(const char *numstr, int *count_ret,
+ range_callback_t callback, User * u, ...)
+{
+ int n1, n2, i;
+ int res = 0, retval = 0, count = 0;
+ va_list args;
+
+ va_start(args, u);
+
+ /*
+ * This algorithm ignores invalid characters, ignores a dash
+ * when it precedes a comma, and ignores everything from the
+ * end of a valid number or range to the next comma or null.
+ */
+ for (;;) {
+ n1 = n2 = strtol(numstr, (char **) &numstr, 10);
+ numstr += strcspn(numstr, "0123456789,-");
+ if (*numstr == '-') {
+ numstr++;
+ numstr += strcspn(numstr, "0123456789,");
+ if (isdigit(*numstr)) {
+ n2 = strtol(numstr, (char **) &numstr, 10);
+ numstr += strcspn(numstr, "0123456789,-");
+ }
+ }
+ for (i = n1; i <= n2 && i >= 0; i++) {
+ int res = callback(u, i, args);
+ count++;
+ if (res < 0)
+ break;
+ retval += res;
+ if (count >= 32767) {
+ if (count_ret)
+ *count_ret = count;
+ return retval;
+ }
+ }
+ if (res < -1)
+ break;
+ numstr += strcspn(numstr, ",");
+ if (*numstr)
+ numstr++;
+ else
+ break;
+ }
+ if (count_ret)
+ *count_ret = count;
+ return retval;
+}
+
+/*************************************************************************/
+
+/* dotime: Return the number of seconds corresponding to the given time
+ * string. If the given string does not represent a valid time,
+ * return -1.
+ *
+ * A time string is either a plain integer (representing a number
+ * of seconds), or an integer followed by one of these characters:
+ * "s" (seconds), "m" (minutes), "h" (hours), or "d" (days).
+ */
+
+int dotime(const char *s)
+{
+ int amount;
+
+ amount = strtol(s, (char **) &s, 10);
+ if (*s) {
+ switch (*s) {
+ case 's':
+ return amount;
+ case 'm':
+ return amount * 60;
+ case 'h':
+ return amount * 3600;
+ case 'd':
+ return amount * 86400;
+ default:
+ return -1;
+ }
+ } else {
+ return amount;
+ }
+}
+
+/*************************************************************************/
+
+/* Expresses in a string the period of time represented by a given amount
+ of seconds (with days/hours/minutes). */
+
+char *duration(NickAlias * na, char *buf, int bufsize, time_t seconds)
+{
+ int days = 0, hours = 0, minutes = 0;
+ int need_comma = 0;
+
+ char buf2[64], *end;
+ char *comma = getstring(na, COMMA_SPACE);
+
+ /* We first calculate everything */
+ days = seconds / 86400;
+ seconds -= (days * 86400);
+ hours = seconds / 3600;
+ seconds -= (hours * 3600);
+ minutes = seconds / 60;
+
+ if (!days && !hours && !minutes) {
+ snprintf(buf, bufsize,
+ getstring(na,
+ (seconds <=
+ 1 ? DURATION_SECOND : DURATION_SECONDS)),
+ seconds);
+ } else {
+ end = buf;
+ if (days) {
+ snprintf(buf2, sizeof(buf2),
+ getstring(na,
+ (days == 1 ? DURATION_DAY : DURATION_DAYS)),
+ days);
+ end += snprintf(end, bufsize - (end - buf), "%s", buf2);
+ need_comma = 1;
+ }
+ if (hours) {
+ snprintf(buf2, sizeof(buf2),
+ getstring(na,
+ (hours ==
+ 1 ? DURATION_HOUR : DURATION_HOURS)),
+ hours);
+ end +=
+ snprintf(end, bufsize - (end - buf), "%s%s",
+ (need_comma ? comma : ""), buf2);
+ need_comma = 1;
+ }
+ if (minutes) {
+ snprintf(buf2, sizeof(buf2),
+ getstring(na,
+ (minutes ==
+ 1 ? DURATION_MINUTE : DURATION_MINUTES)),
+ minutes);
+ end +=
+ snprintf(end, bufsize - (end - buf), "%s%s",
+ (need_comma ? comma : ""), buf2);
+ need_comma = 1;
+ }
+ }
+
+ return buf;
+}
+
+/*************************************************************************/
+
+/* Generates a human readable string of type "expires in ..." */
+
+char *expire_left(NickAlias * na, char *buf, int len, time_t expires)
+{
+ time_t now = time(NULL);
+
+ if (!expires) {
+ strncpy(buf, getstring(na, NO_EXPIRE), len);
+ } else if (expires <= now) {
+ strncpy(buf, getstring(na, EXPIRES_SOON), len);
+ } else {
+ time_t diff = expires - now + 59;
+
+ if (diff >= 86400) {
+ int days = diff / 86400;
+ snprintf(buf, len,
+ getstring(na, (days == 1) ? EXPIRES_1D : EXPIRES_D),
+ days);
+ } else {
+ if (diff <= 3600) {
+ int minutes = diff / 60;
+ snprintf(buf, len,
+ getstring(na,
+ (minutes ==
+ 1) ? EXPIRES_1M : EXPIRES_M), minutes);
+ } else {
+ int hours = diff / 3600, minutes;
+ diff -= (hours * 3600);
+ minutes = diff / 60;
+ snprintf(buf, len,
+ getstring(na,
+ ((hours == 1
+ && minutes ==
+ 1) ? EXPIRES_1H1M : ((hours == 1
+ && minutes !=
+ 1) ? EXPIRES_1HM
+ : ((hours != 1
+ && minutes ==
+ 1) ?
+ EXPIRES_H1M :
+ EXPIRES_HM)))),
+ hours, minutes);
+ }
+ }
+ }
+
+ return buf;
+}
+
+/**
+ * Return 1 if a host is valid, 0 if it isnt.
+ * host = string to check
+ * type = format, 1 = ip4addr, 2 = hostname
+ *
+ * shortname = ( letter / digit ) *( letter / digit / "-" ) *( letter / digit )
+ * hostname = shortname *( "." shortname )
+ * ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
+ *
+ **/
+
+int doValidHost(const char *host, int type)
+{
+ int idx = 0;
+ int len = 0;
+ int sec_len = 0;
+ int dots = 1;
+ if (type != 1 && type != 2) {
+ return 0;
+ }
+ if (!host) {
+ return 0;
+ }
+
+ len = strlen(host);
+
+ if (len > HOSTMAX) {
+ return 0;
+ }
+
+ switch (type) {
+ case 1:
+ for (idx = 0; idx < len; idx++) {
+ if (isdigit(host[idx])) {
+ if (sec_len < 3) {
+ sec_len++;
+ } else {
+ return 0;
+ }
+ } else {
+ if (idx == 0) {
+ return 0;
+ } /* cant start with a non-digit */
+ if (host[idx] != '.') {
+ return 0;
+ } /* only . is a valid non-digit */
+ if (sec_len > 3) {
+ return 0;
+ } /* sections cant be more than 3 digits */
+ sec_len = 0;
+ dots++;
+ }
+ }
+ if (dots != 4) {
+ return 0;
+ }
+ break;
+ case 2:
+ dots = 0;
+ for (idx = 0; idx < len; idx++) {
+ if (!isalnum(host[idx])) {
+ if (idx == 0) {
+ return 0;
+ }
+ if ((host[idx] != '.') && (host[idx] != '-')) {
+ return 0;
+ }
+ if (host[idx] == '.') {
+ dots++;
+ }
+ }
+ }
+ if (host[len - 1] == '.') {
+ return 0;
+ }
+ /**
+ * Ultimate3 dosnt like a non-dotted hosts at all, nor does unreal,
+ * so just dont allow them.
+ **/
+ if (dots == 0) {
+ return 0;
+ }
+
+ break;
+ }
+ return 1;
+}
+
+/**
+ * Return 1 if a host is valid, 0 if it isnt.
+ * host = string to check
+ * type = format, 1 = ip4addr, 2 = hostname, 3 = either
+ *
+ * shortname = ( letter / digit ) *( letter / digit / "-" ) *( letter / digit )
+ * hostname = shortname *( "." shortname )
+ * ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
+ *
+ **/
+int isValidHost(const char *host, int type)
+{
+ int status = 0;
+ if (type == 3) {
+ if (!(status = doValidHost(host, 1))) {
+ status = doValidHost(host, 2);
+ }
+ } else {
+ status = doValidHost(host, type);
+ }
+ return status;
+}
+
+int isvalidchar(const char c)
+{
+ if (((c >= 'A') && (c <= 'Z')) ||
+ ((c >= 'a') && (c <= 'z')) ||
+ ((c >= '0') && (c <= '9')) || (c == '.') || (c == '-'))
+ return 1;
+ else
+ return 0;
+}
+
+char *myStrGetToken(const char *str, const char dilim, int token_number)
+{
+ int len, idx, counter = 0, start_pos = 0;
+ char *substring = NULL;
+ if (!str) {
+ return NULL;
+ }
+ len = strlen(str);
+ for (idx = 0; idx <= len; idx++) {
+ if ((str[idx] == dilim) || (idx == len)) {
+ if (counter == token_number) {
+ substring = myStrSubString(str, start_pos, idx);
+ counter++;
+ } else {
+ start_pos = idx + 1;
+ counter++;
+ }
+ }
+ }
+ return substring;
+}
+
+char *myStrGetOnlyToken(const char *str, const char dilim,
+ int token_number)
+{
+ int len, idx, counter = 0, start_pos = 0;
+ char *substring = NULL;
+ if (!str) {
+ return NULL;
+ }
+ len = strlen(str);
+ for (idx = 0; idx <= len; idx++) {
+ if (str[idx] == dilim) {
+ if (counter == token_number) {
+ if (str[idx] == '\r')
+ substring = myStrSubString(str, start_pos, idx - 1);
+ else
+ substring = myStrSubString(str, start_pos, idx);
+ counter++;
+ } else {
+ start_pos = idx + 1;
+ counter++;
+ }
+ }
+ }
+ return substring;
+}
+
+char *myStrGetTokenRemainder(const char *str, const char dilim,
+ int token_number)
+{
+ int len, idx, counter = 0, start_pos = 0;
+ char *substring = NULL;
+ if (!str) {
+ return NULL;
+ }
+ len = strlen(str);
+
+ for (idx = 0; idx <= len; idx++) {
+ if ((str[idx] == dilim) || (idx == len)) {
+ if (counter == token_number) {
+ substring = myStrSubString(str, start_pos, len);
+ counter++;
+ } else {
+ start_pos = idx + 1;
+ counter++;
+ }
+ }
+ }
+ return substring;
+}
+
+char *myStrSubString(const char *src, int start, int end)
+{
+ char *substring = NULL;
+ int len, idx;
+ if (!src) {
+ return NULL;
+ }
+ len = strlen(src);
+ if (((start >= 0) && (end <= len)) && (end > start)) {
+ substring = (char *) malloc(sizeof(char) * ((end - start) + 1));
+ for (idx = 0; idx <= end - start; idx++) {
+ substring[idx] = src[start + idx];
+ }
+ substring[end - start] = '\0';
+ }
+ return substring;
+}
+
+void doCleanBuffer(char *str)
+{
+ char *in = str;
+ char *out = str;
+ char ch;
+
+ while (issp(ch = *in++));
+ if (ch != '\0')
+ for (;;) {
+ *out++ = ch;
+ ch = *in++;
+ if (ch == '\0')
+ break;
+ if (!issp(ch))
+ continue;
+ while (issp(ch = *in++));
+ if (ch == '\0')
+ break;
+ *out++ = ' ';
+ }
+ *out = ch; // == '\0'
+}
+
+void EnforceQlinedNick(char *nick, char *killer)
+{
+ User *u2;
+
+ if ((u2 = finduser(nick))) {
+ alog("Killed Q-lined nick: %s!%s@%s", u2->nick, u2->username,
+ u2->host);
+ kill_user(killer, u2->nick,
+ "This nick is reserved for Services. Please use a non Q-Lined nick.");
+ }
+}
+
+int nickIsServices(char *nick)
+{
+ int found = 0;
+
+ if (s_NickServ && (stricmp(nick, s_NickServ) == 0))
+ found++;
+ else if (s_ChanServ && (stricmp(nick, s_ChanServ) == 0))
+ found++;
+ else if (s_HostServ && (stricmp(nick, s_HostServ) == 0))
+ found++;
+ else if (s_MemoServ && (stricmp(nick, s_MemoServ) == 0))
+ found++;
+ else if (s_BotServ && (stricmp(nick, s_BotServ) == 0))
+ found++;
+ else if (s_HelpServ && (stricmp(nick, s_HelpServ) == 0))
+ found++;
+ else if (s_OperServ && (stricmp(nick, s_OperServ) == 0))
+ found++;
+ else if (s_DevNull && (stricmp(nick, s_DevNull) == 0))
+ found++;
+ else if (s_GlobalNoticer && (stricmp(nick, s_GlobalNoticer) == 0))
+ found++;
+ else if (s_NickServAlias && (stricmp(nick, s_NickServAlias) == 0))
+ found++;
+ else if (s_ChanServAlias && (stricmp(nick, s_ChanServAlias) == 0))
+ found++;
+ else if (s_MemoServAlias && (stricmp(nick, s_MemoServAlias) == 0))
+ found++;
+ else if (s_BotServAlias && (stricmp(nick, s_BotServAlias) == 0))
+ found++;
+ else if (s_HelpServAlias && (stricmp(nick, s_HelpServAlias) == 0))
+ found++;
+ else if (s_OperServAlias && (stricmp(nick, s_OperServAlias) == 0))
+ found++;
+ else if (s_DevNullAlias && (stricmp(nick, s_DevNullAlias) == 0))
+ found++;
+ else if (s_HostServAlias && (stricmp(nick, s_HostServAlias) == 0))
+ found++;
+ else if (s_GlobalNoticerAlias
+ && (stricmp(nick, s_GlobalNoticerAlias) == 0))
+ found++;
+ else if (s_BotServ) {
+ BotInfo *bi;
+ int i;
+ for (i = 0; i < 256; i++) {
+ for (bi = botlists[i]; bi; bi = bi->next) {
+ if (stricmp(nick, bi->nick) == 0) {
+ found++;
+ continue;
+ }
+ }
+ }
+ }
+
+ return found;
+}
diff --git a/src/modules.c b/src/modules.c
new file mode 100644
index 000000000..1ba223634
--- /dev/null
+++ b/src/modules.c
@@ -0,0 +1,2023 @@
+/* Modular support
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+#include "modules.h"
+#include "language.h"
+
+#ifdef USE_MODULES
+#include <dlfcn.h>
+/* Define these for systems without them */
+#ifndef RTLD_NOW
+#define RTLD_NOW 0
+#endif
+#ifndef RTLD_LAZY
+#define RTLD_LAZY RTLD_NOW
+#endif
+#ifndef RTLD_GLOBAL
+#define RTLD_GLOBAL 0
+#endif
+#ifndef RTLD_LOCAL
+#define RTLD_LOCAL 0
+#endif
+#endif
+
+/**
+ * Declare all the list's we want to use here
+ **/
+CommandHash *HOSTSERV[MAX_CMD_HASH];
+CommandHash *BOTSERV[MAX_CMD_HASH];
+CommandHash *MEMOSERV[MAX_CMD_HASH];
+CommandHash *NICKSERV[MAX_CMD_HASH];
+CommandHash *CHANSERV[MAX_CMD_HASH];
+CommandHash *HELPSERV[MAX_CMD_HASH];
+CommandHash *OPERSERV[MAX_CMD_HASH];
+MessageHash *IRCD[MAX_CMD_HASH];
+ModuleHash *MODULE_HASH[MAX_CMD_HASH];
+
+Module *mod_current_module;
+char *mod_current_module_name = NULL;
+char *mod_current_buffer = NULL;
+int mod_current_op;
+User *mod_current_user;
+ModuleCallBack *moduleCallBackHead = NULL;
+int displayCommand(Command * c);
+int displayCommandFromHash(CommandHash * cmdTable[], char *name);
+int displayMessageFromHashl(char *name);
+int displayMessage(Message * m);
+
+/**
+ * Automaticaly load modules at startup.
+ * This will load modules at startup before the IRCD link is attempted, this
+ * allows admins to have a module relating to ircd support load
+ */
+void modules_init(void)
+{
+#ifdef USE_MODULES
+ int idx;
+ Module *m;
+ for (idx = 0; idx < ModulesNumber; idx++) {
+ m = findModule(ModulesAutoload[idx]);
+ if (!m) {
+ m = createModule(ModulesAutoload[idx]);
+ mod_current_module = m;
+ mod_current_user = NULL;
+ alog("trying to load [%s]", mod_current_module->name);
+ alog("status: [%d]", loadModule(mod_current_module, NULL));
+ mod_current_module = NULL;
+ mod_current_user = NULL;
+ }
+ }
+#endif
+}
+
+/**
+ * Automaticaly load modules at startup, delayed.
+ * This function waits until the IRCD link has been made, and then attempts
+ * to load the specified modules.
+ */
+void modules_delayed_init(void)
+{
+#ifdef USE_MODULES
+ int idx;
+ Module *m;
+ for (idx = 0; idx < ModulesDelayedNumber; idx++) {
+ m = findModule(ModulesDelayedAutoload[idx]);
+ if (!m) {
+ m = createModule(ModulesDelayedAutoload[idx]);
+ mod_current_module = m;
+ mod_current_user = NULL;
+ alog("trying to load [%s]", mod_current_module->name);
+ alog("status: [%d]", loadModule(mod_current_module, NULL));
+ mod_current_module = NULL;
+ mod_current_user = NULL;
+ }
+ }
+#endif
+}
+
+/**
+ * Create a new module, setting up the default values as needed.
+ * @param filename the filename of the new module
+ * @return a newly created module struct
+ */
+Module *createModule(char *filename)
+{
+ Module *m;
+ if (!filename) {
+ return NULL;
+ }
+ if ((m = malloc(sizeof(Module))) == NULL) {
+ fatal("Out of memory!");
+ }
+ m->name = sstrdup(filename); /* Our Name */
+ m->handle = NULL; /* Handle */
+ m->version = NULL;
+ m->author = NULL;
+ m->nickHelp = NULL;
+ m->chanHelp = NULL;
+ m->memoHelp = NULL;
+ m->botHelp = NULL;
+ m->operHelp = NULL;
+ m->hostHelp = NULL;
+ m->helpHelp = NULL;
+
+ return m; /* return a nice new module */
+}
+
+/**
+ * Destory the module.
+ * free up all memory used by our module struct.
+ * @param m the module to free
+ * @return MOD_ERR_OK on success, anything else on fail
+ */
+int destroyModule(Module * m)
+{
+ if (!m) {
+ return MOD_ERR_PARAMS;
+ }
+ if (m->name) {
+ free(m->name);
+ }
+ if (m->filename) {
+ remove(m->filename);
+ free(m->filename);
+ }
+ m->handle = NULL;
+ if (m->author) {
+ free(m->author);
+ }
+ if (m->version) {
+ free(m->version);
+ }
+ /* No need to free our cmd/msg list, as they will always be empty by the module is destroyed */
+ free(m);
+ return MOD_ERR_OK;
+}
+
+/**
+ * Add the module to the list of currently loaded modules.
+ * @param m the currently loaded module
+ * @return MOD_ERR_OK on success, anything else on fail
+ */
+int addModule(Module * m)
+{
+ int index = 0;
+ ModuleHash *current = NULL;
+ ModuleHash *newHash = NULL;
+ ModuleHash *lastHash = NULL;
+
+ index = CMD_HASH(m->name);
+
+ for (current = MODULE_HASH[index]; current; current = current->next) {
+ if (stricmp(m->name, current->name) == 0)
+ return MOD_ERR_EXISTS;
+ lastHash = current;
+ }
+
+ if ((newHash = malloc(sizeof(ModuleHash))) == NULL) {
+ fatal("Out of memory");
+ }
+ m->time = time(NULL);
+ newHash->next = NULL;
+ newHash->name = sstrdup(m->name);
+ newHash->m = m;
+
+ if (lastHash == NULL)
+ MODULE_HASH[index] = newHash;
+ else
+ lastHash->next = newHash;
+ return MOD_ERR_OK;
+}
+
+/**
+ * Remove the module from the list of loaded modules.
+ * @param m module to remove
+ * @return MOD_ERR_OK on success anything else on fail
+ */
+int delModule(Module * m)
+{
+ int index = 0;
+ ModuleHash *current = NULL;
+ ModuleHash *lastHash = NULL;
+
+ if (!m) {
+ return MOD_ERR_PARAMS;
+ }
+
+ index = CMD_HASH(m->name);
+
+ for (current = MODULE_HASH[index]; current; current = current->next) {
+ if (stricmp(m->name, current->name) == 0) {
+ if (!lastHash) {
+ MODULE_HASH[index] = current->next;
+ } else {
+ lastHash->next = current->next;
+ }
+ destroyModule(current->m);
+ free(current->name);
+ free(current);
+ return MOD_ERR_OK;
+ }
+ lastHash = current;
+ }
+ return MOD_ERR_NOEXIST;
+}
+
+/**
+ * Search the list of loaded modules for the given name.
+ * @param name the name of the module to find
+ * @return a pointer to the module found, or NULL
+ */
+Module *findModule(char *name)
+{
+ int idx;
+ ModuleHash *current = NULL;
+ if (!name) {
+ return NULL;
+ }
+ idx = CMD_HASH(name);
+
+ for (current = MODULE_HASH[idx]; current; current = current->next) {
+ if (stricmp(name, current->name) == 0) {
+ return current->m;
+ }
+ }
+ return NULL;
+
+}
+
+/**
+ * Copy the module from the modules folder to the runtime folder.
+ * This will prevent module updates while the modules is loaded from
+ * triggering a segfault, as the actaul file in use will be in the
+ * runtime folder.
+ * @param name the name of the module to copy
+ * @return MOD_ERR_OK on success
+ */
+int moduleCopyFile(char *name)
+{
+#ifdef USE_MODULES
+ int ch;
+ FILE *source, *target;
+ char output[4096];
+ char input[4096];
+ int len;
+
+ strncpy(output, MODULE_PATH, 4095); /* Get full path with .so extension */
+ strncpy(input, MODULE_PATH, 4095); /* Get full path with .so extension */
+ len = strlen(output);
+ strncat(output, "runtime/", 4095 - len);
+ len += strlen(output);
+ strncat(output, name, 4095 - len);
+ strncat(input, name, 4095 - len);
+ len += strlen(output);
+ strncat(output, ".so", 4095 - len);
+ strncat(input, ".so", 4095 - len);
+
+ if ((source = fopen(input, "r")) == NULL) {
+ return MOD_ERR_NOEXIST;
+ }
+
+ if ((target = fopen(output, "w")) == NULL) {
+ return MOD_ERR_FILE_IO;
+ }
+ while ((ch = fgetc(source)) != EOF) {
+ fputc(ch, target);
+ }
+ fclose(source);
+ if (fclose(target) != 0) {
+ return MOD_ERR_FILE_IO;
+ }
+#endif
+ return MOD_ERR_OK;
+}
+
+/**
+ * Loads a given module.
+ * @param m the module to load
+ * @param u the user who loaded it, NULL for auto-load
+ * @return MOD_ERR_OK on success, anything else on fail
+ */
+int loadModule(Module * m, User * u)
+{
+#ifdef USE_MODULES
+ char buf[4096];
+ int len;
+ const char *err;
+ int (*func) (int, char **);
+ int ret = 0;
+ Module *m2;
+ if (!m || !m->name) {
+ return MOD_ERR_PARAMS;
+ }
+ if (m->handle) {
+ return MOD_ERR_EXISTS;
+ }
+ if ((m2 = findModule(m->name)) != NULL) {
+ return MOD_ERR_EXISTS;
+ }
+
+ moduleCopyFile(m->name);
+
+ strncpy(buf, MODULE_PATH, 4095); /* Get full path with .so extension */
+ len = strlen(buf);
+ strncat(buf, "runtime/", 4095 - len);
+ len += strlen(buf);
+ strncat(buf, m->name, 4095 - len);
+ len += strlen(buf);
+ strncat(buf, ".so", 4095 - len);
+
+ m->filename = sstrdup(buf);
+#ifdef HAS_RTLD_LOCAL
+ m->handle = dlopen(m->filename, RTLD_LAZY | RTLD_LOCAL);
+#else
+ m->handle = dlopen(m->filename, RTLD_LAZY);
+#endif
+ if ((err = dlerror()) != NULL) {
+ alog(err);
+ if (u) {
+ notice_lang(s_OperServ, u, OPER_MODULE_LOAD_FAIL, m->name);
+ }
+ return MOD_ERR_NOLOAD;
+ }
+
+ func = dlsym(m->handle, DL_PREFIX "AnopeInit");
+ if ((err = dlerror()) != NULL) {
+ dlclose(m->handle); /* If no AnopeInit - it isnt an Anope Module, close it */
+ return MOD_ERR_NOLOAD;
+ }
+ if (func) {
+ mod_current_module_name = m->name;
+ ret = func(0, NULL); /* exec AnopeInit */
+ if (ret == MOD_STOP) {
+ alog("%s requested unload...", m->name);
+ unloadModule(m, NULL);
+ mod_current_module_name = NULL;
+ return MOD_ERR_NOLOAD;
+ }
+
+ mod_current_module_name = NULL;
+ }
+
+ if (u) {
+ wallops(s_OperServ, "%s loaded module %s", u->nick, m->name);
+ notice_lang(s_OperServ, u, OPER_MODULE_LOADED, m->name);
+ }
+ addModule(m);
+ return MOD_ERR_OK;
+
+#else
+ return MOD_ERR_NOLOAD;
+#endif
+}
+
+/**
+ * Unload the given module.
+ * @param m the module to unload
+ * @param u the user who unloaded it
+ * @return MOD_ERR_OK on success, anything else on fail
+ */
+int unloadModule(Module * m, User * u)
+{
+#ifdef USE_MODULES
+ void (*func) ();
+
+ if (!m || !m->handle) {
+ if (u) {
+ notice_lang(s_OperServ, u, OPER_MODULE_REMOVE_FAIL, m->name);
+ }
+ return MOD_ERR_PARAMS;
+ }
+
+ if (prepForUnload(mod_current_module) != MOD_ERR_OK) {
+ return MOD_ERR_UNKNOWN;
+ }
+
+ func = dlsym(m->handle, DL_PREFIX "AnopeFini");
+ if (func) {
+ func(); /* exec AnopeFini */
+ }
+
+ if ((dlclose(m->handle)) != 0) {
+ alog(dlerror());
+ if (u) {
+ notice_lang(s_OperServ, u, OPER_MODULE_REMOVE_FAIL, m->name);
+ }
+ return MOD_ERR_NOUNLOAD;
+ } else {
+ if (u) {
+ wallops(s_OperServ, "%s unloaded module %s", u->nick, m->name);
+ notice_lang(s_OperServ, u, OPER_MODULE_UNLOADED, m->name);
+ }
+ delModule(m);
+ return MOD_ERR_OK;
+ }
+#else
+ return MOD_ERR_NOUNLOAD;
+#endif
+}
+
+/**
+ * Prepare a module to be unloaded.
+ * Remove all commands and messages this module is providing, and delete
+ * any callbacks which are still pending.
+ * @param m the module to prepare for unload
+ * @return MOD_ERR_OK on success
+ */
+int prepForUnload(Module * m)
+{
+ int idx;
+ CommandHash *current = NULL;
+ MessageHash *mcurrent = NULL;
+ Command *c;
+ Message *msg;
+
+ if (!m) {
+ return MOD_ERR_PARAMS;
+ }
+
+ /* Kill any active callbacks this module has */
+ moduleCallBackPrepForUnload(m->name);
+
+ /* Remove any stored data this module has */
+ moduleDelAllDataMod(m);
+
+ /**
+ * ok, im going to walk every hash looking for commands we own, now, not exactly elegant or efficiant :)
+ **/
+ for (idx = 0; idx < MAX_CMD_HASH; idx++) {
+ for (current = HS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(HOSTSERV, c->name);
+ }
+ }
+ }
+
+ for (current = BS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(BOTSERV, c->name);
+ }
+ }
+ }
+
+ for (current = MS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(MEMOSERV, c->name);
+ }
+ }
+ }
+
+ for (current = NS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(NICKSERV, c->name);
+ }
+ }
+ }
+
+ for (current = CS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(CHANSERV, c->name);
+ }
+ }
+ }
+
+ for (current = HE_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (strcmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(HELPSERV, c->name);
+ }
+ }
+ }
+
+ for (current = OS_cmdTable[idx]; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (stricmp(c->mod_name, m->name) == 0)) {
+ moduleDelCommand(OPERSERV, c->name);
+ }
+ }
+ }
+
+ for (mcurrent = IRCD[idx]; mcurrent; mcurrent = mcurrent->next) {
+ for (msg = mcurrent->m; msg; msg = msg->next) {
+ if ((msg->mod_name)
+ && (stricmp(msg->mod_name, m->name) == 0)) {
+ moduleDelMessage(msg->name);
+ }
+ }
+ }
+ }
+ return MOD_ERR_OK;
+}
+
+/*******************************************************************************
+ * Command Functions
+ *******************************************************************************/
+/**
+ * Create a Command struct ready for use in anope.
+ * @param name the name of the command
+ * @param func pointer to the function to execute when command is given
+ * @param has_priv pointer to function to check user priv's
+ * @param help_all help file index for all users
+ * @param help_reg help file index for all regustered users
+ * @param help_oper help file index for all opers
+ * @param help_admin help file index for all admins
+ * @param help_root help file indenx for all services roots
+ * @return a "ready to use" Command struct will be returned
+ */
+Command *createCommand(const char *name, int (*func) (User * u),
+ int (*has_priv) (User * u), int help_all,
+ int help_reg, int help_oper, int help_admin,
+ int help_root)
+{
+ Command *c;
+ if ((c = malloc(sizeof(Command))) == NULL) {
+ fatal("Out of memory!");
+ }
+ c->name = sstrdup(name);
+ c->routine = func;
+ c->has_priv = has_priv;
+ c->helpmsg_all = help_all;
+ c->helpmsg_reg = help_reg;
+ c->helpmsg_oper = help_oper;
+ c->helpmsg_admin = help_admin;
+ c->helpmsg_root = help_root;
+ c->help_param1 = NULL;
+ c->help_param2 = NULL;
+ c->help_param3 = NULL;
+ c->help_param4 = NULL;
+ c->next = NULL;
+ c->mod_name = NULL;
+ c->service = NULL;
+ c->all_help = NULL;
+ c->regular_help = NULL;
+ c->oper_help = NULL;
+ c->admin_help = NULL;
+ c->root_help = NULL;
+ return c;
+}
+
+/**
+ * Destroy a command struct freeing any memory.
+ * @param c Command to destroy
+ * @return MOD_ERR_OK on success, anything else on fail
+ */
+int destroyCommand(Command * c)
+{
+ if (!c) {
+ return MOD_ERR_PARAMS;
+ }
+ if (c->core == 1) {
+ return MOD_ERR_UNKNOWN;
+ }
+ if (c->name) {
+ free(c->name);
+ }
+ c->routine = NULL;
+ c->has_priv = NULL;
+ c->helpmsg_all = -1;
+ c->helpmsg_reg = -1;
+ c->helpmsg_oper = -1;
+ c->helpmsg_admin = -1;
+ c->helpmsg_root = -1;
+ if (c->help_param1) {
+ free(c->help_param1);
+ }
+ if (c->help_param2) {
+ free(c->help_param2);
+ }
+ if (c->help_param3) {
+ free(c->help_param3);
+ }
+ if (c->help_param4) {
+ free(c->help_param4);
+ }
+ if (c->mod_name) {
+ free(c->mod_name);
+ }
+ if (c->service) {
+ free(c->service);
+ }
+ c->next = NULL;
+ free(c);
+ return MOD_ERR_OK;
+}
+
+/**
+ * Add a CORE command ot the given command hash
+ * @param cmdTable the command table to add the command to
+ * @param c the command to add
+ * @return MOD_ERR_OK on success
+ */
+int addCoreCommand(CommandHash * cmdTable[], Command * c)
+{
+ if (!cmdTable || !c) {
+ return MOD_ERR_PARAMS;
+ }
+ c->core = 1;
+ c->next = NULL;
+ return addCommand(cmdTable, c, 0);
+}
+
+/**
+ * Add a module provided command to the given service.
+ * e.g. moduleAddCommand(NICKSERV,c,MOD_HEAD);
+ * @param cmdTable the services to add the command to
+ * @param c the command to add
+ * @param pos the position to add to, MOD_HEAD, MOD_TAIL, MOD_UNIQUE
+ * @see createCommand
+ * @return MOD_ERR_OK on successfully adding the command
+ */
+int moduleAddCommand(CommandHash * cmdTable[], Command * c, int pos)
+{
+ int status;
+
+ if (!cmdTable || !c) {
+ return MOD_ERR_PARAMS;
+ }
+ /* ok, this appears to be a module adding a command from outside of AnopeInit, try to look up its module struct for it */
+ if ((mod_current_module_name) && (!mod_current_module)) {
+ mod_current_module = findModule(mod_current_module_name);
+ }
+
+ if (!mod_current_module) {
+ return MOD_ERR_UNKNOWN;
+ } /* shouldnt happen */
+ c->core = 0;
+ if (!c->mod_name) {
+ c->mod_name = sstrdup(mod_current_module->name);
+ }
+
+
+ if (cmdTable == HOSTSERV) {
+ if (s_HostServ) {
+ c->service = sstrdup(s_HostServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == BOTSERV) {
+ if (s_BotServ) {
+ c->service = sstrdup(s_BotServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == MEMOSERV) {
+ if (s_MemoServ) {
+ c->service = sstrdup(s_MemoServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == CHANSERV) {
+ if (s_ChanServ) {
+ c->service = sstrdup(s_ChanServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == NICKSERV) {
+ if (s_NickServ) {
+ c->service = sstrdup(s_NickServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == HELPSERV) {
+ if (s_HelpServ) {
+ c->service = sstrdup(s_HelpServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else if (cmdTable == OPERSERV) {
+ if (s_OperServ) {
+ c->service = sstrdup(s_OperServ);
+ } else {
+ return MOD_ERR_NOSERVICE;
+ }
+ } else
+ c->service = sstrdup("Unknown");
+
+ if (debug)
+ displayCommandFromHash(cmdTable, c->name);
+ status = addCommand(cmdTable, c, pos);
+ if (debug)
+ displayCommandFromHash(cmdTable, c->name);
+ if (status != MOD_ERR_OK) {
+ alog("ERROR! [%d]", status);
+ }
+ return status;
+}
+
+/**
+ * Delete a command from the service given.
+ * @param cmdTable the cmdTable for the services to remove the command from
+ * @param name the name of the command to delete from the service
+ * @return returns MOD_ERR_OK on success
+ */
+int moduleDelCommand(CommandHash * cmdTable[], char *name)
+{
+ Command *c = NULL;
+ Command *cmd = NULL;
+ int status = 0;
+
+ if (!mod_current_module) {
+ return MOD_ERR_UNKNOWN;
+ }
+
+ c = findCommand(cmdTable, name);
+ if (!c) {
+ return MOD_ERR_NOEXIST;
+ }
+
+
+ for (cmd = c; cmd; cmd = cmd->next) {
+ if (cmd->mod_name
+ && stricmp(cmd->mod_name, mod_current_module->name) == 0) {
+ if (debug) {
+ displayCommandFromHash(cmdTable, name);
+ }
+ status = delCommand(cmdTable, cmd, mod_current_module->name);
+ if (debug) {
+ displayCommandFromHash(cmdTable, name);
+ }
+ }
+ }
+ return status;
+}
+
+/**
+ * Output the command stack into the log files.
+ * This will print the call-stack for a given command into the log files, very useful for debugging.
+ * @param cmdTable the command table to read from
+ * @param name the name of the command to print
+ * @return 0 is returned, it has no relevence yet :)
+ */
+int displayCommandFromHash(CommandHash * cmdTable[], char *name)
+{
+ CommandHash *current = NULL;
+ int index = 0;
+ index = CMD_HASH(name);
+ if (debug > 1) {
+ alog("trying to display command %s", name);
+ }
+ for (current = cmdTable[index]; current; current = current->next) {
+ if (stricmp(name, current->name) == 0) {
+ displayCommand(current->c);
+ }
+ }
+ if (debug > 1) {
+ alog("done displaying command %s", name);
+ }
+ return 0;
+}
+
+/**
+ * Output the command stack into the log files.
+ * This will print the call-stack for a given command into the log files, very useful for debugging.
+ * @param c the command struct to print
+ * @return 0 is returned, it has no relevence yet :)
+ */
+
+int displayCommand(Command * c)
+{
+ Command *cmd = NULL;
+ int i = 0;
+ alog("Displaying command list for %s", c->name);
+ for (cmd = c; cmd; cmd = cmd->next) {
+ alog("%d: %p", ++i, cmd);
+ }
+ alog("end");
+ return 0;
+}
+
+/**
+ * Display the message call stak.
+ * Prints the call stack for a message based on the message name, again useful for debugging and little lese :)
+ * @param name the name of the message to print info for
+ * @return the return int has no relevence atm :)
+ */
+int displayMessageFromHash(char *name)
+{
+ MessageHash *current = NULL;
+ int index = 0;
+ index = CMD_HASH(name);
+ if (debug > 1) {
+ alog("trying to display message %s", name);
+ }
+ for (current = IRCD[index]; current; current = current->next) {
+ if (stricmp(name, current->name) == 0) {
+ displayMessage(current->m);
+ }
+ }
+ if (debug > 1) {
+ alog("done displaying message %s", name);
+ }
+ return 0;
+}
+
+/**
+ * Displays a message list for a given message.
+ * Again this is of little use other than debugging.
+ * @param m the message to display
+ * @return 0 is returned and has no meaning
+ */
+int displayMessage(Message * m)
+{
+ Message *msg = NULL;
+ int i = 0;
+ alog("Displaying message list for %s", m->name);
+ for (msg = m; msg; msg = msg->next) {
+ alog("%d: %p", ++i, msg);
+ }
+ alog("end");
+ return 0;
+}
+
+
+/**
+ * Add a command to a command table.
+ * only add if were unique, pos = 0;
+ * if we want it at the "head" of that command, pos = 1
+ * at the tail, pos = 2
+ * @param cmdTable the table to add the command to
+ * @param c the command to add
+ * @param pos the position in the cmd call stack to add the command
+ * @return MOD_ERR_OK will be returned on success.
+ */
+int addCommand(CommandHash * cmdTable[], Command * c, int pos)
+{
+ /* We can assume both param's have been checked by this point.. */
+ int index = 0;
+ CommandHash *current = NULL;
+ CommandHash *newHash = NULL;
+ CommandHash *lastHash = NULL;
+ Command *tail = NULL;
+
+ if (!cmdTable || !c || (pos < 0 || pos > 2)) {
+ return MOD_ERR_PARAMS;
+ }
+
+ index = CMD_HASH(c->name);
+
+ for (current = cmdTable[index]; current; current = current->next) {
+ if ((c->service) && (current->c) && (current->c->service)
+ && (!strcmp(c->service, current->c->service) == 0)) {
+ continue;
+ }
+ if ((stricmp(c->name, current->name) == 0)) { /* the cmd exist's we are a addHead */
+ if (pos == 1) {
+ c->next = current->c;
+ current->c = c;
+ if (debug)
+ alog("existing cmd: (%p), new cmd (%p)", c->next, c);
+ return MOD_ERR_OK;
+ } else if (pos == 2) {
+
+ tail = current->c;
+ while (tail->next)
+ tail = tail->next;
+ if (debug)
+ alog("existing cmd: (%p), new cmd (%p)", tail, c);
+ tail->next = c;
+ c->next = NULL;
+
+ return MOD_ERR_OK;
+ } else
+ return MOD_ERR_EXISTS;
+ }
+ lastHash = current;
+ }
+
+ if ((newHash = malloc(sizeof(CommandHash))) == NULL) {
+ fatal("Out of memory");
+ }
+ newHash->next = NULL;
+ newHash->name = sstrdup(c->name);
+ newHash->c = c;
+
+ if (lastHash == NULL)
+ cmdTable[index] = newHash;
+ else
+ lastHash->next = newHash;
+
+ return MOD_ERR_OK;
+}
+
+/**
+ * Remove a command from the command hash.
+ * @param cmdTable the command table to remove the command from
+ * @param c the command to remove
+ * @param mod_name the name of the module who owns the command
+ * @return MOD_ERR_OK will be returned on success
+ */
+int delCommand(CommandHash * cmdTable[], Command * c, char *mod_name)
+{
+ int index = 0;
+ CommandHash *current = NULL;
+ CommandHash *lastHash = NULL;
+ Command *tail = NULL, *last = NULL;
+
+ if (!c || !cmdTable) {
+ return MOD_ERR_PARAMS;
+ }
+
+ index = CMD_HASH(c->name);
+ for (current = cmdTable[index]; current; current = current->next) {
+ if (stricmp(c->name, current->name) == 0) {
+ if (!lastHash) {
+ tail = current->c;
+ if (tail->next) {
+ while (tail) {
+ if (mod_name && tail->mod_name
+ && (stricmp(mod_name, tail->mod_name) == 0)) {
+ if (last) {
+ last->next = tail->next;
+ } else {
+ current->c = tail->next;
+ }
+ return MOD_ERR_OK;
+ }
+ last = tail;
+ tail = tail->next;
+ }
+ } else {
+ cmdTable[index] = current->next;
+ free(current->name);
+ return MOD_ERR_OK;
+ }
+ } else {
+ tail = current->c;
+ if (tail->next) {
+ while (tail) {
+ if (mod_name && tail->mod_name
+ && (stricmp(mod_name, tail->mod_name) == 0)) {
+ if (last) {
+ last->next = tail->next;
+ } else {
+ current->c = tail->next;
+ }
+ return MOD_ERR_OK;
+ }
+ last = tail;
+ tail = tail->next;
+ }
+ } else {
+ lastHash->next = current->next;
+ free(current->name);
+ return MOD_ERR_OK;
+ }
+ }
+ }
+ lastHash = current;
+ }
+ return MOD_ERR_NOEXIST;
+}
+
+/**
+ * Search the command table gieven for a command.
+ * @param cmdTable the name of the command table to search
+ * @param name the name of the command to look for
+ * @return returns a pointer to the found command struct, or NULL
+ */
+Command *findCommand(CommandHash * cmdTable[], const char *name)
+{
+ int idx;
+ CommandHash *current = NULL;
+ if (!cmdTable || !name) {
+ return NULL;
+ }
+
+ idx = CMD_HASH(name);
+
+ for (current = cmdTable[idx]; current; current = current->next) {
+ if (stricmp(name, current->name) == 0) {
+ return current->c;
+ }
+ }
+ return NULL;
+}
+
+/*******************************************************************************
+ * Message Functions
+ *******************************************************************************/
+
+ /**
+ * Create a new Message struct.
+ * @param name the name of the message
+ * @param func a pointer to the function to call when we recive this message
+ * @return a new Message object
+ **/
+Message *createMessage(char *name,
+ int (*func) (char *source, int ac, char **av))
+{
+ Message *m = NULL;
+ if (!name || !func) {
+ return NULL;
+ }
+ if ((m = malloc(sizeof(Message))) == NULL) {
+ fatal("Out of memory!");
+ }
+ m->name = sstrdup(name);
+ m->func = func;
+ m->mod_name = NULL;
+ m->next = NULL;
+ return m;
+}
+
+/**
+ * find a message in the given table.
+ * Looks up the message <name> in the MessageHash given
+ * @param MessageHash the message table to search for this command, will almost always be IRCD
+ * @param name the name of the command were looking for
+ * @return NULL if we cant find it, or a pointer to the Message if we can
+ **/
+Message *findMessage(MessageHash * msgTable[], const char *name)
+{
+ int idx;
+ MessageHash *current = NULL;
+ if (!msgTable || !name) {
+ return NULL;
+ }
+ idx = CMD_HASH(name);
+
+ for (current = msgTable[idx]; current; current = current->next) {
+ if (stricmp(name, current->name) == 0) {
+ return current->m;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Add a message to the MessageHash.
+ * @param msgTable the MessageHash we want to add a message to
+ * @param m the Message we want to add
+ * @param pos the position we want to add the message to, E.G. MOD_HEAD, MOD_TAIL, MOD_UNIQUE
+ * @return MOD_ERR_OK on a successful add.
+ **/
+
+int addMessage(MessageHash * msgTable[], Message * m, int pos)
+{
+ /* We can assume both param's have been checked by this point.. */
+ int index = 0;
+ MessageHash *current = NULL;
+ MessageHash *newHash = NULL;
+ MessageHash *lastHash = NULL;
+ Message *tail = NULL;
+
+ if (!msgTable || !m || (pos < 0 || pos > 2)) {
+ return MOD_ERR_PARAMS;
+ }
+
+ index = CMD_HASH(m->name);
+
+ for (current = msgTable[index]; current; current = current->next) {
+ if (stricmp(m->name, current->name) == 0) { /* the msg exist's we are a addHead */
+ if (pos == 1) {
+ m->next = current->m;
+ current->m = m;
+ if (debug)
+ alog("existing msg: (%p), new msg (%p)", m->next, m);
+ return MOD_ERR_OK;
+ } else if (pos == 2) {
+ tail = current->m;
+ while (tail->next)
+ tail = tail->next;
+ if (debug)
+ alog("existing msg: (%p), new msg (%p)", tail, m);
+ tail->next = m;
+ m->next = NULL;
+ return MOD_ERR_OK;
+ } else
+ return MOD_ERR_EXISTS;
+ }
+ lastHash = current;
+ }
+
+ if ((newHash = malloc(sizeof(MessageHash))) == NULL) {
+ fatal("Out of memory");
+ }
+ newHash->next = NULL;
+ newHash->name = sstrdup(m->name);
+ newHash->m = m;
+
+ if (lastHash == NULL)
+ msgTable[index] = newHash;
+ else
+ lastHash->next = newHash;
+ return MOD_ERR_OK;
+}
+
+/**
+ * Add the given message (m) to the MessageHash marking it as a core command
+ * @param msgTable the MessageHash we want to add to
+ * @param m the Message we are adding
+ * @return MOD_ERR_OK on a successful add.
+ **/
+int addCoreMessage(MessageHash * msgTable[], Message * m)
+{
+ if (!msgTable || !m) {
+ return MOD_ERR_PARAMS;
+ }
+ m->core = 1;
+ return addMessage(msgTable, m, 0);
+}
+
+/**
+ * Add a module message to the IRCD message hash
+ * @param m the Message to add
+ * @param pos the Position to add the message to, e.g. MOD_HEAD, MOD_TAIL, MOD_UNIQUE
+ * @return MOD_ERR_OK on success, althing else on fail.
+ **/
+int moduleAddMessage(Message * m, int pos)
+{
+ int status;
+
+ if (!m) {
+ return MOD_ERR_PARAMS;
+ }
+
+ /* ok, this appears to be a module adding a message from outside of AnopeInit, try to look up its module struct for it */
+ if ((mod_current_module_name) && (!mod_current_module)) {
+ mod_current_module = findModule(mod_current_module_name);
+ }
+
+ if (!mod_current_module) {
+ return MOD_ERR_UNKNOWN;
+ } /* shouldnt happen */
+ m->core = 0;
+ if (!m->mod_name) {
+ m->mod_name = sstrdup(mod_current_module->name);
+ }
+
+ status = addMessage(IRCD, m, pos);
+ if (debug) {
+ displayMessageFromHash(m->name);
+ }
+ return status;
+}
+
+/**
+ * remove the given message from the IRCD message hash
+ * @param name the name of the message to remove
+ * @return MOD_ERR_OK on success, althing else on fail.
+ **/
+int moduleDelMessage(char *name)
+{
+ Message *m;
+ int status;
+
+ if (!mod_current_module) {
+ return MOD_ERR_UNKNOWN;
+ }
+ m = findMessage(IRCD, name);
+ if (!m) {
+ return MOD_ERR_NOEXIST;
+ }
+
+ status = delMessage(IRCD, m, mod_current_module->name);
+ if (debug) {
+ displayMessageFromHash(m->name);
+ }
+ return status;
+}
+
+/**
+ * remove the given message from the given message hash, for the given module
+ * @param msgTable which MessageHash we are removing from
+ * @param m the Message we want to remove
+ * @mod_name the name of the module we are removing
+ * @return MOD_ERR_OK on success, althing else on fail.
+ **/
+int delMessage(MessageHash * msgTable[], Message * m, char *mod_name)
+{
+ int index = 0;
+ MessageHash *current = NULL;
+ MessageHash *lastHash = NULL;
+ Message *tail = NULL, *last = NULL;
+
+ if (!m || !msgTable) {
+ return MOD_ERR_PARAMS;
+ }
+
+ index = CMD_HASH(m->name);
+
+ for (current = msgTable[index]; current; current = current->next) {
+ if (stricmp(m->name, current->name) == 0) {
+ if (!lastHash) {
+ tail = current->m;
+ if (tail->next) {
+ while (tail) {
+ if (mod_name && tail->mod_name
+ && (stricmp(mod_name, tail->mod_name) == 0)) {
+ if (last) {
+ last->next = tail->next;
+ } else {
+ current->m = tail->next;
+ }
+ return MOD_ERR_OK;
+ }
+ last = tail;
+ tail = tail->next;
+ }
+ } else {
+ msgTable[index] = current->next;
+ free(current->name);
+ return MOD_ERR_OK;
+ }
+ } else {
+ tail = current->m;
+ if (tail->next) {
+ while (tail) {
+ if (mod_name && tail->mod_name
+ && (stricmp(mod_name, tail->mod_name) == 0)) {
+ if (last) {
+ last->next = tail->next;
+ } else {
+ current->m = tail->next;
+ }
+ return MOD_ERR_OK;
+ }
+ last = tail;
+ tail = tail->next;
+ }
+ } else {
+ lastHash->next = current->next;
+ free(current->name);
+ return MOD_ERR_OK;
+ }
+ }
+ }
+ lastHash = current;
+ }
+ return MOD_ERR_NOEXIST;
+}
+
+/**
+ * Destory a message, freeing its memory.
+ * @param m the message to be destroyed
+ * @return MOD_ERR_SUCCESS on success
+ **/
+int destroyMessage(Message * m)
+{
+ if (!m) {
+ return MOD_ERR_PARAMS;
+ }
+ if (m->name) {
+ free(m->name);
+ }
+ m->func = NULL;
+ if (m->mod_name) {
+ free(m->mod_name);
+ }
+ m->next = NULL;
+ return MOD_ERR_OK;
+}
+
+/**
+ * Add the modules version info.
+ * @param version the version of the current module
+ **/
+void moduleAddVersion(char *version)
+{
+ if (mod_current_module && version) {
+ mod_current_module->version = sstrdup(version);
+ }
+}
+
+/**
+ * Add the modules author info
+ * @param author the author of the module
+ **/
+void moduleAddAuthor(char *author)
+{
+ if (mod_current_module && author) {
+ mod_current_module->author = sstrdup(author);
+ }
+}
+
+/*******************************************************************************
+ * Module Callback Functions
+ *******************************************************************************/
+ /**
+ * Adds a timed callback for the current module.
+ * This allows modules to request that anope executes one of there functions at a time in the future, without an event to trigger it
+ * @param name the name of the callback, this is used for refrence mostly, but is needed it you want to delete this particular callback later on
+ * @param when when should the function be executed, this is a time in the future, seconds since 00:00:00 1970-01-01 UTC
+ * @param func the function to be executed when the callback is ran, its format MUST be int func(int argc, char **argv);
+ * @param argc the argument count for the argv paramter
+ * @param atgv a argument list to be passed to the called function.
+ * @return MOD_ERR_OK on success, anything else on fail.
+ * @see moduleDelCallBack
+ **/
+int moduleAddCallback(char *name, time_t when,
+ int (*func) (int argc, char *argv[]), int argc,
+ char **argv)
+{
+ ModuleCallBack *new, *tmp, *prev;
+ int i;
+ new = malloc(sizeof(ModuleCallBack));
+ if (!new)
+ return MOD_ERR_MEMORY;
+
+ if (name)
+ new->name = sstrdup(name);
+ else
+ new->name = NULL;
+ new->when = when;
+ if (mod_current_module_name) {
+ new->owner_name = sstrdup(mod_current_module_name);
+ } else {
+ new->owner_name = NULL;
+ }
+ new->func = func;
+ new->argc = argc;
+ new->argv = malloc(sizeof(char *) * argc);
+ for (i = 0; i < argc; i++) {
+ new->argv[i] = sstrdup(argv[i]);
+ }
+ new->next = NULL;
+
+ if (moduleCallBackHead == NULL) {
+ moduleCallBackHead = new;
+ } else { /* find place in list */
+ tmp = moduleCallBackHead;
+ prev = tmp;
+ if (new->when < tmp->when) {
+ new->next = tmp;
+ moduleCallBackHead = new;
+ } else {
+ while (tmp && new->when >= tmp->when) {
+ prev = tmp;
+ tmp = tmp->next;
+ }
+ prev->next = new;
+ new->next = tmp;
+ }
+ }
+ if (debug)
+ alog("Added module CallBack: [%s] due to execute at %ld",
+ new->name ? new->name : "?", new->when);
+ return MOD_ERR_OK;
+}
+
+/**
+ * Execute a stored call back
+ **/
+void moduleCallBackRun(void)
+{
+ ModuleCallBack *tmp;
+ if (!moduleCallBackHead) {
+ return;
+ }
+ tmp = moduleCallBackHead;
+ if (tmp->when <= time(NULL)) {
+ if (debug)
+ alog("Executing callback: %s", tmp->name ? tmp->name : "?");
+ if (tmp->func) {
+ mod_current_module_name = tmp->owner_name;
+ tmp->func(tmp->argc, tmp->argv);
+ mod_current_module = NULL;
+ moduleCallBackDeleteEntry(NULL); /* delete the head */
+ }
+ }
+ return;
+}
+
+/**
+ * Removes a entry from the modules callback list
+ * @param prev a pointer to the previous entry in the list, NULL for the head
+ **/
+void moduleCallBackDeleteEntry(ModuleCallBack * prev)
+{
+ ModuleCallBack *tmp = NULL;
+ int i;
+ if (prev == NULL) {
+ tmp = moduleCallBackHead;
+ moduleCallBackHead = tmp->next;
+ } else {
+ tmp = prev->next;
+ prev->next = tmp->next;
+ }
+ if (tmp->name)
+ free(tmp->name);
+ if (tmp->owner_name)
+ free(tmp->owner_name);
+ tmp->func = NULL;
+ for (i = 0; i < tmp->argc; i++) {
+ free(tmp->argv[i]);
+ }
+ tmp->argc = 0;
+ tmp->next = NULL;
+ free(tmp);
+}
+
+/**
+ * Search the module callback list for a given module
+ * @param mod_name the name of the module were looking for
+ * @param found have we found it?
+ * @return a pointer to the ModuleCallBack struct or NULL - dont forget to check the found paramter!
+ **/
+ModuleCallBack *moduleCallBackFindEntry(char *mod_name, boolean * found)
+{
+ ModuleCallBack *prev = NULL, *current = NULL;
+ *found = false;
+ current = moduleCallBackHead;
+ while (current != NULL) {
+ if (current->owner_name
+ && (strcmp(mod_name, current->owner_name) == 0)) {
+ *found = true;
+ break;
+ } else {
+ prev = current;
+ current = current->next;
+ }
+ }
+ if (current == moduleCallBackHead) {
+ return NULL;
+ } else {
+ return prev;
+ }
+}
+
+/**
+ * Allow module coders to delete a callback by name.
+ * @param name the name of the callback they wish to delete
+ **/
+void moduleDelCallback(char *name)
+{
+ ModuleCallBack *current = NULL;
+ ModuleCallBack *prev = NULL, *tmp = NULL;
+ int del = 0;
+ if (!mod_current_module_name) {
+ return;
+ }
+ if (!name) {
+ return;
+ }
+ current = moduleCallBackHead;
+ while (current) {
+ if ((current->owner_name) && (current->name)) {
+ if ((strcmp(mod_current_module_name, current->owner_name) == 0)
+ && (strcmp(current->name, name) == 0)) {
+ if (debug) {
+ alog("Removing CallBack %s for module %s", name,
+ mod_current_module_name);
+ }
+ tmp = current->next; /* get a pointer to the next record, as once we delete this record, we'll lose it :) */
+ moduleCallBackDeleteEntry(prev); /* delete this record */
+ del = 1; /* set the record deleted flag */
+ }
+ }
+ if (del == 1) { /* if a record was deleted */
+ current = tmp; /* use the value we stored in temp */
+ tmp = NULL; /* clear it for next time */
+ del = 0; /* reset the flag */
+ } else {
+ prev = current; /* just carry on as normal */
+ current = current->next;
+ }
+ }
+}
+
+/**
+ * Remove all outstanding module callbacks for the given module.
+ * When a module is unloaded, any callbacks it had outstanding must be removed, else when they attempt to execute the func pointer will no longer be valid, and we'll seg.
+ * @param mod_name the name of the module we are preping for unload
+ **/
+void moduleCallBackPrepForUnload(char *mod_name)
+{
+ boolean found = false;
+ ModuleCallBack *tmp = NULL;
+
+ tmp = moduleCallBackFindEntry(mod_name, &found);
+ while (found) {
+ if (debug) {
+ alog("Removing CallBack for module %s", mod_name);
+ }
+ moduleCallBackDeleteEntry(tmp);
+ tmp = moduleCallBackFindEntry(mod_name, &found);
+ }
+}
+
+/**
+ * Return a copy of the complete last buffer.
+ * This is needed for modules who cant trust the strtok() buffer, as we dont know who will have already messed about with it.
+ * @reutrn a pointer to a copy of the last buffer - DONT mess with this, copy if first if you must do things to it.
+ **/
+char *moduleGetLastBuffer(void)
+{
+ char *tmp = NULL;
+ if (mod_current_buffer) {
+ tmp = strchr(mod_current_buffer, ' ');
+ if (tmp) {
+ tmp++;
+ }
+ }
+ return tmp;
+}
+
+/*******************************************************************************
+ * Module HELP Functions
+ *******************************************************************************/
+ /**
+ * Add help for Root admins.
+ * @param c the Command to add help for
+ * @param func the function to run when this help is asked for
+ **/
+int moduleAddRootHelp(Command * c, int (*func) (User * u))
+{
+ if (c) {
+ c->root_help = func;
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+ /**
+ * Add help for Admins.
+ * @param c the Command to add help for
+ * @param func the function to run when this help is asked for
+ **/
+int moduleAddAdminHelp(Command * c, int (*func) (User * u))
+{
+ if (c) {
+ c->admin_help = func;
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+ /**
+ * Add help for opers..
+ * @param c the Command to add help for
+ * @param func the function to run when this help is asked for
+ **/
+int moduleAddOperHelp(Command * c, int (*func) (User * u))
+{
+ if (c) {
+ c->oper_help = func;
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+/**
+ * Add help for registered users
+ * @param c the Command to add help for
+ * @param func the function to run when this help is asked for
+ **/
+int moduleAddRegHelp(Command * c, int (*func) (User * u))
+{
+ if (c) {
+ c->regular_help = func;
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+/**
+ * Add help for all users
+ * @param c the Command to add help for
+ * @param func the function to run when this help is asked for
+ **/
+int moduleAddHelp(Command * c, int (*func) (User * u))
+{
+ if (c) {
+ c->all_help = func;
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+/**
+ * Add output to nickserv help.
+ * when doing a /msg nickserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetNickHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->nickHelp = func;
+ }
+}
+
+/**
+ * Add output to chanserv help.
+ * when doing a /msg chanserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetChanHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->chanHelp = func;
+ }
+}
+
+/**
+ * Add output to memoserv help.
+ * when doing a /msg memoserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetMemoHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->memoHelp = func;
+ }
+}
+
+/**
+ * Add output to botserv help.
+ * when doing a /msg botserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetBotHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->botHelp = func;
+ }
+}
+
+/**
+ * Add output to operserv help.
+ * when doing a /msg operserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetOperHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->operHelp = func;
+ }
+}
+
+/**
+ * Add output to hostserv help.
+ * when doing a /msg hostserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetHostHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->hostHelp = func;
+ }
+}
+
+/**
+ * Add output to helpserv help.
+ * when doing a /msg helpserv help, your function will be called to allow it to send out a notice() with the code you wish to dispaly
+ * @param func a pointer to the function which will display the code
+ **/
+void moduleSetHelpHelp(void (*func) (User * u))
+{
+ if (mod_current_module) {
+ mod_current_module->helpHelp = func;
+ }
+}
+
+/**
+ * Display any extra module help for the given service.
+ * @param services which services is help being dispalyed for?
+ * @param u which user is requesting the help
+ **/
+void moduleDisplayHelp(int service, User * u)
+{
+#ifdef USE_MODULES
+ int idx;
+ int header_shown = 0;
+ ModuleHash *current = NULL;
+
+ for (idx = 0; idx != MAX_CMD_HASH; idx++) {
+ for (current = MODULE_HASH[idx]; current; current = current->next) {
+ if ((service == 1) && current->m->nickHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_NickServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->nickHelp(u);
+ } else if ((service == 2) && current->m->chanHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_ChanServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->chanHelp(u);
+ } else if ((service == 3) && current->m->memoHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_MemoServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->memoHelp(u);
+ } else if ((service == 4) && current->m->botHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_BotServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->botHelp(u);
+ } else if ((service == 5) && current->m->operHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_OperServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->operHelp(u);
+ } else if ((service == 6) && current->m->hostHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_HostServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->hostHelp(u);
+ } else if ((service == 7) && current->m->helpHelp) {
+ if (header_shown == 0) {
+ notice_lang(s_HelpServ, u, MODULE_HELP_HEADER);
+ header_shown = 1;
+ }
+ current->m->helpHelp(u);
+ }
+ }
+ }
+#endif
+}
+
+/**
+ * Add module data to a struct.
+ * This allows module coders to add data to an existing struct
+ * @param md The module data for the struct to be used
+ * @param key The Key for the key/value pair
+ * @param value The value for the key/value pair, this is what will be stored for you
+ * @return MOD_ERR_OK will be returned on success
+ **/
+int moduleAddData(ModuleData * md[], char *key, char *value)
+{
+ char *mod_name = sstrdup(mod_current_module_name);
+
+ int index = 0;
+ ModuleData *current = NULL;
+ ModuleData *newHash = NULL;
+ ModuleData *lastHash = NULL;
+ ModuleDataItem *item = NULL;
+ ModuleDataItem *itemCurrent = NULL;
+ ModuleDataItem *lastItem = NULL;
+ index = CMD_HASH(mod_name);
+
+ if (!key || !value) {
+ alog("A module tried to use ModuleAddData() with one ore more NULL arguments... returning");
+ return MOD_ERR_PARAMS;
+ }
+
+ for (current = md[index]; current; current = current->next) {
+ if (stricmp(current->moduleName, mod_name) == 0)
+ lastHash = current;
+ }
+
+ if (!lastHash) {
+ newHash = malloc(sizeof(ModuleData));
+ if (!newHash) {
+ return MOD_ERR_MEMORY;
+ }
+ newHash->next = NULL;
+ newHash->di = NULL;
+ newHash->moduleName = strdup(mod_name);
+ md[index] = newHash;
+ lastHash = newHash;
+ }
+
+ /**
+ * Ok, at this point lastHash will always be a valid ModuleData struct, and will always be "our" module Data Struct
+ **/
+ for (itemCurrent = lastHash->di; itemCurrent;
+ itemCurrent = itemCurrent->next) {
+ if (stricmp(itemCurrent->key, key) == 0) {
+ item = itemCurrent;
+ }
+ lastItem = itemCurrent;
+ }
+ if (!item) {
+ item = malloc(sizeof(ModuleDataItem));
+ if (!item) {
+ return MOD_ERR_MEMORY;
+ }
+ item->next = NULL;
+ item->key = strdup(key);
+ item->value = strdup(value);
+ if (lastItem)
+ lastItem->next = item;
+ else
+ lastHash->di = item;
+ } else {
+ free(item->key);
+ free(item->value);
+ item->key = strdup(key);
+ item->value = strdup(value);
+ }
+ free(mod_name);
+ return MOD_ERR_OK;
+
+}
+
+/**
+ * Returns the value from a key/value pair set.
+ * This allows module coders to retrive any data they have previuosly stored in any given struct
+ * @param md The module data for the struct to be used
+ * @param key The key to find the data for
+ * @return the value paired to the given key will be returned, or NULL
+ **/
+char *moduleGetData(ModuleData * md[], char *key)
+{
+ char *mod_name = sstrdup(mod_current_module_name);
+ int index = 0;
+ char *ret = NULL;
+ ModuleData *current = NULL;
+ ModuleData *lastHash = NULL;
+ ModuleDataItem *itemCurrent = NULL;
+ index = CMD_HASH(mod_name);
+ if(!md) { return NULL; }
+ for (current = md[index]; current; current = current->next) {
+ if (stricmp(current->moduleName, mod_name) == 0)
+ lastHash = current;
+ }
+
+ if (lastHash) {
+ for (itemCurrent = lastHash->di; itemCurrent;
+ itemCurrent = itemCurrent->next) {
+ if (strcmp(itemCurrent->key, key) == 0) {
+ ret = strdup(itemCurrent->value);
+ }
+ }
+ }
+ free(mod_name);
+ return ret;
+}
+
+/**
+ * Delete the key/value pair indicated by "key" for the current module.
+ * This allows module coders to remove a previously stored key/value pair.
+ * @param md The module data for the struct to be used
+ * @param key The key to delete the key/value pair for
+ **/
+void moduleDelData(ModuleData * md[], char *key)
+{
+ char *mod_name = sstrdup(mod_current_module_name);
+ int index = 0;
+ ModuleData *current = NULL;
+ ModuleData *lastHash = NULL;
+
+ ModuleDataItem *itemCurrent = NULL;
+ ModuleDataItem *prev = NULL;
+ ModuleDataItem *next = NULL;
+ index = CMD_HASH(mod_name);
+
+ for (current = md[index]; current; current = current->next) {
+ if (stricmp(current->moduleName, mod_name) == 0)
+ lastHash = current;
+ }
+ if (lastHash) {
+ for (itemCurrent = lastHash->di; itemCurrent; itemCurrent = next) {
+ next = itemCurrent->next;
+ if (strcmp(key, itemCurrent->key) == 0) {
+ free(itemCurrent->key);
+ free(itemCurrent->value);
+ itemCurrent->next = NULL;
+ free(itemCurrent);
+ if (prev) {
+ prev->next = next;
+ } else {
+ lastHash->di = next;
+ }
+ } else {
+ prev = itemCurrent;
+ }
+ }
+ }
+ free(mod_name);
+}
+
+/**
+ * This will remove all data for a particular module from existing structs.
+ * Its primary use is modulePrepForUnload() however, based on past expericance with module coders wanting to
+ * do just about anything and everything, its safe to use from inside the module.
+ * @param md The module data for the struct to be used
+ **/
+void moduleDelAllData(ModuleData * md[])
+{
+ char *mod_name = sstrdup(mod_current_module_name);
+ int index = 0;
+ ModuleData *current = NULL;
+ ModuleData *lastHash = NULL;
+
+ ModuleDataItem *itemCurrent = NULL;
+ ModuleDataItem *prev = NULL;
+ ModuleDataItem *next = NULL;
+ index = CMD_HASH(mod_name);
+
+ for (current = md[index]; current; current = current->next) {
+ if (stricmp(current->moduleName, mod_name) == 0)
+ lastHash = current;
+ }
+ if (lastHash) {
+ for (itemCurrent = lastHash->di; itemCurrent; itemCurrent = next) {
+ next = itemCurrent->next;
+ free(itemCurrent->key);
+ free(itemCurrent->value);
+ itemCurrent->next = NULL;
+ free(itemCurrent);
+ if (prev) {
+ prev->next = next;
+ } else {
+ lastHash->di = next;
+ }
+ }
+ }
+ free(mod_name);
+}
+
+/**
+ * This will delete all module data used in any struct by module m.
+ * @param m The module to clear all data for
+ **/
+void moduleDelAllDataMod(Module * m)
+{
+ boolean freeme = false;
+ int i, j;
+ User *user;
+ NickAlias *na;
+ NickCore *nc;
+ ChannelInfo *ci;
+
+ if (!mod_current_module_name) {
+ mod_current_module_name = sstrdup(m->name);
+ freeme = true;
+ }
+
+ for (i = 0; i < 1024; i++) {
+ /* Remove the users */
+ for (user = userlist[i]; user; user = user->next) {
+ moduleDelAllData(user->moduleData);
+ }
+ /* Remove the nick Cores */
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ moduleDelAllData(nc->moduleData);
+ /* Remove any memo data for this nick core */
+ for (j = 0; j < nc->memos.memocount; j++) {
+ moduleCleanStruct(nc->memos.memos[j].moduleData);
+ }
+ }
+ /* Remove the nick Aliases */
+ for (na = nalists[i]; na; na = na->next) {
+ moduleDelAllData(na->moduleData);
+ }
+ }
+
+ for (i = 0; i < 256; i++) {
+ /* Remove any chan info data */
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ moduleDelAllData(ci->moduleData);
+ /* Remove any memo data for this nick core */
+ for (j = 0; j < ci->memos.memocount; j++) {
+ moduleCleanStruct(ci->memos.memos[j].moduleData);
+ }
+ }
+ }
+
+ if (freeme) {
+ free(mod_current_module_name);
+ mod_current_module_name = NULL;
+ }
+}
+
+/**
+ * Remove any data fro many module used in the given struct.
+ * Useful for cleaning up when a User leave's the net, a NickCore is deleted, etc...
+ * @param moduleData the moduleData struct to "clean"
+ **/
+void moduleCleanStruct(ModuleData * moduleData[])
+{
+ ModuleData *md = NULL, *nextMd = NULL;
+ ModuleDataItem *item = NULL, *nextItem = NULL;
+ int i;
+
+ for (i = 0; i < 1024; i++) {
+ for (md = moduleData[i]; md; md = nextMd) {
+ nextMd = md->next;
+ for (item = md->di; item; item = nextItem) {
+ nextItem = item->next;
+ free(item->key);
+ free(item->value);
+ item->next = NULL;
+ free(item);
+ }
+ free(md->moduleName);
+ free(md);
+ }
+ }
+}
+
+/* EOF */
diff --git a/src/modules/Makefile b/src/modules/Makefile
new file mode 100644
index 000000000..adb331238
--- /dev/null
+++ b/src/modules/Makefile
@@ -0,0 +1,32 @@
+include ./Makefile.inc
+
+MAKEARGS = 'CFLAGS=${CFLAGS}' 'CC=${CC}' 'ANOPELIBS=${ANOPELIBS}' \
+ 'LDFLAGS=${LDFLAGS}' 'BINDEST=${BINDEST}' 'INSTALL=${INSTALL}' \
+ 'INCLUDEDIR=${INCLUDEDIR}' 'RM=${RM}' 'CP=${CP}' \
+ 'TOUCH=${TOUCH}' 'SHELL=${SHELL}' 'DATDEST=${DATDEST}' \
+ 'RUNGROUP=${RUNGROUP}' 'MODULE_PATH=${MODULE_PATH}'
+
+OBJECTS= $(SRCS:.c=.o)
+SO_FILES=$(OBJECTS:.o=.s)
+CDEFS= -g -rdynamic -Wall
+CFLAGS=$(CFLAGS) $(CDEFS)
+
+all: $(OBJECTS)
+
+install: $(SO_FILES)
+ $(CP) ./*.so $(MODULE_PATH)
+
+distclean: clean spotless
+
+.c.o:
+ $(CC) $(CFLAGS) -I../${INCLUDEDIR} -c $<
+
+.o.s:
+ ld -shared $< -o $*.so
+
+clean:
+ rm -f *.o *.so *.c~ core
+
+spotless: clean
+ rm -f *.so Makefile.inc
+
diff --git a/src/modules/README b/src/modules/README
new file mode 100644
index 000000000..6aee0ac47
--- /dev/null
+++ b/src/modules/README
@@ -0,0 +1 @@
+Please read the "MODULES" file located on the "docs" directory.
diff --git a/src/modules/compile.sh b/src/modules/compile.sh
new file mode 100755
index 000000000..912780b65
--- /dev/null
+++ b/src/modules/compile.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# Disabled for Anope 1.6 until we figure out a better way to
+# download modules safely.
+# if [ "$1" = "getmods" ] ; then
+# wget -nv -r -l 1 -A c -nd -nc http://modules.anope.org/download/
+# rm -f robots.txt
+# exit 0
+# fi
+
+if [ ! -f ../Makefile.inc ]; then
+ echo ""
+ echo "*** ERROR: Unable to find ../Makefile.inc. You must ./configure Anope before"
+ echo "*** ERROR: compiling modules. Please read the INSTALL document for details."
+ echo ""
+ exit 1
+fi
+
+echo -n "SRCS=" > ./Makefile.inc
+FIRST=1
+for oldfile in *.c
+do
+ if [ "$FIRST" = 1 ] ; then
+ echo -n " "$oldfile >> ./Makefile.inc
+ else
+ echo "\\" >> ./Makefile.inc
+ echo -n " " $oldfile >> ./Makefile.inc
+ fi
+ FIRST=0
+done
+echo "" >> ./Makefile.inc
+
+make
+
+if [ "$1" = "install" ] ; then
+ make install
+fi
diff --git a/src/modules/configure b/src/modules/configure
new file mode 100755
index 000000000..5d75aaad9
--- /dev/null
+++ b/src/modules/configure
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+echo -n "SRCS=" > ./Makefile.inc
+FIRST=1
+for oldfile in *.c
+do
+ if [ "$FIRST" = 1 ] ; then
+ echo -n " "$oldfile >> ./Makefile.inc
+ else
+ echo "\\" >> ./Makefile.inc
+ echo -n " " $oldfile >> ./Makefile.inc
+ fi
+ FIRST=0
+done
+echo "" >> ./Makefile.inc
+
+cat <<EOT
+All done! Now run "make" (or possibly "gmake") to compile your modules.
+See the INSTALL, README and FAQ files if you have any problems.
+EOT
+exit 0
diff --git a/src/modules/hs_moo.c b/src/modules/hs_moo.c
new file mode 100644
index 000000000..3a2fccfe1
--- /dev/null
+++ b/src/modules/hs_moo.c
@@ -0,0 +1,104 @@
+/**
+ * This is an EXAMPLE module, which adds the "moo" command to HostServ
+ *
+ * This command does NOT do anything useful at all!
+ *
+ * Please visit http://modules.anope.org for useful modules!
+ *
+ **/
+#include "module.h"
+
+#define AUTHOR "Anope" /* Set the Author for a modinfo reply */
+#define VERSION "1.1" /* Set the version for a modinfo reply */
+
+int hs_moo_show(User * u); /* Function to use when a /hs moo command is recived */
+int test(int argc, char **argv);
+void myHostServHelp(User *u); /* Function to display out help in a /hs help response */
+int myHostServMooHelp(User *u); /* Function to display help to _everyone_ when a /hs help moo is called*/
+int myHostServMooRegHelp(User *u); /* Function to display extra help to regular-users when a /hs help moo is called*/
+int myHostServMooOperHelp(User *u); /* Function to display extra help to opers when a /hs help moo is called*/
+int myHostServMooAdminHelp(User *u); /* Function to display extra help to admins when a /hs help moo is called*/
+int myHostServMooRootHelp(User *u); /* Function to display extra help to roors when a /hs help moo is called*/
+
+int AnopeInit(int argc, char **argv) /* This will be executed when the module is loaded */
+{
+ Command *c; /* Pointer to a Command */
+ int status = 0; /* the status of our new command */
+ c = createCommand("moo", hs_moo_show, NULL, -1, -1, -1, -1, -1); /* Create a new command "moo" pointing to hs_moo */
+
+ moduleAddHelp(c,myHostServMooHelp); /* add help for all users to this command */
+ moduleAddRegHelp(c,myHostServMooRegHelp); /* add extra regular-user only help to this command */
+ moduleAddOperHelp(c,myHostServMooOperHelp); /* add extra oper only help to this command */
+ moduleAddAdminHelp(c,myHostServMooAdminHelp); /* add extra admin only help to this command */
+ moduleAddRootHelp(c,myHostServMooRootHelp); /* add extra root only help to this command */
+
+ moduleSetHostHelp(myHostServHelp); /* add us to the .hs help list */
+
+ status = moduleAddCommand(HOSTSERV, c, MOD_HEAD); /* Add the command to the HOSTSERV cmd table */
+ alog("hs_moo.so: Add Command 'moo' Status: %d",status); /* Log the command being added */
+
+ moduleAddCallback("test",time(NULL)+dotime("15s"),test,0,NULL); /* set a call-back function to exec in 3 mins time */
+ moduleDelCallback("test");
+ moduleAddAuthor(AUTHOR); /* tell Anope about the author */
+ moduleAddVersion(VERSION); /* Tell Anope about the verison */
+
+ if(status!=MOD_ERR_OK) {
+ return MOD_STOP;
+ }
+ return MOD_CONT;
+}
+
+int hs_moo_show(User * u)
+{
+ notice(s_HostServ, u->nick, "MOO! - This command was loaded via a module!"); /* Just notice the user */
+ return MOD_STOP; /* MOD_STOP means we will NOT pass control back to other */
+} /* modules waiting to handle the /hs moo command! */
+
+int test(int argc, char **argv) {
+ alog("CallBack from hs_moo with %d paramaters",argc);
+ return MOD_CONT;
+}
+
+
+/***************************************************************************************************************************************/
+/* The code below here shows various ways of dealing with the module help system */
+/***************************************************************************************************************************************/
+
+void myHostServHelp(User *u) {
+ notice(s_HostServ,u->nick, " MOO Moo's at the user!"); /* this will appear in the help list */
+}
+
+int myHostServMooHelp(User *u) {
+ notice(s_HostServ,u->nick,"Syntax: Moo"); /* this will be sent to everyone who does /msg hostserv help moo */
+ notice(s_HostServ,u->nick,"This command is an example provided");
+ notice(s_HostServ,u->nick,"by the Anope development team.");
+ return MOD_CONT; /* allow any other module's with help for /hs moo to run */
+}
+
+int myHostServMooRootHelp(User *u) { /* this will only be sent to ROOTS ONLY who /msg hostserv moo */
+ myHostServMooAdminHelp(u); /* this line lets us show roots the ADMIN help as well as the root help */
+ notice(s_HostServ,u->nick,"Only roots will see this part of the help");
+ return MOD_CONT;
+}
+
+int myHostServMooAdminHelp(User *u) { /* this will only be sent to ADMINS ONLY who /msg hostserv moo */
+ myHostServMooOperHelp(u); /* this line lets us show admins the OPER help as well as the admin help */
+ notice(s_HostServ,u->nick,"Only admins will see this part of the help");
+ notice(s_HostServ,u->nick,"why not visit us on www.anope.org ?");
+ return MOD_CONT;
+}
+
+int myHostServMooOperHelp(User *u) { /* this will only be sent to OPERS ONLY who /msg hostserv moo */
+ notice(s_HostServ,u->nick,"Only opers will see this part of the help");
+ notice(s_HostServ,u->nick,"for more help/support with modules");
+ notice(s_HostServ,u->nick,"visit us on irc.anope.org #anope! :)");
+ return MOD_CONT;
+}
+
+int myHostServMooRegHelp(User *u) { /* this will only be sent to REGULAR USERS ONLY who /msg hostserv moo */
+ notice(s_HostServ,u->nick,"Only non-opers will see this part of the help");
+ notice(s_HostServ,u->nick,"as we've left it hidden from opers");
+ return MOD_CONT;
+}
+
+/* EOF */
diff --git a/src/modules/ircd_catserv.c b/src/modules/ircd_catserv.c
new file mode 100644
index 000000000..436b18dcc
--- /dev/null
+++ b/src/modules/ircd_catserv.c
@@ -0,0 +1,128 @@
+/**
+ * Simple module to load up a client called CatServ and process commands for it
+ * This module is an example, and has no useful purpose!
+ *
+ * Please visit http://modules.anope.org for useful modules!
+ *
+ **/
+
+#include "module.h"
+
+#define AUTHOR "Anope"
+#define VERSION "1.1"
+
+int my_privmsg(char *source, int ac, char **av);
+CommandHash *Catserv_cmdTable[MAX_CMD_HASH];
+
+void addClient(char *nick, char *realname);
+void addMessageList(void);
+void delClient(void);
+char *s_CatServ = "CatServ";
+void catserv(User * u, char *buf);
+
+int do_meow(User * u);
+int do_purr(User * u);
+
+int AnopeInit(int argc, char **argv)
+{
+ Message *msg = NULL;
+ int status;
+ msg = createMessage("PRIVMSG", my_privmsg);
+ status = moduleAddMessage(msg, MOD_HEAD);
+ if (status == MOD_ERR_OK) {
+ addClient(s_CatServ, "meow!");
+ addMessageList();
+ }
+ moduleAddAuthor(AUTHOR);
+ moduleAddVersion(VERSION);
+ alog("ircd_catserv.so: loaded, message status [%d]", status);
+ return MOD_CONT;
+}
+
+void AnopeFini(void)
+{
+ delClient();
+}
+
+int my_privmsg(char *source, int ac, char **av)
+{
+ User *u;
+ char *s;
+
+ /* First, some basic checks */
+ if (ac != 2)
+ return MOD_CONT; /* bleh */
+ if (!(u = finduser(source))) {
+ return MOD_CONT;
+ } /* non-user source */
+ if (*av[0] == '#') {
+ return MOD_CONT;
+ }
+ /* Channel message */
+ /* we should prolly honour the ignore list here, but i cba for this... */
+ s = strchr(av[0], '@');
+ if (s) {
+ *s++ = 0;
+ if (stricmp(s, ServerName) != 0)
+ return MOD_CONT;
+ }
+ if ((stricmp(av[0], s_CatServ)) == 0) { /* its for US! */
+ catserv(u, av[1]);
+ return MOD_STOP;
+ } else { /* ok it isnt us, let the old code have it */
+ return MOD_CONT;
+ }
+}
+
+void addClient(char *nick, char *realname)
+{
+ NEWNICK(nick, "catserv", "meow.meow.land", realname, "+", 1);
+}
+
+void delClient(void)
+{
+ send_cmd(s_CatServ, "QUIT :Module Unloaded!");
+}
+
+void addMessageList(void)
+{
+ Command *c;
+ c = createCommand("meow", do_meow, NULL, -1, -1, -1, -1, -1);
+ addCommand(Catserv_cmdTable, c, MOD_UNIQUE);
+ c = createCommand("purr", do_purr, NULL, -1, -1, -1, -1, -1);
+ addCommand(Catserv_cmdTable, c, MOD_UNIQUE);
+}
+
+/*****************************************************************************/
+/* Main CatServ routine. */
+void catserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_CatServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_CatServ, u, SERVICE_OFFLINE, s_CatServ);
+ } else {
+ mod_run_cmd(s_CatServ, u, Catserv_cmdTable, cmd);
+ }
+}
+
+int do_meow(User * u)
+{
+ notice(s_CatServ, u->nick, "MEOW!");
+ return MOD_STOP;
+}
+
+int do_purr(User * u)
+{
+ notice(s_CatServ, u->nick, "PURR!");
+ return MOD_STOP;
+}
+
diff --git a/src/modules/module.h b/src/modules/module.h
new file mode 100644
index 000000000..336762955
--- /dev/null
+++ b/src/modules/module.h
@@ -0,0 +1,9 @@
+#include "../services.h"
+#include "../commands.h"
+#include "../language.h"
+#include "../modules.h"
+
+#define MOD_UNIQUE 0
+#define MOD_HEAD 1
+#define MOD_TAIL 2
+
diff --git a/src/mysql.c b/src/mysql.c
new file mode 100644
index 000000000..1b83502c2
--- /dev/null
+++ b/src/mysql.c
@@ -0,0 +1,1638 @@
+/* MySQL functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+#include "services.h"
+
+/*************************************************************************/
+
+/* Database Global Variables */
+MYSQL *mysql; /* MySQL Handler */
+MYSQL_RES *mysql_res; /* MySQL Result */
+MYSQL_FIELD *mysql_fields; /* MySQL Fields */
+MYSQL_ROW mysql_row; /* MySQL Row */
+
+/*************************************************************************/
+
+void db_mysql_error(int severity, char *msg)
+{
+ static char buf[512];
+
+ if (mysql_error(mysql)) {
+ snprintf(buf, sizeof(buf), "MySQL %s %s: %s", msg,
+ severity == MYSQL_WARNING ? "warning" : "error",
+ mysql_error(mysql));
+ } else {
+ snprintf(buf, sizeof(buf), "MySQL %s %s", msg,
+ severity == MYSQL_WARNING ? "warning" : "error");
+ }
+
+ log_perror(buf);
+
+ if (severity == MYSQL_ERROR) {
+ log_perror("MySQL FATAL error... aborting.");
+ exit(0);
+ }
+
+}
+
+/*************************************************************************/
+
+int db_mysql_init()
+{
+
+ /* If the host is not defined, assume we don't want MySQL */
+ if (!MysqlHost) {
+ do_mysql = 0;
+ alog("MySQL has been disabled.");
+ } else {
+ do_mysql = 1;
+ alog("MySQL has been enabled.");
+ }
+
+ /* The following configuration options are required.
+ * If missing disable MySQL to avoid any problems.
+ */
+
+ if (!MysqlName || !MysqlUser) {
+ do_mysql = 0;
+ alog("MySQL Error: Set all required configuration options.");
+ }
+
+ if (!db_mysql_open())
+ do_mysql = 0;
+
+ return 1;
+}
+
+/*************************************************************************/
+
+int db_mysql_open()
+{
+ /* If MySQL is disabled, return 0 */
+ if (!do_mysql)
+ return 0;
+
+ mysql = mysql_init(NULL);
+ if (mysql == NULL)
+ db_mysql_error(MYSQL_WARNING, "Unable to create mysql object");
+
+ if (!MysqlPort)
+ MysqlPort = 3306;
+
+ if (MysqlSock) {
+ if ((!mysql_real_connect
+ (mysql, MysqlHost, MysqlUser, MysqlPass, MysqlName, MysqlPort,
+ MysqlSock, 0))) {
+ log_perror("Cant connect to MySQL: %s\n", mysql_error(mysql));
+ return 0;
+ }
+ } else {
+ if ((!mysql_real_connect
+ (mysql, MysqlHost, MysqlUser, MysqlPass, MysqlName, MysqlPort,
+ NULL, 0))) {
+ log_perror("Cant connect to MySQL: %s\n", mysql_error(mysql));
+ return 0;
+ }
+ }
+
+ return 1;
+
+}
+
+/*************************************************************************/
+
+int db_mysql_query(char *sql)
+{
+ int result, lcv;
+ char *s;
+
+ if (!do_mysql) {
+ return -1;
+ }
+
+ if (debug) {
+ s = db_mysql_quote(sql);
+ alog(s);
+ free(s);
+
+ }
+
+ result = mysql_query(mysql, sql);
+
+ if (result) {
+ switch (mysql_errno(mysql)) {
+ case CR_SERVER_GONE_ERROR:
+ case CR_SERVER_LOST:
+
+ for (lcv = 0; lcv < MysqlRetries; lcv++) {
+ if (db_mysql_open()) {
+ result = mysql_query(mysql, sql);
+ return (result);
+ }
+ sleep(MysqlRetryGap);
+ }
+
+ /* If we get here, we could not connect. */
+ log_perror("Unable to reconnect to database: %s\n",
+ mysql_error(mysql));
+ db_mysql_error(MYSQL_ERROR, "connect");
+
+ /* Never reached. */
+ break;
+
+ default:
+ /* Unhandled error. */
+ return (result);
+ }
+ }
+
+ return (0);
+
+}
+
+/*************************************************************************/
+
+char *db_mysql_quote(char *sql)
+{
+ int slen;
+ char *quoted;
+
+
+ if (!sql) {
+ return sstrdup("");
+ }
+
+ slen = strlen(sql);
+ quoted = malloc((1 + (slen * 2)) * sizeof(char));
+
+ mysql_real_escape_string(mysql, quoted, sql, slen);
+ return quoted;
+
+}
+
+/*************************************************************************/
+
+/* I don't like using res here, maybe we can pass it as a param? */
+int db_mysql_close()
+{
+ if (mysql_res)
+ mysql_free_result(mysql_res);
+ mysql_close(mysql);
+ return 1;
+}
+
+/*************************************************************************/
+
+/*
+ * NickServ Specific Secion
+ */
+
+/*************************************************************************/
+void db_mysql_save_ns_req(NickRequest * nr)
+{
+ char *qnick, *qpasscode, *qpassword, *qemail;
+ char sqlcmd[MAX_SQL_BUF];
+
+ qnick = db_mysql_quote(nr->nick);
+ qpasscode = db_mysql_quote(nr->passcode);
+ qpassword = db_mysql_quote(nr->password);
+ qemail = db_mysql_quote(nr->email);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "REPLACE anope_ns_request (nick,passcode,password,email,requested,active)"
+ " VALUES ('%s','%s','%s','%s','%d','1')",
+ qnick, qpasscode, qpassword, qemail, (int) nr->requested);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(qnick);
+ free(qpasscode);
+ free(qpassword);
+ free(qemail);
+}
+
+char *db_mysql_secure(char *pass)
+{
+
+ char epass[BUFSIZE];
+
+ /* Initialize the buffer. Bug #86 */
+ memset(epass, '\0', BUFSIZE);
+
+#ifdef USE_ENCRYPTION
+ /* If we use the builtin encryption don't double encrypt! */
+ snprintf(epass, sizeof(epass), "'%s'", pass);
+#else
+
+ if (!pass) {
+ snprintf(epass, sizeof(epass), "''");
+ } else if ((!MysqlSecure) || (strcmp(MysqlSecure, "") == 0)) {
+ snprintf(epass, sizeof(epass), "'%s'", pass);
+ } else if (strcmp(MysqlSecure, "des") == 0) {
+ snprintf(epass, sizeof(epass), "ENCRYPT('%s')", pass);
+ } else if (strcmp(MysqlSecure, "md5") == 0) {
+ snprintf(epass, sizeof(epass), "MD5('%s')", pass);
+ } else if (strcmp(MysqlSecure, "sha") == 0) {
+ snprintf(epass, sizeof(epass), "SHA('%s')", pass);
+ } else {
+ snprintf(epass, sizeof(epass), "ENCODE('%s','%s')", pass,
+ MysqlSecure);
+ }
+
+#endif
+
+ return sstrdup(epass);
+
+}
+
+/*************************************************************************/
+void db_mysql_save_ns_core(NickCore * nc)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ int j;
+ char **access;
+ Memo *memos;
+ char *cnick, *cpass, *epass, *cemail, *cgreet, *curl, *caccess,
+ *msender, *mtext;
+
+ cnick = db_mysql_quote(nc->display);
+ cpass = db_mysql_quote(nc->pass);
+ cemail = db_mysql_quote(nc->email);
+ cgreet = db_mysql_quote(nc->greet);
+ curl = db_mysql_quote(nc->url);
+
+ epass = db_mysql_secure(cpass);
+ free(cpass);
+
+ /* Let's take care of the core itself */
+ /* Update the existing records */
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "UPDATE anope_ns_core SET pass=%s,email='%s',greet='%s',icq='%d',url='%s',flags='%d',"
+ "language='%d',accesscount='%d',memocount='%d',memomax='%d',channelcount='%d'"
+ ",channelmax='%d',active='1' WHERE display='%s'",
+ epass, cemail, cgreet, nc->icq, curl, nc->flags,
+ nc->language, nc->accesscount, nc->memos.memocount,
+ nc->memos.memomax, nc->channelcount, nc->channelmax, cnick);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+
+ /* need to write a wrapper for mysql_affected_rows */
+ /* Our previous UPDATE affected no rows, therefore this is a new record */
+ if ((int) mysql_affected_rows(mysql) <= 0) {
+
+ /* Let's take care of the core itself */
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_ns_core (display,pass,email,greet,icq,url,flags,"
+ "language,accesscount,memocount,memomax,channelcount,channelmax,active)"
+ " VALUES ('%s',%s,'%s','%s','%d','%s','%d','%d','%d','%d','%d','%d','%d','1')",
+ cnick, epass, cemail, cgreet, nc->icq, curl, nc->flags,
+ nc->language, nc->accesscount, nc->memos.memocount,
+ nc->memos.memomax, nc->channelcount, nc->channelmax);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ }
+
+ /* Now let's do the access */
+ for (j = 0, access = nc->access; j < nc->accesscount; j++, access++) {
+ caccess = db_mysql_quote(*access);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_ns_access (display,access) VALUES ('%s','%s')",
+ cnick, caccess);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(caccess);
+ }
+
+ /* And... memos */
+ memos = nc->memos.memos;
+ for (j = 0; j < nc->memos.memocount; j++, memos++) {
+ msender = db_mysql_quote(memos->sender);
+ mtext = db_mysql_quote(memos->text);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_ms_info (receiver,number,flags,time,sender,text,serv)"
+ " VALUES ('%s','%d','%d','%d','%s','%s','NICK')",
+ cnick, memos->number, memos->flags,
+ (int) memos->time, msender, mtext);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(msender);
+ free(mtext);
+ }
+
+ free(cnick);
+ free(epass);
+ free(cemail);
+ free(cgreet);
+ free(curl);
+}
+
+
+/*************************************************************************/
+void db_mysql_save_ns_alias(NickAlias * na)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *nnick, *nlmask, *nlrname, *nlquit, *nncnick;
+ nnick = db_mysql_quote(na->nick);
+ nlmask = db_mysql_quote(na->last_usermask);
+ nlrname = db_mysql_quote(na->last_realname);
+ nlquit = db_mysql_quote(na->last_quit);
+ nncnick = db_mysql_quote(na->nc->display);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "UPDATE anope_ns_alias SET last_usermask='%s',last_realname='%s',last_quit='%s',time_registered='%d',last_seen='%d',status='%d',display='%s',active='1' WHERE nick='%s'",
+ nlmask, nlrname, nlquit, (int) na->time_registered,
+ (int) na->last_seen, (int) na->status, nncnick, nnick);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ /* Our previous UPDATE affected no rows, therefore this is a new record */
+ if ((int) mysql_affected_rows(mysql) <= 0) {
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT INTO anope_ns_alias (nick,last_usermask,last_realname,last_quit,time_registered,last_seen,status,display,active) VALUES ('%s','%s','%s','%s','%d','%d','%d','%s','1')",
+ nnick, nlmask, nlrname, nlquit, (int) na->time_registered,
+ (int) na->last_seen, (int) na->status, nncnick);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ }
+
+ free(nnick);
+ free(nlmask);
+ free(nlrname);
+ free(nlquit);
+ free(nncnick);
+
+ return;
+}
+
+/*************************************************************************/
+
+/*
+ * ChanServ Specific Secion
+ */
+
+/*************************************************************************/
+void db_mysql_save_cs_info(ChannelInfo * ci)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ int j, position;
+ Memo *memos;
+ char *ciname, *cifoundernick, *cisuccessornick, *cifounderpass,
+ *cidesc, *ciurl, *ciemail, *cilasttopic, *cilasttopicsetter,
+ *ciforbidby, *ciforbidreason, *cimlock_key, *cimlock_flood,
+ *cimlock_redirect, *cientrymsg, *cibotnick, *msender, *mtext,
+ *ciaccessdisp, *ciakickdisp, *ciakickreason, *ciakickcreator,
+ *cbadwords, *efounderpass;
+
+ ciname = db_mysql_quote(ci->name);
+ cifoundernick =
+ ci->founder ? db_mysql_quote(ci->founder->display) : "";
+ cisuccessornick =
+ ci->successor ? db_mysql_quote(ci->successor->display) : "";
+ cifounderpass = db_mysql_quote(ci->founderpass);
+ cidesc = db_mysql_quote(ci->desc);
+ ciurl = db_mysql_quote(ci->url);
+ ciemail = db_mysql_quote(ci->email);
+ cilasttopic = db_mysql_quote(ci->last_topic);
+ cilasttopicsetter = db_mysql_quote(ci->last_topic_setter);
+ ciforbidby = db_mysql_quote(ci->forbidby);
+ ciforbidreason = db_mysql_quote(ci->forbidreason);
+ cimlock_key = db_mysql_quote(ci->mlock_key);
+#ifdef HAS_FMODE
+ cimlock_flood = db_mysql_quote(ci->mlock_flood);
+#else
+ cimlock_flood = NULL;
+#endif
+#ifdef HAS_LMODE
+ cimlock_redirect = db_mysql_quote(ci->mlock_redirect);
+#else
+ cimlock_redirect = NULL;
+#endif
+ cientrymsg = db_mysql_quote(ci->entry_message);
+ cibotnick = ci->bi ? db_mysql_quote(ci->bi->nick) : "";
+
+ efounderpass = db_mysql_secure(cifounderpass);
+ free(cifounderpass);
+
+ /* Let's take care of the core itself */
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "UPDATE anope_cs_info SET founder='%s',successor='%s',founderpass=%s,"
+ "descr='%s',url='%s',email='%s',time_registered='%d',last_used='%d',"
+ "last_topic='%s',last_topic_setter='%s',last_topic_time='%d',flags='%d',"
+ "forbidby='%s',forbidreason='%s',bantype='%d',accesscount='%d',"
+ "akickcount='%d',mlock_on='%d',mlock_off='%d',mlock_limit='%d',"
+ "mlock_key='%s',mlock_flood='%s',mlock_redirect='%s',entry_message='%s',"
+ "memomax='%d',botnick='%s',botflags='%d',ttb='%d',bwcount='%d',"
+ "capsmin='%d',capspercent='%d',floodlines='%d',floodsecs='%d',"
+ "repeattimes='%d',active='1' WHERE name='%s'",
+ cifoundernick,
+ cisuccessornick,
+ efounderpass, cidesc, ciurl, ciemail,
+ (int) ci->time_registered, (int) ci->last_used,
+ cilasttopic, cilasttopicsetter,
+ (int) ci->last_topic_time, (int) ci->flags,
+ ciforbidby, ciforbidreason, (int) ci->bantype,
+ (int) ci->accesscount, (int) ci->akickcount,
+ (int) ci->mlock_on, (int) ci->mlock_off,
+ (int) ci->mlock_limit, cimlock_key,
+#ifdef HAS_FMODE
+ cimlock_flood,
+#else
+ "",
+#endif
+#ifdef HAS_LMODE
+ cimlock_redirect,
+#else
+ "",
+#endif
+ cientrymsg,
+ (int) ci->memos.memomax,
+ cibotnick,
+ (int) ci->botflags,
+ (int) ci->ttb,
+ (int) ci->bwcount,
+ (int) ci->capsmin,
+ (int) ci->capspercent,
+ (int) ci->floodlines,
+ (int) ci->floodsecs, (int) ci->repeattimes, ciname);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+
+ /* Our previous UPDATE affected no rows, therefore this is a new record */
+ if ((int) mysql_affected_rows(mysql) <= 0) {
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_cs_info (name,founder,successor,founderpass,"
+ "descr,url,email,time_registered,last_used,last_topic,last_topic_setter"
+ ",last_topic_time,flags,forbidby,forbidreason,bantype,accesscount,akickcount"
+ ",mlock_on,mlock_off,mlock_limit,mlock_key,mlock_flood,mlock_redirect,"
+ "entry_message,botnick,botflags,bwcount,capsmin,capspercent,floodlines,"
+ "floodsecs,repeattimes,active) VALUES ('%s','%s','%s',%s,'%s','%s','%s'"
+ ",'%d','%d','%s','%s','%d','%d','%s','%s','%d','%d','%d','%d','%d','%d',"
+ "'%s','%s','%s','%s','%s','%d','%d','%d','%d','%d','%d','%d','1')",
+ ciname,
+ cifoundernick,
+ cisuccessornick,
+ efounderpass, cidesc, ciurl, ciemail,
+ (int) ci->time_registered, (int) ci->last_used,
+ cilasttopic, cilasttopicsetter,
+ (int) ci->last_topic_time, (int) ci->flags,
+ ciforbidby, ciforbidreason, (int) ci->bantype,
+ (int) ci->accesscount, (int) ci->akickcount,
+ (int) ci->mlock_on, (int) ci->mlock_off,
+ (int) ci->mlock_limit, cimlock_key,
+#ifdef HAS_FMODE
+ cimlock_flood,
+#else
+ "",
+#endif
+#ifdef HAS_LMODE
+ cimlock_redirect,
+#else
+ "",
+#endif
+ cientrymsg,
+ cibotnick,
+ (int) ci->botflags,
+ (int) ci->bwcount,
+ (int) ci->capsmin,
+ (int) ci->capspercent,
+ (int) ci->floodlines,
+ (int) ci->floodsecs, (int) ci->repeattimes);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ }
+
+ /* Memos */
+ memos = ci->memos.memos;
+ for (j = 0; j < ci->memos.memocount; j++, memos++) {
+ msender = db_mysql_quote(memos->sender);
+ mtext = db_mysql_quote(memos->text);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_ms_info (receiver,number,flags,time,sender,text,serv)"
+ " VALUES ('%s','%d','%d','%d','%s','%s','CHAN')",
+ ciname, memos->number, memos->flags,
+ (int) memos->time, msender, mtext);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(msender);
+ free(mtext);
+ }
+
+ /* Access */
+ for (j = 0; j < ci->accesscount; j++) {
+ if (ci->access[j].in_use) {
+ ciaccessdisp = db_mysql_quote(ci->access[j].nc->display);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_cs_access (in_use,level,display,channel,last_seen)"
+ " VALUES ('%d','%d','%s','%s','%d')",
+ (int) ci->access[j].in_use, (int) ci->access[j].level,
+ ciaccessdisp, ciname, (int) ci->access[j].last_seen);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(ciaccessdisp);
+ }
+ }
+
+ /* Levels */
+ position = 0;
+ for (j = 0; j < CA_SIZE; j++) {
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_cs_levels (channel, position, level) VALUES ('%s','%d','%d')",
+ ciname, position++, (int) ci->levels[j]);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ }
+
+ /* Akicks */
+ for (j = 0; j < ci->akickcount; j++) {
+ ciakickdisp =
+ ci->akick[j].flags & AK_USED ? ci->akick[j].
+ flags & AK_ISNICK ? db_mysql_quote(ci->akick[j].u.nc->
+ display) :
+ db_mysql_quote(ci->akick[j].u.mask) : "";
+ ciakickreason =
+ ci->akick[j].flags & AK_USED ? db_mysql_quote(ci->akick[j].
+ reason) : "";
+ ciakickcreator =
+ ci->akick[j].flags & AK_USED ? db_mysql_quote(ci->akick[j].
+ creator) : "";
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_cs_akicks (channel, flags, dmask, reason, creator,"
+ " addtime) VALUES ('%s','%d','%s','%s','%s','%d')",
+ ciname, (int) ci->akick[j].flags, ciakickdisp,
+ ciakickreason, ciakickcreator,
+ ci->akick[j].flags & AK_USED ? (int) ci->akick[j].
+ addtime : 0);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ if (ci->akick[j].flags & AK_USED) {
+ free(ciakickdisp);
+ free(ciakickreason);
+ free(ciakickcreator);
+ }
+ }
+
+ /* Bad Words */
+ for (j = 0; j < ci->bwcount; j++) {
+ if (ci->badwords[j].in_use) {
+ cbadwords = db_mysql_quote(ci->badwords[j].word);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_cs_badwords (channel, word, type)"
+ " VALUES ('%s','%s','%d')", ciname, cbadwords,
+ (int) ci->badwords[j].type);
+ free(cbadwords);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ }
+ }
+
+ free(ciname);
+ if (!(ci->flags & CI_VERBOTEN)) {
+ free(cifoundernick);
+ if (strlen(cisuccessornick) > 0)
+ free(cisuccessornick);
+ free(efounderpass);
+ free(cidesc);
+ free(ciurl);
+ free(ciemail);
+ free(cilasttopic);
+ free(cilasttopicsetter);
+ free(cimlock_key);
+ free(cimlock_flood);
+ free(cimlock_redirect);
+ free(cientrymsg);
+ if (ci->bi)
+ free(cibotnick);
+ } else {
+ free(ciforbidby);
+ free(ciforbidreason);
+ }
+
+ return;
+}
+
+/*************************************************************************/
+
+
+/*
+ * OperServ Specific Section
+ */
+
+/*************************************************************************/
+void db_mysql_save_os_db(unsigned int maxucnt, unsigned int maxutime,
+ SList * ak, SList * sgl, SList * sql, SList * szl,
+ HostCache * hc)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ Akill *t_ak;
+ SXLine *t_sl;
+ HostCache *t_hc;
+ char *takuser, *takhost, *takby, *takreason, *tslmask, *tslby,
+ *tslreason, *thchost;
+
+ int i, j;
+
+ rdb_clear_table("anope_os_core");
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_core (maxusercnt,maxusertime,akills_count,"
+ "sglines_count,sqlines_count,szlines_count) VALUES "
+ "('%d','%d','%d','%d','%d','%d')", maxucnt, maxutime,
+ ak->count, sgl->count, sql->count, szl->count);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+
+ /* now the akills saving */
+ rdb_clear_table("anope_os_akills");
+
+ j = ak->count;
+ for (i = 0; i < j; i++) {
+ t_ak = ak->list[i];
+ takuser = db_mysql_quote(t_ak->user);
+ takhost = db_mysql_quote(t_ak->host);
+ takby = db_mysql_quote(t_ak->by);
+ takreason = db_mysql_quote(t_ak->reason);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_akills (user,host,xby,reason,seton,expire) VALUES ('%s','%s','%s','%s','%d','%d')",
+ takuser,
+ takhost,
+ takby, takreason, (int) t_ak->seton, (int) t_ak->expires);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(takuser);
+ free(takhost);
+ free(takby);
+ free(takreason);
+ }
+
+/* sglines save */
+ rdb_clear_table("anope_os_sglines");
+
+ j = sgl->count;
+ for (i = 0; i < j; i++) {
+ t_sl = sgl->list[i];
+ tslmask = db_mysql_quote(t_sl->mask);
+ tslby = db_mysql_quote(t_sl->by);
+ tslreason = db_mysql_quote(t_sl->reason);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_sglines (mask,xby,reason,seton,expire) VALUES"
+ " ('%s','%s','%s','%d','%d')",
+ tslmask,
+ tslby, tslreason, (int) t_sl->seton, (int) t_sl->expires);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(tslmask);
+ free(tslby);
+ free(tslreason);
+ }
+
+/* sqlines save */
+ rdb_clear_table("anope_os_sqlines");
+
+ j = sql->count;
+ for (i = 0; i < j; i++) {
+ t_sl = sql->list[i];
+ tslmask = db_mysql_quote(t_sl->mask);
+ tslby = db_mysql_quote(t_sl->by);
+ tslreason = db_mysql_quote(t_sl->reason);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_sqlines (mask,xby,reason,seton,expire) VALUES ('%s','%s','%s','%d','%d')",
+ tslmask,
+ tslby, tslreason, (int) t_sl->seton, (int) t_sl->expires);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(tslmask);
+ free(tslby);
+ free(tslreason);
+ }
+
+/* szlines save */
+ rdb_clear_table("anope_os_szlines");
+
+ j = szl->count;
+ for (i = 0; i < j; i++) {
+ t_sl = szl->list[i];
+ tslmask = db_mysql_quote(t_sl->mask);
+ tslby = db_mysql_quote(t_sl->by);
+ tslreason = db_mysql_quote(t_sl->reason);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_szlines (mask,xby,reason,seton,expire) VALUES"
+ " ('%s','%s','%s','%d','%d')",
+ tslmask,
+ tslby, tslreason, (int) t_sl->seton, (int) t_sl->expires);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(tslmask);
+ free(tslby);
+ free(tslreason);
+ }
+
+/* and finally we save hcache */
+ rdb_clear_table("anope_os_hcache");
+ for (i = 0; i < 1024; i++) {
+ for (t_hc = hcache[i]; t_hc; t_hc = t_hc->next) {
+ /* Don't save in-progress scans */
+ if (t_hc->status < HC_NORMAL)
+ continue;
+ thchost = db_mysql_quote(t_hc->host);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_hcache (mask,status,used) VALUES ('%s','%d','%d')",
+ thchost, (int) t_hc->status, (int) t_hc->used);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(thchost);
+ }
+ }
+
+ return;
+}
+
+/*************************************************************************/
+void db_mysql_save_news(NewsItem * ni)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *nitext, *niwho;
+ nitext = db_mysql_quote(ni->text);
+ niwho = db_mysql_quote(ni->who);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_news (type,num,ntext,who,`time`)"
+ " VALUES ('%d','%d','%s','%s','%d')",
+ ni->type, ni->num, nitext, niwho, (int) ni->time);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(nitext);
+ free(niwho);
+
+ return;
+}
+
+/*************************************************************************/
+void db_mysql_save_exceptions(Exception * e)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *emask, *ewho, *ereason;
+ emask = db_mysql_quote(e->mask);
+ ewho = db_mysql_quote(e->who);
+ ereason = db_mysql_quote(e->reason);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_os_exceptions (mask,lim,who,reason,`time`,expires)"
+ " VALUES ('%s','%d','%s','%s','%d','%d')",
+ emask, e->limit, ewho,
+ ereason, (int) e->time, (int) e->expires);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(emask);
+ free(ewho);
+ free(ereason);
+ return;
+}
+
+/*************************************************************************/
+
+
+/*
+ * HostServ Specific Section
+ */
+
+/*************************************************************************/
+/* TODO: Add vident to tables! */
+void db_mysql_save_hs_core(HostCore * hc)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *hcnick, *hcvident, *hcvhost, *hccreator;
+ hcnick = db_mysql_quote(hc->nick);
+ hcvident = db_mysql_quote(hc->vIdent);
+ hcvhost = db_mysql_quote(hc->vHost);
+ hccreator = db_mysql_quote(hc->creator);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_hs_core (nick,vident,vhost,creator,`time`)"
+ " VALUES ('%s','%s','%s','%s','%d')",
+ hcnick, hcvident, hcvhost, hccreator, (int) hc->time);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(hcnick);
+ free(hcvident);
+ free(hcvhost);
+ free(hccreator);
+
+ return;
+}
+
+/*************************************************************************/
+
+/*
+ * HostServ Specific Section
+ */
+
+/*************************************************************************/
+void db_mysql_save_bs_core(BotInfo * bi)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *binick, *biuser, *bihost, *bireal;
+ binick = db_mysql_quote(bi->nick);
+ biuser = db_mysql_quote(bi->user);
+ bihost = db_mysql_quote(bi->host);
+ bireal = db_mysql_quote(bi->real);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "INSERT DELAYED INTO anope_bs_core (nick,user,host,rname,flags,created"
+ ",chancount) VALUES ('%s','%s','%s','%s','%d','%d','%d')",
+ binick, biuser,
+ bihost, bireal, bi->flags, (int) bi->created, bi->chancount);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ free(binick);
+ free(biuser);
+ free(bihost);
+ free(bireal);
+}
+
+/*************************************************************************/
+
+void db_mysql_load_bs_dbase(void)
+{
+ BotInfo *bi;
+ char sqlcmd[MAX_SQL_BUF];
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `nick`,`user`,`host`,`rname`,`flags`,`created`,`chancount` FROM `anope_bs_core`");
+
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ bi = makebot(mysql_row[0]);
+ bi->user = sstrdup(mysql_row[1]);
+ bi->host = sstrdup(mysql_row[2]);
+ bi->real = sstrdup(mysql_row[3]);
+ bi->flags = atoi(mysql_row[4]);
+ bi->created = atoi(mysql_row[5]);
+ bi->chancount = atoi(mysql_row[6]);
+ }
+ mysql_free_result(mysql_res);
+}
+
+void db_mysql_load_hs_dbase(void)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ char *nick;
+ char *vHost;
+ char *creator;
+ char *vIdent;
+ int32 time;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `nick`,`vident`,`vhost`,`creator`,`time` FROM `anope_hs_core`");
+
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ nick = sstrdup(mysql_row[0]);
+ vIdent = sstrdup(mysql_row[1]);
+ vHost = sstrdup(mysql_row[2]);
+ creator = sstrdup(mysql_row[3]);
+ time = atoi(mysql_row[4]);
+ addHostCore(nick, vIdent, vHost, creator, time);
+ free(nick);
+ free(vHost);
+ free(creator);
+ free(vIdent);
+ }
+ mysql_free_result(mysql_res);
+}
+
+void db_mysql_load_news(void)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ int j;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `type`,`num`,`ntext`,`who`,`time` FROM `anope_os_news`");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ nnews = mysql_num_rows(mysql_res);
+ if (nnews < 8)
+ news_size = 16;
+ else if (nnews >= 16384)
+ news_size = 32767;
+ else
+ news_size = 2 * nnews;
+ news = scalloc(sizeof(*news) * news_size, 1);
+ if (!nnews) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ j = 0;
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ news[j].type = atoi(mysql_row[0]);
+ news[j].num = atoi(mysql_row[1]);
+ news[j].text = sstrdup(mysql_row[2]);
+ snprintf(news[j].who, NICKMAX, "%s", mysql_row[3]);
+ news[j].time = atoi(mysql_row[4]);
+ j++;
+ }
+ mysql_free_result(mysql_res);
+}
+
+void db_mysql_load_exceptions(void)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ int j;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `mask`,`lim`,`who`,`reason`,`time`,`expires` FROM `anope_os_exceptions`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ nexceptions = mysql_num_rows(mysql_res);
+ exceptions = scalloc(sizeof(Exception) * nexceptions, 1);
+ j = 0;
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ exceptions[j].mask = sstrdup(mysql_row[0]);
+ exceptions[j].limit = atoi(mysql_row[1]);
+ snprintf(exceptions[j].who, NICKMAX, "%s", mysql_row[2]);
+ exceptions[j].reason = sstrdup(mysql_row[3]);
+ exceptions[j].time = atoi(mysql_row[4]);
+ exceptions[j].expires = atoi(mysql_row[5]);
+ j++;
+ }
+ mysql_free_result(mysql_res);
+}
+
+#define HASH(host) ((tolower((host)[0])&31)<<5 | (tolower((host)[1])&31))
+
+void db_mysql_load_os_dbase(void)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ Akill *ak;
+ SXLine *sx;
+ HostCache *hc;
+ int akc, sgc, sqc, szc, j;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `maxusercnt`,`maxusertime`,`akills_count`,`sglines_count`,`sqlines_count`,`szlines_count` FROM `anope_os_core`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if ((mysql_row = mysql_fetch_row(mysql_res))) {
+ maxusercnt = atoi(mysql_row[0]);
+ maxusertime = atoi(mysql_row[1]);
+ akc = atoi(mysql_row[2]);
+ sgc = atoi(mysql_row[3]);
+ sqc = atoi(mysql_row[4]);
+ szc = atoi(mysql_row[5]);
+ } else {
+ maxusercnt = 0;
+ maxusertime = time(NULL);
+ akc = sgc = sqc = szc = 0;
+ }
+ mysql_free_result(mysql_res);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `user`,`host`,`xby`,`reason`,`seton`,`expire` FROM `anope_os_akills`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ slist_setcapacity(&akills, akc);
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ ak = scalloc(sizeof(Akill), 1);
+ ak->user = sstrdup(mysql_row[0]);
+ ak->host = sstrdup(mysql_row[1]);
+ ak->by = sstrdup(mysql_row[2]);
+ ak->reason = sstrdup(mysql_row[3]);
+ ak->seton = atoi(mysql_row[4]);
+ ak->expires = atoi(mysql_row[5]);
+ slist_add(&akills, ak);
+ }
+ mysql_free_result(mysql_res);
+
+ slist_setcapacity(&sglines, sgc);
+ slist_setcapacity(&sqlines, sqc);
+ slist_setcapacity(&szlines, szc);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `mask`,`xby`,`reason`,`seton`,`expire` FROM `anope_os_sglines`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql statement: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ sx = scalloc(sizeof(SXLine), 1);
+ sx->mask = sstrdup(mysql_row[0]);
+ sx->by = sstrdup(mysql_row[1]);
+ sx->reason = sstrdup(mysql_row[2]);
+ sx->seton = atoi(mysql_row[3]);
+ sx->expires = atoi(mysql_row[4]);
+ slist_add(&sglines, sx);
+ }
+ mysql_free_result(mysql_res);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `mask`,`xby`,`reason`,`seton`,`expire` FROM `anope_os_sqlines`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql statement: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ sx = scalloc(sizeof(SXLine), 1);
+ sx->mask = sstrdup(mysql_row[0]);
+ sx->by = sstrdup(mysql_row[1]);
+ sx->reason = sstrdup(mysql_row[2]);
+ sx->seton = atoi(mysql_row[3]);
+ sx->expires = atoi(mysql_row[4]);
+ slist_add(&sqlines, sx);
+ }
+ mysql_free_result(mysql_res);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `mask`,`xby`,`reason`,`seton`,`expire` FROM `anope_os_szlines`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql statement: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ sx = scalloc(sizeof(SXLine), 1);
+ sx->mask = sstrdup(mysql_row[0]);
+ sx->by = sstrdup(mysql_row[1]);
+ sx->reason = sstrdup(mysql_row[2]);
+ sx->seton = atoi(mysql_row[3]);
+ sx->expires = atoi(mysql_row[4]);
+ slist_add(&szlines, sx);
+ }
+ mysql_free_result(mysql_res);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `mask`,`status`,`used` FROM `anope_os_hcache`");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ j = HASH(mysql_row[0]);
+ hc = scalloc(1, sizeof(HostCache));
+ hc->host = sstrdup(mysql_row[0]);
+ hc->status = atoi(mysql_row[1]);
+ hc->used = atoi(mysql_row[2]);
+
+ hc->prev = NULL;
+ hc->next = hcache[j];
+ if (hc->next)
+ hc->next->prev = hc;
+ hcache[j] = hc;
+ }
+ mysql_free_result(mysql_res);
+}
+
+#undef HASH
+
+void db_mysql_load_cs_dbase(void)
+{
+ char sqlcmd[MAX_SQL_BUF], *tempstr;
+ ChannelInfo *ci;
+ int n_levels, j, m;
+ MYSQL_RES *res;
+ MYSQL_ROW row;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `name`,`founder`,`successor`,`founderpass`,`descr`,`url`,`email`,`time_registered`,`last_used`,`last_topic`,`last_topic_setter`,`last_topic_time`,`flags`,`forbidby`,`forbidreason`,`bantype`,`accesscount`,`akickcount`,`mlock_on`,`mlock_off`,`mlock_limit`,`mlock_key`,`mlock_flood`,`mlock_redirect`,`entry_message`,`memomax`,`botnick`,`botflags`,`ttb`,`bwcount`,`capsmin`,`capspercent`,`floodlines`,`floodsecs`,`repeattimes` FROM `anope_cs_info`");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ ci = scalloc(sizeof(ChannelInfo), 1);
+ snprintf(ci->name, CHANMAX, "%s", mysql_row[0]);
+ ci->founder = findcore(mysql_row[1]);
+ ci->successor = findcore(mysql_row[2]);
+ snprintf(ci->founderpass, PASSMAX, "%s", mysql_row[3]);
+ ci->desc = sstrdup(mysql_row[4]);
+ ci->url = sstrdup(mysql_row[5]);
+ if (strlen(ci->url) == 0) {
+ free(ci->url);
+ ci->url = NULL;
+ }
+ ci->email = sstrdup(mysql_row[6]);
+ if (strlen(ci->email) == 0) {
+ free(ci->email);
+ ci->email = NULL;
+ }
+ ci->time_registered = atoi(mysql_row[7]);
+ ci->last_used = atoi(mysql_row[8]);
+ ci->last_topic = sstrdup(mysql_row[9]);
+ snprintf(ci->last_topic_setter, NICKMAX, "%s", mysql_row[10]);
+ ci->last_topic_time = atoi(mysql_row[11]);
+ ci->flags = atoi(mysql_row[12]);
+#ifdef USE_ENCRYPTION
+ if (!(ci->flags & (CI_ENCRYPTEDPW | CI_VERBOTEN))) {
+ if (debug)
+ alog("debug: %s: encrypting password for %s on load",
+ s_ChanServ, ci->name);
+ if (encrypt_in_place(ci->founderpass, PASSMAX) < 0)
+ fatal("%s: load database: Can't encrypt %s password!",
+ s_ChanServ, ci->name);
+ ci->flags |= CI_ENCRYPTEDPW;
+ }
+#else
+ if (ci->flags & CI_ENCRYPTEDPW) {
+ fatal
+ ("%s: load database: password for %s encrypted but encryption disabled, aborting",
+ s_ChanServ, ci->name);
+ }
+#endif
+ ci->flags &= ~CI_INHABIT;
+
+ ci->forbidby = sstrdup(mysql_row[13]);
+ ci->forbidreason = sstrdup(mysql_row[14]);
+ ci->bantype = atoi(mysql_row[15]);
+
+ tempstr = db_mysql_quote(ci->name);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `position`,`level` FROM `anope_cs_levels` WHERE `channel` = '%s'",
+ tempstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ n_levels = mysql_num_rows(res);
+ ci->levels = scalloc(2 * CA_SIZE, 1);
+ reset_levels(ci);
+ while ((row = mysql_fetch_row(res))) {
+ ci->levels[atoi(row[0])] = atoi(row[1]);
+ }
+ mysql_free_result(res);
+ ci->accesscount = atoi(mysql_row[16]);
+ if (ci->accesscount) {
+ ci->access = scalloc(ci->accesscount, sizeof(ChanAccess));
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `in_use`,`level`,`display`,`last_seen` FROM `anope_cs_access` WHERE `channel` = '%s'",
+ tempstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ j = 0;
+ while ((row = mysql_fetch_row(res))) {
+ ci->access[j].in_use = atoi(row[0]);
+ if (ci->access[j].in_use) {
+ ci->access[j].level = atoi(row[1]);
+ ci->access[j].nc = findcore(row[2]);
+ if (ci->access[j].nc == NULL)
+ ci->access[j].in_use = 0;
+ ci->access[j].last_seen = atoi(row[3]);
+ }
+ j++;
+ }
+ mysql_free_result(res);
+ } else {
+ ci->access = NULL;
+ }
+ ci->akickcount = atoi(mysql_row[17]);
+ if (ci->akickcount) {
+ ci->akick = scalloc(ci->akickcount, sizeof(AutoKick));
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `flags`,`dmask`,`reason`,`creator`,`addtime` FROM `anope_cs_akicks` WHERE `channel` = '%s'",
+ tempstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ j = 0;
+ while ((row = mysql_fetch_row(res))) {
+ ci->akick[j].flags = atoi(row[0]);
+ if (ci->akick[j].flags & AK_USED) {
+ if (ci->akick[j].flags & AK_ISNICK) {
+ ci->akick[j].u.nc = findcore(row[1]);
+ if (!ci->akick[j].u.nc)
+ ci->akick[j].flags &= ~AK_USED;
+ } else {
+ ci->akick[j].u.mask = sstrdup(row[1]);
+ }
+ ci->akick[j].reason = sstrdup(row[2]);
+ ci->akick[j].creator = sstrdup(row[3]);
+ ci->akick[j].addtime = atoi(row[4]);
+ }
+ j++;
+ }
+ mysql_free_result(res);
+ } else {
+ ci->akick = NULL;
+ }
+ ci->mlock_on = atoi(mysql_row[18]);
+ ci->mlock_off = atoi(mysql_row[19]);
+ ci->mlock_limit = atoi(mysql_row[20]);
+ ci->mlock_key = sstrdup(mysql_row[21]);
+#ifdef HAS_FMODE
+ ci->mlock_flood = sstrdup(mysql_row[22]);
+#endif
+
+#ifdef HAS_LMODE
+ ci->mlock_redirect = sstrdup(mysql_row[23]);
+#endif
+ ci->memos.memomax = atoi(mysql_row[25]);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `number`,`flags`,`time`,`sender`,`text` FROM `anope_ms_info` WHERE `receiver` = '%s'",
+ tempstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ ci->memos.memocount = mysql_num_rows(res);
+ if (ci->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * ci->memos.memocount, 1);
+ ci->memos.memos = memos;
+ while ((row = mysql_fetch_row(res))) {
+ memos->number = atoi(row[0]);
+ memos->flags = atoi(row[1]);
+ memos->time = atoi(row[2]);
+ snprintf(memos->sender, NICKMAX, "%s", row[3]);
+ memos->text = sstrdup(row[4]);
+ for (m = 0; m < MAX_CMD_HASH; m++) {
+ memos->moduleData[m] = NULL;
+ }
+ memos++;
+ }
+ }
+ mysql_free_result(res);
+ ci->entry_message = sstrdup(mysql_row[24]);
+ if (strlen(ci->entry_message) == 0) {
+ free(ci->entry_message);
+ ci->entry_message = NULL;
+ }
+ ci->c = NULL;
+
+ ci->bi = findbot(mysql_row[26]);
+ ci->botflags = atoi(mysql_row[27]);
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (j = 0; j < TTB_SIZE; j++) {
+ ci->ttb[j] = 0;
+ }
+ ci->capsmin = atoi(mysql_row[30]);
+ ci->capspercent = atoi(mysql_row[31]);
+ ci->floodlines = atoi(mysql_row[32]);
+ ci->floodsecs = atoi(mysql_row[33]);
+ ci->repeattimes = atoi(mysql_row[34]);
+
+ ci->bwcount = atoi(mysql_row[29]);
+ if (ci->bwcount) {
+ ci->badwords = scalloc(ci->bwcount, sizeof(BadWord));
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `word`,`type` FROM `anope_cs_badwords` WHERE `channel` = '%s'",
+ tempstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ j = 0;
+ while ((row = mysql_fetch_row(res))) {
+ ci->badwords[j].in_use = 1;
+ if (ci->badwords[j].in_use) { /* I know... but for later */
+ ci->badwords[j].word = sstrdup(row[0]);
+ ci->badwords[j].type = atoi(row[1]);
+ }
+ j++;
+ }
+ mysql_free_result(res);
+ } else {
+ ci->badwords = NULL;
+ }
+ alpha_insert_chan(ci);
+ free(tempstr);
+ }
+ mysql_free_result(mysql_res);
+
+ for (j = 0; j < 256; j++) {
+ ChannelInfo *next;
+ for (ci = chanlists[j]; ci; ci = next) {
+ next = ci->next;
+ if (!(ci->flags & CI_VERBOTEN) && !ci->founder) {
+ alog("%s: database load: Deleting founderless channel %s",
+ s_ChanServ, ci->name);
+ delchan(ci);
+ }
+ }
+ }
+}
+
+void db_mysql_load_ns_req_dbase(void)
+{
+ char sqlcmd[MAX_SQL_BUF];
+ NickRequest *nr;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `nick`,`passcode`,`password`,`email`,`requested`,`active` FROM `anope_ns_request`;");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ nr = scalloc(1, sizeof(NickRequest));
+ nr->nick = sstrdup(mysql_row[0]);
+ nr->passcode = sstrdup(mysql_row[1]);
+ nr->password = sstrdup(mysql_row[2]);
+ nr->email = sstrdup(mysql_row[3]);
+ nr->requested = atoi(mysql_row[4]);
+ insert_requestnick(nr);
+ }
+ mysql_free_result(mysql_res);
+}
+
+void db_mysql_load_ns_dbase(void)
+{
+ char sqlcmd[MAX_SQL_BUF], *tmpstr;
+ NickCore *nc;
+ NickAlias *na;
+ MYSQL_RES *res;
+ MYSQL_ROW row;
+ int i, j, m;
+
+ if (!do_mysql)
+ return;
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `display`,`pass`,`email`,`icq`,`url`,`flags`,`language`,`accesscount`,`memocount`,`memomax`,`channelcount`,`channelmax`,`greet`,`active` FROM `anope_ns_core`");
+
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ if (mysql_num_rows(mysql_res) == 0) {
+ mysql_free_result(mysql_res);
+ return;
+ }
+
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ nc = scalloc(1, sizeof(NickCore));
+
+ nc->display = sstrdup(mysql_row[0]);
+ nc->pass = sstrdup(mysql_row[1]);
+ nc->email = sstrdup(mysql_row[2]);
+ nc->icq = atoi(mysql_row[3]);
+ nc->url = sstrdup(mysql_row[4]);
+ nc->flags = atoi(mysql_row[5]);
+ nc->language = atoi(mysql_row[6]);
+ nc->accesscount = atoi(mysql_row[7]);
+ nc->memos.memocount = atoi(mysql_row[8]);
+ nc->memos.memomax = atoi(mysql_row[9]);
+ nc->channelcount = atoi(mysql_row[10]);
+ nc->channelmax = atoi(mysql_row[11]);
+
+ if (mysql_row[12][0] == '\0') /* check if it's empty */
+ nc->greet = NULL;
+ else
+ nc->greet = sstrdup(mysql_row[12]);
+
+ if (!NSAllowKillImmed)
+ nc->flags &= ~NI_KILL_IMMED;
+
+#ifdef USE_ENCRYPTION
+ if (nc->pass && !(nc->flags & NI_ENCRYPTEDPW)) {
+ if (debug)
+ alog("debug: %s: encrypting password for `%s' on load",
+ s_NickServ, nc->display);
+ if (encrypt_in_place(nc->pass, PASSMAX) < 0)
+ fatal("%s: Can't encrypt `%s' nickname password!",
+ s_NickServ, nc->display);
+
+ nc->flags |= NI_ENCRYPTEDPW;
+ }
+#else
+ if (nc->flags & NI_ENCRYPTEDPW)
+ fatal
+ ("%s: load database: password for %s encrypted but encryption disabled, aborting",
+ s_NickServ, nc->display);
+#endif
+
+ if (nc->flags & NI_SERVICES_ADMIN)
+ slist_add(&servadmins, nc);
+ if (nc->flags & NI_SERVICES_OPER)
+ slist_add(&servopers, nc);
+
+ if (nc->accesscount) {
+ char **access;
+ access = scalloc(sizeof(char *) * nc->accesscount, 1);
+ nc->access = access;
+ tmpstr = db_mysql_quote(nc->display);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `access` FROM `anope_ns_access` WHERE `display` = '%s'",
+ tmpstr);
+ free(tmpstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ while ((row = mysql_fetch_row(res))) {
+ if (strlen(row[0]) > 0) {
+ *access = sstrdup(row[0]);
+ access++;
+ }
+ }
+ mysql_free_result(res);
+ }
+
+ if (nc->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * nc->memos.memocount, 1);
+ nc->memos.memos = memos;
+ tmpstr = db_mysql_quote(nc->display);
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `number`,`flags`,`time`,`sender`,`text` FROM `anope_ms_info` WHERE `receiver` = '%s' ORDER BY `number` ASC",
+ tmpstr);
+ free(tmpstr);
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ res = mysql_store_result(mysql);
+ while ((row = mysql_fetch_row(res))) {
+ memos->number = atoi(row[0]);
+ memos->flags = atoi(row[1]);
+ memos->time = atoi(row[2]);
+ snprintf(memos->sender, NICKMAX, "%s", row[3]);
+ memos->text = sstrdup(row[4]);
+ for (m = 0; m < MAX_CMD_HASH; m++) {
+ memos->moduleData[m] = NULL;
+ }
+ memos++;
+ }
+ mysql_free_result(res);
+ }
+ insert_core(nc);
+ }
+ mysql_free_result(mysql_res);
+
+ snprintf(sqlcmd, MAX_SQL_BUF,
+ "SELECT `display`,`nick`,`time_registered`,`last_seen`,`status`,`last_usermask`,`last_realname`,`last_quit` FROM `anope_ns_alias`");
+ if (db_mysql_query(sqlcmd)) {
+ log_perror("Can't create sql query: %s", sqlcmd);
+ db_mysql_error(MYSQL_WARNING, "query");
+ }
+ mysql_res = mysql_store_result(mysql);
+ while ((mysql_row = mysql_fetch_row(mysql_res))) {
+ na = scalloc(1, sizeof(NickAlias));
+ na->nick = sstrdup(mysql_row[1]);
+
+ na->last_usermask = sstrdup(mysql_row[5]);
+ na->last_realname = sstrdup(mysql_row[6]);
+ na->last_quit = sstrdup(mysql_row[7]);
+ na->time_registered = atoi(mysql_row[2]);
+ na->last_seen = atoi(mysql_row[3]);
+ na->status = atoi(mysql_row[4]);
+ na->status &= ~NS_TEMPORARY;
+ tmpstr = sstrdup(mysql_row[0]);
+ na->nc = findcore(tmpstr);
+ free(tmpstr);
+
+ if (na->nc)
+ slist_add(&na->nc->aliases, na);
+
+ if (!(na->status & NS_VERBOTEN)) {
+ if (!na->last_usermask)
+ na->last_usermask = sstrdup("");
+ if (!na->last_realname)
+ na->last_realname = sstrdup("");
+ }
+
+ if (na->nc)
+ na->nc->flags &= ~NI_SERVICES_ROOT;
+ alpha_insert_alias(na);
+ }
+ mysql_free_result(mysql_res);
+
+ for (j = 0; j < 1024; j++) {
+ NickAlias *next;
+ for (na = nalists[j]; na; na = next) {
+ next = na->next;
+ if (!na->nc) {
+ alog("%s: while loading database: %s has no core! We delete it.", s_NickServ, na->nick);
+ delnick(na);
+ continue;
+ }
+ for (i = 0; i < RootNumber; i++) {
+ if (!stricmp(ServicesRoots[i], na->nick))
+ na->nc->flags |= NI_SERVICES_ROOT;
+ }
+ }
+ }
+}
diff --git a/src/news.c b/src/news.c
new file mode 100644
index 000000000..530a4ae35
--- /dev/null
+++ b/src/news.c
@@ -0,0 +1,541 @@
+/* News functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+
+/*************************************************************************/
+
+int32 nnews = 0;
+int32 news_size = 0;
+NewsItem *news = 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;
+ char *name;
+ int msgs[MSG_MAX + 1];
+};
+
+/* *INDENT-OFF* */
+
+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
+ }
+ },
+ { NEWS_RANDOM, "RANDOM",
+ { NEWS_RANDOM_SYNTAX,
+ NEWS_RANDOM_LIST_HEADER,
+ NEWS_RANDOM_LIST_ENTRY,
+ NEWS_RANDOM_LIST_NONE,
+ NEWS_RANDOM_ADD_SYNTAX,
+ NEWS_RANDOM_ADD_FULL,
+ NEWS_RANDOM_ADDED,
+ NEWS_RANDOM_DEL_SYNTAX,
+ NEWS_RANDOM_DEL_NOT_FOUND,
+ NEWS_RANDOM_DELETED,
+ NEWS_RANDOM_DEL_NONE,
+ NEWS_RANDOM_DELETED_ALL
+ }
+ }
+};
+
+/* *INDENT-ON* */
+
+static int *findmsgs(int16 type, char **typename)
+{
+ int i;
+ for (i = 0; i < lenof(msgarray); i++) {
+ if (msgarray[i].type == type) {
+ if (typename)
+ *typename = msgarray[i].name;
+ return msgarray[i].msgs;
+ }
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Called by the main OperServ routine in response to a NEWS command. */
+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);
+
+/*************************************************************************/
+/****************************** Statistics *******************************/
+/*************************************************************************/
+
+void get_news_stats(long *nrec, long *memuse)
+{
+ long mem;
+ int i;
+
+ mem = sizeof(NewsItem) * news_size;
+ for (i = 0; i < nnews; i++)
+ mem += strlen(news[i].text) + 1;
+ *nrec = nnews;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*********************** News item loading/saving ************************/
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", NewsDBName); \
+ nnews = i; \
+ break; \
+ } \
+} while (0)
+
+void load_news()
+{
+ dbFILE *f;
+ int i;
+ int16 n;
+ int32 tmp32;
+
+ if (!(f = open_db(s_OperServ, NewsDBName, "r", NEWS_VERSION)))
+ return;
+ switch (i = get_file_version(f)) {
+ case 9:
+ case 8:
+ case 7:
+ SAFE(read_int16(&n, f));
+ nnews = n;
+ if (nnews < 8)
+ news_size = 16;
+ else if (nnews >= 16384)
+ news_size = 32767;
+ else
+ news_size = 2 * nnews;
+ news = scalloc(sizeof(*news) * news_size, 1);
+ if (!nnews) {
+ close_db(f);
+ return;
+ }
+ for (i = 0; i < nnews; i++) {
+ SAFE(read_int16(&news[i].type, f));
+ SAFE(read_int32(&news[i].num, f));
+ SAFE(read_string(&news[i].text, f));
+ SAFE(read_buffer(news[i].who, f));
+ SAFE(read_int32(&tmp32, f));
+ news[i].time = tmp32;
+ }
+ break;
+
+ default:
+ fatal("Unsupported version (%d) on %s", i, NewsDBName);
+ } /* switch (ver) */
+
+ close_db(f);
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", NewsDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", NewsDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_news()
+{
+ dbFILE *f;
+ int i;
+ static time_t lastwarn = 0;
+
+ if (!(f = open_db(s_OperServ, NewsDBName, "w", NEWS_VERSION)))
+ return;
+ SAFE(write_int16(nnews, f));
+ for (i = 0; i < nnews; i++) {
+ SAFE(write_int16(news[i].type, f));
+ SAFE(write_int32(news[i].num, f));
+ SAFE(write_string(news[i].text, f));
+ SAFE(write_buffer(news[i].who, f));
+ SAFE(write_int32(news[i].time, f));
+ }
+ close_db(f);
+}
+
+#undef SAFE
+
+void save_rdb_news()
+{
+#ifdef USE_RDB
+ int i;
+ NewsItem *ni;
+
+ if (!rdb_open())
+ return;
+
+ rdb_clear_table("anope_os_news");
+ for (i = 0; i < nnews; i++) {
+ ni = &news[i];
+ rdb_save_news(ni);
+ }
+
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+/***************************** News display ******************************/
+/*************************************************************************/
+
+void display_news(User * u, int16 type)
+{
+ int msg;
+
+ if (type == NEWS_LOGON) {
+ msg = NEWS_LOGON_TEXT;
+ } else if (type == NEWS_OPER) {
+ msg = NEWS_OPER_TEXT;
+ } else if (type == NEWS_RANDOM) {
+ msg = NEWS_RANDOM_TEXT;
+ } else {
+ alog("news: Invalid type (%d) to display_news()", type);
+ return;
+ }
+
+ if (type == NEWS_RANDOM) {
+ static int current_news = -1;
+ int count = 0;
+
+ if (!nnews)
+ return;
+
+ while (count++ < nnews) {
+ if (++current_news >= nnews)
+ current_news = 0;
+
+ if (news[current_news].type == type) {
+ struct tm *tm;
+ char timebuf[64];
+
+ tm = localtime(&news[current_news].time);
+ strftime_lang(timebuf, sizeof(timebuf), u,
+ STRFTIME_SHORT_DATE_FORMAT, tm);
+ notice_lang(s_GlobalNoticer, u, msg, timebuf,
+ news[current_news].text);
+
+ return;
+ }
+ }
+ } else {
+ int i, count = 0; /* Number we're going to show--not more than 3 */
+
+ for (i = nnews - 1; i >= 0; i--) {
+ if (count >= 3)
+ break;
+ if (news[i].type == type)
+ count++;
+ }
+ while (++i < nnews) {
+ if (news[i].type == type) {
+ struct tm *tm;
+ char timebuf[64];
+
+ tm = localtime(&news[i].time);
+ strftime_lang(timebuf, sizeof(timebuf), u,
+ STRFTIME_SHORT_DATE_FORMAT, tm);
+ notice_lang(s_GlobalNoticer, u, msg, timebuf,
+ news[i].text);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+/***************************** News editing ******************************/
+/*************************************************************************/
+
+/* Declared in extern.h */
+int do_logonnews(User * u)
+{
+ do_news(u, NEWS_LOGON);
+ return MOD_CONT;
+}
+
+/* Declared in extern.h */
+int do_opernews(User * u)
+{
+ do_news(u, NEWS_OPER);
+ return MOD_CONT;
+}
+
+/* Declared in extern.h */
+int do_randomnews(User * u)
+{
+ do_news(u, NEWS_RANDOM);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Main news command handling routine. */
+void do_news(User * u, short type)
+{
+ int is_servadmin = is_services_admin(u);
+ char *cmd = strtok(NULL, " ");
+ char *typename;
+ int *msgs;
+
+ msgs = findmsgs(type, &typename);
+ if (!msgs) {
+ alog("news: 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_servadmin)
+ do_news_add(u, type, msgs, typename);
+ else
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+
+ } else if (stricmp(cmd, "DEL") == 0) {
+ if (is_servadmin)
+ 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)
+{
+ int i, count = 0;
+ char timebuf[64];
+ struct tm *tm;
+
+ for (i = 0; i < nnews; i++) {
+ if (news[i].type == type) {
+ if (count == 0)
+ notice_lang(s_OperServ, u, msgs[MSG_LIST_HEADER]);
+ tm = localtime(&news[i].time);
+ strftime_lang(timebuf, sizeof(timebuf),
+ u, STRFTIME_DATE_TIME_FORMAT, tm);
+ notice_lang(s_OperServ, u, msgs[MSG_LIST_ENTRY],
+ news[i].num, timebuf,
+ *news[i].who ? news[i].who : "<unknown>",
+ news[i].text);
+ count++;
+ }
+ }
+ if (count == 0)
+ notice_lang(s_OperServ, u, msgs[MSG_LIST_NONE]);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "News");
+ }
+}
+
+/*************************************************************************/
+
+/* Handle a {LOGON,OPER}NEWS ADD command. */
+
+static void do_news_add(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_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 (32767 items).
+ */
+
+static int add_newsitem(User * u, const char *text, short type)
+{
+ int i, num;
+
+ if (nnews >= 32767)
+ return -1;
+
+ if (nnews >= news_size) {
+ if (news_size < 8)
+ news_size = 8;
+ else
+ news_size *= 2;
+ news = srealloc(news, sizeof(*news) * news_size);
+ }
+ num = 0;
+ for (i = nnews - 1; i >= 0; i--) {
+ if (news[i].type == type) {
+ num = news[i].num;
+ break;
+ }
+ }
+ news[nnews].type = type;
+ news[nnews].num = num + 1;
+ news[nnews].text = sstrdup(text);
+ news[nnews].time = time(NULL);
+ strscpy(news[nnews].who, u->nick, NICKMAX);
+ nnews++;
+ 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, " ");
+ int i;
+
+ 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);
+ /* Reset the order - #0000397 */
+ for (i = 0; i < nnews; i++)
+ news[i].num = i + 1;
+ } 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, short type)
+{
+ int i;
+ int count = 0;
+
+ for (i = 0; i < nnews; i++) {
+ if (news[i].type == type && (num == 0 || news[i].num == num)) {
+ free(news[i].text);
+ count++;
+ nnews--;
+ if (i < nnews)
+ memcpy(news + i, news + i + 1,
+ sizeof(*news) * (nnews - i));
+ i--;
+ }
+ }
+ return count;
+}
+
+/*************************************************************************/
diff --git a/src/nickserv.c b/src/nickserv.c
new file mode 100644
index 000000000..ec10e4fe0
--- /dev/null
+++ b/src/nickserv.c
@@ -0,0 +1,4169 @@
+/* NickServ functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+
+/*************************************************************************/
+
+#define HASH(nick) ((tolower((nick)[0])&31)<<5 | (tolower((nick)[1])&31))
+
+NickAlias *nalists[1024];
+NickCore *nclists[1024];
+NickRequest *nrlists[1024];
+
+unsigned int guestnum; /* Current guest number */
+
+#define TO_COLLIDE 0 /* Collide the user with this nick */
+#define TO_RELEASE 1 /* Release a collided nick */
+
+/*************************************************************************/
+
+extern char *getvHost(char *nick);
+
+static int is_on_access(User * u, NickCore * nc);
+void alpha_insert_alias(NickAlias * na);
+void insert_core(NickCore * nc);
+void insert_requestnick(NickRequest * nr);
+static NickAlias *makenick(const char *nick);
+static NickRequest *makerequest(const char *nick);
+static NickAlias *makealias(const char *nick, NickCore * nc);
+static void change_core_display(NickCore * nc, char *newdisplay);
+static int group_identified(User * u, NickCore * nc);
+
+static void collide(NickAlias * na, int from_timeout);
+static void release(NickAlias * na, int from_timeout);
+static void add_ns_timeout(NickAlias * na, int type, time_t delay);
+static void del_ns_timeout(NickAlias * na, int type);
+int delnickrequest(NickRequest * nr);
+NickRequest *findrequestnick(const char *nick);
+static int do_sendregmail(User * u, NickRequest * nr);
+static int do_setmodes(User * u);
+
+static int do_help(User * u);
+static int do_register(User * u);
+static int do_confirm(User * u);
+static int do_group(User * u);
+static int do_nickupdate(User * u);
+static int do_identify(User * u);
+static int do_logout(User * u);
+static int do_drop(User * u);
+static int do_set(User * u);
+static int do_set_display(User * u, NickCore * nc, char *param);
+static int do_set_password(User * u, NickCore * nc, char *param);
+static int do_set_language(User * u, NickCore * nc, char *param);
+static int do_set_url(User * u, NickCore * nc, char *param);
+static int do_set_email(User * u, NickCore * nc, char *param);
+static int do_set_greet(User * u, NickCore * nc, char *param);
+static int do_set_icq(User * u, NickCore * nc, char *param);
+static int do_set_kill(User * u, NickCore * nc, char *param);
+static int do_set_secure(User * u, NickCore * nc, char *param);
+static int do_set_private(User * u, NickCore * nc, char *param);
+static int do_set_msg(User * u, NickCore * nc, char *param);
+static int do_set_hide(User * u, NickCore * nc, char *param);
+static int do_set_noexpire(User * u, NickAlias * nc, char *param);
+static int do_access(User * u);
+static int do_info(User * u);
+static int do_list(User * u);
+static int do_alist(User * u);
+static int do_glist(User * u);
+static int do_recover(User * u);
+static int do_release(User * u);
+static int do_ghost(User * u);
+static int do_status(User * u);
+static int do_getpass(User * u);
+static int do_getemail(User * u);
+static int do_sendpass(User * u);
+static int do_forbid(User * u);
+static int do_resend(User * u);
+
+/* Obsolete commands */
+static int do_link(User * u);
+static int do_unlink(User * u);
+static int do_listlinks(User * u);
+
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+void moduleAddNickServCmds(void) {
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("REGISTER", do_register, NULL, NICK_HELP_REGISTER, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("RESEND", do_resend, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("CONFIRM", do_confirm, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("GROUP", do_group, NULL, NICK_HELP_GROUP, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("UPDATE", do_nickupdate, NULL, NICK_HELP_UPDATE, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("ID", do_identify, NULL, NICK_HELP_IDENTIFY, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("IDENTIFY", do_identify, NULL, NICK_HELP_IDENTIFY, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SIDENTIFY",do_identify, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("LOGOUT", do_logout, NULL, -1,NICK_HELP_LOGOUT, NICK_SERVADMIN_HELP_LOGOUT,NICK_SERVADMIN_HELP_LOGOUT, NICK_SERVADMIN_HELP_LOGOUT); addCoreCommand(NICKSERV,c);
+ c = createCommand("DROP", do_drop, NULL, -1,NICK_HELP_DROP, NICK_SERVADMIN_HELP_DROP,NICK_SERVADMIN_HELP_DROP, NICK_SERVADMIN_HELP_DROP); addCoreCommand(NICKSERV,c);
+ c = createCommand("LINK", do_link, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("UNLINK", do_unlink, NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("LISTLINKS",do_listlinks,NULL, -1, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("ACCESS", do_access, NULL, NICK_HELP_ACCESS, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET", do_set, NULL, NICK_HELP_SET,-1, NICK_SERVADMIN_HELP_SET,NICK_SERVADMIN_HELP_SET, NICK_SERVADMIN_HELP_SET); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET DISPLAY", NULL, NULL, NICK_HELP_SET_DISPLAY, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET PASSWORD", NULL, NULL, NICK_HELP_SET_PASSWORD, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET URL", NULL, NULL, NICK_HELP_SET_URL, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET EMAIL", NULL, NULL, NICK_HELP_SET_EMAIL, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET ICQ", NULL, NULL, NICK_HELP_SET_ICQ, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET GREET", NULL, NULL, NICK_HELP_SET_GREET, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET KILL", NULL, NULL, NICK_HELP_SET_KILL, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET SECURE", NULL, NULL, NICK_HELP_SET_SECURE, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET PRIVATE", NULL, NULL, NICK_HELP_SET_PRIVATE, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET MSG", NULL, NULL, NICK_HELP_SET_MSG, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET HIDE", NULL, NULL, NICK_HELP_SET_HIDE, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SET NOEXPIRE", NULL, NULL, -1, -1,NICK_SERVADMIN_HELP_SET_NOEXPIRE,NICK_SERVADMIN_HELP_SET_NOEXPIRE,NICK_SERVADMIN_HELP_SET_NOEXPIRE); addCoreCommand(NICKSERV,c);
+ c = createCommand("RECOVER", do_recover, NULL, NICK_HELP_RECOVER, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("RELEASE", do_release, NULL, NICK_HELP_RELEASE, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("GHOST", do_ghost, NULL, NICK_HELP_GHOST, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("INFO", do_info, NULL, NICK_HELP_INFO,-1, NICK_HELP_INFO, NICK_SERVADMIN_HELP_INFO,NICK_SERVADMIN_HELP_INFO); addCoreCommand(NICKSERV,c);
+ c = createCommand("LIST", do_list, NULL, -1,NICK_HELP_LIST, NICK_SERVADMIN_HELP_LIST,NICK_SERVADMIN_HELP_LIST, NICK_SERVADMIN_HELP_LIST); addCoreCommand(NICKSERV,c);
+ c = createCommand("ALIST", do_alist, NULL, -1,NICK_HELP_ALIST, NICK_SERVADMIN_HELP_ALIST,NICK_SERVADMIN_HELP_ALIST, NICK_SERVADMIN_HELP_ALIST); addCoreCommand(NICKSERV,c);
+ c = createCommand("GLIST", do_glist, NULL, -1,NICK_HELP_GLIST, NICK_SERVADMIN_HELP_GLIST,NICK_SERVADMIN_HELP_GLIST, NICK_SERVADMIN_HELP_GLIST); addCoreCommand(NICKSERV,c);
+ c = createCommand("STATUS", do_status, NULL, NICK_HELP_STATUS, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("SENDPASS", do_sendpass, NULL, NICK_HELP_SENDPASS, -1,-1,-1,-1); addCoreCommand(NICKSERV,c);
+ c = createCommand("GETPASS", do_getpass, is_services_admin, -1,-1, NICK_SERVADMIN_HELP_GETPASS,NICK_SERVADMIN_HELP_GETPASS, NICK_SERVADMIN_HELP_GETPASS); addCoreCommand(NICKSERV,c);
+ c = createCommand("GETEMAIL", do_getemail, is_services_admin, -1,-1, NICK_SERVADMIN_HELP_GETEMAIL,NICK_SERVADMIN_HELP_GETEMAIL, NICK_SERVADMIN_HELP_GETEMAIL); addCoreCommand(NICKSERV,c);
+ c = createCommand("FORBID", do_forbid, is_services_admin, -1,-1, NICK_SERVADMIN_HELP_FORBID,NICK_SERVADMIN_HELP_FORBID, NICK_SERVADMIN_HELP_FORBID); addCoreCommand(NICKSERV,c);
+}
+/* *INDENT-ON* */
+/*************************************************************************/
+/*************************************************************************/
+
+/* Display total number of registered nicks and info about each; or, if
+ * a specific nick is given, display information about that nick (like
+ * /msg NickServ INFO <nick>). If count_only != 0, then only display the
+ * number of registered nicks (the nick parameter is ignored).
+ */
+
+void listnicks(int count_only, const char *nick)
+{
+ int count = 0;
+ NickAlias *na;
+ int i;
+ char *end;
+
+ if (count_only) {
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next)
+ count++;
+ }
+ printf("%d nicknames registered.\n", count);
+
+ } else if (nick) {
+
+ struct tm *tm;
+ char buf[512];
+ static const char commastr[] = ", ";
+ int need_comma = 0;
+
+ if (!(na = findnick(nick))) {
+ printf("%s not registered.\n", nick);
+ return;
+ } else if (na->status & NS_VERBOTEN) {
+ printf("%s is FORBIDden.\n", nick);
+ return;
+ }
+ printf("%s is %s\n", nick, na->last_realname);
+ printf("Last seen address: %s\n", na->last_usermask);
+ tm = localtime(&na->time_registered);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Time registered: %s\n", buf);
+ tm = localtime(&na->last_seen);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Last seen time: %s\n", buf);
+ if (na->nc->url)
+ printf(" URL: %s\n", na->nc->url);
+ if (na->nc->email)
+ printf(" E-mail address: %s\n", na->nc->email);
+ if (na->nc->icq)
+ printf(" ICQ #: %d\n", na->nc->icq);
+ if (na->nc->greet)
+ printf(" Greet: %s\n", na->nc->greet);
+ *buf = 0;
+ end = buf;
+ if (na->nc->flags & NI_KILLPROTECT) {
+ end +=
+ snprintf(end, sizeof(buf) - (end - buf),
+ "Kill protection");
+ need_comma = 1;
+ }
+ if (na->nc->flags & NI_SECURE) {
+ end += snprintf(buf, sizeof(buf) - (end - buf), "%sSecurity",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (na->nc->flags & NI_PRIVATE) {
+ end += snprintf(buf, sizeof(buf) - (end - buf), "%sPrivate",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (na->status & NS_NO_EXPIRE) {
+ end += snprintf(buf, sizeof(buf) - (end - buf), "%sNo Expire",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ printf(" Options: %s\n", *buf ? buf : "None");
+
+ } else {
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next) {
+ printf(" %s %-20s %s\n",
+ na->status & NS_NO_EXPIRE ? "!" : " ",
+ na->nick, na->status & NS_VERBOTEN ?
+ "Disallowed (FORBID)" : na->last_usermask);
+ count++;
+ }
+ }
+ printf("%d nicknames registered.\n", count);
+
+ }
+}
+
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_aliases_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i;
+ NickAlias *na;
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next) {
+ count++;
+ mem += sizeof(*na);
+ if (na->nick)
+ mem += strlen(na->nick) + 1;
+ if (na->last_usermask)
+ mem += strlen(na->last_usermask) + 1;
+ if (na->last_realname)
+ mem += strlen(na->last_realname) + 1;
+ if (na->last_quit)
+ mem += strlen(na->last_quit) + 1;
+ }
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_core_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i, j;
+ NickCore *nc;
+ char **accptr;
+
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ count++;
+ mem += sizeof(*nc);
+
+ if (nc->display)
+ mem += strlen(nc->display) + 1;
+ if (nc->pass)
+ mem += strlen(nc->pass) + 1;
+
+ if (nc->url)
+ mem += strlen(nc->url) + 1;
+ if (nc->email)
+ mem += strlen(nc->email) + 1;
+ if (nc->greet)
+ mem += strlen(nc->greet) + 1;
+
+ mem += sizeof(char *) * nc->accesscount;
+ for (accptr = nc->access, j = 0; j < nc->accesscount;
+ accptr++, j++) {
+ if (*accptr)
+ mem += strlen(*accptr) + 1;
+ }
+
+ mem += nc->memos.memocount * sizeof(Memo);
+ for (j = 0; j < nc->memos.memocount; j++) {
+ if (nc->memos.memos[j].text)
+ mem += strlen(nc->memos.memos[j].text) + 1;
+ }
+
+ mem += sizeof(void *) * nc->aliases.count;
+ }
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* NickServ initialization. */
+
+void ns_init(void)
+{
+ moduleAddNickServCmds();
+ guestnum = time(NULL);
+ while (guestnum > 9999999)
+ guestnum -= 10000000;
+}
+
+/*************************************************************************/
+
+/* Main NickServ routine. */
+
+void nickserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_NickServ, u->nick, "\1PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_NickServ, u, SERVICE_OFFLINE, s_NickServ);
+ } else {
+ mod_run_cmd(s_NickServ, u, NICKSERV, cmd);
+ }
+
+}
+
+/*************************************************************************/
+
+/* Load/save data files. */
+
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", NickDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+/* Loads NickServ database versions 5 to 11 (<= 4 is not supported) */
+
+void load_old_ns_dbase(void)
+{
+ dbFILE *f;
+ int ver, i, j, c, m;
+ NickAlias *na, *na2, *next;
+ NickCore *nc;
+ int failed = 0;
+
+ int16 tmp16;
+ int32 tmp32;
+
+ char bufn[NICKMAX], bufp[PASSMAX];
+ char *email, *greet, *url, *forbidby, *forbidreason;
+ uint32 icq;
+
+ if (!(f = open_db(s_NickServ, NickDBName, "r", NICK_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+ if (ver <= 4) {
+ fatal("Unsupported version number (%d) on %s", ver, NickDBName);
+ close_db(f);
+ return;
+ }
+
+ for (i = 0; i < 256 && !failed; i++) {
+ while ((c = getc_db(f)) == 1) {
+ if (c != 1)
+ fatal("Invalid format in %s", NickDBName);
+
+ na = scalloc(sizeof(NickAlias), 1);
+
+ SAFE(read_buffer(bufn, f));
+ na->nick = sstrdup(bufn);
+ SAFE(read_buffer(bufp, f)); /* Will be used later if needed */
+
+ SAFE(read_string(&url, f));
+ SAFE(read_string(&email, f));
+ if (ver >= 10)
+ SAFE(read_int32(&icq, f));
+ else
+ icq = 0;
+ if (ver >= 9)
+ SAFE(read_string(&greet, f));
+ else
+ greet = NULL;
+
+ SAFE(read_string(&na->last_usermask, f));
+ SAFE(read_string(&na->last_realname, f));
+ SAFE(read_string(&na->last_quit, f));
+
+ SAFE(read_int32(&tmp32, f));
+ na->time_registered = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ na->last_seen = tmp32;
+
+ SAFE(read_int16(&na->status, f));
+ na->status &= ~NS_TEMPORARY;
+#ifdef USE_ENCRYPTION
+ if (!(na->status & (NS_OLD_ENCRYPTEDPW | NS_VERBOTEN))) {
+ if (debug)
+ alog("debug: %s: encrypting password for `%s' on load",
+ s_NickServ, na->nick);
+ if (encrypt_in_place(bufp, PASSMAX) < 0)
+ fatal("%s: Can't encrypt `%s' nickname password!",
+ s_NickServ, na->nick);
+ na->status |= NS_OLD_ENCRYPTEDPW;
+ }
+#else
+ if (na->status & NS_OLD_ENCRYPTEDPW) {
+ /* Bail: it makes no sense to continue with encrypted
+ * passwords, since we won't be able to verify them */
+ fatal
+ ("%s: load database: password for %s encrypted but encryption disabled, aborting",
+ s_NickServ, na->nick);
+ }
+#endif
+ if (ver >= 9) {
+ SAFE(read_string(&forbidby, f));
+ SAFE(read_string(&forbidreason, f));
+ /* Cleanup */
+ if (forbidby && *forbidby == '@') {
+ free(forbidby);
+ forbidby = NULL;
+ }
+ if (forbidreason && *forbidreason == 0) {
+ free(forbidreason);
+ forbidreason = NULL;
+ }
+ } else {
+ forbidby = NULL;
+ forbidreason = NULL;
+ }
+
+ if (na->status & NS_VERBOTEN) {
+ if (na->last_usermask)
+ free(na->last_usermask);
+ if (na->last_realname)
+ free(na->last_realname);
+
+ na->last_usermask = forbidby;
+ na->last_realname = forbidreason;
+ } else {
+ if (!na->last_usermask)
+ na->last_usermask = sstrdup("");
+ if (!na->last_realname)
+ na->last_realname = sstrdup("");
+ }
+
+ /* Store the reference for later resolving */
+ SAFE(read_string((char **) &na->nc, f));
+ SAFE(read_int16(&tmp16, f)); /* Was linkcount */
+
+ if (na->nc) {
+ SAFE(read_int16(&tmp16, f)); /* Was channelcount */
+ } else {
+ /* This nick was a master nick, so it also has all the
+ * core info! =)
+ */
+ nc = scalloc(1, sizeof(NickCore));
+ slist_init(&nc->aliases);
+
+ /* The initial display is what used to be the master nick */
+ nc->display = sstrdup(na->nick);
+
+ /* We grabbed info before; fill the appropriate fields now */
+ if (*bufp)
+ nc->pass = sstrdup(bufp);
+ else
+ nc->pass = NULL; /* Which may be the case for forbidden nicks .. */
+
+ nc->email = email;
+ nc->greet = greet;
+ nc->icq = icq;
+ nc->url = url;
+
+ /* We check whether the e-mail is valid because it was not tested
+ * in older versions.
+ */
+ if (ver <= 10 && nc->email && !MailValidate(nc->email)) {
+ free(nc->email);
+ nc->email = NULL;
+ }
+
+ SAFE(read_int32(&nc->flags, f));
+ if (!NSAllowKillImmed)
+ nc->flags &= ~NI_KILL_IMMED;
+
+ /* Status flags cleanup */
+ if (na->status & NS_OLD_ENCRYPTEDPW) {
+ nc->flags |= NI_ENCRYPTEDPW;
+ na->status &= ~NS_OLD_ENCRYPTEDPW;
+ }
+
+ /* Add services opers and admins to the appropriate list, but
+ only if the database version is equal to or more than 10. */
+ if (ver >= 10) {
+ if (nc->flags & NI_SERVICES_ADMIN)
+ slist_add(&servadmins, nc);
+ if (nc->flags & NI_SERVICES_OPER)
+ slist_add(&servopers, nc);
+ }
+
+ /* Add the Services root flag if needed. */
+ if (nc)
+ for (j = 0; j < RootNumber; j++)
+ if (!stricmp(ServicesRoots[j], na->nick))
+ nc->flags |= NI_SERVICES_ROOT;
+
+ SAFE(read_int16(&nc->accesscount, f));
+ if (nc->accesscount) {
+ char **access;
+ access = scalloc(sizeof(char *) * nc->accesscount, 1);
+ nc->access = access;
+ for (j = 0; j < nc->accesscount; j++, access++)
+ SAFE(read_string(access, f));
+ }
+
+ SAFE(read_int16(&nc->memos.memocount, f));
+ SAFE(read_int16(&nc->memos.memomax, f));
+ if (nc->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * nc->memos.memocount, 1);
+ nc->memos.memos = memos;
+
+ for (j = 0; j < nc->memos.memocount; j++, memos++) {
+ SAFE(read_int32(&memos->number, f));
+ SAFE(read_int16(&memos->flags, f));
+ SAFE(read_int32(&tmp32, f));
+ memos->time = tmp32;
+ SAFE(read_buffer(memos->sender, f));
+ SAFE(read_string(&memos->text, f));
+ for (m = 0; m < MAX_CMD_HASH; m++) {
+ memos->moduleData[m] = NULL;
+ }
+ }
+ }
+
+ /* We read the channel count, but don't take care of it.
+ load_cs_dbase will regenerate it correctly. */
+ SAFE(read_int16(&tmp16, f));
+ SAFE(read_int16(&nc->channelmax, f));
+ if (ver == 5)
+ nc->channelmax = CSMaxReg;
+
+ SAFE(read_int16(&nc->language, f));
+
+ if (ver >= 11 && ver < 13) {
+ char *s;
+
+ SAFE(read_int16(&tmp16, f));
+ SAFE(read_int32(&tmp32, f));
+ SAFE(read_int16(&tmp16, f));
+ SAFE(read_string(&s, f));
+ }
+
+ /* Set us as being a master nick; fill the nc field also.
+ The NS_MASTER flag will not be cleared in this function. */
+ na->status |= NS_MASTER;
+ na->nc = nc;
+ slist_add(&nc->aliases, na);
+
+ /* Insert our new core in the core list */
+ insert_core(nc);
+ }
+
+ alpha_insert_alias(na);
+
+ } /* while (getc_db(f) != 0) */
+ } /* for (i) */
+
+ /* Now resolve what were called links */
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = next) {
+ next = na->next;
+
+ /* Master nicks are already resolved */
+ if (na->status & NS_MASTER)
+ continue;
+
+ na2 = na;
+ /* While the reference resolves and it's not a master nick */
+ while ((na2 = findnick((char *) na2->nc))
+ && !(na2->status & NS_MASTER));
+
+ /* It didn't resolve. This is problematic since there is no core. :/
+ We delete the nick. */
+ if (!na2) {
+ alog("%s: while loading database: %s was linked to inexistant %s", s_NickServ, na->nick, (char *) na->nc);
+ delnick(na);
+ continue;
+ }
+
+ /* OK we have information on the core. We mark the current alias
+ as a master nick because it now contains a valid core. */
+ na->nc = na2->nc;
+ na->status |= NS_MASTER;
+ slist_add(&na->nc->aliases, na);
+ }
+ }
+
+ close_db(f);
+}
+
+void load_ns_req_db(void)
+{
+ dbFILE *f;
+ int i, c, ver;
+ NickRequest *nr;
+ int32 tmp32;
+ int failed = 0;
+
+ if (!(f = open_db(s_NickServ, PreNickDBName, "r", PRE_NICK_VERSION)))
+ return;
+ ver = get_file_version(f);
+ for (i = 0; i < 1024 && !failed; i++) {
+ while ((c = getc_db(f)) == 1) {
+ if (c != 1)
+ fatal("Invalid format in %s", PreNickDBName);
+ nr = scalloc(1, sizeof(NickRequest));
+ SAFE(read_string(&nr->nick, f));
+ SAFE(read_string(&nr->passcode, f));
+ SAFE(read_string(&nr->password, f));
+ SAFE(read_string(&nr->email, f));
+ SAFE(read_int32(&tmp32, f));
+ nr->requested = tmp32;
+ insert_requestnick(nr);
+ }
+ }
+ close_db(f);
+}
+
+void load_ns_dbase(void)
+{
+ dbFILE *f;
+ int ver, i, j, c, m;
+ NickAlias *na, **nalast, *naprev;
+ NickCore *nc, **nclast, *ncprev;
+ int failed = 0;
+ int16 tmp16;
+ int32 tmp32;
+ char *s;
+
+ if (!(f = open_db(s_NickServ, NickDBName, "r", NICK_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+
+ if (ver <= 11) {
+ close_db(f);
+ load_old_ns_dbase();
+ return;
+ }
+
+ /* First we load nick cores */
+ for (i = 0; i < 1024 && !failed; i++) {
+ nclast = &nclists[i];
+ ncprev = NULL;
+
+ while ((c = getc_db(f)) == 1) {
+ if (c != 1)
+ fatal("Invalid format in %s", NickDBName);
+
+ nc = scalloc(1, sizeof(NickCore));
+ *nclast = nc;
+ nclast = &nc->next;
+ nc->prev = ncprev;
+ ncprev = nc;
+
+ slist_init(&nc->aliases);
+
+ SAFE(read_string(&nc->display, f));
+ SAFE(read_string(&nc->pass, f));
+ SAFE(read_string(&nc->email, f));
+ SAFE(read_string(&nc->greet, f));
+ SAFE(read_int32(&nc->icq, f));
+ SAFE(read_string(&nc->url, f));
+
+ SAFE(read_int32(&nc->flags, f));
+ if (!NSAllowKillImmed)
+ nc->flags &= ~NI_KILL_IMMED;
+#ifdef USE_ENCRYPTION
+ if (nc->pass && !(nc->flags & NI_ENCRYPTEDPW)) {
+ if (debug)
+ alog("debug: %s: encrypting password for `%s' on load",
+ s_NickServ, nc->display);
+ if (encrypt_in_place(nc->pass, PASSMAX) < 0)
+ fatal("%s: Can't encrypt `%s' nickname password!",
+ s_NickServ, nc->display);
+ nc->flags |= NI_ENCRYPTEDPW;
+ }
+#else
+ if (nc->flags & NI_ENCRYPTEDPW) {
+ /* Bail: it makes no sense to continue with encrypted
+ * passwords, since we won't be able to verify them */
+ fatal
+ ("%s: load database: password for %s encrypted but encryption disabled, aborting",
+ s_NickServ, nc->display);
+ }
+#endif
+ SAFE(read_int16(&nc->language, f));
+
+ /* Add services opers and admins to the appropriate list, but
+ only if the database version is more than 10. */
+ if (nc->flags & NI_SERVICES_ADMIN)
+ slist_add(&servadmins, nc);
+ if (nc->flags & NI_SERVICES_OPER)
+ slist_add(&servopers, nc);
+
+ SAFE(read_int16(&nc->accesscount, f));
+ if (nc->accesscount) {
+ char **access;
+ access = scalloc(sizeof(char *) * nc->accesscount, 1);
+ nc->access = access;
+ for (j = 0; j < nc->accesscount; j++, access++)
+ SAFE(read_string(access, f));
+ }
+
+ SAFE(read_int16(&nc->memos.memocount, f));
+ SAFE(read_int16(&nc->memos.memomax, f));
+ if (nc->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * nc->memos.memocount, 1);
+ nc->memos.memos = memos;
+ for (j = 0; j < nc->memos.memocount; j++, memos++) {
+ SAFE(read_int32(&memos->number, f));
+ SAFE(read_int16(&memos->flags, f));
+ SAFE(read_int32(&tmp32, f));
+ memos->time = tmp32;
+ SAFE(read_buffer(memos->sender, f));
+ SAFE(read_string(&memos->text, f));
+ for (m = 0; m < MAX_CMD_HASH; m++) {
+ memos->moduleData[m] = NULL;
+ }
+ }
+ }
+
+ SAFE(read_int16(&nc->channelcount, f));
+ SAFE(read_int16(&tmp16, f));
+ nc->channelmax = CSMaxReg;
+
+ if (ver < 13) {
+ /* Used to be dead authentication system */
+ SAFE(read_int16(&tmp16, f));
+ SAFE(read_int32(&tmp32, f));
+ SAFE(read_int16(&tmp16, f));
+ SAFE(read_string(&s, f));
+ }
+
+ } /* while (getc_db(f) != 0) */
+ *nclast = NULL;
+ } /* for (i) */
+
+ for (i = 0; i < 1024 && !failed; i++) {
+ nalast = &nalists[i];
+ naprev = NULL;
+ while ((c = getc_db(f)) == 1) {
+ if (c != 1)
+ fatal("Invalid format in %s", NickDBName);
+
+ na = scalloc(1, sizeof(NickAlias));
+
+ SAFE(read_string(&na->nick, f));
+
+ SAFE(read_string(&na->last_usermask, f));
+ SAFE(read_string(&na->last_realname, f));
+ SAFE(read_string(&na->last_quit, f));
+
+ SAFE(read_int32(&tmp32, f));
+ na->time_registered = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ na->last_seen = tmp32;
+ SAFE(read_int16(&na->status, f));
+ na->status &= ~NS_TEMPORARY;
+
+ SAFE(read_string(&s, f));
+ na->nc = findcore(s);
+ free(s);
+
+ slist_add(&na->nc->aliases, na);
+
+ if (!(na->status & NS_VERBOTEN)) {
+ if (!na->last_usermask)
+ na->last_usermask = sstrdup("");
+ if (!na->last_realname)
+ na->last_realname = sstrdup("");
+ }
+
+ na->nc->flags &= ~NI_SERVICES_ROOT;
+
+ *nalast = na;
+ nalast = &na->next;
+ na->prev = naprev;
+ naprev = na;
+
+ } /* while (getc_db(f) != 0) */
+
+ *nalast = NULL;
+ } /* for (i) */
+
+ close_db(f);
+
+ for (i = 0; i < 1024; i++) {
+ NickAlias *next;
+
+ for (na = nalists[i]; na; na = next) {
+ next = na->next;
+ /* We check for coreless nicks (although it should never happen) */
+ if (!na->nc) {
+ alog("%s: while loading database: %s has no core! We delete it.", s_NickServ, na->nick);
+ delnick(na);
+ continue;
+ }
+
+ /* Add the Services root flag if needed. */
+ for (j = 0; j < RootNumber; j++)
+ if (!stricmp(ServicesRoots[j], na->nick))
+ na->nc->flags |= NI_SERVICES_ROOT;
+ }
+ }
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", NickDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", NickDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+
+
+void save_ns_dbase(void)
+{
+ dbFILE *f;
+ int i, j;
+ NickAlias *na;
+ NickCore *nc;
+ char **access;
+ Memo *memos;
+ static time_t lastwarn = 0;
+
+ if (!(f = open_db(s_NickServ, NickDBName, "w", NICK_VERSION)))
+ return;
+
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ SAFE(write_int8(1, f));
+
+ SAFE(write_string(nc->display, f));
+ SAFE(write_string(nc->pass, f));
+
+ SAFE(write_string(nc->email, f));
+ SAFE(write_string(nc->greet, f));
+ SAFE(write_int32(nc->icq, f));
+ SAFE(write_string(nc->url, f));
+
+ SAFE(write_int32(nc->flags, f));
+ SAFE(write_int16(nc->language, f));
+
+ SAFE(write_int16(nc->accesscount, f));
+ for (j = 0, access = nc->access; j < nc->accesscount;
+ j++, access++)
+ SAFE(write_string(*access, f));
+
+ SAFE(write_int16(nc->memos.memocount, f));
+ SAFE(write_int16(nc->memos.memomax, f));
+ memos = nc->memos.memos;
+ for (j = 0; j < nc->memos.memocount; j++, memos++) {
+ SAFE(write_int32(memos->number, f));
+ SAFE(write_int16(memos->flags, f));
+ SAFE(write_int32(memos->time, f));
+ SAFE(write_buffer(memos->sender, f));
+ SAFE(write_string(memos->text, f));
+ }
+
+ SAFE(write_int16(nc->channelcount, f));
+ SAFE(write_int16(nc->channelmax, f));
+
+ } /* for (nc) */
+
+ SAFE(write_int8(0, f));
+
+ } /* for (i) */
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next) {
+ SAFE(write_int8(1, f));
+
+ SAFE(write_string(na->nick, f));
+
+ SAFE(write_string(na->last_usermask, f));
+ SAFE(write_string(na->last_realname, f));
+ SAFE(write_string(na->last_quit, f));
+
+ SAFE(write_int32(na->time_registered, f));
+ SAFE(write_int32(na->last_seen, f));
+
+ SAFE(write_int16(na->status, f));
+
+ SAFE(write_string(na->nc->display, f));
+
+ } /* for (na) */
+ SAFE(write_int8(0, f));
+ } /* for (i) */
+
+ close_db(f);
+
+}
+
+void save_ns_req_dbase(void)
+{
+ dbFILE *f;
+ int i;
+ NickRequest *nr;
+ static time_t lastwarn = 0;
+
+ if (!(f = open_db(s_NickServ, PreNickDBName, "w", PRE_NICK_VERSION)))
+ return;
+
+ for (i = 0; i < 1024; i++) {
+ for (nr = nrlists[i]; nr; nr = nr->next) {
+ SAFE(write_int8(1, f));
+ SAFE(write_string(nr->nick, f));
+ SAFE(write_string(nr->passcode, f));
+ SAFE(write_string(nr->password, f));
+ SAFE(write_string(nr->email, f));
+ SAFE(write_int32(nr->requested, f));
+ SAFE(write_int8(0, f));
+ }
+ }
+ close_db(f);
+
+}
+
+#undef SAFE
+
+void save_ns_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ int i;
+ NickAlias *na;
+ NickCore *nc;
+
+ if (!rdb_open())
+ return;
+
+ rdb_tag_table("anope_ns_core");
+ rdb_tag_table("anope_ns_alias");
+ rdb_scrub_table("anope_ms_info", "serv='NICK'");
+ rdb_clear_table("anope_ns_access");
+
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ rdb_save_ns_core(nc);
+
+ } /* for (nc) */
+ } /* for (i) */
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next) {
+ rdb_save_ns_alias(na);
+
+ } /* for (na) */
+ } /* for (i) */
+
+ rdb_scrub_table("anope_ns_core", "active='0'");
+ rdb_scrub_table("anope_ns_alias", "active='0'");
+ rdb_close();
+#endif
+}
+
+void save_ns_req_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ int i;
+ NickRequest *nr;
+
+ if (!rdb_open())
+ return;
+
+ rdb_tag_table("anope_ns_request");
+
+ for (i = 0; i < 1024; i++) {
+ for (nr = nrlists[i]; nr; nr = nr->next) {
+ rdb_save_ns_req(nr);
+ }
+ }
+
+ rdb_scrub_table("anope_ns_request", "active='0'");
+ rdb_close();
+#endif
+
+}
+
+/*************************************************************************/
+
+/* Check whether a user is on the access list of the nick they're using 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).
+ * If the user's nick is not registered, 0 is returned.
+ */
+
+int validate_user(User * u)
+{
+ NickAlias *na;
+ NickRequest *nr;
+
+ int on_access;
+
+ if ((nr = findrequestnick(u->nick))) {
+ notice_lang(s_NickServ, u, NICK_IS_PREREG);
+ }
+
+ if (!(na = u->na))
+ return 0;
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_MAY_NOT_BE_USED);
+ collide(na, 0);
+ return 0;
+ }
+
+ on_access = is_on_access(u, na->nc);
+ if (on_access)
+ na->status |= NS_ON_ACCESS;
+
+ if (!(na->nc->flags & NI_SECURE) && on_access) {
+ na->status |= NS_RECOGNIZED;
+ na->last_seen = time(NULL);
+ if (na->last_usermask)
+ free(na->last_usermask);
+ na->last_usermask =
+ scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
+ sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
+ if (na->last_realname)
+ free(na->last_realname);
+ na->last_realname = sstrdup(u->realname);
+ return 1;
+ }
+
+ if (on_access || !(na->nc->flags & NI_KILL_IMMED)) {
+ if (na->nc->flags & NI_SECURE)
+ notice_lang(s_NickServ, u, NICK_IS_SECURE, s_NickServ);
+ else
+ notice_lang(s_NickServ, u, NICK_IS_REGISTERED, s_NickServ);
+ }
+
+ if ((na->nc->flags & NI_KILLPROTECT) && !on_access) {
+ if (na->nc->flags & NI_KILL_IMMED) {
+ notice_lang(s_NickServ, u, FORCENICKCHANGE_NOW);
+ collide(na, 0);
+ } else if (na->nc->flags & NI_KILL_QUICK) {
+ notice_lang(s_NickServ, u, FORCENICKCHANGE_IN_20_SECONDS);
+ add_ns_timeout(na, TO_COLLIDE, 20);
+ } else {
+ notice_lang(s_NickServ, u, FORCENICKCHANGE_IN_1_MINUTE);
+ add_ns_timeout(na, TO_COLLIDE, 60);
+ }
+ }
+
+ 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. */
+
+void cancel_user(User * u)
+{
+ NickAlias *na = u->na;
+
+ if (na) {
+ if (na->status & NS_GUESTED) {
+#ifdef HAS_SVSHOLD
+ if (UseSVSHOLD) {
+ send_cmd(ServerName, "SVSHOLD %s %d :%s", na->nick,
+ NSReleaseTimeout,
+ "Being held for registered user");
+ } else {
+#endif
+ NEWNICK(u->nick, NSEnforcerUser, NSEnforcerHost,
+ "Services Enforcer", "+", 0);
+ add_ns_timeout(na, TO_RELEASE, NSReleaseTimeout);
+#ifdef HAS_SVSHOLD
+ }
+#endif
+ na->status &= ~NS_TEMPORARY;
+ na->status |= NS_KILL_HELD;
+ } else {
+ na->status &= ~NS_TEMPORARY;
+ }
+
+ del_ns_timeout(na, TO_COLLIDE);
+ }
+}
+
+/*************************************************************************/
+
+/* Return whether a user has identified for their nickname. */
+
+int nick_identified(User * u)
+{
+ return u->na && (u->na->status & NS_IDENTIFIED);
+}
+
+/*************************************************************************/
+
+/* Return whether a user is recognized for their nickname. */
+
+int nick_recognized(User * u)
+{
+ return u->na && (u->na->status & (NS_IDENTIFIED | NS_RECOGNIZED));
+}
+
+/*************************************************************************/
+
+/* Returns whether a user is identified AND in the group nc */
+
+static int group_identified(User * u, NickCore * nc)
+{
+ return nick_identified(u) && u->na->nc == nc;
+}
+
+/*************************************************************************/
+
+/* Remove all nicks which have expired. Also update last-seen time for all
+ * nicks.
+ */
+
+void expire_nicks()
+{
+ int i;
+ NickAlias *na, *next;
+ time_t now = time(NULL);
+
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = next) {
+ next = na->next;
+
+ if (na->u
+ && ((na->nc->flags & NI_SECURE) ? nick_identified(na->u) :
+ nick_recognized(na->u))) {
+ if (debug >= 2)
+ alog("debug: NickServ: updating last seen time for %s",
+ na->nick);
+ na->last_seen = now;
+ continue;
+ }
+
+ if (NSExpire && now - na->last_seen >= NSExpire
+ && !(na->status & (NS_VERBOTEN | NS_NO_EXPIRE))) {
+ alog("Expiring nickname %s (group: %s) (e-mail: %s)",
+ na->nick, na->nc->display,
+ (na->nc->email ? na->nc->email : "none"));
+ delnick(na);
+ }
+ }
+ }
+}
+
+void expire_requests()
+{
+ int i;
+ NickRequest *nr, *next;
+ time_t now = time(NULL);
+ for (i = 0; i < 1024; i++) {
+ for (nr = nrlists[i]; nr; nr = next) {
+ next = nr->next;
+ if (NSRExpire && now - nr->requested >= NSRExpire) {
+ alog("Request for nick %s expiring", nr->nick);
+ delnickrequest(nr);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+/*************************************************************************/
+/* Return the NickRequest structire for the given nick, or NULL */
+
+NickRequest *findrequestnick(const char *nick)
+{
+ NickRequest *nr;
+
+ if (!nick)
+ return NULL;
+ for (nr = nrlists[HASH(nick)]; nr; nr = nr->next) {
+ if (stricmp(nr->nick, nick) == 0)
+ return nr;
+ }
+ return NULL;
+}
+
+/* Return the NickAlias structure for the given nick, or NULL if the nick
+ * isn't registered. */
+
+NickAlias *findnick(const char *nick)
+{
+ NickAlias *na;
+
+ for (na = nalists[HASH(nick)]; na; na = na->next) {
+ if (stricmp(na->nick, nick) == 0)
+ return na;
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Return the NickCore structure for the given nick, or NULL if the core
+ * doesn't exist. */
+
+NickCore *findcore(const char *nick)
+{
+ NickCore *nc;
+
+ for (nc = nclists[HASH(nick)]; nc; nc = nc->next) {
+ if (stricmp(nc->display, nick) == 0)
+ return nc;
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+/*********************** NickServ private routines ***********************/
+/*************************************************************************/
+
+/* Is the given user's address on the given nick's access list? Return 1
+ * if so, 0 if not. */
+
+static int is_on_access(User * u, NickCore * nc)
+{
+ int i;
+ char *buf;
+#ifdef HAS_VHOST
+ char *buf2 = NULL;
+#endif
+
+ if (nc->accesscount == 0)
+ return 0;
+
+ buf = scalloc(strlen(u->username) + strlen(u->host) + 2, 1);
+ sprintf(buf, "%s@%s", u->username, u->host);
+#ifdef HAS_VHOST
+ if (u->vhost) {
+ buf2 = scalloc(strlen(u->username) + strlen(u->vhost) + 2, 1);
+ sprintf(buf2, "%s@%s", u->username, u->vhost);
+ }
+#endif
+
+ for (i = 0; i < nc->accesscount; i++) {
+ if (match_wild_nocase(nc->access[i], buf)
+#ifdef HAS_VHOST
+ || (u->vhost ? match_wild_nocase(nc->access[i], buf2) : 0)
+#endif
+ ) {
+ free(buf);
+#ifdef HAS_VHOST
+ free(buf2);
+#endif
+ return 1;
+ }
+ }
+ free(buf);
+#ifdef HAS_VHOST
+ free(buf2);
+#endif
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Insert a nick alias alphabetically into the database. */
+
+void alpha_insert_alias(NickAlias * na)
+{
+ NickAlias *ptr, *prev;
+ char *nick = na->nick;
+ int index = HASH(nick);
+
+ for (prev = NULL, ptr = nalists[index];
+ ptr && stricmp(ptr->nick, nick) < 0; prev = ptr, ptr = ptr->next);
+ na->prev = prev;
+ na->next = ptr;
+ if (!prev)
+ nalists[index] = na;
+ else
+ prev->next = na;
+ if (ptr)
+ ptr->prev = na;
+}
+
+/*************************************************************************/
+
+/* Insert a nick core into the database. */
+
+void insert_core(NickCore * nc)
+{
+ int index = HASH(nc->display);
+
+ nc->prev = NULL;
+ nc->next = nclists[index];
+ if (nc->next)
+ nc->next->prev = nc;
+ nclists[index] = nc;
+}
+
+/*************************************************************************/
+void insert_requestnick(NickRequest * nr)
+{
+ int index = HASH(nr->nick);
+
+ nr->prev = NULL;
+ nr->next = nrlists[index];
+ if (nr->next)
+ nr->next->prev = nr;
+ nrlists[index] = nr;
+}
+
+/*************************************************************************/
+/* Creates a new Nick Request */
+static NickRequest *makerequest(const char *nick)
+{
+ NickRequest *nr;
+
+ nr = scalloc(1, sizeof(NickRequest));
+ nr->nick = sstrdup(nick);
+ insert_requestnick(nr);
+ alog("%s: Nick %s has been requested", s_NickServ, nr->nick);
+ return nr;
+}
+
+
+
+/* Creates a full new nick (alias + core) in NickServ database. */
+
+static NickAlias *makenick(const char *nick)
+{
+ NickAlias *na;
+ NickCore *nc;
+
+ /* First make the core */
+ nc = scalloc(1, sizeof(NickCore));
+ nc->display = sstrdup(nick);
+ slist_init(&nc->aliases);
+ insert_core(nc);
+ alog("%s: group %s has been created", s_NickServ, nc->display);
+
+ /* Then make the alias */
+ na = scalloc(1, sizeof(NickAlias));
+ na->nick = sstrdup(nick);
+ na->nc = nc;
+ slist_add(&nc->aliases, na);
+ alpha_insert_alias(na);
+ return na;
+}
+
+/*************************************************************************/
+
+/* Creates a new alias in NickServ database. */
+
+static NickAlias *makealias(const char *nick, NickCore * nc)
+{
+ NickAlias *na;
+
+ /* Just need to make the alias */
+ na = scalloc(1, sizeof(NickAlias));
+ na->nick = sstrdup(nick);
+ na->nc = nc;
+ slist_add(&nc->aliases, na);
+ alpha_insert_alias(na);
+ return na;
+}
+
+/*************************************************************************/
+
+/* Sets nc->display to newdisplay. If newdisplay is NULL, it will change
+ * it to the first alias in the list.
+ */
+
+static void change_core_display(NickCore * nc, char *newdisplay)
+{
+ if (!newdisplay) {
+ NickAlias *na;
+
+ if (nc->aliases.count <= 0)
+ return;
+
+ na = nc->aliases.list[0];
+ newdisplay = na->nick;
+ }
+
+ /* Log ... */
+ alog("%s: changing %s nickname group display to %s", s_NickServ,
+ nc->display, newdisplay);
+
+#ifdef USE_RDB
+ /* Reflect this change in the database right away. This
+ * ensures that we know how to deal with this "new" nick
+ * on the next /OS UPDATE might need it on /NS DROP too...
+ */
+ if (rdb_open()) {
+ rdb_ns_set_display(newdisplay, nc->display);
+ rdb_close();
+ }
+#endif
+
+ /* Remove the core from the list */
+ if (nc->next)
+ nc->next->prev = nc->prev;
+ if (nc->prev)
+ nc->prev->next = nc->next;
+ else
+ nclists[HASH(nc->display)] = nc->next;
+
+ free(nc->display);
+ nc->display = sstrdup(newdisplay);
+ insert_core(nc);
+
+}
+
+/*************************************************************************/
+
+/* Deletes the core. This must be called only when there is no more
+ * aliases for it, because no cleanup is done.
+ * This function removes all references to the core as well.
+ */
+
+static int delcore(NickCore * nc)
+{
+ int i;
+#ifdef USE_RDB
+ static char clause[128];
+#endif
+ /* (Hopefully complete) cleanup */
+ cs_remove_nick(nc);
+ os_remove_nick(nc);
+
+ /* Remove the core from the list */
+ if (nc->next)
+ nc->next->prev = nc->prev;
+ if (nc->prev)
+ nc->prev->next = nc->next;
+ else
+ nclists[HASH(nc->display)] = nc->next;
+
+ /* Log .. */
+ alog("%s: deleting nickname group %s", s_NickServ, nc->display);
+
+#ifdef USE_RDB
+ /* Reflect this change in the database right away. */
+ if (rdb_open()) {
+
+ snprintf(clause, sizeof(clause), "display='%s'", nc->display);
+ rdb_scrub_table("anope_ns_access", clause);
+ rdb_scrub_table("anope_ns_core", clause);
+ rdb_scrub_table("anope_cs_access", clause);
+ /* I'm unsure how to clean up the OS ADMIN/OPER list on the db */
+ /* I wish the "display" primary key would be the same on all tables */
+ snprintf(clause, sizeof(clause), "receiver='%s' AND serv='NICK'",
+ nc->display);
+ rdb_scrub_table("anope_ms_info", clause);
+ rdb_close();
+ }
+#endif
+
+ /* Now we can safely free it. */
+ free(nc->display);
+ if (nc->pass)
+ free(nc->pass);
+
+ if (nc->email)
+ free(nc->email);
+ if (nc->greet)
+ free(nc->greet);
+ if (nc->url)
+ free(nc->url);
+
+ if (nc->access) {
+ for (i = 0; i < nc->accesscount; i++) {
+ if (nc->access[i])
+ free(nc->access[i]);
+ }
+ free(nc->access);
+ }
+
+ if (nc->memos.memos) {
+ for (i = 0; i < nc->memos.memocount; i++) {
+ if (nc->memos.memos[i].text)
+ moduleCleanStruct(nc->memos.memos[i].moduleData);
+ free(nc->memos.memos[i].text);
+ }
+ free(nc->memos.memos);
+ }
+
+ moduleCleanStruct(nc->moduleData);
+
+ free(nc);
+
+ return 1;
+}
+
+
+/*************************************************************************/
+int delnickrequest(NickRequest * nr)
+{
+ if (nr) {
+ nrlists[HASH(nr->nick)] = nr->next;
+ if (nr->nick)
+ free(nr->nick);
+ if (nr->passcode)
+ free(nr->passcode);
+ if (nr->password)
+ free(nr->password);
+ if (nr->email)
+ free(nr->email);
+ free(nr);
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Deletes an alias. The core will also be deleted if it has no more
+ * nicks attached to it. Easy but powerful.
+ * Well, we must also take care that the nick being deleted is not
+ * the core display, and if so, change it to the next alias in the list,
+ * otherwise weird things will happen.
+ * Returns 1 on success, 0 otherwise.
+ */
+
+int delnick(NickAlias * na)
+{
+#ifdef USE_RDB
+ static char clause[128];
+#endif
+ /* First thing to do: remove any timeout belonging to the nick we're deleting */
+ clean_ns_timeouts(na);
+
+ /* Second thing to do: look for an user using the alias
+ * being deleted, and make appropriate changes */
+
+ if (na->u) {
+ na->u->na = NULL;
+
+#ifndef IRC_PTLINK
+ change_user_mode(na->u, "-r+d", "1");
+#else
+ change_user_mode(na->u, "-r", NULL);
+#endif
+
+
+ }
+
+ delHostCore(na->nick); /* delete any vHost's for this nick */
+
+ /* Accept nicks that have no core, because of database load functions */
+ if (na->nc) {
+ /* Next: see if our core is still useful. */
+ slist_remove(&na->nc->aliases, na);
+ if (na->nc->aliases.count == 0) {
+ if (!delcore(na->nc))
+ return 0;
+ na->nc = NULL;
+ } else {
+ /* Display updating stuff */
+ if (!stricmp(na->nick, na->nc->display))
+ change_core_display(na->nc, NULL);
+ }
+ }
+
+ /* Remove us from the aliases list */
+ if (na->next)
+ na->next->prev = na->prev;
+ if (na->prev)
+ na->prev->next = na->next;
+ else
+ nalists[HASH(na->nick)] = na->next;
+
+#ifdef USE_RDB
+ /* Reflect this change in the database right away. */
+ if (rdb_open()) {
+
+ snprintf(clause, sizeof(clause), "nick='%s'", na->nick);
+ rdb_scrub_table("anope_ns_alias", clause);
+ rdb_close();
+ }
+#endif
+
+ free(na->nick);
+ if (na->last_usermask)
+ free(na->last_usermask);
+ if (na->last_realname)
+ free(na->last_realname);
+ if (na->last_quit)
+ free(na->last_quit);
+
+ moduleCleanStruct(na->moduleData);
+
+ free(na);
+
+
+ return 1;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Collide a nick.
+ *
+ * When connected to a network using DALnet servers, version 4.4.15 and above,
+ * Services is now able to force a nick change instead of killing the user.
+ * The new nick takes the form "Guest######". If a nick change is forced, we
+ * do not introduce the enforcer nick until the user's nick actually changes.
+ * This is watched for and done in cancel_user(). -TheShadow
+ */
+
+static void collide(NickAlias * na, int from_timeout)
+{
+#ifndef IRC_HYBRID
+ char guestnick[NICKMAX];
+#endif
+
+ if (!from_timeout)
+ del_ns_timeout(na, TO_COLLIDE);
+
+ /* Old system was unsure since there can be more than one collide
+ * per second. So let use another safer method.
+ * --lara
+ */
+ /* So you should check the length of NSGuestNickPrefix, eh Lara?
+ * --Certus
+ */
+
+#ifdef IRC_HYBRID
+ kill_user(s_NickServ, na->nick, "Services nickname-enforcer kill");
+#else
+ snprintf(guestnick, sizeof(guestnick), "%s%d", NSGuestNickPrefix,
+ guestnum++);
+ notice_lang(s_NickServ, na->u, FORCENICKCHANGE_CHANGING, guestnick);
+ send_cmd(NULL, "SVSNICK %s %s :%ld", na->nick, guestnick, time(NULL));
+ na->status |= NS_GUESTED;
+#endif
+}
+
+/*************************************************************************/
+
+/* Release hold on a nick. */
+
+static void release(NickAlias * na, int from_timeout)
+{
+ if (!from_timeout)
+ del_ns_timeout(na, TO_RELEASE);
+#ifdef HAS_SVSHOLD
+ if (UseSVSHOLD)
+ send_cmd(ServerName, "SVSHOLD %s 0", na->nick);
+ else
+#endif
+ send_cmd(na->nick, "QUIT");
+ na->status &= ~NS_KILL_HELD;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+static struct my_timeout {
+ struct my_timeout *next, *prev;
+ NickAlias *na;
+ Timeout *to;
+ int type;
+} *my_timeouts;
+
+/*************************************************************************/
+
+/* Remove a collide/release timeout from our private list. */
+
+static void rem_ns_timeout(NickAlias * na, int type)
+{
+ struct my_timeout *t, *t2;
+
+ t = my_timeouts;
+ while (t) {
+ if (t->na == na && t->type == type) {
+ t2 = t->next;
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ my_timeouts = t->next;
+ free(t);
+ t = t2;
+ } else {
+ t = t->next;
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Collide a nick on timeout. */
+
+static void timeout_collide(Timeout * t)
+{
+ NickAlias *na = t->data;
+
+ rem_ns_timeout(na, TO_COLLIDE);
+ /* If they identified or don't exist anymore, don't kill them. */
+ if ((na->status & NS_IDENTIFIED) || !na->u
+ || na->u->my_signon > t->settime)
+ return;
+ /* 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(na, 1);
+}
+
+/*************************************************************************/
+
+/* Release a nick on timeout. */
+
+static void timeout_release(Timeout * t)
+{
+ NickAlias *na = t->data;
+
+ rem_ns_timeout(na, TO_RELEASE);
+ release(na, 1);
+}
+
+/*************************************************************************/
+
+/* Add a collide/release timeout. */
+
+void add_ns_timeout(NickAlias * na, int type, time_t delay)
+{
+ Timeout *to;
+ struct my_timeout *t;
+ void (*timeout_routine) (Timeout *);
+
+ if (type == TO_COLLIDE)
+ timeout_routine = timeout_collide;
+ else if (type == TO_RELEASE)
+ timeout_routine = timeout_release;
+ else {
+ alog("NickServ: unknown timeout type %d! na=%p (%s), delay=%ld",
+ type, na, na->nick, delay);
+ return;
+ }
+
+ to = add_timeout(delay, timeout_routine, 0);
+ to->data = na;
+
+ t = scalloc(sizeof(struct my_timeout), 1);
+ t->na = na;
+ t->to = to;
+ t->type = type;
+
+ t->prev = NULL;
+ t->next = my_timeouts;
+ my_timeouts = t;
+ /* Andy Church should stop coding while being drunk.
+ * Here's the two lines he forgot that produced the timed_update evil bug
+ * and a *big* memory leak.
+ */
+ if (t->next)
+ t->next->prev = t;
+}
+
+/*************************************************************************/
+
+/* Delete a collide/release timeout. */
+
+static void del_ns_timeout(NickAlias * na, int type)
+{
+ struct my_timeout *t, *t2;
+
+ t = my_timeouts;
+ while (t) {
+ if (t->na == na && t->type == type) {
+ t2 = t->next;
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ my_timeouts = t->next;
+ del_timeout(t->to);
+ free(t);
+ t = t2;
+ } else {
+ t = t->next;
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Deletes all timeouts belonging to a given nick.
+ * This should only be called before nick deletion.
+ */
+
+void clean_ns_timeouts(NickAlias * na)
+{
+ struct my_timeout *t, *next;
+
+ for (t = my_timeouts; t; t = next) {
+ next = t->next;
+ if (t->na == na) {
+ if (debug)
+ alog("%s: deleting timeout type %d from %s", s_NickServ,
+ t->type, t->na->nick);
+ /* If the timeout has the TO_RELEASE type, we should release the user */
+ if (t->type == TO_RELEASE)
+ release(na, 1);
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ my_timeouts = t->next;
+ del_timeout(t->to);
+ free(t);
+ }
+ }
+}
+
+/*************************************************************************/
+/*********************** NickServ command routines ***********************/
+/*************************************************************************/
+
+/* Return a help message. */
+
+static int do_help(User * u)
+{
+ char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_NickServ, u, NICK_HELP);
+ if (NSExpire >= 86400)
+ notice_help(s_NickServ, u, NICK_HELP_EXPIRES,
+ NSExpire / 86400);
+ if (is_services_oper(u))
+ notice_help(s_NickServ, u, NICK_SERVADMIN_HELP);
+ moduleDisplayHelp(1, u);
+ } else if (stricmp(cmd, "SET LANGUAGE") == 0) {
+ int i;
+ notice_help(s_NickServ, u, NICK_HELP_SET_LANGUAGE);
+ for (i = 0; i < NUM_LANGS && langlist[i] >= 0; i++)
+ notice_user(s_NickServ, u, " %2d) %s", i + 1,
+ langnames[langlist[i]]);
+ } else {
+ mod_help_cmd(s_NickServ, u, NICKSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Register a nick. */
+
+static int do_register(User * u)
+{
+ NickRequest *nr = NULL, *anr = NULL;
+ int prefixlen = strlen(NSGuestNickPrefix);
+ int nicklen = strlen(u->nick);
+ char *pass = strtok(NULL, " ");
+ char *email = strtok(NULL, " ");
+ char passcode[11];
+ int idx, min = 1, max = 62;
+ int chars[] =
+ { ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+ 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
+ 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ if (readonly) {
+ notice_lang(s_NickServ, u, NICK_REGISTRATION_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
+ notice_lang(s_NickServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!is_oper(u) && NickRegDelay
+ && ((time(NULL) - u->my_signon) < NickRegDelay)) {
+ notice_lang(s_NickServ, u, NICK_REG_DELAY, NickRegDelay);
+ return MOD_CONT;
+ }
+
+ if ((anr = findrequestnick(u->nick))) {
+ notice_lang(s_NickServ, u, NICK_REQUESTED);
+ return MOD_CONT;
+ }
+ /* Prevent "Guest" nicks from being registered. -TheShadow */
+
+ /* Guest nick can now have a series of between 1 and 7 digits.
+ * --lara
+ */
+ if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1 &&
+ stristr(u->nick, NSGuestNickPrefix) == u->nick &&
+ strspn(u->nick + prefixlen, "1234567890") == nicklen - prefixlen) {
+ notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED, u->nick);
+ return MOD_CONT;
+ }
+
+ if (!pass || (NSForceEmail && !email)) {
+ syntax_error(s_NickServ, u, "REGISTER",
+ NICK_REGISTER_SYNTAX_EMAIL);
+ } else if (time(NULL) < u->lastnickreg + NSRegDelay) {
+ notice_lang(s_NickServ, u, NICK_REG_PLEASE_WAIT, NSRegDelay);
+ } else if (u->na) { /* i.e. there's already such a nick regged */
+ if (u->na->status & NS_VERBOTEN) {
+ alog("%s: %s@%s tried to register FORBIDden nick %s",
+ s_NickServ, u->username, GetHost(u), u->nick);
+ notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED, u->nick);
+ } else {
+ notice_lang(s_NickServ, u, NICK_ALREADY_REGISTERED, u->nick);
+ }
+ } else if (stricmp(u->nick, pass) == 0
+ || (StrictPasswords && strlen(pass) < 5)) {
+ notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
+ } else if (email && !MailValidate(email)) {
+ notice_lang(s_NickServ, u, MAIL_X_INVALID, email);
+ } else {
+#ifdef USE_ENCRYPTION
+ if (strlen(pass) > PASSMAX) {
+ pass[PASSMAX] = 0;
+ notice_lang(s_NickServ, u, PASSWORD_TRUNCATED, PASSMAX);
+ }
+#else
+ if (strlen(pass) > PASSMAX - 1) { /* -1 for null byte */
+ pass[PASSMAX] = 0;
+ notice_lang(s_NickServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
+ }
+#endif
+ srand((unsigned) time(NULL));
+ for (idx = 0; idx < 9; idx++) {
+ passcode[idx] =
+ chars[(1 +
+ (int) (((float) (max - min)) * rand() /
+ (RAND_MAX + 1.0)) + min)];
+ } passcode[idx] = '\0';
+ nr = makerequest(u->nick);
+ nr->passcode = sstrdup(passcode);
+ nr->password = sstrdup(pass);
+ if (email) {
+ nr->email = sstrdup(email);
+ }
+ nr->requested = time(NULL);
+ if (NSEmailReg) {
+ if (do_sendregmail(u, nr) == 0) {
+ notice_lang(s_NickServ, u, NICK_ENTER_REG_CODE, email,
+ s_NickServ);
+ alog("%s: sent registration verification code to %s",
+ s_NickServ, nr->email);
+ } else {
+ alog("%s: Unable to send registration verification mail",
+ s_NickServ);
+ notice_lang(s_NickServ, u, NICK_REG_UNABLE);
+ delnickrequest(nr); /* Delete the NickRequest if we couldnt send the mail */
+ return MOD_CONT;
+ }
+ } else {
+ do_confirm(u);
+ }
+
+ }
+ return MOD_CONT;
+}
+
+
+static int do_resend(User * u)
+{
+ NickRequest *nr = NULL;
+ if (NSEmailReg) {
+ if ((nr = findrequestnick(u->nick))) {
+ if (do_sendregmail(u, nr) == 0) {
+ notice_lang(s_NickServ, u, NICK_REG_RESENT, nr->email);
+ alog("%s: re-sent registration verification code for %s to %s", s_NickServ, nr->nick, nr->email);
+ } else {
+ alog("%s: Unable to re-send registration verification mail for %s", s_NickServ, nr->nick);
+ return MOD_CONT;
+ }
+ }
+ }
+ return MOD_CONT;
+}
+
+static int do_sendregmail(User * u, NickRequest * nr)
+{
+ MailInfo *mail = NULL;
+ char buf[BUFSIZE];
+
+ if (!(nr || u)) {
+ return -1;
+ }
+ snprintf(buf, sizeof(buf), getstring2(NULL, NICK_REG_MAIL_SUBJECT),
+ nr->nick);
+ mail = MailRegBegin(u, nr, buf, s_NickServ);
+ if (!mail) {
+ return -1;
+ }
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_HEAD));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_1), nr->nick);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_2), s_NickServ,
+ nr->passcode);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_3));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_4));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring2(NULL, NICK_REG_MAIL_LINE_5),
+ NetworkName);
+ fprintf(mail->pipe, "\n.\n");
+ MailEnd(mail);
+ return 0;
+}
+
+static int do_confirm(User * u)
+{
+
+ NickRequest *nr = NULL;
+ NickAlias *na = NULL;
+ char *passcode = strtok(NULL, " ");
+ char *pass = NULL;
+ char *email = NULL;
+ int forced = 0;
+ User *utmp = NULL;
+#ifdef USE_ENCRYPTION
+ int len;
+#endif
+ nr = findrequestnick(u->nick);
+
+ if (NSEmailReg) {
+ if (!passcode) {
+ notice_lang(s_NickServ, u, NICK_CONFIRM_INVALID);
+ return MOD_CONT;
+ }
+
+ if (!nr) {
+ if (is_services_admin(u)) {
+/* If an admin, thier nick is obviously already regged, so look at the passcode to get the nick
+ of the user they are trying to validate, and push that user through regardless of passcode */
+ nr = findrequestnick(passcode);
+ if (nr) {
+ utmp = finduser(passcode);
+ if (utmp) {
+ sprintf(passcode,
+ "FORCE_ACTIVATION_DUE_TO_OPER_CONFIRM %s",
+ nr->passcode);
+ passcode = strtok(passcode, " ");
+ notice_lang(s_NickServ, u, NICK_FORCE_REG,
+ nr->nick);
+ do_confirm(utmp);
+ return MOD_CONT;
+ } else {
+ passcode = sstrdup(nr->passcode);
+ forced = 1;
+ }
+ } else {
+ notice_lang(s_NickServ, u, NICK_CONFIRM_NOT_FOUND,
+ s_NickServ);
+ return MOD_CONT;
+ }
+ } else {
+ notice_lang(s_NickServ, u, NICK_CONFIRM_NOT_FOUND,
+ s_NickServ);
+ return MOD_CONT;
+ }
+ }
+
+ if (stricmp(nr->passcode, passcode) != 0) {
+ notice_lang(s_NickServ, u, NICK_CONFIRM_INVALID);
+ return MOD_CONT;
+ }
+ }
+
+ if (!nr) {
+ notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
+ return MOD_CONT;
+ }
+ pass = sstrdup(nr->password);
+
+ if (nr->email) {
+ email = sstrdup(nr->email);
+ }
+ na = makenick(nr->nick);
+
+ if (na) {
+ int i;
+#if !defined(IRC_PTLINK)
+ char tsbuf[16];
+#endif
+
+#ifdef USE_ENCRYPTION
+ len = strlen(pass);
+ na->nc->pass = smalloc(PASSMAX);
+ if (encrypt(pass, len, na->nc->pass, PASSMAX) < 0) {
+ memset(pass, 0, strlen(pass));
+ alog("%s: Failed to encrypt password for %s (register)",
+ s_NickServ, nr->nick);
+ notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
+ return MOD_CONT;
+ }
+ memset(pass, 0, strlen(pass));
+ na->status = NS_IDENTIFIED | NS_RECOGNIZED;
+ na->nc->flags |= NI_ENCRYPTEDPW;
+#else
+ na->nc->pass = sstrdup(pass);
+ na->status = NS_IDENTIFIED | NS_RECOGNIZED;
+#endif
+ na->nc->flags |= NSDefFlags;
+ for (i = 0; i < RootNumber; i++) {
+ if (!stricmp(ServicesRoots[i], nr->nick)) {
+ na->nc->flags |= NI_SERVICES_ROOT;
+ break;
+ }
+ }
+ na->nc->memos.memomax = MSMaxMemos;
+ na->nc->channelmax = CSMaxReg;
+ if (forced == 1) {
+ na->last_usermask = sstrdup("*@*");
+ na->last_realname = sstrdup("unknown");
+ } else {
+ na->last_usermask =
+ scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
+ sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
+ na->last_realname = sstrdup(u->realname);
+ }
+ na->time_registered = na->last_seen = time(NULL);
+ na->nc->accesscount = 1;
+ na->nc->access = scalloc(sizeof(char *), 1);
+ na->nc->access[0] = create_mask(u);
+ na->nc->language = NSDefLanguage;
+ if (email)
+ na->nc->email = sstrdup(email);
+ if (forced != 1) {
+ u->na = na;
+ na->u = u;
+ alog("%s: '%s' registered by %s@%s (e-mail: %s)", s_NickServ,
+ u->nick, u->username, GetHost(u),
+ (email ? email : "none"));
+ notice_lang(s_NickServ, u, NICK_REGISTERED, u->nick,
+ na->nc->access[0]);
+#ifndef USE_ENCRYPTION
+ notice_lang(s_NickServ, u, NICK_PASSWORD_IS, na->nc->pass);
+#endif
+ u->lastnickreg = time(NULL);
+#if !defined(IRC_PTLINK)
+ snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
+ change_user_mode(u, "+rd", tsbuf);
+#else
+ change_user_mode(u, "+r", NULL);
+#endif
+
+ } else {
+ notice_lang(s_NickServ, u, NICK_FORCE_REG, nr->nick);
+ }
+ delnickrequest(nr); /* remove the nick request */
+ } else {
+ alog("%s: makenick(%s) failed", s_NickServ, u->nick);
+ notice_lang(s_NickServ, u, NICK_REGISTRATION_FAILED);
+ }
+
+ /* Enable nick tracking if enabled */
+ if (NSNickTracking)
+ nsStartNickTracking(u);
+
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Register a nick in a specified group. */
+
+static int do_group(User * u)
+{
+ NickAlias *na, *target;
+ char *nick = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ int i;
+#if !defined(IRC_PTLINK)
+ char tsbuf[16];
+#endif
+
+ if (NSEmailReg && (findrequestnick(u->nick))) {
+ notice_lang(s_NickServ, u, NICK_REQUESTED);
+ return MOD_CONT;
+ }
+
+ if (readonly) {
+ notice_lang(s_NickServ, u, NICK_GROUP_DISABLED);
+ return MOD_CONT;
+ }
+ if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
+ notice_lang(s_NickServ, u, OPER_DEFCON_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!nick || !pass) {
+ syntax_error(s_NickServ, u, "GROUP", NICK_GROUP_SYNTAX);
+ } else if (time(NULL) < u->lastnickreg + NSRegDelay) {
+ notice_lang(s_NickServ, u, NICK_GROUP_PLEASE_WAIT, NSRegDelay);
+ } else if (u->na && (u->na->status & NS_VERBOTEN)) {
+ alog("%s: %s@%s tried to use GROUP from FORBIDden nick %s",
+ s_NickServ, u->username, GetHost(u), u->nick);
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u->nick);
+ } else if (u->na && NSNoGroupChange) {
+ notice_lang(s_NickServ, u, NICK_GROUP_CHANGE_DISABLED, s_NickServ);
+ } else if (u->na && !nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else if (!(target = findnick(nick))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (target->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
+ } else if (u->na && target->nc == u->na->nc) {
+ notice_lang(s_NickServ, u, NICK_GROUP_SAME, target->nick);
+ } else if (NSMaxAliases && (target->nc->aliases.count >= NSMaxAliases)
+ && !nick_is_services_admin(target->nc)) {
+ notice_lang(s_NickServ, u, NICK_GROUP_TOO_MANY, target->nick,
+ s_NickServ, s_NickServ);
+ } else if (check_password(pass, target->nc->pass) != 1) {
+ alog("%s: Failed GROUP for %s!%s@%s (invalid password)",
+ s_NickServ, u->nick, u->username, GetHost(u));
+ notice_lang(s_NickServ, u, PASSWORD_INCORRECT);
+ bad_password(u);
+ } else {
+ /* If the nick is already registered, drop it.
+ * If not, check that it is valid.
+ */
+ if (u->na) {
+ delnick(u->na);
+ } else {
+ int prefixlen = strlen(NSGuestNickPrefix);
+ int nicklen = strlen(u->nick);
+
+ if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1
+ && stristr(u->nick, NSGuestNickPrefix) == u->nick
+ && strspn(u->nick + prefixlen,
+ "1234567890") == nicklen - prefixlen) {
+ notice_lang(s_NickServ, u, NICK_CANNOT_BE_REGISTERED,
+ u->nick);
+ return MOD_CONT;
+ }
+ }
+ na = makealias(u->nick, target->nc);
+
+ if (na) {
+ na->last_usermask =
+ scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
+ sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
+ na->last_realname = sstrdup(u->realname);
+ na->time_registered = na->last_seen = time(NULL);
+ na->status = NS_IDENTIFIED | NS_RECOGNIZED;
+
+ if (!(na->nc->flags & NI_SERVICES_ROOT)) {
+ for (i = 0; i < RootNumber; i++) {
+ if (!stricmp(ServicesRoots[i], u->nick)) {
+ na->nc->flags |= NI_SERVICES_ROOT;
+ break;
+ }
+ }
+ }
+
+ u->na = na;
+ na->u = u;
+
+#ifdef USE_RDB
+ /* Is this really needed? Since this is a new alias it will get
+ * its unique id on the next update, since it was previously
+ * deleted by delnick. Must observe...
+ */
+ if (rdb_open()) {
+ rdb_save_ns_alias(na);
+ rdb_close();
+ }
+#endif
+ alog("%s: %s!%s@%s makes %s join group of %s (%s) (e-mail: %s)", s_NickServ, u->nick, u->username, GetHost(u), u->nick, target->nick, target->nc->display, (target->nc->email ? target->nc->email : "none"));
+ notice_lang(s_NickServ, u, NICK_GROUP_JOINED, target->nick);
+
+ u->lastnickreg = time(NULL);
+#if !defined(IRC_PTLINK)
+ snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
+ change_user_mode(u, "+rd", tsbuf);
+#else
+ change_user_mode(u, "+r", NULL);
+#endif
+
+ check_memos(u);
+ } else {
+ alog("%s: makealias(%s) failed", s_NickServ, u->nick);
+ notice_lang(s_NickServ, u, NICK_GROUP_FAILED);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_nickupdate(User * u)
+{
+ NickAlias *na;
+
+ if (!nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else {
+ na = u->na;
+ if (NSModeOnID)
+ do_setmodes(u);
+ check_memos(u);
+ if (na->last_realname)
+ free(na->last_realname);
+ na->last_realname = sstrdup(u->realname);
+ na->status |= NS_IDENTIFIED;
+ na->last_seen = time(NULL);
+#ifdef HAS_VHOST
+ do_on_id(u);
+#endif
+ notice_lang(s_NickServ, u, NICK_UPDATE_SUCCESS, s_NickServ);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_identify(User * u)
+{
+ char *pass = strtok(NULL, " ");
+ NickAlias *na;
+ NickRequest *nr;
+ int res;
+#if !defined(IRC_PTLINK)
+ char tsbuf[16];
+#endif
+
+ if (!pass) {
+ syntax_error(s_NickServ, u, "IDENTIFY", NICK_IDENTIFY_SYNTAX);
+ } else if (!(na = u->na)) {
+ if ((nr = findrequestnick(u->nick))) {
+ notice_lang(s_NickServ, u, NICK_IS_PREREG);
+ } else {
+ notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
+ }
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (!(res = check_password(pass, na->nc->pass))) {
+ alog("%s: Failed IDENTIFY for %s!%s@%s", s_NickServ, u->nick,
+ u->username, GetHost(u));
+ notice_lang(s_NickServ, u, PASSWORD_INCORRECT);
+ bad_password(u);
+ } else if (res == -1) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_FAILED);
+ } else if (nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_ALREADY_IDENTIFIED);
+ } else {
+ if (!(na->status & NS_IDENTIFIED) && !(na->status & NS_RECOGNIZED)) {
+ if (na->last_usermask)
+ free(na->last_usermask);
+ na->last_usermask =
+ scalloc(strlen(GetIdent(u)) + strlen(GetHost(u)) + 2, 1);
+ sprintf(na->last_usermask, "%s@%s", GetIdent(u), GetHost(u));
+ if (na->last_realname)
+ free(na->last_realname);
+ na->last_realname = sstrdup(u->realname);
+ }
+
+ na->status |= NS_IDENTIFIED;
+ na->last_seen = time(NULL);
+
+#ifndef IRC_PTLINK
+ snprintf(tsbuf, sizeof(tsbuf), "%lu", u->timestamp);
+ change_user_mode(u, "+rd", tsbuf);
+#else
+ change_user_mode(u, "+r", "");
+#endif /* IRC_PTLINK */
+
+
+ alog("%s: %s!%s@%s identified for nick %s", s_NickServ, u->nick,
+ u->username, GetHost(u), u->nick);
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_SUCCEEDED);
+#ifdef HAS_VHOST
+ do_on_id(u);
+#endif
+ if (NSModeOnID) {
+ do_setmodes(u);
+ }
+
+ if (NSForceEmail && u->na && !u->na->nc->email) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_EMAIL_REQUIRED);
+ notice_help(s_NickServ, u, NICK_IDENTIFY_EMAIL_HOWTO);
+ }
+
+ if (!(na->status & NS_RECOGNIZED))
+ check_memos(u);
+
+ /* Enable nick tracking if enabled */
+ if (NSNickTracking)
+ nsStartNickTracking(u);
+ }
+ return MOD_CONT;
+}
+
+int should_mode_change(int16 status, int16 mode)
+{
+ switch (mode) {
+ case CUS_OP:
+ if (status & CUS_OP) {
+ return 0;
+ }
+ break;
+ case CUS_VOICE:
+ if (status & CUS_OP) {
+ return 0;
+ }
+#ifdef HAS_HALFOP
+ if (status & CUS_HALFOP) {
+ return 0;
+ }
+#endif
+ if (status & CUS_VOICE) {
+ return 0;
+ }
+ return 1;
+ break;
+#ifdef HAS_HALFOP
+
+
+ case CUS_HALFOP:
+ if (status & CUS_OP) {
+ return 0;
+ }
+ if (status & CUS_HALFOP) {
+ return 0;
+ }
+ return 1;
+ break;
+#endif
+#ifdef IRC_UNREAL
+ case CUS_OWNER:
+ if (status & CUS_OWNER) {
+ return 0;
+ }
+ break;
+ case CUS_PROTECT:
+ if (status & CUS_OWNER) {
+ return 0;
+ }
+ if (status & CUS_PROTECT) {
+ return 0;
+ }
+ break;
+#endif
+#ifdef IRC_VIAGRA
+ case CUS_OWNER:
+ if (status & CUS_OWNER) {
+ return 0;
+ }
+ break;
+ case CUS_PROTECT:
+ if (status & CUS_OWNER) {
+ return 0;
+ }
+ if (status & CUS_PROTECT) {
+ return 0;
+ }
+ break;
+#endif
+#if defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ case CUS_PROTECT:
+ if (status & CUS_PROTECT) {
+ return 0;
+ }
+ break;
+#endif
+ }
+ return 1;
+}
+
+static int do_setmodes(User * u)
+{
+ struct u_chanlist *uc;
+ Channel *c;
+ char *chan;
+
+ /* Walk users current channels */
+ for (uc = u->chans; uc; uc = uc->next) {
+ if ((c = uc->chan)) {
+ chan = c->name;
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)
+ if (should_mode_change(uc->status, CUS_OWNER)
+ && check_should_owner(u, chan)) {
+ chan_set_user_status(c, u, CUS_OWNER);
+ } else
+#endif
+#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2) || defined(IRC_PTLINK)
+ if (should_mode_change(uc->status, CUS_PROTECT)
+ && check_should_protect(u, chan)) {
+ chan_set_user_status(c, u, CUS_PROTECT);
+ } else
+#endif
+ if (should_mode_change(uc->status, CUS_OP)
+ && check_should_op(u, chan)) {
+ chan_set_user_status(c, u, CUS_OP);
+ } else
+#ifdef HAS_HALFOP
+ if (should_mode_change(uc->status, CUS_HALFOP)
+ && check_should_halfop(u, chan)) {
+ chan_set_user_status(c, u, CUS_HALFOP);
+ } else
+#endif
+ if (should_mode_change(uc->status, CUS_VOICE)
+ && check_should_voice(u, chan)) {
+ chan_set_user_status(c, u, CUS_VOICE);
+ }
+ }
+ }
+ return MOD_CONT;
+}
+
+
+/*************************************************************************/
+
+static int do_logout(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *param = strtok(NULL, " ");
+ User *u2;
+
+ if (!is_services_admin(u) && nick) {
+ syntax_error(s_NickServ, u, "LOGOUT", NICK_LOGOUT_SYNTAX);
+ } else if (!(u2 = (nick ? finduser(nick) : u))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (!u2->na) {
+ if (nick)
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ else
+ notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
+ } else if (u2->na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, u2->na->nick);
+ } else if (!nick && !nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else if (nick && is_services_admin(u2)) {
+ notice_lang(s_NickServ, u, NICK_LOGOUT_SERVICESADMIN, nick);
+ } else {
+ if (nick && param && !stricmp(param, "REVALIDATE")) {
+ cancel_user(u2);
+ validate_user(u2);
+ } else {
+ u2->na->status &= ~(NS_IDENTIFIED | NS_RECOGNIZED);
+ }
+
+ change_user_mode(u2, "-r+d", "1");
+
+ u->isSuperAdmin = 0; /* Dont let people logout and remain a SuperAdmin */
+ alog("%s: %s!%s@%s logged out nickname %s", s_NickServ, u->nick,
+ u->username, GetHost(u), u2->nick);
+
+ if (nick)
+ notice_lang(s_NickServ, u, NICK_LOGOUT_X_SUCCEEDED, nick);
+ else
+ notice_lang(s_NickServ, u, NICK_LOGOUT_SUCCEEDED);
+
+ /* Stop nick tracking if enabled */
+ if (NSNickTracking)
+ nsStopNickTracking(u);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_drop(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+ NickRequest *nr = NULL;
+ int is_servadmin = is_services_admin(u);
+ int is_mine; /* Does the nick being dropped belong to the user that is dropping? */
+
+ if (readonly && !is_servadmin) {
+ notice_lang(s_NickServ, u, NICK_DROP_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!(na = (nick ? findnick(nick) : u->na))) {
+ if (nick) {
+ if ((nr = findrequestnick(nick)) && is_servadmin) {
+ if (readonly)
+ notice_lang(s_NickServ, u, READ_ONLY_MODE);
+ if (WallDrop)
+ wallops(s_NickServ, "\2%s\2 used DROP on \2%s\2",
+ u->nick, nick);
+ alog("%s: %s!%s@%s dropped nickname %s (e-mail: %s)",
+ s_NickServ, u->nick, u->username, GetHost(u),
+ nr->nick, nr->email);
+ delnickrequest(nr);
+ notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
+ } else {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ }
+ } else
+ notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
+ return MOD_CONT;
+ }
+
+ is_mine = (u->na && (u->na->nc == na->nc));
+
+ if (is_mine && !nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else if (!is_mine && !is_servadmin) {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ } else if (NSSecureAdmins && !is_mine && nick_is_services_admin(na->nc)
+ && !is_services_root(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ } else {
+ if (readonly)
+ notice_lang(s_NickServ, u, READ_ONLY_MODE);
+
+ alog("%s: %s!%s@%s dropped nickname %s (group %s) (e-mail: %s)",
+ s_NickServ, u->nick, u->username, GetHost(u), na->nick,
+ na->nc->display, (na->nc->email ? na->nc->email : "none"));
+ delnick(na);
+
+ if (!is_mine) {
+ if (WallDrop)
+ wallops(s_NickServ, "\2%s\2 used DROP on \2%s\2", u->nick,
+ nick);
+ notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
+ } else {
+ if (nick)
+ notice_lang(s_NickServ, u, NICK_X_DROPPED, nick);
+ else
+ notice_lang(s_NickServ, u, NICK_DROPPED);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *param = strtok(NULL, " ");
+
+ NickAlias *na;
+ int is_servadmin = is_services_admin(u);
+ int set_nick = 0;
+
+ if (readonly) {
+ notice_lang(s_NickServ, u, NICK_SET_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (is_servadmin && cmd && (na = findnick(cmd))) {
+ cmd = param;
+ param = strtok(NULL, " ");
+ set_nick = 1;
+ } else {
+ na = u->na;
+ }
+
+ if (!param
+ && (!cmd
+ || (stricmp(cmd, "URL") != 0 && stricmp(cmd, "EMAIL") != 0
+ && stricmp(cmd, "GREET") != 0
+ && stricmp(cmd, "ICQ") != 0))) {
+ if (is_servadmin) {
+ syntax_error(s_NickServ, u, "SET", NICK_SET_SERVADMIN_SYNTAX);
+ } else {
+ syntax_error(s_NickServ, u, "SET", NICK_SET_SYNTAX);
+ }
+ } else if (!na) {
+ notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (!is_servadmin && !nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+ } else if (stricmp(cmd, "DISPLAY") == 0) {
+ do_set_display(u, na->nc, param);
+ } else if (stricmp(cmd, "PASSWORD") == 0) {
+ do_set_password(u, na->nc, param);
+ } else if (stricmp(cmd, "LANGUAGE") == 0) {
+ do_set_language(u, na->nc, param);
+ } else if (stricmp(cmd, "URL") == 0) {
+ do_set_url(u, na->nc, param);
+ } else if (stricmp(cmd, "EMAIL") == 0) {
+ do_set_email(u, na->nc, param);
+ } else if (stricmp(cmd, "ICQ") == 0) {
+ do_set_icq(u, na->nc, param);
+ } else if (stricmp(cmd, "GREET") == 0) {
+ do_set_greet(u, na->nc, param);
+ } else if (stricmp(cmd, "KILL") == 0) {
+ do_set_kill(u, na->nc, param);
+ } else if (stricmp(cmd, "SECURE") == 0) {
+ do_set_secure(u, na->nc, param);
+ } else if (stricmp(cmd, "PRIVATE") == 0) {
+ do_set_private(u, na->nc, param);
+ } else if (stricmp(cmd, "MSG") == 0) {
+ do_set_msg(u, na->nc, param);
+ } else if (stricmp(cmd, "HIDE") == 0) {
+ do_set_hide(u, na->nc, param);
+ } else if (stricmp(cmd, "NOEXPIRE") == 0) {
+ do_set_noexpire(u, na, param);
+ } else {
+ if (is_servadmin)
+ notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION_OR_BAD_NICK,
+ cmd);
+ else
+ notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_display(User * u, NickCore * nc, char *param)
+{
+ int i;
+ NickAlias *na;
+
+ /* First check whether param is a valid nick of the group */
+ for (i = 0; i < nc->aliases.count; i++) {
+ na = nc->aliases.list[i];
+ if (!stricmp(na->nick, param)) {
+ param = na->nick; /* Because case may differ */
+ break;
+ }
+ }
+
+ if (i == nc->aliases.count) {
+ notice_lang(s_NickServ, u, NICK_SET_DISPLAY_INVALID);
+ return MOD_CONT;
+ }
+
+ change_core_display(nc, param);
+ notice_lang(s_NickServ, u, NICK_SET_DISPLAY_CHANGED, nc->display);
+
+ /* Enable nick tracking if enabled */
+ if (NSNickTracking)
+ nsStartNickTracking(u);
+
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_password(User * u, NickCore * nc, char *param)
+{
+ int len = strlen(param);
+
+ if (NSSecureAdmins && u->na->nc != nc && nick_is_services_admin(nc)
+ && !is_services_root(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ } else if (stricmp(nc->display, param) == 0
+ || (StrictPasswords && len < 5)) {
+ notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
+ return MOD_CONT;
+ }
+
+ if (nc->pass)
+ free(nc->pass);
+
+#ifdef USE_ENCRYPTION
+ nc->pass = smalloc(PASSMAX);
+
+ if (encrypt(param, len, nc->pass, PASSMAX) < 0) {
+ memset(param, 0, len);
+ alog("%s: Failed to encrypt password for %s (set)", s_NickServ,
+ nc->display);
+ notice_lang(s_NickServ, u, NICK_SET_PASSWORD_FAILED);
+ return MOD_CONT;
+ }
+
+ memset(param, 0, len);
+ notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED);
+#else
+ nc->pass = sstrdup(param);
+ notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED_TO, nc->pass);
+#endif
+
+ if (u->na && u->na->nc != nc && is_services_admin(u)) {
+ alog("%s: %s!%s@%s used SET PASSWORD as Services admin on %s (e-mail: %s)", s_NickServ, u->nick, u->username, GetHost(u), nc->display, (nc->email ? nc->email : "none"));
+ if (WallSetpass)
+ wallops(s_NickServ,
+ "\2%s\2 used SET PASSWORD as Services admin on \2%s\2",
+ u->nick, nc->display);
+ } else {
+ alog("%s: %s!%s@%s (e-mail: %s) changed its password.", s_NickServ,
+ u->nick, u->username, GetHost(u),
+ (nc->email ? nc->email : "none"));
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_language(User * u, NickCore * nc, 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 MOD_CONT;
+ }
+ 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 MOD_CONT;
+ }
+ nc->language = langlist[langnum];
+ notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_CHANGED);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_url(User * u, NickCore * nc, char *param)
+{
+ if (nc->url)
+ free(nc->url);
+
+ if (param) {
+ nc->url = sstrdup(param);
+ notice_lang(s_NickServ, u, NICK_SET_URL_CHANGED, param);
+ } else {
+ nc->url = NULL;
+ notice_lang(s_NickServ, u, NICK_SET_URL_UNSET);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_email(User * u, NickCore * nc, char *param)
+{
+ if (!param && NSForceEmail) {
+ notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNSET_IMPOSSIBLE);
+ return MOD_CONT;
+ } else if (param && !MailValidate(param)) {
+ notice_lang(s_NickServ, u, MAIL_X_INVALID, param);
+ return MOD_CONT;
+ }
+
+ alog("%s: %s!%s@%s (e-mail: %s) changed its e-mail to %s.", s_NickServ,
+ u->nick, u->username, GetHost(u),
+ (nc->email ? nc->email : "none"), (param ? param : "none"));
+
+ if (nc->email)
+ free(nc->email);
+
+ if (param) {
+ nc->email = sstrdup(param);
+ notice_lang(s_NickServ, u, NICK_SET_EMAIL_CHANGED, param);
+ } else {
+ nc->email = NULL;
+ notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNSET);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_icq(User * u, NickCore * nc, char *param)
+{
+ if (param) {
+ int32 tmp = atol(param);
+ if (!tmp) {
+ notice_lang(s_NickServ, u, NICK_SET_ICQ_INVALID, param);
+ } else {
+ nc->icq = tmp;
+ notice_lang(s_NickServ, u, NICK_SET_ICQ_CHANGED, param);
+ }
+ } else {
+ nc->icq = 0;
+ notice_lang(s_NickServ, u, NICK_SET_ICQ_UNSET);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_greet(User * u, NickCore * nc, char *param)
+{
+ if (nc->greet)
+ free(nc->greet);
+
+ if (param) {
+ char buf[BUFSIZE];
+ char *end = strtok(NULL, "");
+
+ snprintf(buf, sizeof(buf), "%s%s%s", param, (end ? " " : ""),
+ (end ? end : ""));
+
+ nc->greet = sstrdup(buf);
+ notice_lang(s_NickServ, u, NICK_SET_GREET_CHANGED, buf);
+ } else {
+ nc->greet = NULL;
+ notice_lang(s_NickServ, u, NICK_SET_GREET_UNSET);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_kill(User * u, NickCore * nc, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ nc->flags |= NI_KILLPROTECT;
+ nc->flags &= ~(NI_KILL_QUICK | NI_KILL_IMMED);
+ notice_lang(s_NickServ, u, NICK_SET_KILL_ON);
+ } else if (stricmp(param, "QUICK") == 0) {
+ nc->flags |= NI_KILLPROTECT | NI_KILL_QUICK;
+ nc->flags &= ~NI_KILL_IMMED;
+ notice_lang(s_NickServ, u, NICK_SET_KILL_QUICK);
+ } else if (stricmp(param, "IMMED") == 0) {
+ if (NSAllowKillImmed) {
+ nc->flags |= NI_KILLPROTECT | NI_KILL_IMMED;
+ nc->flags &= ~NI_KILL_QUICK;
+ notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED);
+ } else {
+ notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED_DISABLED);
+ }
+ } else if (stricmp(param, "OFF") == 0) {
+ nc->flags &= ~(NI_KILLPROTECT | NI_KILL_QUICK | NI_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 MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_secure(User * u, NickCore * nc, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ nc->flags |= NI_SECURE;
+ notice_lang(s_NickServ, u, NICK_SET_SECURE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ nc->flags &= ~NI_SECURE;
+ notice_lang(s_NickServ, u, NICK_SET_SECURE_OFF);
+ } else {
+ syntax_error(s_NickServ, u, "SET SECURE", NICK_SET_SECURE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_private(User * u, NickCore * nc, char *param)
+{
+ if (stricmp(param, "ON") == 0) {
+ nc->flags |= NI_PRIVATE;
+ notice_lang(s_NickServ, u, NICK_SET_PRIVATE_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ nc->flags &= ~NI_PRIVATE;
+ notice_lang(s_NickServ, u, NICK_SET_PRIVATE_OFF);
+ } else {
+ syntax_error(s_NickServ, u, "SET PRIVATE",
+ NICK_SET_PRIVATE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_msg(User * u, NickCore * nc, char *param)
+{
+ if (!UsePrivmsg) {
+ notice_lang(s_NickServ, u, NICK_SET_OPTION_DISABLED, "MSG");
+ return MOD_CONT;
+ }
+
+ if (stricmp(param, "ON") == 0) {
+ nc->flags |= NI_MSG;
+ notice_lang(s_NickServ, u, NICK_SET_MSG_ON);
+ } else if (stricmp(param, "OFF") == 0) {
+ nc->flags &= ~NI_MSG;
+ notice_lang(s_NickServ, u, NICK_SET_MSG_OFF);
+ } else {
+ syntax_error(s_NickServ, u, "SET MSG", NICK_SET_MSG_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_hide(User * u, NickCore * nc, char *param)
+{
+ int flag, onmsg, offmsg;
+
+ if (stricmp(param, "EMAIL") == 0) {
+ flag = NI_HIDE_EMAIL;
+ onmsg = NICK_SET_HIDE_EMAIL_ON;
+ offmsg = NICK_SET_HIDE_EMAIL_OFF;
+ } else if (stricmp(param, "USERMASK") == 0) {
+ flag = NI_HIDE_MASK;
+ onmsg = NICK_SET_HIDE_MASK_ON;
+ offmsg = NICK_SET_HIDE_MASK_OFF;
+ } else if (stricmp(param, "STATUS") == 0) {
+ flag = NI_HIDE_STATUS;
+ onmsg = NICK_SET_HIDE_STATUS_ON;
+ offmsg = NICK_SET_HIDE_STATUS_OFF;
+ } else if (stricmp(param, "QUIT") == 0) {
+ flag = NI_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 MOD_CONT;
+ }
+
+ param = strtok(NULL, " ");
+ if (!param) {
+ syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
+ } else if (stricmp(param, "ON") == 0) {
+ nc->flags |= flag;
+ notice_lang(s_NickServ, u, onmsg, s_NickServ);
+ } else if (stricmp(param, "OFF") == 0) {
+ nc->flags &= ~flag;
+ notice_lang(s_NickServ, u, offmsg, s_NickServ);
+ } else {
+ syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_set_noexpire(User * u, NickAlias * na, char *param)
+{
+ if (!is_services_admin(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ if (!param) {
+ syntax_error(s_NickServ, u, "SET NOEXPIRE",
+ NICK_SET_NOEXPIRE_SYNTAX);
+ return MOD_CONT;
+ }
+ if (stricmp(param, "ON") == 0) {
+ na->status |= NS_NO_EXPIRE;
+ notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_ON, na->nick);
+ } else if (stricmp(param, "OFF") == 0) {
+ na->status &= ~NS_NO_EXPIRE;
+ notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_OFF, na->nick);
+ } else {
+ syntax_error(s_NickServ, u, "SET NOEXPIRE",
+ NICK_SET_NOEXPIRE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_link(User * u)
+{
+ notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "GROUP");
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_unlink(User * u)
+{
+ notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "DROP");
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_listlinks(User * u)
+{
+ notice_lang(s_NickServ, u, OBSOLETE_COMMAND, "GLIST");
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_access(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *mask = strtok(NULL, " ");
+ NickAlias *na;
+ int i;
+ char **access;
+
+ if (cmd && stricmp(cmd, "LIST") == 0 && mask && is_services_admin(u)
+ && (na = findnick(mask))) {
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ return MOD_CONT;
+ }
+
+ notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X, mask);
+ mask = strtok(NULL, " ");
+ for (access = na->nc->access, i = 0; i < na->nc->accesscount;
+ access++, i++) {
+ if (mask && !match_wild(mask, *access))
+ continue;
+ notice_user(s_NickServ, u, " %s", *access);
+ }
+
+ } else if (!cmd || ((stricmp(cmd, "LIST") == 0) ? !!mask : !mask)) {
+ 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 (!(na = u->na)) {
+ notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
+
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+
+ } else if (!nick_identified(u)) {
+ notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
+
+ } else if (stricmp(cmd, "ADD") == 0) {
+ if (na->nc->accesscount >= NSAccessMax) {
+ notice_lang(s_NickServ, u, NICK_ACCESS_REACHED_LIMIT,
+ NSAccessMax);
+ return MOD_CONT;
+ }
+
+ for (access = na->nc->access, i = 0; i < na->nc->accesscount;
+ access++, i++) {
+ if (strcmp(*access, mask) == 0) {
+ notice_lang(s_NickServ, u, NICK_ACCESS_ALREADY_PRESENT,
+ *access);
+ return MOD_CONT;
+ }
+ }
+
+ na->nc->accesscount++;
+ na->nc->access =
+ srealloc(na->nc->access, sizeof(char *) * na->nc->accesscount);
+ na->nc->access[na->nc->accesscount - 1] = sstrdup(mask);
+ notice_lang(s_NickServ, u, NICK_ACCESS_ADDED, mask);
+
+ } else if (stricmp(cmd, "DEL") == 0) {
+
+ for (access = na->nc->access, i = 0; i < na->nc->accesscount;
+ access++, i++) {
+ if (stricmp(*access, mask) == 0)
+ break;
+ }
+ if (i == na->nc->accesscount) {
+ notice_lang(s_NickServ, u, NICK_ACCESS_NOT_FOUND, mask);
+ return MOD_CONT;
+ }
+
+ notice_lang(s_NickServ, u, NICK_ACCESS_DELETED, *access);
+ free(*access);
+ na->nc->accesscount--;
+ if (i < na->nc->accesscount) /* if it wasn't the last entry... */
+ memmove(access, access + 1,
+ (na->nc->accesscount - i) * sizeof(char *));
+ if (na->nc->accesscount) /* if there are any entries left... */
+ na->nc->access =
+ srealloc(na->nc->access,
+ na->nc->accesscount * sizeof(char *));
+ else {
+ free(na->nc->access);
+ na->nc->access = NULL;
+ }
+ } else if (stricmp(cmd, "LIST") == 0) {
+ notice_lang(s_NickServ, u, NICK_ACCESS_LIST);
+ for (access = na->nc->access, i = 0; i < na->nc->accesscount;
+ access++, i++) {
+ if (mask && !match_wild(mask, *access))
+ continue;
+ notice_user(s_NickServ, u, " %s", *access);
+ }
+ } else {
+ syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX);
+
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Show hidden info to nick owners and sadmins when the "ALL" parameter is
+ * supplied. If a nick is online, the "Last seen address" changes to "Is
+ * online from".
+ * Syntax: INFO <nick> {ALL}
+ * -TheShadow (13 Mar 1999)
+ */
+
+static int do_info(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *param = strtok(NULL, " ");
+
+ NickAlias *na;
+ NickRequest *nr = NULL;
+ int is_servadmin = is_services_admin(u);
+
+#ifdef HAS_VHOST
+ char *vHost;
+#endif
+
+ if (!nick) {
+ syntax_error(s_NickServ, u, "INFO", NICK_INFO_SYNTAX);
+ } else if (!(na = findnick(nick))) {
+ if ((nr = findrequestnick(nick))) {
+ notice_lang(s_NickServ, u, NICK_IS_PREREG);
+ if (param && stricmp(param, "ALL") == 0 && is_servadmin) {
+ notice_lang(s_NickServ, u, NICK_INFO_EMAIL, nr->email);
+ } else {
+ if (is_servadmin) {
+ notice_lang(s_NickServ, u, NICK_INFO_FOR_MORE,
+ s_NickServ, nr->nick);
+ }
+ }
+ } else if (nickIsServices(nick)) {
+ notice_lang(s_NickServ, u, NICK_X_IS_SERVICES, nick);
+ } else {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ }
+ } else if (na->status & NS_VERBOTEN) {
+ if (is_oper(u) && na->last_usermask)
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN_OPER, nick,
+ na->last_usermask,
+ (na->last_realname ? na->
+ last_realname : getstring(u->na, NO_REASON)));
+ else
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
+ } else {
+ struct tm *tm;
+ char buf[BUFSIZE], *end;
+ const char *commastr = getstring(u->na, COMMA_SPACE);
+ int need_comma = 0;
+ int nick_online = 0;
+ int show_hidden = 0;
+
+ /* Is the real owner of the nick we're looking up online? -TheShadow */
+ if (na->status & (NS_RECOGNIZED | NS_IDENTIFIED))
+ nick_online = 1;
+
+ /* Only show hidden fields to owner and sadmins and only when the ALL
+ * parameter is used. -TheShadow */
+ if (param && stricmp(param, "ALL") == 0 && u->na
+ && ((nick_identified(u) && (na->nc == u->na->nc))
+ || is_servadmin))
+ show_hidden = 1;
+
+ notice_lang(s_NickServ, u, NICK_INFO_REALNAME, na->nick,
+ na->last_realname);
+
+ if ((nick_identified(u) && (na->nc == u->na->nc)) || is_servadmin) {
+
+ if (nick_is_services_root(na->nc))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_ROOT,
+ na->nick);
+ else if (nick_is_services_admin(na->nc))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_ADMIN,
+ na->nick);
+ else if (nick_is_services_oper(na->nc))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_OPER,
+ na->nick);
+
+ } else {
+
+ if (nick_is_services_root(na->nc)
+ && !(na->nc->flags & NI_HIDE_STATUS))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_ROOT,
+ na->nick);
+ else if (nick_is_services_admin(na->nc)
+ && !(na->nc->flags & NI_HIDE_STATUS))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_ADMIN,
+ na->nick);
+ else if (nick_is_services_oper(na->nc)
+ && !(na->nc->flags & NI_HIDE_STATUS))
+ notice_lang(s_NickServ, u, NICK_INFO_SERVICES_OPER,
+ na->nick);
+
+ }
+
+
+ if (nick_online) {
+ if (show_hidden || !(na->nc->flags & NI_HIDE_MASK))
+ notice_lang(s_NickServ, u, NICK_INFO_ADDRESS_ONLINE,
+ na->last_usermask);
+ else
+ notice_lang(s_NickServ, u, NICK_INFO_ADDRESS_ONLINE_NOHOST,
+ na->nick);
+ } else {
+ if (show_hidden || !(na->nc->flags & NI_HIDE_MASK))
+ notice_lang(s_NickServ, u, NICK_INFO_ADDRESS,
+ na->last_usermask);
+ }
+
+ tm = localtime(&na->time_registered);
+ strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
+ notice_lang(s_NickServ, u, NICK_INFO_TIME_REGGED, buf);
+
+ if (!nick_online) {
+ tm = localtime(&na->last_seen);
+ strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT,
+ tm);
+ notice_lang(s_NickServ, u, NICK_INFO_LAST_SEEN, buf);
+ }
+
+ if (na->last_quit
+ && (show_hidden || !(na->nc->flags & NI_HIDE_QUIT)))
+ notice_lang(s_NickServ, u, NICK_INFO_LAST_QUIT, na->last_quit);
+
+ if (na->nc->url)
+ notice_lang(s_NickServ, u, NICK_INFO_URL, na->nc->url);
+ if (na->nc->email
+ && (show_hidden || !(na->nc->flags & NI_HIDE_EMAIL)))
+ notice_lang(s_NickServ, u, NICK_INFO_EMAIL, na->nc->email);
+ if (na->nc->icq)
+ notice_lang(s_NickServ, u, NICK_INFO_ICQ, na->nc->icq);
+
+ if (show_hidden) {
+#ifdef HAS_VHOST
+ if (s_HostServ) {
+ if (getvHost(na->nick) != NULL) {
+ vHost = smalloc(strlen(getvHost(na->nick)) + 2);
+ bzero(vHost, sizeof(vHost));
+ snprintf(vHost, strlen(getvHost(na->nick)) + 2, "%s",
+ getvHost(na->nick));
+ notice_lang(s_NickServ, u, NICK_INFO_VHOST, vHost);
+ free(vHost);
+ }
+ }
+#endif
+ if (na->nc->greet)
+ notice_lang(s_NickServ, u, NICK_INFO_GREET, na->nc->greet);
+
+ *buf = 0;
+ end = buf;
+
+ if (na->nc->flags & NI_KILLPROTECT) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s",
+ getstring(u->na, NICK_INFO_OPT_KILL));
+ need_comma = 1;
+ }
+ if (na->nc->flags & NI_SECURE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, NICK_INFO_OPT_SECURE));
+ need_comma = 1;
+ }
+ if (na->nc->flags & NI_PRIVATE) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, NICK_INFO_OPT_PRIVATE));
+ need_comma = 1;
+ }
+ if (na->nc->flags & NI_MSG) {
+ end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
+ need_comma ? commastr : "",
+ getstring(u->na, NICK_INFO_OPT_MSG));
+ need_comma = 1;
+ }
+
+ notice_lang(s_NickServ, u, NICK_INFO_OPTIONS,
+ *buf ? buf : getstring(u->na, NICK_INFO_OPT_NONE));
+
+ if (na->status & NS_NO_EXPIRE)
+ notice_lang(s_NickServ, u, NICK_INFO_NO_EXPIRE);
+ }
+
+ if (!show_hidden
+ && ((u->na && (na->nc == u->na->nc) && nick_identified(u))
+ || is_servadmin))
+ notice_lang(s_NickServ, u, NICK_INFO_FOR_MORE, s_NickServ,
+ na->nick);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* SADMINS can search for nicks based on their NS_VERBOTEN and NS_NO_EXPIRE
+ * status. The keywords FORBIDDEN and NOEXPIRE represent these two states
+ * respectively. These keywords should be included after the search pattern.
+ * Multiple keywords are accepted and should be separated by spaces. Only one
+ * of the keywords needs to match a nick's state for the nick to be displayed.
+ * Forbidden nicks can be identified by "[Forbidden]" appearing in the last
+ * seen address field. Nicks with NOEXPIRE set are preceeded by a "!". Only
+ * SADMINS will be shown forbidden nicks and the "!" indicator.
+ * Syntax for sadmins: LIST pattern [FORBIDDEN] [NOEXPIRE]
+ * -TheShadow
+ */
+
+static int do_list(User * u)
+{
+ char *pattern = strtok(NULL, " ");
+ char *keyword;
+ NickAlias *na;
+ NickCore *mync;
+ int nnicks, i;
+ char buf[BUFSIZE];
+ int is_servadmin = is_services_admin(u);
+ int16 matchflags = 0;
+ NickRequest *nr = NULL;
+ int nronly = 0;
+ char noexpire_char = ' ';
+ int count = 0, from = 0, to = 0;
+ char *tmp = NULL;
+ char *s = NULL;
+
+ if (NSListOpersOnly && !(is_oper(u))) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!pattern) {
+ syntax_error(s_NickServ, u, "LIST",
+ is_servadmin ? NICK_LIST_SERVADMIN_SYNTAX :
+ NICK_LIST_SYNTAX);
+ } else {
+
+ if (pattern) {
+ if (pattern[0] == '#') {
+ tmp = myStrGetOnlyToken((pattern + 1), '-', 0); /* Read FROM out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ from = atoi(tmp);
+ tmp = myStrGetTokenRemainder(pattern, '-', 1); /* Read TO out */
+ if (!tmp) {
+ return MOD_CONT;
+ }
+ for (s = tmp; *s; s++) {
+ if (!isdigit(*s)) {
+ return MOD_CONT;
+ }
+ }
+ to = atoi(tmp);
+ pattern = sstrdup("*");
+ }
+ }
+
+ nnicks = 0;
+
+ while (is_servadmin && (keyword = strtok(NULL, " "))) {
+ if (stricmp(keyword, "FORBIDDEN") == 0)
+ matchflags |= NS_VERBOTEN;
+ if (stricmp(keyword, "NOEXPIRE") == 0)
+ matchflags |= NS_NO_EXPIRE;
+ if (stricmp(keyword, "UNCONFIRMED") == 0)
+ nronly = 1;
+ }
+
+ mync = (nick_identified(u) ? u->na->nc : NULL);
+
+ notice_lang(s_NickServ, u, NICK_LIST_HEADER, pattern);
+ if (nronly != 1) {
+ for (i = 0; i < 1024; i++) {
+ for (na = nalists[i]; na; na = na->next) {
+ /* Don't show private and forbidden nicks to non-services admins. */
+ if ((na->status & NS_VERBOTEN) && !is_servadmin)
+ continue;
+ if ((na->nc->flags & NI_PRIVATE) && !is_servadmin
+ && na->nc != mync)
+ continue;
+ if ((matchflags != 0) && !(na->status & matchflags))
+ continue;
+
+ /* We no longer compare the pattern against the output buffer.
+ * Instead we build a nice nick!user@host buffer to compare.
+ * The output is then generated separately. -TheShadow */
+ snprintf(buf, sizeof(buf), "%s!%s", na->nick,
+ (na->last_usermask
+ && !(na->status & NS_VERBOTEN)) ? na->
+ last_usermask : "*@*");
+ if (stricmp(pattern, na->nick) == 0
+ || match_wild_nocase(pattern, buf)) {
+
+ if ((((count + 1 >= from) && (count + 1 <= to))
+ || ((from == 0) && (to == 0)))
+ && (++nnicks <= NSListMax)) {
+ if (is_servadmin
+ && (na->status & NS_NO_EXPIRE))
+ noexpire_char = '!';
+ else {
+ noexpire_char = ' ';
+ }
+ if ((na->nc->flags & NI_HIDE_MASK)
+ && !is_servadmin && na->nc != mync) {
+ snprintf(buf, sizeof(buf),
+ "%-20s [Hostname Hidden]",
+ na->nick);
+ } else if (na->status & NS_VERBOTEN) {
+ snprintf(buf, sizeof(buf),
+ "%-20s [Forbidden]", na->nick);
+ } else {
+ snprintf(buf, sizeof(buf), "%-20s %s",
+ na->nick, na->last_usermask);
+ }
+ notice_user(s_NickServ, u, " %c%s",
+ noexpire_char, buf);
+ }
+ count++;
+ }
+ }
+ }
+ }
+
+ if (nronly == 1 || (is_servadmin && matchflags == 0)) {
+ noexpire_char = ' ';
+ for (i = 0; i < 1024; i++) {
+ for (nr = nrlists[i]; nr; nr = nr->next) {
+ snprintf(buf, sizeof(buf), "%s!*@*", nr->nick);
+ if (stricmp(pattern, nr->nick) == 0
+ || match_wild_nocase(pattern, buf)) {
+ if (++nnicks <= NSListMax) {
+ snprintf(buf, sizeof(buf),
+ "%-20s [UNCONFIRMED]", nr->nick);
+ notice_user(s_NickServ, u, " %c%s",
+ noexpire_char, buf);
+ }
+ }
+ }
+ }
+ }
+ notice_lang(s_NickServ, u, NICK_LIST_RESULTS,
+ nnicks > NSListMax ? NSListMax : nnicks, nnicks);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_glist(User * u)
+{
+ char *nick = strtok(NULL, " ");
+
+ NickAlias *na, *na2;
+ int i;
+
+ if ((nick ? !is_services_admin(u) : !nick_identified(u))) {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ } else if ((!nick ? !(na = u->na) : !(na = findnick(nick)))) {
+ notice_lang(s_NickServ, u,
+ (!nick ? NICK_NOT_REGISTERED : NICK_X_NOT_REGISTERED),
+ nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else {
+ notice_lang(s_NickServ, u,
+ nick ? NICK_GLIST_HEADER_X : NICK_GLIST_HEADER,
+ na->nc->display);
+ for (i = 0; i < na->nc->aliases.count; i++) {
+ na2 = na->nc->aliases.list[i];
+ if (na2->nc == na->nc)
+ notice_user(s_NickServ, u, " %c%s",
+ ((na2->status & NS_NO_EXPIRE) ? '!' : ' '),
+ na2->nick);
+ }
+ notice_lang(s_NickServ, u, NICK_GLIST_FOOTER,
+ na->nc->aliases.count);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/**
+ * List the channels that the given nickname has access on
+ *
+ * /ns ALIST [level]
+ * /ns ALIST [nickname] [level]
+ *
+ * -jester
+ */
+static int do_alist(User * u)
+{
+ char *nick = NULL;
+ char *lev = NULL;
+
+ NickAlias *na;
+
+ int min_level = 0;
+ int is_servadmin = is_services_admin(u);
+
+ if (!is_servadmin) {
+ /* Non service admins can only see their own levels */
+ na = u->na;
+ } else {
+ /* Services admins can request ALIST on nicks.
+ * The first argument for service admins must
+ * always be a nickname.
+ */
+ nick = strtok(NULL, " ");
+
+ /* If an argument was passed, use it as the nick to see levels
+ * for, else check levels for the user calling the command */
+ if (nick) {
+ na = findnick(nick);
+ } else {
+ na = u->na;
+ }
+ }
+
+ /* If available, get level from arguments */
+ lev = strtok(NULL, " ");
+
+ /* if a level was given, make sure it's an int for later */
+ if (lev) {
+ if (stricmp(lev, "FOUNDER") == 0) {
+ min_level = ACCESS_FOUNDER;
+ } else if (stricmp(lev, "SOP") == 0) {
+ min_level = ACCESS_SOP;
+ } else if (stricmp(lev, "AOP") == 0) {
+ min_level = ACCESS_AOP;
+#ifdef HAS_HALFOP
+ } else if (stricmp(lev, "HOP") == 0) {
+ min_level = ACCESS_HOP;
+#endif
+ } else if (stricmp(lev, "VOP") == 0) {
+ min_level = ACCESS_VOP;
+ } else {
+ min_level = atoi(lev);
+ }
+ }
+
+ if (!nick_identified(u)) {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ } else if (is_servadmin && nick && !na) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else {
+ int i, level;
+ int chan_count = 0;
+ int match_count = 0;
+ ChannelInfo *ci;
+
+ notice_lang(s_NickServ, u, (is_servadmin ? NICK_ALIST_HEADER_X :
+ NICK_ALIST_HEADER), na->nick);
+
+ for (i = 0; i < 256; i++) {
+ for ((ci = chanlists[i]); ci; (ci = ci->next)) {
+
+ if ((level = get_access_level(ci, na))) {
+ chan_count++;
+
+ if (min_level > level) {
+ continue;
+ }
+
+ match_count++;
+
+ if ((ci->flags & CI_XOP) || (level == ACCESS_FOUNDER)) {
+ char *xop;
+
+ xop = get_xop_level(level);
+
+ notice_lang(s_NickServ, u, NICK_ALIST_XOP_FORMAT,
+ match_count,
+ ((ci->
+ flags & CI_NO_EXPIRE) ? '!' : ' '),
+ ci->name, xop,
+ (ci->desc ? ci->desc : ""));
+ } else {
+ notice_lang(s_NickServ, u,
+ NICK_ALIST_ACCESS_FORMAT, match_count,
+ ((ci->
+ flags & CI_NO_EXPIRE) ? '!' : ' '),
+ ci->name, level,
+ (ci->desc ? ci->desc : ""));
+
+ }
+ }
+ }
+ }
+
+ notice_lang(s_NickServ, u, NICK_ALIST_FOOTER, match_count,
+ chan_count);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_recover(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ NickAlias *na;
+ User *u2;
+
+ if (!nick) {
+ syntax_error(s_NickServ, u, "RECOVER", NICK_RECOVER_SYNTAX);
+ } else if (!(u2 = finduser(nick))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (!(na = u2->na)) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (stricmp(nick, u->nick) == 0) {
+ notice_lang(s_NickServ, u, NICK_NO_RECOVER_SELF);
+ } else if (pass) {
+ int res = check_password(pass, na->nc->pass);
+
+ if (res == 1) {
+ notice_lang(s_NickServ, u2, FORCENICKCHANGE_NOW);
+ collide(na, 0);
+ notice_lang(s_NickServ, u, NICK_RECOVERED, s_NickServ, nick);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ if (res == 0) {
+ alog("%s: RECOVER: invalid password for %s by %s!%s@%s",
+ s_NickServ, nick, u->nick, u->username, GetHost(u));
+ bad_password(u);
+ }
+ }
+ } else {
+ if (group_identified(u, na->nc)
+ || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
+ notice_lang(s_NickServ, u2, FORCENICKCHANGE_NOW);
+ collide(na, 0);
+ notice_lang(s_NickServ, u, NICK_RECOVERED, s_NickServ, nick);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_release(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ NickAlias *na;
+
+ if (!nick) {
+ syntax_error(s_NickServ, u, "RELEASE", NICK_RELEASE_SYNTAX);
+ } else if (!(na = findnick(nick))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (!(na->status & NS_KILL_HELD)) {
+ notice_lang(s_NickServ, u, NICK_RELEASE_NOT_HELD, nick);
+ } else if (pass) {
+ int res = check_password(pass, na->nc->pass);
+ if (res == 1) {
+ release(na, 0);
+ notice_lang(s_NickServ, u, NICK_RELEASED);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ if (res == 0) {
+ alog("%s: RELEASE: invalid password for %s by %s!%s@%s",
+ s_NickServ, nick, u->nick, u->username, GetHost(u));
+ bad_password(u);
+ }
+ }
+ } else {
+ if (group_identified(u, na->nc)
+ || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
+ release(na, 0);
+ notice_lang(s_NickServ, u, NICK_RELEASED);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_ghost(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *pass = strtok(NULL, " ");
+ NickAlias *na;
+ User *u2;
+
+ if (!nick) {
+ syntax_error(s_NickServ, u, "GHOST", NICK_GHOST_SYNTAX);
+ } else if (!(u2 = finduser(nick))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (!(na = u2->na)) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (stricmp(nick, u->nick) == 0) {
+ notice_lang(s_NickServ, u, NICK_NO_GHOST_SELF);
+ } else if (pass) {
+ int res = check_password(pass, na->nc->pass);
+ if (res == 1) {
+ char buf[NICKMAX + 32];
+ snprintf(buf, sizeof(buf), "GHOST command used by %s",
+ u->nick);
+ kill_user(s_NickServ, nick, buf);
+ notice_lang(s_NickServ, u, NICK_GHOST_KILLED, nick);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ if (res == 0) {
+ alog("%s: GHOST: invalid password for %s by %s!%s@%s",
+ s_NickServ, nick, u->nick, u->username, GetHost(u));
+ bad_password(u);
+ }
+ }
+ } else {
+ if (group_identified(u, na->nc)
+ || (!(na->nc->flags & NI_SECURE) && is_on_access(u, na->nc))) {
+ char buf[NICKMAX + 32];
+ snprintf(buf, sizeof(buf), "GHOST command used by %s",
+ u->nick);
+ kill_user(s_NickServ, nick, buf);
+ notice_lang(s_NickServ, u, NICK_GHOST_KILLED, nick);
+ } else {
+ notice_lang(s_NickServ, u, ACCESS_DENIED);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_status(User * u)
+{
+ char *nick;
+ User *u2;
+ int i = 0;
+
+ while ((nick = strtok(NULL, " ")) && (i++ < 16)) {
+ if (!(u2 = finduser(nick)))
+ notice_user(s_NickServ, u, "STATUS %s 0", nick);
+ else if (nick_identified(u2))
+ notice_user(s_NickServ, u, "STATUS %s 3", nick);
+ else if (nick_recognized(u2))
+ notice_user(s_NickServ, u, "STATUS %s 2", nick);
+ else
+ notice_user(s_NickServ, u, "STATUS %s 1", nick);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+/* A simple call to check for all emails that a user may have registered */
+/* with. It returns the nicks that match the email you provide. Wild */
+/* Cards are not excepted. Must use user@email-host. */
+/*************************************************************************/
+static int do_getemail(User * u)
+{
+ char *email = strtok(NULL, " ");
+ int i, j = 0;
+ NickCore *nc;
+
+ if (!email) {
+ syntax_error(s_NickServ, u, "GETMAIL", NICK_GETEMAIL_SYNTAX);
+ return MOD_CONT;
+ }
+ alog("%s: %s!%s@%s used GETEMAIL on %s", s_NickServ, u->nick,
+ u->username, GetHost(u), email);
+ for (i = 0; i < 1024; i++) {
+ for (nc = nclists[i]; nc; nc = nc->next) {
+ if (nc->email) {
+ if (stricmp(nc->email, email) == 0) {
+ j++;
+ notice_lang(s_NickServ, u, NICK_GETEMAIL_EMAILS_ARE,
+ nc->display, email);
+ }
+ }
+ }
+ }
+ if (j <= 0) {
+ notice_lang(s_NickServ, u, NICK_GETEMAIL_NOT_USED, email);
+ return MOD_CONT;
+ }
+ return MOD_CONT;
+}
+
+/**************************************************************************/
+
+static int do_getpass(User * u)
+{
+#ifndef USE_ENCRYPTION
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+ NickRequest *nr = NULL;
+#endif
+
+ /* Assumes that permission checking has already been done. */
+#ifdef USE_ENCRYPTION
+ notice_lang(s_NickServ, u, NICK_GETPASS_UNAVAILABLE);
+#else
+ if (!nick) {
+ syntax_error(s_NickServ, u, "GETPASS", NICK_GETPASS_SYNTAX);
+ } else if (!(na = findnick(nick))) {
+ if ((nr = findrequestnick(nick))) {
+ alog("%s: %s!%s@%s used GETPASS on %s", s_NickServ, u->nick,
+ u->username, GetHost(u), nick);
+ if (WallGetpass)
+ wallops(s_NickServ, "\2%s\2 used GETPASS on \2%s\2",
+ u->nick, nick);
+ notice_lang(s_NickServ, u, NICK_GETPASS_PASSCODE_IS, nick,
+ nr->passcode);
+ } else {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ }
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else if (NSSecureAdmins && nick_is_services_admin(na->nc)
+ && !is_services_root(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ } else if (NSRestrictGetPass && !is_services_root(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ } else {
+ alog("%s: %s!%s@%s used GETPASS on %s", s_NickServ, u->nick,
+ u->username, GetHost(u), nick);
+ if (WallGetpass)
+ wallops(s_NickServ, "\2%s\2 used GETPASS on \2%s\2", u->nick,
+ nick);
+ notice_lang(s_NickServ, u, NICK_GETPASS_PASSWORD_IS, nick,
+ na->nc->pass);
+ }
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_sendpass(User * u)
+{
+#ifndef USE_ENCRYPTION
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+#endif
+
+#ifdef USE_ENCRYPTION
+ notice_lang(s_NickServ, u, NICK_SENDPASS_UNAVAILABLE);
+#else
+ if (!nick) {
+ syntax_error(s_NickServ, u, "SENDPASS", NICK_SENDPASS_SYNTAX);
+ } else if (RestrictMail && !is_oper(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ } else if (!(na = findnick(nick))) {
+ notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
+ } else if (na->status & NS_VERBOTEN) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, na->nick);
+ } else {
+ char buf[BUFSIZE];
+ MailInfo *mail;
+
+ snprintf(buf, sizeof(buf), getstring(na, NICK_SENDPASS_SUBJECT),
+ na->nick);
+ mail = MailBegin(u, na->nc, buf, s_NickServ);
+ if (!mail)
+ return MOD_CONT;
+
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_HEAD));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_1), na->nick);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_2),
+ na->nc->pass);
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_3));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_4));
+ fprintf(mail->pipe, "\n\n");
+ fprintf(mail->pipe, getstring(na, NICK_SENDPASS_LINE_5),
+ NetworkName);
+ fprintf(mail->pipe, "\n.\n");
+
+ MailEnd(mail);
+
+ alog("%s: %s!%s@%s used SENDPASS on %s", s_NickServ, u->nick,
+ u->username, GetHost(u), nick);
+ notice_lang(s_NickServ, u, NICK_SENDPASS_OK, nick);
+ }
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_forbid(User * u)
+{
+ NickAlias *na;
+ char *nick = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+
+ /* Assumes that permission checking has already been done. */
+ if (!nick || (ForceForbidReason && !reason)) {
+ syntax_error(s_NickServ, u, "FORBID",
+ (ForceForbidReason ? NICK_FORBID_SYNTAX_REASON :
+ NICK_FORBID_SYNTAX));
+ return MOD_CONT;
+ }
+
+ if (readonly)
+ notice_lang(s_NickServ, u, READ_ONLY_MODE);
+ if ((na = findnick(nick)) != NULL) {
+ if (NSSecureAdmins && nick_is_services_admin(na->nc)
+ && !is_services_root(u)) {
+ notice_lang(s_NickServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+ delnick(na);
+ }
+ na = makenick(nick);
+ if (na) {
+ na->status |= NS_VERBOTEN;
+ na->last_usermask = sstrdup(u->nick);
+ if (reason)
+ na->last_realname = sstrdup(reason);
+
+ na->u = finduser(na->nick);
+ if (na->u)
+ na->u->na = na;
+
+ if (na->u) {
+ notice_lang(s_NickServ, na->u, FORCENICKCHANGE_NOW);
+ collide(na, 0);
+ }
+
+ if (WallForbid)
+ wallops(s_NickServ, "\2%s\2 used FORBID on \2%s\2", u->nick,
+ nick);
+
+ alog("%s: %s set FORBID for nick %s", s_NickServ, u->nick, nick);
+ notice_lang(s_NickServ, u, NICK_FORBID_SUCCEEDED, nick);
+ } else {
+ alog("%s: Valid FORBID for %s by %s failed", s_NickServ, nick,
+ u->nick);
+ notice_lang(s_NickServ, u, NICK_FORBID_FAILED, nick);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+int ns_do_register(User * u)
+{
+ return do_register(u);
+}
+
+/*************************************************************************/
+/*
+ * Nick tracking
+ */
+
+/**
+ * Start Nick tracking and store the nick core display under the user struct.
+ * @param u The user to track nicks for
+ **/
+void nsStartNickTracking(User * u)
+{
+ NickCore *nc;
+
+ /* We only track identified users */
+ if (nick_identified(u)) {
+ nc = u->na->nc;
+
+ /* Release memory if needed */
+ if (u->nickTrack)
+ free(u->nickTrack);
+
+ /* Copy the nick core displayed nick to
+ the user structure for further checks */
+ u->nickTrack = sstrdup(nc->display);
+ }
+}
+
+/**
+ * Stop Nick tracking and remove the nick core display under the user struct.
+ * @param u The user to stop tracking for
+ **/
+void nsStopNickTracking(User * u)
+{
+ /* Simple enough. If its there, release it */
+ if (u->nickTrack) {
+ free(u->nickTrack);
+ u->nickTrack = NULL;
+ }
+}
+
+/**
+ * Boolean function to check if the user requesting a nick has the tracking
+ * signature of that core in its structure.
+ * @param u The user whom to check tracking for
+ **/
+int nsCheckNickTracking(User * u)
+{
+ NickCore *nc;
+ NickAlias *na;
+ char *nick;
+
+ /* No nick alias or nick return false by default */
+ if ((!(na = u->na)) || (!(nick = na->nick)))
+ return 0;
+
+ /* Get the core for the requested nick */
+ nc = na->nc;
+
+ /* If the core and the tracking displayed nick are there,
+ * and they match, return true
+ */
+ if (nc && u->nickTrack && (strcmp(nc->display, u->nickTrack) == 0))
+ return 1;
+ else
+ return 0;
+}
diff --git a/src/operserv.c b/src/operserv.c
new file mode 100644
index 000000000..e070b55b0
--- /dev/null
+++ b/src/operserv.c
@@ -0,0 +1,5064 @@
+/* OperServ functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+
+extern Module *mod_current_module;
+extern int mod_current_op;
+extern User *mod_current_user;
+extern ModuleHash *MODULE_HASH[MAX_CMD_HASH];
+/*************************************************************************/
+
+struct clone {
+ char *host;
+ long time;
+};
+
+/* List of most recent users - statically initialized to zeros */
+static struct clone clonelist[CLONE_DETECT_SIZE];
+
+/* Which hosts have we warned about, and when? This is used to keep us
+ * from sending out notices over and over for clones from the same host. */
+static struct clone warnings[CLONE_DETECT_SIZE];
+
+/* List of Services administrators */
+SList servadmins;
+/* List of Services operators */
+SList servopers;
+/* AKILL, SGLINE, SQLINE and SZLINE lists */
+SList akills, sglines, sqlines, szlines;
+
+/*************************************************************************/
+
+static void get_operserv_stats(long *nrec, long *memuse);
+
+static int compare_adminlist_entries(SList * slist, void *item1,
+ void *item2);
+static int compare_operlist_entries(SList * slist, void *item1,
+ void *item2);
+static void free_adminlist_entry(SList * slist, void *item);
+static void free_operlist_entry(SList * slist, void *item);
+
+static int is_akill_entry_equal(SList * slist, void *item1, void *item2);
+static void free_akill_entry(SList * slist, void *item);
+#ifdef IRC_BAHAMUT
+static int is_sgline_entry_equal(SList * slist, void *item1, void *item2);
+static void free_sgline_entry(SList * slist, void *item);
+#endif
+static int is_sqline_entry_equal(SList * slist, void *item1, void *item2);
+static void free_sqline_entry(SList * slist, void *item);
+#ifdef IRC_BAHAMUT
+static int is_szline_entry_equal(SList * slist, void *item1, void *item2);
+static void free_szline_entry(SList * slist, void *item);
+#endif
+
+static int do_help(User * u);
+static int do_global(User * u);
+static int do_stats(User * u);
+static int do_admin(User * u);
+static int do_oper(User * u);
+static int do_os_mode(User * u);
+static int do_clearmodes(User * u);
+static int do_os_kick(User * u);
+static int do_akill(User * u);
+static int do_sgline(User * u);
+static int do_sqline(User * u);
+static int do_szline(User * u);
+static int do_set(User * u);
+static int do_noop(User * u);
+static int do_jupe(User * u);
+static int do_raw(User * u);
+static int do_update(User * u);
+static int do_reload(User * u);
+static int do_os_quit(User * u);
+static int do_shutdown(User * u);
+static int do_restart(User * u);
+static int do_ignorelist(User * u);
+static int do_clearignore(User * u);
+static int do_killclones(User * u);
+static int do_chanlist(User * u);
+static int do_userlist(User * u);
+static int do_ignoreuser(User * u);
+static int do_staff(User * u);
+static int do_defcon(User * u);
+static int do_chankill(User * u);
+static void defcon_sendlvls(User * u);
+char *defconReverseModes(const char *modes);
+int DefConModesSet = 0;
+time_t DefContimer;
+void runDefCon(void);
+void resetDefCon(int level);
+void oper_global(char *nick, char *fmt, ...);
+
+#ifdef USE_MODULES
+int do_modload(User * u);
+int do_modunload(User * u);
+int do_modlist(User * u);
+int do_modinfo(User * u);
+static int showModuleCmdLoaded(CommandHash * cmdList, char *mod_name,
+ User * u);
+static int showModuleMsgLoaded(MessageHash * msgList, char *mod_name,
+ User * u);
+#endif
+
+
+#ifdef USE_OSSVS
+#ifndef IRC_HYBRID
+static int do_operumodes(User * u);
+#endif
+static int do_svsnick(User * u);
+#endif
+
+#if defined(IRC_UNREAL) && defined(USE_OSSVS)
+static int do_operoline(User * u);
+#endif
+
+#ifdef DEBUG_COMMANDS
+static void send_clone_lists(User * u);
+static int do_matchwild(User * u);
+#endif
+
+/* OperServ restart needs access to this if were gonna avoid sending ourself a signal */
+extern int do_restart_services(void);
+void moduleAddOperServCmds(void);
+/*************************************************************************/
+
+/* Options for the lists */
+SListOpts akopts = { 0, NULL, &is_akill_entry_equal, &free_akill_entry };
+SListOpts saopts = { SLISTF_SORT, &compare_adminlist_entries, NULL,
+ &free_adminlist_entry
+};
+
+#ifdef IRC_BAHAMUT
+SListOpts sgopts = { 0, NULL, &is_sgline_entry_equal, &free_sgline_entry };
+#endif
+SListOpts soopts =
+ { SLISTF_SORT, &compare_operlist_entries, NULL, &free_operlist_entry };
+SListOpts sqopts =
+ { SLISTF_SORT, NULL, &is_sqline_entry_equal, &free_sqline_entry };
+#ifdef IRC_BAHAMUT
+SListOpts szopts = { 0, NULL, &is_szline_entry_equal, &free_szline_entry };
+#endif
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+void moduleAddOperServCmds(void) {
+ Command *c;
+ c = createCommand("HELP", do_help, NULL, -1, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("GLOBAL", do_global, NULL, OPER_HELP_GLOBAL, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("STATS", do_stats, NULL, OPER_HELP_STATS, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("UPTIME", do_stats, NULL, OPER_HELP_STATS, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+
+ /* Anyone can use the LIST option to the ADMIN and OPER commands; those
+ * routines check privileges to ensure that only authorized users
+ * modify the list. */
+ c = createCommand("ADMIN", do_admin, NULL, OPER_HELP_ADMIN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("OPER", do_oper, NULL, OPER_HELP_OPER, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("STAFF", do_staff, NULL, OPER_HELP_STAFF, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ /* Similarly, anyone can use *NEWS LIST, but *NEWS {ADD,DEL} are
+ * reserved for Services admins. */
+ c = createCommand("LOGONNEWS", do_logonnews, NULL, NEWS_HELP_LOGON, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("OPERNEWS", do_opernews, NULL, NEWS_HELP_OPER, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("RANDOMNEWS", do_randomnews, NULL, NEWS_HELP_RANDOM, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+
+ /* Commands for Services opers: */
+ c = createCommand("MODE", do_os_mode, is_services_oper,OPER_HELP_MODE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("CLEARMODES", do_clearmodes, is_services_oper,OPER_HELP_CLEARMODES, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("KICK", do_os_kick, is_services_oper,OPER_HELP_KICK, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("KILLCLONES", do_killclones, is_services_oper,OPER_HELP_KILLCLONES, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
+ c = createCommand("AKILL", do_akill, is_services_oper,OPER_HELP_AKILL, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SGLINE", do_sgline, is_services_oper,OPER_HELP_SGLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SQLINE", do_sqline, is_services_oper,OPER_HELP_SQLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SZLINE", do_szline, is_services_oper,OPER_HELP_SZLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+
+ /* Commands for Services admins: */
+ c = createCommand("SET", do_set, is_services_admin,OPER_HELP_SET, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SET READONLY", NULL, NULL,OPER_HELP_SET_READONLY, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SET LOGCHAN",NULL, NULL,OPER_HELP_SET_LOGCHAN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SET DEBUG", NULL, NULL,OPER_HELP_SET_DEBUG, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SET NOEXPIRE",NULL, NULL,OPER_HELP_SET_NOEXPIRE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SET SUPERADMIN",NULL, NULL,OPER_HELP_SET_SUPERADMIN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#ifdef USE_OSSVS
+ c = createCommand("SVSNICK", do_svsnick, is_services_admin,OPER_HELP_SVSNICK, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#ifndef IRC_HYBRID
+ c = createCommand("UMODE", do_operumodes, is_services_admin,OPER_HELP_UMODE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#endif
+#endif
+ c = createCommand("NOOP", do_noop, is_services_admin,OPER_HELP_NOOP, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("JUPE", do_jupe, is_services_admin,OPER_HELP_JUPE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("RAW", do_raw, is_services_admin,OPER_HELP_RAW, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("IGNORE", do_ignoreuser, is_services_admin,OPER_HELP_IGNORE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#if defined(IRC_UNREAL) && defined(USE_OSSVS)
+ c = createCommand("OLINE", do_operoline, is_services_admin,OPER_HELP_OLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#endif
+ c = createCommand("UPDATE", do_update, is_services_admin,OPER_HELP_UPDATE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("RELOAD", do_reload, is_services_admin,OPER_HELP_RELOAD, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("QUIT", do_os_quit, is_services_admin,OPER_HELP_QUIT, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("SHUTDOWN", do_shutdown, is_services_admin,OPER_HELP_SHUTDOWN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("RESTART", do_restart, is_services_admin,OPER_HELP_RESTART, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#ifndef STREAMLINED
+ c = createCommand("SESSION", do_session, is_services_admin,OPER_HELP_SESSION, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
+ c = createCommand("EXCEPTION", do_exception, is_services_admin,OPER_HELP_EXCEPTION, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
+#endif
+ c = createCommand("CHANLIST", do_chanlist, is_services_admin,OPER_HELP_CHANLIST, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("USERLIST", do_userlist, is_services_admin,OPER_HELP_USERLIST, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("CACHE", do_cache, is_services_admin,OPER_HELP_CACHE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("DEFCON", do_defcon, is_services_admin, OPER_HELP_DEFCON,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("CHANKILL", do_chankill, is_services_admin, OPER_HELP_CHANKILL,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ /* Commands for Services root: */
+#ifdef USE_MODULES
+ c = createCommand("MODLOAD", do_modload, is_services_root, -1,-1,-1,-1,OPER_HELP_MODLOAD); addCoreCommand(OPERSERV,c);
+ c = createCommand("MODUNLOAD", do_modunload, is_services_root, -1,-1,-1,-1,OPER_HELP_MODUNLOAD); addCoreCommand(OPERSERV,c);
+ c = createCommand("MODLIST", do_modlist, is_services_root, -1,-1,-1,-1,OPER_HELP_MODLIST); addCoreCommand(OPERSERV,c);
+ c = createCommand("MODINFO", do_modinfo, is_services_root, -1,-1,-1,-1,OPER_HELP_MODINFO); addCoreCommand(OPERSERV,c);
+#endif
+#ifdef DEBUG_COMMANDS
+ c = createCommand("LISTTIMERS", send_timeout_list, is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("MATCHWILD", do_matchwild, is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+ c = createCommand("LISTCLONES", send_clone_lists, is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
+#endif
+}
+
+/* *INDENT-ON* */
+/*************************************************************************/
+/*************************************************************************/
+
+/* OperServ initialization. */
+
+void os_init(void)
+{
+ Command *cmd;
+ moduleAddOperServCmds();
+ cmd = findCommand(OPERSERV, "GLOBAL");
+ if (cmd)
+ cmd->help_param1 = s_GlobalNoticer;
+ cmd = findCommand(OPERSERV, "ADMIN");
+ if (cmd)
+ cmd->help_param1 = s_NickServ;
+ cmd = findCommand(OPERSERV, "OPER");
+ if (cmd)
+ cmd->help_param1 = s_NickServ;
+
+ /* Initialization of the lists */
+ slist_init(&servadmins);
+ servadmins.opts = &saopts;
+ slist_init(&servopers);
+ servopers.opts = &soopts;
+
+ slist_init(&akills);
+ akills.opts = &akopts;
+ slist_init(&sglines);
+#ifdef IRC_BAHAMUT
+ sglines.opts = &sgopts;
+#endif
+ slist_init(&sqlines);
+ sqlines.opts = &sqopts;
+ slist_init(&szlines);
+#ifdef IRC_BAHAMUT
+ szlines.opts = &szopts;
+#endif
+}
+
+/*************************************************************************/
+
+/* Main OperServ routine. */
+
+void operserv(User * u, char *buf)
+{
+ char *cmd;
+ char *s;
+
+ alog("%s: %s: %s", s_OperServ, u->nick, buf);
+
+ cmd = strtok(buf, " ");
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, "")))
+ s = "\1";
+ notice(s_OperServ, u->nick, "\1PING %s", s);
+ } else {
+ mod_run_cmd(s_OperServ, u, OPERSERV, cmd);
+ }
+}
+
+static void get_operserv_stats(long *nrec, long *memuse)
+{
+ int i;
+ long mem = 0, count = 0, mem2 = 0, count2 = 0;
+ Akill *ak;
+ SXLine *sx;
+
+ if (CheckClones) {
+ mem = sizeof(struct clone) * CLONE_DETECT_SIZE * 2;
+ for (i = 0; i < CLONE_DETECT_SIZE; i++) {
+ if (clonelist[i].host) {
+ count++;
+ mem += strlen(clonelist[i].host) + 1;
+ }
+ if (warnings[i].host) {
+ count++;
+ mem += strlen(warnings[i].host) + 1;
+ }
+ }
+ }
+
+ count += akills.count;
+ mem += akills.capacity;
+ mem += akills.count * sizeof(Akill);
+
+ for (i = 0; i < akills.count; i++) {
+ ak = akills.list[i];
+ mem += strlen(ak->user) + 1;
+ mem += strlen(ak->host) + 1;
+ mem += strlen(ak->by) + 1;
+ mem += strlen(ak->reason) + 1;
+ }
+
+#ifdef IRC_BAHAMUT
+
+ count += sglines.count;
+ mem += sglines.capacity;
+ mem += sglines.count * sizeof(SXLine);
+
+ for (i = 0; i < sglines.count; i++) {
+ sx = sglines.list[i];
+ mem += strlen(sx->mask) + 1;
+ mem += strlen(sx->by) + 1;
+ mem += strlen(sx->reason) + 1;
+ }
+
+#endif
+
+ count += sqlines.count;
+ mem += sqlines.capacity;
+ mem += sqlines.count * sizeof(SXLine);
+
+ for (i = 0; i < sqlines.count; i++) {
+ sx = sqlines.list[i];
+ mem += strlen(sx->mask) + 1;
+ mem += strlen(sx->by) + 1;
+ mem += strlen(sx->reason) + 1;
+ }
+
+#ifdef IRC_BAHAMUT
+
+ count += szlines.count;
+ mem += szlines.capacity;
+ mem += szlines.count * sizeof(SXLine);
+
+ for (i = 0; i < szlines.count; i++) {
+ sx = szlines.list[i];
+ mem += strlen(sx->mask) + 1;
+ mem += strlen(sx->by) + 1;
+ mem += strlen(sx->reason) + 1;
+ }
+
+#endif
+
+ get_news_stats(&count2, &mem2);
+ count += count2;
+ mem += mem2;
+ get_exception_stats(&count2, &mem2);
+ count += count2;
+ mem += mem2;
+
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/**************************** Privilege checks ***************************/
+/*************************************************************************/
+
+/* Load old AKILL data. */
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", AutokillDBName); \
+ break; \
+ } \
+} while (0)
+
+static void load_old_akill(void)
+{
+ dbFILE *f;
+ int i, j;
+ int16 tmp16;
+ int32 tmp32;
+ char buf[NICKMAX], mask2[BUFSIZE], *mask, *s;
+ Akill *ak, *entry;
+
+ if (!
+ (f =
+ open_db("AKILL", AutokillDBName ? AutokillDBName : "akill.db",
+ "r", 9)))
+ return;
+
+ get_file_version(f);
+
+ read_int16(&tmp16, f);
+ slist_setcapacity(&akills, tmp16);
+
+ for (j = 0; j < akills.capacity; j++) {
+ ak = scalloc(sizeof(Akill), 1);
+
+ SAFE(read_string(&mask, f));
+ s = strchr(mask, '@');
+ *s = 0;
+ s++;
+ ak->user = sstrdup(mask);
+ ak->host = sstrdup(s);
+ SAFE(read_string(&ak->reason, f));
+ SAFE(read_buffer(buf, f));
+ if (!*buf)
+ ak->by = sstrdup("<unknown>");
+ else
+ ak->by = sstrdup(buf);
+ SAFE(read_int32(&tmp32, f));
+ ak->seton = tmp32 ? tmp32 : time(NULL);
+ SAFE(read_int32(&tmp32, f));
+ ak->expires = tmp32;
+
+ /* Sanity checks *sigh* */
+
+ /* No nicknames allowed! */
+ if (strchr(ak->user, '!')) {
+ s_rakill(ak->user, ak->host);
+ free(ak);
+ continue;
+ }
+
+ snprintf(mask2, sizeof(mask2), "%s@%s", ak->user, ak->host);
+
+ /* Is the mask already in the AKILL list? */
+ if (slist_indexof(&akills, mask2) != -1) {
+ free(ak);
+ continue;
+ }
+
+ /* Checks whether there is an AKILL that already covers
+ * the one we want to add, and whether there are AKILLs
+ * that would be covered by this one. Expiry time
+ * does *also* matter.
+ */
+
+ if (akills.count > 0) {
+
+ for (i = akills.count - 1; i >= 0; i--) {
+
+ char amask[BUFSIZE];
+
+ entry = akills.list[i];
+
+ if (!entry)
+ continue;
+
+ snprintf(amask, sizeof(amask), "%s@%s", entry->user,
+ entry->host);
+
+ if (match_wild_nocase(amask, mask2)
+ && (entry->expires >= ak->expires
+ || entry->expires == 0)) {
+ s_rakill(ak->user, ak->host);
+ free(ak);
+ ak = NULL;
+ break;
+ }
+
+ if (match_wild_nocase(mask2, amask)
+ && (entry->expires <= ak->expires || ak->expires == 0))
+ slist_delete(&akills, i);
+ }
+
+ }
+
+ if (ak)
+ slist_add(&akills, ak);
+ }
+
+ close_db(f);
+}
+
+#undef SAFE
+
+/* Load OperServ data. */
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", OperDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+void load_os_dbase(void)
+{
+ dbFILE *f;
+ int16 i, n, ver, c;
+ HostCache *hc, **hclast, *hcprev;
+ int16 tmp16;
+ int32 tmp32;
+ char *s;
+ int failed = 0;
+
+ if (!(f = open_db(s_OperServ, OperDBName, "r", OPER_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+
+ if (ver <= 9) {
+ NickAlias *na;
+
+ SAFE(read_int16(&n, f));
+ for (i = 0; i < n && !failed; i++) {
+ SAFE(read_string(&s, f));
+ if (s) {
+ na = findnick(s);
+ if (na) {
+ na->nc->flags |= NI_SERVICES_ADMIN;
+ if (slist_indexof(&servadmins, na) == -1)
+ slist_add(&servadmins, na);
+ }
+ free(s);
+ }
+ }
+ if (!failed)
+ SAFE(read_int16(&n, f));
+ for (i = 0; i < n && !failed; i++) {
+ SAFE(read_string(&s, f));
+ if (s) {
+ na = findnick(s);
+ if (na) {
+ na->nc->flags |= NI_SERVICES_OPER;
+ if (slist_indexof(&servopers, na) == -1)
+ slist_add(&servopers, na);
+ }
+ free(s);
+ }
+ }
+ }
+
+ if (ver >= 7) {
+ int32 tmp32;
+ SAFE(read_int32(&maxusercnt, f));
+ SAFE(read_int32(&tmp32, f));
+ maxusertime = tmp32;
+ }
+
+ if (ver <= 10)
+ load_old_akill();
+ else {
+ Akill *ak;
+
+ read_int16(&tmp16, f);
+ slist_setcapacity(&akills, tmp16);
+
+ for (i = 0; i < akills.capacity; i++) {
+ ak = scalloc(sizeof(Akill), 1);
+
+ SAFE(read_string(&ak->user, f));
+ SAFE(read_string(&ak->host, f));
+ SAFE(read_string(&ak->by, f));
+ SAFE(read_string(&ak->reason, f));
+ SAFE(read_int32(&tmp32, f));
+ ak->seton = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ ak->expires = tmp32;
+
+ slist_add(&akills, ak);
+ }
+ }
+
+ if (ver >= 11) {
+ SXLine *sx;
+
+ read_int16(&tmp16, f);
+ slist_setcapacity(&sglines, tmp16);
+
+ for (i = 0; i < sglines.capacity; i++) {
+ sx = scalloc(sizeof(SXLine), 1);
+
+ SAFE(read_string(&sx->mask, f));
+ SAFE(read_string(&sx->by, f));
+ SAFE(read_string(&sx->reason, f));
+ SAFE(read_int32(&tmp32, f));
+ sx->seton = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ sx->expires = tmp32;
+
+ slist_add(&sglines, sx);
+ }
+
+ if (ver >= 13) {
+ read_int16(&tmp16, f);
+ slist_setcapacity(&sqlines, tmp16);
+
+ for (i = 0; i < sqlines.capacity; i++) {
+ sx = scalloc(sizeof(SXLine), 1);
+
+ SAFE(read_string(&sx->mask, f));
+ SAFE(read_string(&sx->by, f));
+ SAFE(read_string(&sx->reason, f));
+ SAFE(read_int32(&tmp32, f));
+ sx->seton = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ sx->expires = tmp32;
+
+ slist_add(&sqlines, sx);
+ }
+ }
+
+ read_int16(&tmp16, f);
+ slist_setcapacity(&szlines, tmp16);
+
+ for (i = 0; i < szlines.capacity; i++) {
+ sx = scalloc(sizeof(SXLine), 1);
+
+ SAFE(read_string(&sx->mask, f));
+ SAFE(read_string(&sx->by, f));
+ SAFE(read_string(&sx->reason, f));
+ SAFE(read_int32(&tmp32, f));
+ sx->seton = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ sx->expires = tmp32;
+
+ slist_add(&szlines, sx);
+ }
+ }
+
+ if (ver >= 12) {
+ for (i = 0; i < 1024 && !failed; i++) {
+ hclast = &hcache[i];
+ hcprev = NULL;
+
+ while ((c = getc_db(f)) != 0) {
+ if (c != 1)
+ fatal("Invalid format in %s", OperDBName);
+
+ hc = scalloc(1, sizeof(HostCache));
+
+ SAFE(read_string(&hc->host, f));
+ SAFE(read_int16(&tmp16, f));
+ hc->status = tmp16;
+ SAFE(read_int32(&tmp32, f));
+ hc->used = tmp32;
+
+ *hclast = hc;
+ hclast = &hc->next;
+ hc->prev = hcprev;
+ hcprev = hc;
+ } /* while (getc_db(f) != 0) */
+
+ *hclast = NULL;
+ } /* for (i) */
+ }
+
+ close_db(f);
+
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+/* Save OperServ data. */
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", OperDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", OperDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_os_dbase(void)
+{
+ int i;
+ dbFILE *f;
+ static time_t lastwarn = 0;
+ Akill *ak;
+ SXLine *sx;
+ HostCache *hc;
+
+ if (!(f = open_db(s_OperServ, OperDBName, "w", OPER_VERSION)))
+ return;
+ SAFE(write_int32(maxusercnt, f));
+ SAFE(write_int32(maxusertime, f));
+
+ SAFE(write_int16(akills.count, f));
+ for (i = 0; i < akills.count; i++) {
+ ak = akills.list[i];
+
+ SAFE(write_string(ak->user, f));
+ SAFE(write_string(ak->host, f));
+ SAFE(write_string(ak->by, f));
+ SAFE(write_string(ak->reason, f));
+ SAFE(write_int32(ak->seton, f));
+ SAFE(write_int32(ak->expires, f));
+ }
+
+ SAFE(write_int16(sglines.count, f));
+ for (i = 0; i < sglines.count; i++) {
+ sx = sglines.list[i];
+
+ SAFE(write_string(sx->mask, f));
+ SAFE(write_string(sx->by, f));
+ SAFE(write_string(sx->reason, f));
+ SAFE(write_int32(sx->seton, f));
+ SAFE(write_int32(sx->expires, f));
+ }
+
+ SAFE(write_int16(sqlines.count, f));
+ for (i = 0; i < sqlines.count; i++) {
+ sx = sqlines.list[i];
+
+ SAFE(write_string(sx->mask, f));
+ SAFE(write_string(sx->by, f));
+ SAFE(write_string(sx->reason, f));
+ SAFE(write_int32(sx->seton, f));
+ SAFE(write_int32(sx->expires, f));
+ }
+
+ SAFE(write_int16(szlines.count, f));
+ for (i = 0; i < szlines.count; i++) {
+ sx = szlines.list[i];
+
+ SAFE(write_string(sx->mask, f));
+ SAFE(write_string(sx->by, f));
+ SAFE(write_string(sx->reason, f));
+ SAFE(write_int32(sx->seton, f));
+ SAFE(write_int32(sx->expires, f));
+ }
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = hc->next) {
+ /* Don't save in-progress scans */
+ if (hc->status < HC_NORMAL)
+ continue;
+
+ SAFE(write_int8(1, f));
+
+ SAFE(write_string(hc->host, f));
+ SAFE(write_int16(hc->status, f));
+ SAFE(write_int32(hc->used, f));
+
+ } /* for (hc) */
+ SAFE(write_int8(0, f));
+ } /* for (i) */
+
+ close_db(f);
+
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+void save_os_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ if (!rdb_open())
+ return;
+ rdb_save_os_db(maxusercnt, maxusertime, &akills, &sglines, &sqlines,
+ &szlines, hcache[0]);
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+
+/* Removes the nick structure from OperServ lists. */
+
+void os_remove_nick(NickCore * nc)
+{
+ slist_remove(&servadmins, nc);
+ slist_remove(&servopers, nc);
+}
+
+/*************************************************************************/
+
+/* Does the given user have Services root privileges?
+ Now enhanced. */
+
+int is_services_root(User * u)
+{
+ if ((NSStrictPrivileges && !is_oper(u))
+ || (!skeleton && !nick_identified(u)))
+ return 0;
+ if (skeleton || (u->na->nc->flags & NI_SERVICES_ROOT))
+ return 1;
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Does the given user have Services admin privileges? */
+
+int is_services_admin(User * u)
+{
+ if ((NSStrictPrivileges && !is_oper(u))
+ || (!skeleton && !nick_identified(u)))
+ return 0;
+ if (skeleton
+ || (u->na->nc->flags & (NI_SERVICES_ADMIN | NI_SERVICES_ROOT)))
+ return 1;
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Does the given user have Services oper privileges? */
+
+int is_services_oper(User * u)
+{
+ if ((NSStrictPrivileges && !is_oper(u))
+ || (!skeleton && !nick_identified(u)))
+ return 0;
+ if (skeleton
+ || (u->na->nc->
+ flags & (NI_SERVICES_OPER | NI_SERVICES_ADMIN |
+ NI_SERVICES_ROOT)))
+ return 1;
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Is the given nick a Services root nick? */
+
+int nick_is_services_root(NickCore * nc)
+{
+ if (nc->flags & (NI_SERVICES_ROOT))
+ return 1;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Is the given nick a Services admin/root nick? */
+
+int nick_is_services_admin(NickCore * nc)
+{
+ if (nc->flags & (NI_SERVICES_ADMIN | NI_SERVICES_ROOT))
+ return 1;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Is the given nick a Services oper/admin/root nick? */
+
+int nick_is_services_oper(NickCore * nc)
+{
+ if (nc->
+ flags & (NI_SERVICES_OPER | NI_SERVICES_ADMIN | NI_SERVICES_ROOT))
+ return 1;
+
+ return 0;
+}
+
+/*************************************************************************/
+/**************************** Clone detection ****************************/
+/*************************************************************************/
+
+/* We just got a new user; does it look like a clone? If so, send out a
+ * wallops.
+ */
+
+void check_clones(User * user)
+{
+#ifndef STREAMLINED
+ int i, clone_count;
+ long last_time;
+
+ if (!CheckClones)
+ return;
+
+ if (clonelist[0].host)
+ free(clonelist[0].host);
+ i = CLONE_DETECT_SIZE - 1;
+ memmove(clonelist, clonelist + 1, sizeof(struct clone) * i);
+ clonelist[i].host = sstrdup(GetHost(user));
+ last_time = clonelist[i].time = time(NULL);
+ clone_count = 1;
+ while (--i >= 0 && clonelist[i].host) {
+ if (clonelist[i].time < last_time - CloneMaxDelay)
+ break;
+ if (stricmp(clonelist[i].host, GetHost(user)) == 0) {
+ ++clone_count;
+ last_time = clonelist[i].time;
+ if (clone_count >= CloneMinUsers)
+ break;
+ }
+ }
+ if (clone_count >= CloneMinUsers) {
+ /* Okay, we have clones. Check first to see if we already know
+ * about them. */
+ for (i = CLONE_DETECT_SIZE - 1; i >= 0 && warnings[i].host; --i) {
+ if (stricmp(warnings[i].host, GetHost(user)) == 0)
+ break;
+ }
+ if (i < 0
+ || warnings[i].time < user->my_signon - CloneWarningDelay) {
+ /* Send out the warning, and note it. */
+ wallops(s_OperServ,
+ "\2WARNING\2 - possible clones detected from %s",
+ GetHost(user));
+ alog("%s: possible clones detected from %s", s_OperServ,
+ GetHost(user));
+ i = CLONE_DETECT_SIZE - 1;
+ if (warnings[0].host)
+ free(warnings[0].host);
+ memmove(warnings, warnings + 1, sizeof(struct clone) * i);
+ warnings[i].host = sstrdup(GetHost(user));
+ warnings[i].time = clonelist[i].time;
+ if (KillClones)
+ kill_user(s_OperServ, user->nick, "Clone kill");
+ }
+ }
+#endif /* !STREAMLINED */
+}
+
+/*************************************************************************/
+
+#ifdef DEBUG_COMMANDS
+
+/* Send clone arrays to given nick. */
+
+static void send_clone_lists(User * u)
+{
+ int i;
+
+ if (!CheckClones) {
+ notice(s_OperServ, u->nick, "CheckClones not enabled.");
+ return;
+ }
+
+ notice(s_OperServ, u->nick, "clonelist[]");
+ for (i = 0; i < CLONE_DETECT_SIZE; i++) {
+ if (clonelist[i].host)
+ notice(s_OperServ, u->nick, " %10ld %s", clonelist[i].time,
+ clonelist[i].host ? clonelist[i].host : "(null)");
+ }
+ notice(s_OperServ, u->nick, "warnings[]");
+ for (i = 0; i < CLONE_DETECT_SIZE; i++) {
+ if (clonelist[i].host)
+ notice(s_OperServ, u->nick, " %10ld %s", warnings[i].time,
+ warnings[i].host ? warnings[i].host : "(null)");
+ }
+}
+
+#endif /* DEBUG_COMMANDS */
+
+/*************************************************************************/
+/*********************** OperServ command functions **********************/
+/*************************************************************************/
+
+/* HELP command. */
+
+static int do_help(User * u)
+{
+ const char *cmd = strtok(NULL, "");
+
+ if (!cmd) {
+ notice_help(s_OperServ, u, OPER_HELP);
+ if (is_services_oper(u))
+ notice_help(s_OperServ, u, OPER_HELP_OPER_CMD);
+ if (is_services_admin(u))
+ notice_help(s_OperServ, u, OPER_HELP_ADMIN_CMD);
+#ifdef USE_MODULES
+ if (is_services_root(u))
+ notice_help(s_OperServ, u, OPER_HELP_ROOT_CMD);
+#endif
+ moduleDisplayHelp(5, u);
+ notice_help(s_OperServ, u, OPER_HELP_LOGGED);
+ } else {
+ mod_help_cmd(s_OperServ, u, OPERSERV, cmd);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_global(User * u)
+{
+ char *msg = strtok(NULL, "");
+
+ if (!msg) {
+ syntax_error(s_OperServ, u, "GLOBAL", OPER_GLOBAL_SYNTAX);
+ return MOD_CONT;
+ }
+ if (WallOSGlobal)
+ wallops(s_OperServ, "\2%s\2 just used GLOBAL command.", u->nick);
+ oper_global(u->nick, "%s", msg);
+ return MOD_CONT;
+}
+
+Server *server_global(Server * s, char *msg)
+{
+ Server *sl;
+
+ while (s) {
+ notice_server(s_GlobalNoticer, s, "%s", msg);
+ if (s->links) {
+ sl = server_global(s->links, msg);
+ if (sl)
+ s = sl;
+ else
+ s = s->next;
+ } else {
+ s = s->next;
+ }
+ }
+ return s;
+
+}
+
+void oper_global(char *nick, char *fmt, ...)
+{
+ va_list args;
+ char msg[2048]; /* largest valid message is 512, this should cover any global */
+ char dmsg[2048]; /* largest valid message is 512, this should cover any global */
+
+ va_start(args, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, args);
+ va_end(args);
+
+ /* I don't like the way this is coded... */
+ if ((nick) && (!AnonymousGlobal)) {
+ snprintf(dmsg, sizeof(dmsg), "[%s] %s", nick, msg);
+ server_global(servlist, dmsg);
+ } else {
+ server_global(servlist, msg);
+ }
+
+}
+
+/*************************************************************************/
+
+/* STATS command. */
+
+static int do_stats(User * u)
+{
+ time_t uptime = time(NULL) - start_time;
+ char *extra = strtok(NULL, "");
+ int days = uptime / 86400, hours = (uptime / 3600) % 24,
+ mins = (uptime / 60) % 60, secs = uptime % 60;
+ struct tm *tm;
+ char timebuf[64];
+
+ if (extra && stricmp(extra, "ALL") != 0) {
+ if (stricmp(extra, "AKILL") == 0) {
+ int timeout;
+ /* AKILLs */
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_COUNT,
+ akills.count);
+ timeout = AutokillExpiry + 59;
+ if (timeout >= 172800)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_DAYS,
+ timeout / 86400);
+ else if (timeout >= 86400)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_DAY);
+ else if (timeout >= 7200)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_HOURS,
+ timeout / 3600);
+ else if (timeout >= 3600)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_HOUR);
+ else if (timeout >= 120)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_MINS,
+ timeout / 60);
+ else if (timeout >= 60)
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_MIN);
+ else
+ notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_NONE);
+#ifdef IRC_BAHAMUT
+ /* SGLINEs */
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_COUNT,
+ sglines.count);
+ timeout = SGLineExpiry + 59;
+ if (timeout >= 172800)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_DAYS,
+ timeout / 86400);
+ else if (timeout >= 86400)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_DAY);
+ else if (timeout >= 7200)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_HOURS,
+ timeout / 3600);
+ else if (timeout >= 3600)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_HOUR);
+ else if (timeout >= 120)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_MINS,
+ timeout / 60);
+ else if (timeout >= 60)
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_MIN);
+ else
+ notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_NONE);
+#endif
+ /* SQLINEs */
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_COUNT,
+ sqlines.count);
+ timeout = SQLineExpiry + 59;
+ if (timeout >= 172800)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_DAYS,
+ timeout / 86400);
+ else if (timeout >= 86400)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_DAY);
+ else if (timeout >= 7200)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_HOURS,
+ timeout / 3600);
+ else if (timeout >= 3600)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_HOUR);
+ else if (timeout >= 120)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_MINS,
+ timeout / 60);
+ else if (timeout >= 60)
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_MIN);
+ else
+ notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_NONE);
+#ifdef IRC_BAHAMUT
+ /* SZLINEs */
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_COUNT,
+ szlines.count);
+ timeout = SZLineExpiry + 59;
+ if (timeout >= 172800)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_DAYS,
+ timeout / 86400);
+ else if (timeout >= 86400)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_DAY);
+ else if (timeout >= 7200)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_HOURS,
+ timeout / 3600);
+ else if (timeout >= 3600)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_HOUR);
+ else if (timeout >= 120)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_MINS,
+ timeout / 60);
+ else if (timeout >= 60)
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_MIN);
+ else
+ notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_NONE);
+#endif
+ return MOD_CONT;
+ } else if (!stricmp(extra, "RESET")) {
+ if (is_services_admin(u)) {
+ maxusercnt = usercnt;
+ notice_lang(s_OperServ, u, OPER_STATS_RESET);
+ } else {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ }
+ return MOD_CONT;
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UNKNOWN_OPTION, extra);
+ }
+ }
+
+ notice_lang(s_OperServ, u, OPER_STATS_CURRENT_USERS, usercnt, opcnt);
+ tm = localtime(&maxusertime);
+ strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_DATE_TIME_FORMAT,
+ tm);
+ notice_lang(s_OperServ, u, OPER_STATS_MAX_USERS, maxusercnt, timebuf);
+ if (days > 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_DHMS,
+ days, hours, mins, secs);
+ } else if (days == 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1DHMS,
+ days, hours, mins, secs);
+ } else {
+ if (hours > 1) {
+ if (mins != 1) {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_HMS,
+ hours, mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_HM1S,
+ hours, mins, secs);
+ }
+ } else {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_H1MS,
+ hours, mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_H1M1S,
+ hours, mins, secs);
+ }
+ }
+ } else if (hours == 1) {
+ if (mins != 1) {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1HMS,
+ hours, mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1HM1S,
+ hours, mins, secs);
+ }
+ } else {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1H1MS,
+ hours, mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1H1M1S,
+ hours, mins, secs);
+ }
+ }
+ } else {
+ if (mins != 1) {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_MS,
+ mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_M1S,
+ mins, secs);
+ }
+ } else {
+ if (secs != 1) {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1MS,
+ mins, secs);
+ } else {
+ notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1M1S,
+ mins, secs);
+ }
+ }
+ }
+ }
+
+ if (extra && stricmp(extra, "ALL") == 0 && is_services_admin(u)) {
+ long count, mem;
+
+ notice_lang(s_OperServ, u, OPER_STATS_BYTES_READ,
+ total_read / 1024);
+ notice_lang(s_OperServ, u, OPER_STATS_BYTES_WRITTEN,
+ total_written / 1024);
+
+ get_user_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_USER_MEM, count,
+ (mem + 512) / 1024);
+ get_channel_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_CHANNEL_MEM, count,
+ (mem + 512) / 1024);
+ get_core_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_GROUPS_MEM, count,
+ (mem + 512) / 1024);
+ get_aliases_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_ALIASES_MEM, count,
+ (mem + 512) / 1024);
+ get_chanserv_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_CHANSERV_MEM, count,
+ (mem + 512) / 1024);
+ get_botserv_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_BOTSERV_MEM, count,
+ (mem + 512) / 1024);
+ get_operserv_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_OPERSERV_MEM, count,
+ (mem + 512) / 1024);
+ get_session_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_SESSIONS_MEM, count,
+ (mem + 512) / 1024);
+#ifdef USE_THREADS
+ if (ProxyDetect) {
+ get_proxy_stats(&count, &mem);
+ notice_lang(s_OperServ, u, OPER_STATS_PROXY_MEM, count,
+ (mem + 512) / 1024);
+ }
+#endif
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* make Services ignore users for a certain time */
+
+static int do_ignoreuser(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ int t;
+
+ if (!cmd) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (!stricmp(cmd, "ADD")) {
+
+ char *time = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ char *rest = strtok(NULL, "");
+
+ if (!nick) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
+ return MOD_CONT;
+ } else if (!time) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
+ return MOD_CONT;
+ } else {
+ t = dotime(time);
+ rest = NULL;
+
+ if (t <= -1) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_VALID_TIME);
+ return MOD_CONT;
+ } else if (t == 0) {
+ t = 157248000; /* if 0 is given, we set time to 157248000 seconds == 5 years (let's hope the next restart will be before that time ;-)) */
+ add_ignore(nick, t);
+ notice_lang(s_OperServ, u, OPER_IGNORE_PERM_DONE, nick);
+ } else {
+ add_ignore(nick, t);
+ notice_lang(s_OperServ, u, OPER_IGNORE_TIME_DONE, nick,
+ time);
+ }
+ }
+ } else if (!stricmp(cmd, "LIST")) {
+ do_ignorelist(u);
+ }
+
+ else if (!stricmp(cmd, "DEL")) {
+ char *nick = strtok(NULL, " ");
+ if (!nick) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
+ } else {
+ if (get_ignore(nick) == 0) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_LIST_NOMATCH, nick);
+ return MOD_CONT;
+ } else {
+ delete_ignore(nick);
+ notice_lang(s_OperServ, u, OPER_IGNORE_DEL_DONE, nick);
+ }
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ do_clearignore(u);
+
+ } else
+ notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* deletes a nick from the ignore list */
+
+void delete_ignore(const char *nick)
+{
+ IgnoreData *ign, *prev;
+ IgnoreData **whichlist = &ignore[tolower(nick[0])];
+
+ for (ign = *whichlist, prev = NULL; ign; prev = ign, ign = ign->next) {
+ if (stricmp(ign->who, nick) == 0)
+ break;
+ }
+ if (prev)
+ prev->next = ign->next;
+ else
+ *whichlist = ign->next;
+ free(ign);
+ ign = NULL;
+}
+
+/*************************************************************************/
+
+/* shows the Services ignore list */
+
+static int do_ignorelist(User * u)
+{
+ int sent_header = 0;
+ IgnoreData *id;
+ int i;
+
+ for (i = 0; i < 256; i++) {
+ for (id = ignore[i]; id; id = id->next) {
+ if (!sent_header) {
+ notice_lang(s_OperServ, u, OPER_IGNORE_LIST);
+ sent_header = 1;
+ }
+ notice(s_OperServ, u->nick, "%s", id->who);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_IGNORE_LIST_EMPTY);
+ return MOD_CONT;
+}
+
+/**************************************************************************/
+/* Cleares the Services ignore list */
+
+static int do_clearignore(User * u)
+{
+ IgnoreData *id = NULL, *next = NULL;
+ int i;
+ for (i = 0; i < 256; i++) {
+ for (id = ignore[i]; id; id = next) {
+ next = id->next;
+ free(id);
+ if (!next) {
+ ignore[i] = NULL;
+ }
+ }
+ }
+ notice_lang(s_OperServ, u, OPER_IGNORE_LIST_CLEARED);
+ return MOD_CONT;
+}
+
+/**************************************************************************/
+
+/* Channel mode changing (MODE command). */
+
+static int do_os_mode(User * u)
+{
+ int ac;
+ char **av;
+ char *chan = strtok(NULL, " "), *modes = strtok(NULL, "");
+ Channel *c;
+
+ if (!chan || !modes) {
+ syntax_error(s_OperServ, u, "MODE", OPER_MODE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (!(c = findchan(chan))) {
+ notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (c->bouncy_modes) {
+ notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
+ return MOD_CONT;
+#ifdef CMODE_A
+ } else if ((!is_services_admin(u)) && (c->mode & CMODE_A)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+#endif
+ } else {
+ send_mode(s_OperServ, chan, "%s", modes);
+
+ ac = split_buf(modes, &av, 1);
+ chan_set_modes(s_OperServ, c, ac, av, 0);
+
+ if (WallOSMode)
+ wallops(s_OperServ, "%s used MODE %s on %s", u->nick, modes,
+ chan);
+ }
+ return MOD_CONT;
+}
+
+/**************************************************************************/
+
+/**
+ * Change any user's UMODES
+ *
+ * modified to be part of the SuperAdmin directive -jester
+ * check user flag for SuperAdmin -rob
+ */
+#ifdef USE_OSSVS
+#ifndef IRC_HYBRID
+static int do_operumodes(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *modes = strtok(NULL, "");
+
+ User *u2;
+
+ /* Only allow this if SuperAdmin is enabled */
+ if (!u->isSuperAdmin) {
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
+ return MOD_CONT;
+ }
+
+ if (!nick || !modes) {
+ syntax_error(s_OperServ, u, "UMODE", OPER_UMODE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ /**
+ * Only accept a +/- mode string
+ *-rob
+ **/
+ if ((modes[0] != '+') && (modes[0] != '-')) {
+ syntax_error(s_OperServ, u, "UMODE", OPER_UMODE_SYNTAX);
+ return MOD_CONT;
+ }
+ if (!(u2 = finduser(nick))) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
+ } else {
+ send_mode(s_OperServ, nick, "%s", modes);
+
+ change_user_mode(u2, modes, NULL);
+
+ notice_lang(s_OperServ, u, OPER_UMODE_SUCCESS, nick);
+ notice_lang(s_OperServ, u2, OPER_UMODE_CHANGED, u->nick);
+
+ if (WallOSMode)
+ wallops(s_OperServ, "\2%s\2 used UMODE on %s", u->nick, nick);
+ }
+ return MOD_CONT;
+}
+#endif
+#endif
+/**************************************************************************/
+
+/**
+ * give Operflags to any user
+ *
+ * modified to be part of the SuperAdmin directive -jester
+ * check u-> for SuperAdmin -rob
+ */
+#if defined (IRC_UNREAL) && defined (USE_OSSVS)
+
+static int do_operoline(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *flags = strtok(NULL, "");
+ User *u2 = NULL;
+
+ /* Only allow this if SuperAdmin is enabled */
+ if (!u->isSuperAdmin) {
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
+ return MOD_CONT;
+ }
+
+ if (!nick || !flags) {
+ syntax_error(s_OperServ, u, "OLINE", OPER_OLINE_SYNTAX);
+ return MOD_CONT;
+ } else {
+ u2 = finduser(nick);
+
+/* let's check whether the user is online */
+
+ if (!finduser(nick)) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (u2 && flags[0] == '+') {
+ send_cmd(s_OperServ, "SVSO %s %s", nick, flags);
+ send_mode(s_OperServ, nick, "+o");
+ change_user_mode(u2, "+o", NULL);
+ notice_lang(s_OperServ, u2, OPER_OLINE_IRCOP);
+ notice_lang(s_OperServ, u, OPER_OLINE_SUCCESS, flags, nick);
+ wallops(s_OperServ, "\2%s\2 used OLINE for %s", u->nick, nick);
+ } else if (u2 && flags[0] == '-') {
+ send_cmd(s_OperServ, "SVSO %s %s", nick, flags);
+ notice_lang(s_OperServ, u, OPER_OLINE_SUCCESS, flags, nick);
+ wallops(s_OperServ, "\2%s\2 used OLINE for %s", u->nick, nick);
+ } else
+ syntax_error(s_OperServ, u, "OLINE", OPER_OLINE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+#endif
+/*************************************************************************/
+
+/* Clear all modes from a channel. */
+
+static int do_clearmodes(User * u)
+{
+ char *s;
+ int i;
+ char *argv[2];
+ char *chan = strtok(NULL, " ");
+ Channel *c;
+ int all = 0;
+ int count; /* For saving ban info */
+ char **bans; /* For saving ban info */
+#ifdef HAS_EXCEPT
+ int exceptcount; /* For saving except info */
+ char **excepts; /* For saving except info */
+#endif
+ struct c_userlist *cu, *next;
+
+ if (!chan) {
+ syntax_error(s_OperServ, u, "CLEARMODES", OPER_CLEARMODES_SYNTAX);
+ } else if (!(c = findchan(chan))) {
+ notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (c->bouncy_modes) {
+ notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
+ return MOD_CONT;
+ } else {
+ s = strtok(NULL, " ");
+ if (s) {
+ if (stricmp(s, "ALL") == 0) {
+ all = 1;
+ } else {
+ syntax_error(s_OperServ, u, "CLEARMODES",
+ OPER_CLEARMODES_SYNTAX);
+ return MOD_CONT;
+ }
+ }
+
+ if (WallOSClearmodes)
+ wallops(s_OperServ, "%s used CLEARMODES%s on %s", u->nick,
+ all ? " ALL" : "", chan);
+
+ if (all) {
+ /* Clear mode +o */
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+
+ if (!chan_has_user_status(c, cu->user, CUS_OP))
+ continue;
+
+ argv[0] = sstrdup("-o");
+ argv[1] = cu->user->nick;
+
+ send_mode(s_OperServ, c->name, "-o %s", cu->user->nick);
+ chan_set_modes(s_OperServ, c, 2, argv, 0);
+
+ free(argv[0]);
+ }
+
+ /* Clear mode +v */
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+
+ if (!chan_has_user_status(c, cu->user, CUS_VOICE))
+ continue;
+
+ argv[0] = sstrdup("-v");
+ argv[1] = sstrdup(cu->user->nick);
+
+ send_mode(s_OperServ, c->name, "-v %s", cu->user->nick);
+ chan_set_modes(s_OperServ, c, 2, argv, 0);
+
+ free(argv[0]);
+ }
+#ifdef HAS_HALFOP
+ /* Clear mode +h */
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+
+ if (!chan_has_user_status(c, cu->user, CUS_HALFOP))
+ continue;
+
+ argv[0] = sstrdup("-h");
+ argv[1] = sstrdup(cu->user->nick);
+
+ send_mode(s_OperServ, c->name, "-h %s", cu->user->nick);
+ chan_set_modes(s_OperServ, c, 2, argv, 0);
+
+ free(argv[0]);
+ }
+#endif
+ }
+
+ /* Clear modes */
+ send_mode(s_OperServ, c->name, "%s %s", MODESTOREMOVE,
+ c->key ? c->key : "");
+ argv[0] = sstrdup(MODESTOREMOVE);
+ argv[1] = c->key ? c->key : NULL;
+ chan_set_modes(s_OperServ, c, c->key ? 2 : 1, argv, 0);
+ free(argv[0]);
+
+ /* Clear bans */
+ count = c->bancount;
+ bans = scalloc(sizeof(char *) * count, 1);
+
+ for (i = 0; i < count; i++)
+ bans[i] = sstrdup(c->bans[i]);
+
+ for (i = 0; i < count; i++) {
+ argv[0] = sstrdup("-b");
+ argv[1] = bans[i];
+ send_mode(s_OperServ, c->name, "-b %s", argv[1]);
+ chan_set_modes(s_OperServ, c, 2, argv, 0);
+ free(argv[1]);
+ free(argv[0]);
+ }
+
+ free(bans);
+
+#ifdef HAS_EXCEPT
+ /* Clear excepts */
+ exceptcount = c->exceptcount;
+ excepts = scalloc(sizeof(char *) * exceptcount, 1);
+
+ for (i = 0; i < exceptcount; i++)
+ excepts[i] = sstrdup(c->excepts[i]);
+
+ for (i = 0; i < exceptcount; i++) {
+ argv[0] = sstrdup("-e");
+ argv[1] = excepts[i];
+ send_mode(s_OperServ, c->name, "-e %s", argv[1]);
+ chan_set_modes(s_OperServ, c, 2, argv, 0);
+ free(argv[1]);
+ free(argv[0]);
+ }
+
+ free(excepts);
+#endif
+ }
+
+ notice_lang(s_OperServ, u, OPER_CLEARMODES_ALL_DONE, chan);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Kick a user from a channel (KICK command). */
+
+static int do_os_kick(User * u)
+{
+ char *argv[3];
+ char *chan, *nick, *s;
+ Channel *c;
+
+ chan = strtok(NULL, " ");
+ nick = strtok(NULL, " ");
+ s = strtok(NULL, "");
+ if (!chan || !nick || !s) {
+ syntax_error(s_OperServ, u, "KICK", OPER_KICK_SYNTAX);
+ return MOD_CONT;
+ }
+ if (!(c = findchan(chan))) {
+ notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
+ } else if (c->bouncy_modes) {
+ notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
+ return MOD_CONT;
+ }
+ send_cmd(s_OperServ, "KICK %s %s :%s (%s)", chan, nick, u->nick, s);
+ if (WallOSKick)
+ wallops(s_OperServ, "%s used KICK on %s/%s", u->nick, nick, chan);
+ argv[0] = sstrdup(chan);
+ argv[1] = sstrdup(nick);
+ argv[2] = sstrdup(s);
+ do_kick(s_OperServ, 3, argv);
+ free(argv[2]);
+ free(argv[1]);
+ free(argv[0]);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Forcefully change a user's nickname */
+#ifdef USE_OSSVS
+
+static int do_svsnick(User * u)
+{
+ char *nick = strtok(NULL, " ");
+ char *newnick = strtok(NULL, " ");
+
+ NickAlias *na;
+ char *c;
+
+ /* Only allow this if SuperAdmin is enabled */
+ if (!u->isSuperAdmin) {
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
+ return MOD_CONT;
+ }
+
+ if (!nick || !newnick) {
+ syntax_error(s_OperServ, u, "SVSNICK", OPER_SVSNICK_SYNTAX);
+ return MOD_CONT;
+ }
+
+ /* Truncate long nicknames to NICKMAX-2 characters */
+ if (strlen(newnick) > (NICKMAX - 2)) {
+ notice_lang(s_NickServ, u, NICK_X_TRUNCATED,
+ newnick, NICKMAX - 2, newnick);
+ newnick[NICKMAX - 2] = '\0';
+ }
+
+ /* Check for valid characters */
+ if (*newnick == '-' || isdigit(*newnick)) {
+ notice_lang(s_OperServ, u, NICK_X_ILLEGAL, newnick);
+ return MOD_CONT;
+ }
+#define isvalid(c) (((c) >= 'A' && (c) <= '~') || isdigit(c) || (c) == '-')
+ for (c = newnick; *c && (c - newnick) < NICKMAX; c++) {
+ if (!isvalid(*c) || isspace(*c)) {
+ notice_lang(s_OperServ, u, NICK_X_ILLEGAL, nick);
+ return MOD_CONT;
+ }
+ }
+
+ /* Check for a nick in use or a forbidden/suspended nick */
+ if (!finduser(nick)) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
+ } else if (finduser(newnick)) {
+ notice_lang(s_NickServ, u, NICK_X_IN_USE, newnick);
+ } else if ((na = findnick(newnick)) && (na->status & NS_VERBOTEN)) {
+ notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, newnick);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SVSNICK_NEWNICK, nick, newnick);
+ wallops(s_OperServ, "%s used SVSNICK to change %s to %s",
+ u->nick, nick, newnick);
+ send_cmd(NULL, "SVSNICK %s %s :%ld", nick, newnick, time(NULL));
+ }
+ return MOD_CONT;
+}
+#endif
+/*************************************************************************/
+
+/* Adds an AKILL to the list. Returns >= 0 on success, -1 if it fails, -2
+ * if only the expiry time was changed.
+ * The success result is the number of AKILLs that were deleted to successfully add one.
+ */
+
+int add_akill(User * u, char *mask, const char *by, const time_t expires,
+ const char *reason)
+{
+ int deleted = 0, i;
+ char *user, *mask2, *host;
+ Akill *entry;
+
+ /* Checks whether there is an AKILL that already covers
+ * the one we want to add, and whether there are AKILLs
+ * that would be covered by this one. The masks AND the
+ * expiry times are used to determine this, because some
+ * AKILLs may become useful when another one expires.
+ * If so, warn the user in the first case and cleanup
+ * the useless AKILLs in the second.
+ */
+
+ if (akills.count > 0) {
+
+ for (i = akills.count - 1; i >= 0; i--) {
+ char amask[BUFSIZE];
+
+ entry = akills.list[i];
+
+ if (!entry)
+ continue;
+
+ snprintf(amask, sizeof(amask), "%s@%s", entry->user,
+ entry->host);
+
+ if (!stricmp(amask, mask)) {
+ /* We change the AKILL expiry time if its current one is less than the new.
+ * This is preferable to be sure we don't change an important AKILL
+ * accidentely.
+ */
+ if (entry->expires >= expires || entry->expires == 0) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_AKILL_EXISTS,
+ mask);
+ return -1;
+ } else {
+ entry->expires = expires;
+ if (u)
+ notice_lang(s_OperServ, u, OPER_AKILL_CHANGED,
+ amask);
+ return -2;
+ }
+ }
+
+ if (match_wild_nocase(amask, mask)
+ && (entry->expires >= expires || entry->expires == 0)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_AKILL_ALREADY_COVERED,
+ mask, amask);
+ return -1;
+ }
+
+ if (match_wild_nocase(mask, amask)
+ && (entry->expires <= expires || expires == 0)) {
+ slist_delete(&akills, i);
+ deleted++;
+ }
+ }
+
+ }
+
+ /* We can now check whether the list is full or not. */
+ if (slist_full(&akills)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_AKILL_REACHED_LIMIT,
+ akills.limit);
+ return -1;
+ }
+
+ /* We can now (really) add the AKILL. */
+ mask2 = sstrdup(mask);
+ host = strchr(mask2, '@');
+ if (!host)
+ return -1;
+ user = mask2;
+ *host = 0;
+ host++;
+
+ entry = scalloc(sizeof(Akill), 1);
+ if (!entry)
+ return -1;
+
+ entry->user = sstrdup(user);
+ entry->host = sstrdup(host);
+ entry->by = sstrdup(by);
+ entry->reason = sstrdup(reason);
+ entry->seton = time(NULL);
+ entry->expires = expires;
+
+ slist_add(&akills, entry);
+
+ if (AkillOnAdd)
+ s_akill(entry->user, entry->host, entry->by, entry->seton,
+ entry->expires, entry->reason);
+
+ free(mask2);
+
+ return deleted;
+}
+
+/* Does the user match any AKILLs? */
+
+int check_akill(const char *nick, const char *username, const char *host,
+ const char *vhost, const char *ip)
+{
+ int i;
+ Akill *ak;
+
+ /**
+ * If DefCon is set to NO new users - kill the user ;).
+ **/
+ if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) {
+ kill_user(s_OperServ, nick, DefConAkillReason);
+ return 1;
+ }
+
+ if (akills.count == 0)
+ return 0;
+
+ for (i = 0; i < akills.count; i++) {
+ ak = akills.list[i];
+ if (!ak)
+ continue;
+ if (match_wild_nocase(ak->user, username)
+ && (match_wild_nocase(ak->host, host)
+ || (vhost && match_wild_nocase(ak->host, vhost)))) {
+ s_akill(ak->user, ak->host, ak->by, ak->seton, ak->expires,
+ ak->reason);
+ return 1;
+ }
+#ifdef HAS_NICKIP
+ if (ip)
+ if (match_wild_nocase(ak->user, username)
+ && match_wild_nocase(ak->host, ip)) {
+ s_akill(ak->user, ak->host, ak->by, ak->seton, ak->expires,
+ ak->reason);
+ return 1;
+ }
+#endif
+
+ }
+
+ return 0;
+}
+
+/* Delete any expired autokills. */
+
+void expire_akills(void)
+{
+ int i;
+ time_t now = time(NULL);
+ Akill *ak;
+
+ for (i = akills.count - 1; i >= 0; i--) {
+ ak = akills.list[i];
+
+ if (!ak->expires || ak->expires > now)
+ continue;
+
+ if (WallAkillExpire)
+ wallops(s_OperServ, "AKILL on %s@%s has expired", ak->user,
+ ak->host);
+ slist_delete(&akills, i);
+ }
+}
+
+static void free_akill_entry(SList * slist, void *item)
+{
+ Akill *ak = item;
+
+ /* Remove the AKILLs from all the servers */
+ s_rakill(ak->user, ak->host);
+
+ /* Free the structure */
+ free(ak->user);
+ free(ak->host);
+ free(ak->by);
+ free(ak->reason);
+ free(ak);
+}
+
+/* item1 is not an Akill pointer, but a char
+ */
+
+static int is_akill_entry_equal(SList * slist, void *item1, void *item2)
+{
+ char *ak1 = item1, buf[BUFSIZE];
+ Akill *ak2 = item2;
+
+ if (!ak1 || !ak2)
+ return 0;
+
+ snprintf(buf, sizeof(buf), "%s@%s", ak2->user, ak2->host);
+
+ if (!stricmp(ak1, buf))
+ return 1;
+ else
+ return 0;
+}
+
+/* Lists an AKILL entry, prefixing it with the header if needed */
+
+static int akill_list(int number, Akill * ak, User * u, int *sent_header)
+{
+ char mask[BUFSIZE];
+
+ if (!ak)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_AKILL_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ snprintf(mask, sizeof(mask), "%s@%s", ak->user, ak->host);
+ notice_lang(s_OperServ, u, OPER_AKILL_LIST_FORMAT, number, mask,
+ ak->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int akill_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return akill_list(number, item, u, sent_header);
+}
+
+/* Lists an AKILL entry, prefixing it with the header if needed */
+
+static int akill_view(int number, Akill * ak, User * u, int *sent_header)
+{
+ char mask[BUFSIZE];
+ char timebuf[32], expirebuf[256];
+ struct tm tm;
+
+ if (!ak)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_AKILL_VIEW_HEADER);
+ *sent_header = 1;
+ }
+
+ snprintf(mask, sizeof(mask), "%s@%s", ak->user, ak->host);
+ tm = *localtime(&ak->seton);
+ strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
+ &tm);
+ expire_left(u->na, expirebuf, sizeof(expirebuf), ak->expires);
+ notice_lang(s_OperServ, u, OPER_AKILL_VIEW_FORMAT, number, mask,
+ ak->by, timebuf, expirebuf, ak->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int akill_view_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return akill_view(number, item, u, sent_header);
+}
+
+/* Manage the AKILL list. */
+
+static int do_akill(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char breason[BUFSIZE];
+
+ if (!cmd)
+ cmd = "";
+
+ if (!stricmp(cmd, "ADD")) {
+ int deleted = 0;
+ char *expiry, *mask, *reason;
+ time_t expires;
+
+ mask = strtok(NULL, " ");
+ if (mask && *mask == '+') {
+ expiry = mask;
+ mask = strtok(NULL, " ");
+ } else {
+ expiry = NULL;
+ }
+
+ expires = expiry ? dotime(expiry) : AutokillExpiry;
+ /* If the expiry given does not contain a final letter, it's in days,
+ * said the doc. Ah well.
+ */
+ if (expiry && isdigit(expiry[strlen(expiry) - 1]))
+ expires *= 86400;
+ /* Do not allow less than a minute expiry time */
+ if (expires != 0 && expires < 60) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ if (mask && (reason = strtok(NULL, ""))) {
+ /* We first do some sanity check on the proposed mask. */
+ if (strchr(mask, '!')) {
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_NICK);
+ return MOD_CONT;
+ }
+
+ if (!strchr(mask, '@')) {
+ notice_lang(s_OperServ, u, BAD_USERHOST_MASK);
+ return MOD_CONT;
+ }
+
+ if (mask && strspn(mask, "~@.*?") == strlen(mask)) {
+ notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
+ return MOD_CONT;
+ }
+
+ /**
+ * Changed sprintf() to snprintf()and increased the size of
+ * breason to match bufsize
+ * -Rob
+ **/
+ if (AddAkiller) {
+ snprintf(breason, sizeof(breason), "[%s] %s", u->nick,
+ reason);
+ reason = sstrdup(breason);
+ }
+
+ deleted = add_akill(u, mask, u->nick, expires, reason);
+ if (deleted < 0)
+ return MOD_CONT;
+ else if (deleted)
+ notice_lang(s_OperServ, u, OPER_AKILL_DELETED_SEVERAL,
+ deleted);
+ notice_lang(s_OperServ, u, OPER_AKILL_ADDED, mask);
+
+ if (WallOSAkill) {
+ char buf[128];
+
+ if (!expires) {
+ strcpy(buf, "does not expire");
+ } else {
+ int wall_expiry = expires - time(NULL);
+ char *s = NULL;
+
+ if (wall_expiry >= 86400) {
+ wall_expiry /= 86400;
+ s = "day";
+ } else if (wall_expiry >= 3600) {
+ wall_expiry /= 3600;
+ s = "hour";
+ } else if (wall_expiry >= 60) {
+ wall_expiry /= 60;
+ s = "minute";
+ }
+
+ snprintf(buf, sizeof(buf), "expires in %d %s%s",
+ wall_expiry, s,
+ (wall_expiry == 1) ? "" : "s");
+ }
+
+ wallops(s_OperServ, "%s added an AKILL for %s (%s) (%s)",
+ u->nick, mask, reason, buf);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else {
+ syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
+ }
+
+ } else if (!stricmp(cmd, "DEL")) {
+
+ char *mask;
+ int res = 0;
+
+ mask = strtok(NULL, " ");
+
+ if (!mask) {
+ syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (akills.count == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ /* Deleting a range */
+ res = slist_delete_range(&akills, mask, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_AKILL_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_AKILL_DELETED_SEVERAL,
+ res);
+ }
+ } else {
+ if ((res = slist_indexof(&akills, mask)) == -1) {
+ notice_lang(s_OperServ, u, OPER_AKILL_NOT_FOUND, mask);
+ return MOD_CONT;
+ }
+
+ slist_delete(&akills, res);
+ notice_lang(s_OperServ, u, OPER_AKILL_DELETED, mask);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else if (!stricmp(cmd, "LIST")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (akills.count == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, " ");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&akills, mask, &akill_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
+ return MOD_CONT;
+ } else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Akill");
+ }
+ } else {
+ int i;
+ char amask[BUFSIZE];
+
+ for (i = 0; i < akills.count; i++) {
+ snprintf(amask, sizeof(amask), "%s@%s",
+ ((Akill *) akills.list[i])->user,
+ ((Akill *) akills.list[i])->host);
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ akill_list(i + 1, akills.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Akill");
+ }
+ }
+ } else if (!stricmp(cmd, "VIEW")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (akills.count == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, " ");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&akills, mask, &akill_view_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char amask[BUFSIZE];
+
+ for (i = 0; i < akills.count; i++) {
+ snprintf(amask, sizeof(amask), "%s@%s",
+ ((Akill *) akills.list[i])->user,
+ ((Akill *) akills.list[i])->host);
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ akill_view(i + 1, akills.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ slist_clear(&akills, 1);
+ notice_lang(s_OperServ, u, OPER_AKILL_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+/* Adds an SGLINE to the list. Returns >= 0 on success, -1 if it failed, -2 if
+ * only the expiry time changed.
+ * The success result is the number of SGLINEs that were deleted to successfully add one.
+ */
+
+int add_sgline(User * u, char *mask, const char *by, const time_t expires,
+ const char *reason)
+{
+ int deleted = 0, i;
+ SXLine *entry;
+
+ /* Checks whether there is an SGLINE that already covers
+ * the one we want to add, and whether there are SGLINEs
+ * that would be covered by this one.
+ * If so, warn the user in the first case and cleanup
+ * the useless SGLINEs in the second.
+ */
+
+ if (sglines.count > 0) {
+
+ for (i = sglines.count - 1; i >= 0; i--) {
+ entry = sglines.list[i];
+
+ if (!entry)
+ continue;
+
+ if (!stricmp(entry->mask, mask)) {
+ if (entry->expires >= expires || entry->expires == 0) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SGLINE_EXISTS,
+ mask);
+ return -1;
+ } else {
+ entry->expires = expires;
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SGLINE_CHANGED,
+ entry->mask);
+ return -2;
+ }
+ }
+
+ if (match_wild_nocase(entry->mask, mask)
+ && (entry->expires >= expires || entry->expires == 0)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SGLINE_ALREADY_COVERED,
+ mask, entry->mask);
+ return -1;
+ }
+
+ if (match_wild_nocase(mask, entry->mask)
+ && (entry->expires <= expires || expires == 0)) {
+ slist_delete(&sglines, i);
+ deleted++;
+ }
+ }
+
+ }
+
+ /* We can now check whether the list is full or not. */
+ if (slist_full(&sglines)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SGLINE_REACHED_LIMIT,
+ sglines.limit);
+ return -1;
+ }
+
+ /* We can now (really) add the SGLINE. */
+ entry = scalloc(sizeof(SXLine), 1);
+ if (!entry)
+ return -1;
+
+ entry->mask = sstrdup(mask);
+ entry->by = sstrdup(by);
+ entry->reason = sstrdup(reason);
+ entry->seton = time(NULL);
+ entry->expires = expires;
+
+ slist_add(&sglines, entry);
+
+ s_sgline(entry->mask, entry->reason);
+
+ return deleted;
+}
+
+/* Does the user match any SGLINEs? */
+
+int check_sgline(const char *nick, const char *realname)
+{
+ int i;
+ SXLine *sx;
+
+ if (sglines.count == 0)
+ return 0;
+
+ for (i = 0; i < sglines.count; i++) {
+ sx = sglines.list[i];
+ if (!sx)
+ continue;
+
+ if (match_wild_nocase(sx->mask, realname)) {
+ s_sgline(sx->mask, sx->reason);
+ /* We kill nick since s_sgline can't */
+ send_cmd(ServerName, "SVSKILL %s :G-Lined: %s", nick,
+ sx->reason);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Delete any expired SGLINEs. */
+
+void expire_sglines(void)
+{
+ int i;
+ time_t now = time(NULL);
+ SXLine *sx;
+
+ for (i = sglines.count - 1; i >= 0; i--) {
+ sx = sglines.list[i];
+
+ if (!sx->expires || sx->expires > now)
+ continue;
+
+ if (WallSGLineExpire)
+ wallops(s_OperServ, "SGLINE on \2%s\2 has expired", sx->mask);
+ slist_delete(&sglines, i);
+ }
+}
+
+static void free_sgline_entry(SList * slist, void *item)
+{
+ SXLine *sx = item;
+
+ /* Remove the SGLINE from all the servers */
+ s_unsgline(sx->mask);
+
+ /* Free the structure */
+ free(sx->mask);
+ free(sx->by);
+ free(sx->reason);
+ free(sx);
+}
+
+/* item1 is not an SXLine pointer, but a char */
+
+static int is_sgline_entry_equal(SList * slist, void *item1, void *item2)
+{
+ char *sx1 = item1;
+ SXLine *sx2 = item2;
+
+ if (!sx1 || !sx2)
+ return 0;
+
+ if (!stricmp(sx1, sx2->mask))
+ return 1;
+ else
+ return 0;
+}
+
+/* Lists an SGLINE entry, prefixing it with the header if needed */
+
+static int sgline_list(int number, SXLine * sx, User * u, int *sent_header)
+{
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_SGLINE_LIST_FORMAT, number, sx->mask,
+ sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int sgline_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return sgline_list(number, item, u, sent_header);
+}
+
+/* Lists an SGLINE entry, prefixing it with the header if needed */
+
+static int sgline_view(int number, SXLine * sx, User * u, int *sent_header)
+{
+ char timebuf[32], expirebuf[256];
+ struct tm tm;
+
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_VIEW_HEADER);
+ *sent_header = 1;
+ }
+
+ tm = *localtime(&sx->seton);
+ strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
+ &tm);
+ expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
+ notice_lang(s_OperServ, u, OPER_SGLINE_VIEW_FORMAT, number, sx->mask,
+ sx->by, timebuf, expirebuf, sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int sgline_view_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return sgline_view(number, item, u, sent_header);
+}
+
+#endif
+
+/* Manage the SGLINE list. */
+
+static int do_sgline(User * u)
+{
+#ifdef IRC_BAHAMUT
+ char *cmd = strtok(NULL, " ");
+
+ if (!cmd)
+ cmd = "";
+
+ if (!stricmp(cmd, "ADD")) {
+ int deleted = 0;
+ char *expiry, *mask, *reason;
+ time_t expires;
+
+ mask = strtok(NULL, ":");
+ if (mask && *mask == '+') {
+ expiry = mask;
+ mask = strchr(expiry, ' ');
+ if (mask) {
+ *mask = 0;
+ mask++;
+ }
+ } else {
+ expiry = NULL;
+ }
+
+ expires = expiry ? dotime(expiry) : SGLineExpiry;
+ /* If the expiry given does not contain a final letter, it's in days,
+ * said the doc. Ah well.
+ */
+ if (expiry && isdigit(expiry[strlen(expiry) - 1]))
+ expires *= 86400;
+ /* Do not allow less than a minute expiry time */
+ if (expires != 0 && expires < 60) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ if (mask && (reason = strtok(NULL, ""))) {
+ /* We first do some sanity check on the proposed mask. */
+
+ if (mask && strspn(mask, "*?") == strlen(mask)) {
+ notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
+ return MOD_CONT;
+ }
+
+ deleted = add_sgline(u, mask, u->nick, expires, reason);
+ if (deleted < 0)
+ return MOD_CONT;
+ else if (deleted)
+ notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_SEVERAL,
+ deleted);
+ notice_lang(s_OperServ, u, OPER_SGLINE_ADDED, mask);
+
+ if (WallOSSGLine) {
+ char buf[128];
+
+ if (!expires) {
+ strcpy(buf, "does not expire");
+ } else {
+ int wall_expiry = expires - time(NULL);
+ char *s = NULL;
+
+ if (wall_expiry >= 86400) {
+ wall_expiry /= 86400;
+ s = "day";
+ } else if (wall_expiry >= 3600) {
+ wall_expiry /= 3600;
+ s = "hour";
+ } else if (wall_expiry >= 60) {
+ wall_expiry /= 60;
+ s = "minute";
+ }
+
+ snprintf(buf, sizeof(buf), "expires in %d %s%s",
+ wall_expiry, s,
+ (wall_expiry == 1) ? "" : "s");
+ }
+
+ wallops(s_OperServ, "%s added an SGLINE for %s (%s)",
+ u->nick, mask, buf);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else {
+ syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
+ }
+
+ } else if (!stricmp(cmd, "DEL")) {
+
+ char *mask;
+ int res = 0;
+
+ mask = strtok(NULL, "");
+
+ if (!mask) {
+ syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (sglines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ /* Deleting a range */
+ res = slist_delete_range(&sglines, mask, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_SEVERAL,
+ res);
+ }
+ } else {
+ if ((res = slist_indexof(&sglines, mask)) == -1) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_NOT_FOUND, mask);
+ return MOD_CONT;
+ }
+
+ slist_delete(&sglines, res);
+ notice_lang(s_OperServ, u, OPER_SGLINE_DELETED, mask);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else if (!stricmp(cmd, "LIST")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (sglines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, "");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&sglines, mask, &sgline_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < sglines.count; i++) {
+ amask = ((SXLine *) sglines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ sgline_list(i + 1, sglines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "SGLine");
+ }
+ }
+ } else if (!stricmp(cmd, "VIEW")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (sglines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, "");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&sglines, mask, &sgline_view_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < sglines.count; i++) {
+ amask = ((SXLine *) sglines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ sgline_view(i + 1, sglines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ slist_clear(&sglines, 1);
+ notice_lang(s_OperServ, u, OPER_SGLINE_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
+ }
+#else
+ notice_lang(s_OperServ, u, OPER_SGLINE_UNSUPPORTED);
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Adds an SQLINE to the list. Returns >= 0 on success, -1 if it failed, -2 if
+ * only the expiry time changed.
+ * The success result is the number of SQLINEs that were deleted to successfully add one.
+ */
+
+int add_sqline(User * u, char *mask, const char *by, const time_t expires,
+ const char *reason)
+{
+ int deleted = 0, i;
+ SXLine *entry;
+
+ /* Checks whether there is an SQLINE that already covers
+ * the one we want to add, and whether there are SQLINEs
+ * that would be covered by this one.
+ * If so, warn the user in the first case and cleanup
+ * the useless SQLINEs in the second.
+ */
+
+ if (sqlines.count > 0) {
+
+ for (i = sqlines.count - 1; i >= 0; i--) {
+ entry = sqlines.list[i];
+
+ if (!entry)
+ continue;
+
+ if ((*mask == '#' && *entry->mask != '#') ||
+ (*mask != '#' && *entry->mask == '#'))
+ continue;
+
+ if (!stricmp(entry->mask, mask)) {
+ if (entry->expires >= expires || entry->expires == 0) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SQLINE_EXISTS,
+ mask);
+ return -1;
+ } else {
+ entry->expires = expires;
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SQLINE_CHANGED,
+ entry->mask);
+ return -2;
+ }
+ }
+
+ if (match_wild_nocase(entry->mask, mask)
+ && (entry->expires >= expires || entry->expires == 0)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SQLINE_ALREADY_COVERED,
+ mask, entry->mask);
+ return -1;
+ }
+
+ if (match_wild_nocase(mask, entry->mask)
+ && (entry->expires <= expires || expires == 0)) {
+ slist_delete(&sqlines, i);
+ deleted++;
+ }
+ }
+
+ }
+
+ /* We can now check whether the list is full or not. */
+ if (slist_full(&sqlines)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SQLINE_REACHED_LIMIT,
+ sqlines.limit);
+ return -1;
+ }
+
+ /* We can now (really) add the SQLINE. */
+ entry = scalloc(sizeof(SXLine), 1);
+ if (!entry)
+ return -1;
+
+ entry->mask = sstrdup(mask);
+ entry->by = sstrdup(by);
+ entry->reason = sstrdup(reason);
+ entry->seton = time(NULL);
+ entry->expires = expires;
+
+ slist_add(&sqlines, entry);
+
+ s_sqline(entry->mask, entry->reason);
+
+ return deleted;
+}
+
+/* Does the user match any SQLINEs? */
+
+int check_sqline(const char *nick, int nick_change)
+{
+ int i;
+ SXLine *sx;
+
+ if (sqlines.count == 0)
+ return 0;
+
+ for (i = 0; i < sqlines.count; i++) {
+ sx = sqlines.list[i];
+ if (!sx)
+ continue;
+
+#ifdef IRC_BAHAMUT
+ if (*sx->mask == '#')
+ continue;
+#endif
+
+ if (match_wild_nocase(sx->mask, nick)) {
+ s_sqline(sx->mask, sx->reason);
+ /* We kill nick since s_sqline can't */
+#ifdef IRC_BAHAMUT
+ send_cmd(ServerName, "SVSKILL %s :Q-Lined: %s", nick,
+ sx->reason);
+#else
+ if (!nick_change) {
+ send_cmd(s_OperServ, "KILL %s :Q-Lined: %s", nick,
+ sx->reason);
+ } else {
+ char reason[300];
+ snprintf(reason, sizeof(reason), "Q-Lined: %s",
+ sx->reason);
+ kill_user(s_OperServ, nick, reason);
+ }
+#endif
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef IRC_BAHAMUT
+int check_chan_sqline(const char *chan)
+{
+ int i;
+ SXLine *sx;
+
+ if (sqlines.count == 0)
+ return 0;
+
+ for (i = 0; i < sqlines.count; i++) {
+ sx = sqlines.list[i];
+ if (!sx)
+ continue;
+
+ if (*sx->mask != '#')
+ continue;
+
+ if (match_wild_nocase(sx->mask, chan)) {
+ s_sqline(sx->mask, sx->reason);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/* Delete any expired SQLINEs. */
+
+void expire_sqlines(void)
+{
+ int i;
+ time_t now = time(NULL);
+ SXLine *sx;
+
+ for (i = sqlines.count - 1; i >= 0; i--) {
+ sx = sqlines.list[i];
+
+ if (!sx->expires || sx->expires > now)
+ continue;
+
+ if (WallSQLineExpire)
+ wallops(s_OperServ, "SQLINE on \2%s\2 has expired", sx->mask);
+
+ slist_delete(&sqlines, i);
+ }
+}
+
+static void free_sqline_entry(SList * slist, void *item)
+{
+ SXLine *sx = item;
+
+ /* Remove the SQLINE from all the servers */
+ s_unsqline(sx->mask);
+
+ /* Free the structure */
+ free(sx->mask);
+ free(sx->by);
+ free(sx->reason);
+ free(sx);
+}
+
+/* item1 is not an SXLine pointer, but a char */
+
+static int is_sqline_entry_equal(SList * slist, void *item1, void *item2)
+{
+ char *sx1 = item1;
+ SXLine *sx2 = item2;
+
+ if (!sx1 || !sx2)
+ return 0;
+
+ if (!stricmp(sx1, sx2->mask))
+ return 1;
+ else
+ return 0;
+}
+
+/* Lists an SQLINE entry, prefixing it with the header if needed */
+
+static int sqline_list(int number, SXLine * sx, User * u, int *sent_header)
+{
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_SQLINE_LIST_FORMAT, number, sx->mask,
+ sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int sqline_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return sqline_list(number, item, u, sent_header);
+}
+
+/* Lists an SQLINE entry, prefixing it with the header if needed */
+
+static int sqline_view(int number, SXLine * sx, User * u, int *sent_header)
+{
+ char timebuf[32], expirebuf[256];
+ struct tm tm;
+
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_VIEW_HEADER);
+ *sent_header = 1;
+ }
+
+ tm = *localtime(&sx->seton);
+ strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
+ &tm);
+ expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
+ notice_lang(s_OperServ, u, OPER_SQLINE_VIEW_FORMAT, number, sx->mask,
+ sx->by, timebuf, expirebuf, sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int sqline_view_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return sqline_view(number, item, u, sent_header);
+}
+
+/* Manage the SQLINE list. */
+
+static int do_sqline(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+
+ if (!cmd)
+ cmd = "";
+
+ if (!stricmp(cmd, "ADD")) {
+ int deleted = 0;
+ char *expiry, *mask, *reason;
+ time_t expires;
+
+ mask = strtok(NULL, " ");
+ if (mask && *mask == '+') {
+ expiry = mask;
+ mask = strtok(NULL, " ");
+ } else {
+ expiry = NULL;
+ }
+
+ expires = expiry ? dotime(expiry) : SQLineExpiry;
+ /* If the expiry given does not contain a final letter, it's in days,
+ * said the doc. Ah well.
+ */
+ if (expiry && isdigit(expiry[strlen(expiry) - 1]))
+ expires *= 86400;
+ /* Do not allow less than a minute expiry time */
+ if (expires != 0 && expires < 60) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ if (mask && (reason = strtok(NULL, ""))) {
+
+ /* We first do some sanity check on the proposed mask. */
+ if (strspn(mask, "*?") == strlen(mask)) {
+ notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
+ return MOD_CONT;
+ }
+#ifndef IRC_BAHAMUT
+ /* Channel SQLINEs are only supported on Bahamut servers */
+ if (*mask == '#') {
+ notice_lang(s_OperServ, u,
+ OPER_SQLINE_CHANNELS_UNSUPPORTED);
+ return MOD_CONT;
+ }
+#endif
+
+ deleted = add_sqline(u, mask, u->nick, expires, reason);
+ if (deleted < 0)
+ return MOD_CONT;
+ else if (deleted)
+ notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_SEVERAL,
+ deleted);
+ notice_lang(s_OperServ, u, OPER_SQLINE_ADDED, mask);
+
+ if (WallOSSQLine) {
+ char buf[128];
+
+ if (!expires) {
+ strcpy(buf, "does not expire");
+ } else {
+ int wall_expiry = expires - time(NULL);
+ char *s = NULL;
+
+ if (wall_expiry >= 86400) {
+ wall_expiry /= 86400;
+ s = "day";
+ } else if (wall_expiry >= 3600) {
+ wall_expiry /= 3600;
+ s = "hour";
+ } else if (wall_expiry >= 60) {
+ wall_expiry /= 60;
+ s = "minute";
+ }
+
+ snprintf(buf, sizeof(buf), "expires in %d %s%s",
+ wall_expiry, s,
+ (wall_expiry == 1) ? "" : "s");
+ }
+
+ wallops(s_OperServ, "%s added an SQLINE for %s (%s)",
+ u->nick, mask, buf);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else {
+ syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
+ }
+
+ } else if (!stricmp(cmd, "DEL")) {
+
+ char *mask;
+ int res = 0;
+
+ mask = strtok(NULL, "");
+
+ if (!mask) {
+ syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (sqlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ /* Deleting a range */
+ res = slist_delete_range(&sqlines, mask, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_SEVERAL,
+ res);
+ }
+ } else {
+ if ((res = slist_indexof(&sqlines, mask)) == -1) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_NOT_FOUND, mask);
+ return MOD_CONT;
+ }
+
+ slist_delete(&sqlines, res);
+ notice_lang(s_OperServ, u, OPER_SQLINE_DELETED, mask);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else if (!stricmp(cmd, "LIST")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (sqlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, "");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&sqlines, mask, &sqline_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < sqlines.count; i++) {
+ amask = ((SXLine *) sqlines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ sqline_list(i + 1, sqlines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "SQLine");
+ }
+ }
+ } else if (!stricmp(cmd, "VIEW")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (sqlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, "");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&sqlines, mask, &sqline_view_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < sqlines.count; i++) {
+ amask = ((SXLine *) sqlines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ sqline_view(i + 1, sqlines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ slist_clear(&sqlines, 1);
+ notice_lang(s_OperServ, u, OPER_SQLINE_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef IRC_BAHAMUT
+
+/* Adds an SZLINE to the list. Returns >= 0 on success, -1 on error, -2 if
+ * only the expiry time changed.
+ * The success result is the number of SZLINEs that were deleted to successfully add one.
+ */
+
+int add_szline(User * u, char *mask, const char *by, const time_t expires,
+ const char *reason)
+{
+ int deleted = 0, i;
+ SXLine *entry;
+
+ /* Checks whether there is an SZLINE that already covers
+ * the one we want to add, and whether there are SZLINEs
+ * that would be covered by this one.
+ * If so, warn the user in the first case and cleanup
+ * the useless SZLINEs in the second.
+ */
+
+ if (szlines.count > 0) {
+
+ for (i = szlines.count - 1; i >= 0; i--) {
+ entry = szlines.list[i];
+
+ if (!entry)
+ continue;
+
+ if (!stricmp(entry->mask, mask)) {
+ if (entry->expires >= expires || entry->expires == 0) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SZLINE_EXISTS,
+ mask);
+ return -1;
+ } else {
+ entry->expires = expires;
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SZLINE_EXISTS,
+ mask);
+ return -2;
+ }
+ }
+
+ if (match_wild_nocase(entry->mask, mask)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SZLINE_ALREADY_COVERED,
+ mask, entry->mask);
+ return -1;
+ }
+
+ if (match_wild_nocase(mask, entry->mask)) {
+ slist_delete(&szlines, i);
+ deleted++;
+ }
+ }
+
+ }
+
+ /* We can now check whether the list is full or not. */
+ if (slist_full(&szlines)) {
+ if (u)
+ notice_lang(s_OperServ, u, OPER_SZLINE_REACHED_LIMIT,
+ szlines.limit);
+ return -1;
+ }
+
+ /* We can now (really) add the SZLINE. */
+ entry = scalloc(sizeof(SXLine), 1);
+ if (!entry)
+ return -1;
+
+ entry->mask = sstrdup(mask);
+ entry->by = sstrdup(by);
+ entry->reason = sstrdup(reason);
+ entry->seton = time(NULL);
+ entry->expires = expires;
+
+ slist_add(&szlines, entry);
+ s_szline(entry->mask, entry->reason);
+
+ return deleted;
+}
+
+/* Delete any expired SZLINEs. */
+
+void expire_szlines(void)
+{
+ int i;
+ time_t now = time(NULL);
+ SXLine *sx;
+
+ for (i = szlines.count - 1; i >= 0; i--) {
+ sx = szlines.list[i];
+
+ if (!sx->expires || sx->expires > now)
+ continue;
+
+ if (WallSZLineExpire)
+ wallops(s_OperServ, "SZLINE on \2%s\2 has expired", sx->mask);
+ slist_delete(&szlines, i);
+ }
+}
+
+static void free_szline_entry(SList * slist, void *item)
+{
+ SXLine *sx = item;
+
+ /* Remove the SZLINE from all the servers */
+ s_unszline(sx->mask);
+
+ /* Free the structure */
+ free(sx->mask);
+ free(sx->by);
+ free(sx->reason);
+ free(sx);
+}
+
+/* item1 is not an SXLine pointer, but a char
+ */
+
+static int is_szline_entry_equal(SList * slist, void *item1, void *item2)
+{
+ char *sx1 = item1;
+ SXLine *sx2 = item2;
+
+ if (!sx1 || !sx2)
+ return 0;
+
+ if (!stricmp(sx1, sx2->mask))
+ return 1;
+ else
+ return 0;
+}
+
+/* Lists an SZLINE entry, prefixing it with the header if needed */
+
+static int szline_list(int number, SXLine * sx, User * u, int *sent_header)
+{
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_SZLINE_LIST_FORMAT, number, sx->mask,
+ sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int szline_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return szline_list(number, item, u, sent_header);
+}
+
+/* Lists an SZLINE entry, prefixing it with the header if needed */
+
+static int szline_view(int number, SXLine * sx, User * u, int *sent_header)
+{
+ char timebuf[32], expirebuf[256];
+ struct tm tm;
+
+ if (!sx)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_VIEW_HEADER);
+ *sent_header = 1;
+ }
+
+ tm = *localtime(&sx->seton);
+ strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
+ &tm);
+ expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
+ notice_lang(s_OperServ, u, OPER_SZLINE_VIEW_FORMAT, number, sx->mask,
+ sx->by, timebuf, expirebuf, sx->reason);
+
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int szline_view_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return szline_view(number, item, u, sent_header);
+}
+
+#endif
+
+/* Manage the SZLINE list. */
+
+static int do_szline(User * u)
+{
+#ifdef IRC_BAHAMUT
+ char *cmd = strtok(NULL, " ");
+
+ if (!cmd)
+ cmd = "";
+
+ if (!stricmp(cmd, "ADD")) {
+ int deleted = 0;
+ char *expiry, *mask, *reason;
+ time_t expires;
+
+ mask = strtok(NULL, " ");
+ if (mask && *mask == '+') {
+ expiry = mask;
+ mask = strtok(NULL, " ");
+ } else {
+ expiry = NULL;
+ }
+
+ expires = expiry ? dotime(expiry) : SZLineExpiry;
+ /* If the expiry given does not contain a final letter, it's in days,
+ * said the doc. Ah well.
+ */
+ if (expiry && isdigit(expiry[strlen(expiry) - 1]))
+ expires *= 86400;
+ /* Do not allow less than a minute expiry time */
+ if (expires != 0 && expires < 60) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ if (mask && (reason = strtok(NULL, ""))) {
+ /* We first do some sanity check on the proposed mask. */
+
+ if (strchr(mask, '!') || strchr(mask, '@')) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_ONLY_IPS);
+ return MOD_CONT;
+ }
+
+ if (strspn(mask, "*?") == strlen(mask)) {
+ notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
+ return MOD_CONT;
+ }
+
+ deleted = add_szline(u, mask, u->nick, expires, reason);
+ if (deleted < 0)
+ return MOD_CONT;
+ else if (deleted)
+ notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_SEVERAL,
+ deleted);
+ notice_lang(s_OperServ, u, OPER_SZLINE_ADDED, mask);
+
+ if (WallOSSZLine) {
+ char buf[128];
+
+ if (!expires) {
+ strcpy(buf, "does not expire");
+ } else {
+ int wall_expiry = expires - time(NULL);
+ char *s = NULL;
+
+ if (wall_expiry >= 86400) {
+ wall_expiry /= 86400;
+ s = "day";
+ } else if (wall_expiry >= 3600) {
+ wall_expiry /= 3600;
+ s = "hour";
+ } else if (wall_expiry >= 60) {
+ wall_expiry /= 60;
+ s = "minute";
+ }
+
+ snprintf(buf, sizeof(buf), "expires in %d %s%s",
+ wall_expiry, s,
+ (wall_expiry == 1) ? "" : "s");
+ }
+
+ wallops(s_OperServ, "%s added an SZLINE for %s (%s)",
+ u->nick, mask, buf);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else {
+ syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
+ }
+
+ } else if (!stricmp(cmd, "DEL")) {
+
+ char *mask;
+ int res = 0;
+
+ mask = strtok(NULL, " ");
+
+ if (!mask) {
+ syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (szlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ /* Deleting a range */
+ res = slist_delete_range(&szlines, mask, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_SEVERAL,
+ res);
+ }
+ } else {
+ if ((res = slist_indexof(&szlines, mask)) == -1) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_NOT_FOUND, mask);
+ return MOD_CONT;
+ }
+
+ slist_delete(&szlines, res);
+ notice_lang(s_OperServ, u, OPER_SZLINE_DELETED, mask);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else if (!stricmp(cmd, "LIST")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (szlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, " ");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&szlines, mask, &szline_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < szlines.count; i++) {
+ amask = ((SXLine *) szlines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ szline_list(i + 1, szlines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
+ }
+ } else if (!stricmp(cmd, "VIEW")) {
+ char *mask;
+ int res, sent_header = 0;
+
+ if (szlines.count == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, " ");
+
+ if (!mask || (isdigit(*mask)
+ && strspn(mask, "1234567890,-") == strlen(mask))) {
+ res =
+ slist_enum(&szlines, mask, &szline_view_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
+ return MOD_CONT;
+ }
+ } else {
+ int i;
+ char *amask;
+
+ for (i = 0; i < szlines.count; i++) {
+ amask = ((SXLine *) szlines.list[i])->mask;
+ if (!stricmp(mask, amask)
+ || match_wild_nocase(mask, amask))
+ szline_view(i + 1, szlines.list[i], u, &sent_header);
+ }
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ slist_clear(&szlines, 1);
+ notice_lang(s_OperServ, u, OPER_SZLINE_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
+ }
+#else
+ notice_lang(s_OperServ, u, OPER_SZLINE_UNSUPPORTED);
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_chanlist(User * u)
+{
+ char *pattern = strtok(NULL, " ");
+ char *opt = strtok(NULL, " ");
+
+ int modes = 0;
+ User *u2;
+
+ if (opt && !stricmp(opt, "SECRET"))
+ modes |= (CMODE_s | CMODE_p);
+
+ if (pattern && (u2 = finduser(pattern))) {
+ struct u_chanlist *uc;
+
+ notice_lang(s_OperServ, u, OPER_CHANLIST_HEADER_USER, u2->nick);
+
+ for (uc = u2->chans; uc; uc = uc->next) {
+ if (modes && !(uc->chan->mode & modes))
+ continue;
+ notice_lang(s_OperServ, u, OPER_CHANLIST_RECORD,
+ uc->chan->name, uc->chan->usercount,
+ chan_get_modes(uc->chan, 1, 1),
+ (uc->chan->topic ? uc->chan->topic : ""));
+ }
+ } else {
+ int i;
+ Channel *c;
+
+ notice_lang(s_OperServ, u, OPER_CHANLIST_HEADER);
+
+ for (i = 0; i < 1024; i++) {
+ for (c = chanlist[i]; c; c = c->next) {
+ if (pattern && !match_wild_nocase(pattern, c->name))
+ continue;
+ if (modes && !(c->mode & modes))
+ continue;
+ notice_lang(s_OperServ, u, OPER_CHANLIST_RECORD, c->name,
+ c->usercount, chan_get_modes(c, 1, 1),
+ (c->topic ? c->topic : ""));
+ }
+ }
+ }
+
+ notice_lang(s_OperServ, u, OPER_CHANLIST_END);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_userlist(User * u)
+{
+ char *pattern = strtok(NULL, " ");
+ char *opt = strtok(NULL, " ");
+
+ Channel *c;
+ int modes = 0;
+
+ if (opt && !stricmp(opt, "INVISIBLE"))
+ modes |= UMODE_i;
+
+ if (pattern && (c = findchan(pattern))) {
+ struct c_userlist *cu;
+
+ notice_lang(s_OperServ, u, OPER_USERLIST_HEADER_CHAN, pattern);
+
+ for (cu = c->users; cu; cu = cu->next) {
+ if (modes && !(cu->user->mode & modes))
+ continue;
+ notice_lang(s_OperServ, u, OPER_USERLIST_RECORD,
+ cu->user->nick, GetIdent(cu->user),
+ GetHost(cu->user));
+ }
+ } else {
+ char mask[BUFSIZE];
+ int i;
+ User *u2;
+
+ notice_lang(s_OperServ, u, OPER_USERLIST_HEADER);
+
+ for (i = 0; i < 1024; i++) {
+ for (u2 = userlist[i]; u2; u2 = u2->next) {
+ if (pattern) {
+ snprintf(mask, sizeof(mask), "%s!%s@%s", u2->nick,
+ GetIdent(u2), GetHost(u2));
+ if (!match_wild_nocase(pattern, mask))
+ continue;
+ if (modes && !(u2->mode & modes))
+ continue;
+ }
+ notice_lang(s_OperServ, u, OPER_USERLIST_RECORD, u2->nick,
+ GetIdent(u2), GetHost(u2));
+ }
+ }
+ }
+
+ notice_lang(s_OperServ, u, OPER_USERLIST_END);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Callback function used to sort the admin list */
+
+static int compare_adminlist_entries(SList * slist, void *item1,
+ void *item2)
+{
+ NickCore *nc1 = item1, *nc2 = item2;
+ if (!nc1 || !nc2)
+ return -1; /* To tell to continue */
+ return stricmp(nc1->display, nc2->display);
+}
+
+/* Callback function used when an admin list entry is deleted */
+
+static void free_adminlist_entry(SList * slist, void *item)
+{
+ NickCore *nc = item;
+ nc->flags &= ~NI_SERVICES_ADMIN;
+}
+
+/* Lists an admin entry, prefixing it with the header if needed */
+
+static int admin_list(int number, NickCore * nc, User * u,
+ int *sent_header)
+{
+ if (!nc)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_ADMIN_LIST_FORMAT, number,
+ nc->display);
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int admin_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return admin_list(number, item, u, sent_header);
+}
+
+/* Services admin list viewing/modification. */
+
+static int do_admin(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+ int res = 0;
+
+ if (skeleton) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_SKELETON);
+ return MOD_CONT;
+ }
+
+ if (!cmd || (!nick && stricmp(cmd, "LIST") && stricmp(cmd, "CLEAR"))) {
+ syntax_error(s_OperServ, u, "ADMIN", OPER_ADMIN_SYNTAX);
+ } else if (!stricmp(cmd, "ADD")) {
+ if (!is_services_root(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!(na = findnick(nick))) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
+ return MOD_CONT;
+ }
+
+ if (na->nc->flags & NI_SERVICES_ADMIN
+ || slist_indexof(&servadmins, na->nc) != -1) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_EXISTS, nick);
+ return MOD_CONT;
+ }
+
+ res = slist_add(&servadmins, na->nc);
+ if (res == -2) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_REACHED_LIMIT, nick);
+ return MOD_CONT;
+ } else {
+ na->nc->flags |= NI_SERVICES_ADMIN;
+ notice_lang(s_OperServ, u, OPER_ADMIN_ADDED, nick);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "DEL")) {
+ if (!is_services_root(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (servadmins.count == 0) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
+ /* Deleting a range */
+ res = slist_delete_range(&servadmins, nick, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_ADMIN_DELETED_SEVERAL,
+ res);
+ }
+ } else {
+ if (!(na = findnick(nick))) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
+ return MOD_CONT;
+ }
+
+ if (!(na->nc->flags & NI_SERVICES_ADMIN)
+ || (res = slist_indexof(&servadmins, na->nc)) == -1) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_NOT_FOUND, nick);
+ return MOD_CONT;
+ }
+
+ slist_delete(&servadmins, res);
+ notice_lang(s_OperServ, u, OPER_ADMIN_DELETED, nick);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "LIST")) {
+ int sent_header = 0;
+
+ if (servadmins.count == 0) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (!nick || (isdigit(*nick)
+ && strspn(nick, "1234567890,-") == strlen(nick))) {
+ res =
+ slist_enum(&servadmins, nick, &admin_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
+ return MOD_CONT;
+ } else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Admin");
+ }
+ } else {
+ int i;
+
+ for (i = 0; i < servadmins.count; i++)
+ if (!stricmp
+ (nick, ((NickCore *) servadmins.list[i])->display)
+ || match_wild_nocase(nick,
+ ((NickCore *) servadmins.
+ list[i])->display))
+ admin_list(i + 1, servadmins.list[i], u, &sent_header);
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Admin");
+ }
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ if (!is_services_root(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (servadmins.count == 0) {
+ notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ slist_clear(&servadmins, 1);
+ notice_lang(s_OperServ, u, OPER_ADMIN_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "ADMIN", OPER_ADMIN_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Callback function used to sort the oper list */
+
+static int compare_operlist_entries(SList * slist, void *item1,
+ void *item2)
+{
+ NickCore *nc1 = item1, *nc2 = item2;
+ if (!nc1 || !nc2)
+ return -1; /* To tell to continue */
+ return stricmp(nc1->display, nc2->display);
+}
+
+/* Callback function used when an oper list entry is deleted */
+
+static void free_operlist_entry(SList * slist, void *item)
+{
+ NickCore *nc = item;
+ nc->flags &= ~NI_SERVICES_OPER;
+}
+
+/* Lists an oper entry, prefixing it with the header if needed */
+
+static int oper_list(int number, NickCore * nc, User * u, int *sent_header)
+{
+ if (!nc)
+ return 0;
+
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_OPER_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_OPER_LIST_FORMAT, number, nc->display);
+ return 1;
+}
+
+/* Callback for enumeration purposes */
+
+static int oper_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ int *sent_header = va_arg(args, int *);
+
+ return oper_list(number, item, u, sent_header);
+}
+
+/**
+ * Display an Opers list Entry
+ **/
+static int opers_list(int number, NickCore * nc, User * u, char *level)
+{
+ User *au = NULL;
+ NickAlias *na;
+ int found;
+ int i;
+
+ if (!nc)
+ return 0;
+
+ found = 0;
+ if ((au = finduser(nc->display))) { /* see if user is online */
+ found = 1;
+ notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, '*', level,
+ nc->display);
+ } else {
+ for (i = 0; i < nc->aliases.count; i++) { /* check all aliases */
+ na = nc->aliases.list[i];
+ if ((au = finduser(na->nick))) { /* see if user is online */
+ found = 1;
+ notice_lang(s_OperServ, u, OPER_STAFF_AFORMAT, '*', level,
+ nc->display, na->nick);
+ }
+ }
+ }
+
+ if (!found)
+ notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, ' ', level,
+ nc->display);
+
+ return 1;
+}
+
+/**
+ * Function for the enumerator to call
+ **/
+static int opers_list_callback(SList * slist, int number, void *item,
+ va_list args)
+{
+ User *u = va_arg(args, User *);
+ char *level = va_arg(args, char *);
+
+ return opers_list(number, item, u, level);
+}
+
+/**
+ * Display all Services Opers/Admins with Level + Online Status
+ * /msg OperServ opers
+ **/
+static int do_staff(User * u)
+{
+ int idx = 0;
+ User *au = NULL;
+ NickCore *nc;
+ NickAlias *na;
+ int found;
+ int i;
+
+ notice_lang(s_OperServ, u, OPER_STAFF_LIST_HEADER);
+ slist_enum(&servopers, NULL, &opers_list_callback, u, "OPER");
+ slist_enum(&servadmins, NULL, &opers_list_callback, u, "ADMN");
+
+ for (idx = 0; idx < RootNumber; idx++) {
+ found = 0;
+ if ((au = finduser(ServicesRoots[idx]))) { /* see if user is online */
+ found = 1;
+ notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, '*', "ROOT",
+ ServicesRoots[idx]);
+ } else if ((nc = findcore(ServicesRoots[idx]))) {
+ for (i = 0; i < nc->aliases.count; i++) { /* check all aliases */
+ na = nc->aliases.list[i];
+ if ((au = finduser(na->nick))) { /* see if user is online */
+ found = 1;
+ notice_lang(s_OperServ, u, OPER_STAFF_AFORMAT,
+ '*', "ROOT", ServicesRoots[idx], na->nick);
+ }
+ }
+ }
+
+ if (!found)
+ notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, ' ', "ROOT",
+ ServicesRoots[idx]);
+
+ }
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Staff");
+ return MOD_CONT;
+}
+
+/* Services operator list viewing/modification. */
+
+static int do_oper(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *nick = strtok(NULL, " ");
+ NickAlias *na;
+ int res = 0;
+
+ if (skeleton) {
+ notice_lang(s_OperServ, u, OPER_OPER_SKELETON);
+ return MOD_CONT;
+ }
+
+ if (!cmd || (!nick && stricmp(cmd, "LIST") && stricmp(cmd, "CLEAR"))) {
+ syntax_error(s_OperServ, u, "OPER", OPER_OPER_SYNTAX);
+ } else if (!stricmp(cmd, "ADD")) {
+ if (!is_services_admin(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (!(na = findnick(nick))) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
+ return MOD_CONT;
+ }
+
+ if (na->nc->flags & NI_SERVICES_OPER
+ || slist_indexof(&servopers, na->nc) != -1) {
+ notice_lang(s_OperServ, u, OPER_OPER_EXISTS, nick);
+ return MOD_CONT;
+ }
+
+ res = slist_add(&servopers, na->nc);
+ if (res == -2) {
+ notice_lang(s_OperServ, u, OPER_OPER_REACHED_LIMIT, nick);
+ return MOD_CONT;
+ } else {
+ na->nc->flags |= NI_SERVICES_OPER;
+ notice_lang(s_OperServ, u, OPER_OPER_ADDED, nick);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "DEL")) {
+ if (!is_services_admin(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
+ /* Deleting a range */
+ res = slist_delete_range(&servopers, nick, NULL);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
+ return MOD_CONT;
+ } else if (res == 1) {
+ notice_lang(s_OperServ, u, OPER_OPER_DELETED_ONE);
+ } else {
+ notice_lang(s_OperServ, u, OPER_OPER_DELETED_SEVERAL, res);
+ }
+ } else {
+ if (!(na = findnick(nick))) {
+ notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
+ return MOD_CONT;
+ }
+
+ if (na->status & NS_VERBOTEN) {
+ notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
+ return MOD_CONT;
+ }
+
+ if (!(na->nc->flags & NI_SERVICES_OPER)
+ || (res = slist_indexof(&servopers, na->nc)) == -1) {
+ notice_lang(s_OperServ, u, OPER_OPER_NOT_FOUND, nick);
+ return MOD_CONT;
+ }
+
+ slist_delete(&servopers, res);
+ notice_lang(s_OperServ, u, OPER_OPER_DELETED, nick);
+ }
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "LIST")) {
+ int sent_header = 0;
+
+ if (servopers.count == 0) {
+ notice_lang(s_OperServ, u, OPER_OPER_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ if (!nick || (isdigit(*nick)
+ && strspn(nick, "1234567890,-") == strlen(nick))) {
+ res =
+ slist_enum(&servopers, nick, &oper_list_callback, u,
+ &sent_header);
+ if (res == 0) {
+ notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
+ return MOD_CONT;
+ } else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Oper");
+ }
+ } else {
+ int i;
+
+ for (i = 0; i < servopers.count; i++)
+ if (!stricmp
+ (nick, ((NickCore *) servopers.list[i])->display)
+ || match_wild_nocase(nick,
+ ((NickCore *) servopers.list[i])->
+ display))
+ oper_list(i + 1, servopers.list[i], u, &sent_header);
+
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
+ else {
+ notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Oper");
+ }
+ }
+ } else if (!stricmp(cmd, "CLEAR")) {
+ if (!is_services_admin(u)) {
+ notice_lang(s_OperServ, u, PERMISSION_DENIED);
+ return MOD_CONT;
+ }
+
+ if (servopers.count == 0) {
+ notice_lang(s_OperServ, u, OPER_OPER_LIST_EMPTY);
+ return MOD_CONT;
+ }
+
+ slist_clear(&servopers, 1);
+ notice_lang(s_OperServ, u, OPER_OPER_CLEAR);
+ } else {
+ syntax_error(s_OperServ, u, "OPER", OPER_OPER_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+/* Set various Services runtime options. */
+
+static int do_set(User * u)
+{
+ char *option = strtok(NULL, " ");
+ char *setting = strtok(NULL, " ");
+
+ if (!option || !setting) {
+ syntax_error(s_OperServ, u, "SET", OPER_SET_SYNTAX);
+
+ } else if (stricmp(option, "IGNORE") == 0) {
+ if (stricmp(setting, "on") == 0) {
+ allow_ignore = 1;
+ notice_lang(s_OperServ, u, OPER_SET_IGNORE_ON);
+ } else if (stricmp(setting, "off") == 0) {
+ allow_ignore = 0;
+ notice_lang(s_OperServ, u, OPER_SET_IGNORE_OFF);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_IGNORE_ERROR);
+ }
+
+ } else if (stricmp(option, "READONLY") == 0) {
+ if (stricmp(setting, "on") == 0) {
+ readonly = 1;
+ alog("Read-only mode activated");
+ close_log();
+ notice_lang(s_OperServ, u, OPER_SET_READONLY_ON);
+ } else if (stricmp(setting, "off") == 0) {
+ readonly = 0;
+ open_log();
+ alog("Read-only mode deactivated");
+ notice_lang(s_OperServ, u, OPER_SET_READONLY_OFF);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_READONLY_ERROR);
+ }
+
+ } else if (stricmp(option, "LOGCHAN") == 0) {
+ /* Unlike the other SET commands where only stricmp is necessary,
+ * we also have to ensure that LogChannel is defined or we can't
+ * send to it.
+ *
+ * -jester
+ */
+ if (LogChannel && (stricmp(setting, "on") == 0)) {
+#ifdef IRC_HYBRID
+ send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), LogChannel,
+ s_GlobalNoticer);
+#endif
+ logchan = 1;
+ alog("Now sending log messages to %s", LogChannel);
+ notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_ON, LogChannel);
+ } else if (LogChannel && (stricmp(setting, "off") == 0)) {
+#ifdef IRC_HYBRID
+ send_cmd(s_GlobalNoticer, "PART %s :Parting", LogChannel);
+#endif
+ logchan = 0;
+ alog("No longer sending log messages to a channel");
+ notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_OFF);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_ERROR);
+ }
+ /**
+ * Allow the user to turn super admin on/off
+ *
+ * Rob
+ **/
+ } else if (stricmp(option, "SUPERADMIN") == 0) {
+ if (SuperAdmin && (stricmp(setting, "on") == 0)) {
+ u->isSuperAdmin = 1;
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ON);
+ alog("%s: %s is a SuperAdmin ", s_OperServ, u->nick);
+ wallops(s_OperServ, getstring2(NULL, OPER_SUPER_ADMIN_WALL_ON),
+ u->nick);
+ } else if (SuperAdmin && (stricmp(setting, "off") == 0)) {
+ u->isSuperAdmin = 0;
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_OFF);
+ alog("%s: %s is no longer a SuperAdmin", s_OperServ, u->nick);
+ wallops(s_OperServ,
+ getstring2(NULL, OPER_SUPER_ADMIN_WALL_OFF), u->nick);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_SYNTAX);
+ }
+ } else if (stricmp(option, "DEBUG") == 0) {
+ if (stricmp(setting, "on") == 0) {
+ debug = 1;
+ alog("Debug mode activated");
+ notice_lang(s_OperServ, u, OPER_SET_DEBUG_ON);
+ } else if (stricmp(setting, "off") == 0 ||
+ (*setting == '0' && atoi(setting) == 0)) {
+ alog("Debug mode deactivated");
+ debug = 0;
+ notice_lang(s_OperServ, u, OPER_SET_DEBUG_OFF);
+ } else if (isdigit(*setting) && atoi(setting) > 0) {
+ debug = atoi(setting);
+ alog("Debug mode activated (level %d)", debug);
+ notice_lang(s_OperServ, u, OPER_SET_DEBUG_LEVEL, debug);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_DEBUG_ERROR);
+ }
+
+ } else if (stricmp(option, "NOEXPIRE") == 0) {
+ if (stricmp(setting, "ON") == 0) {
+ noexpire = 1;
+ alog("No expire mode activated");
+ notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_ON);
+ } else if (stricmp(setting, "OFF") == 0) {
+ noexpire = 0;
+ alog("No expire mode deactivated");
+ notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_OFF);
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_ERROR);
+ }
+
+ } else {
+ notice_lang(s_OperServ, u, OPER_SET_UNKNOWN_OPTION, option);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_noop(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *server = strtok(NULL, " ");
+
+ if (!cmd || !server) {
+ syntax_error(s_OperServ, u, "NOOP", OPER_NOOP_SYNTAX);
+ } else if (!stricmp(cmd, "SET")) {
+ User *u2;
+ User *u3 = NULL;
+ char reason[NICKMAX + 32];
+
+ /* Remove the O:lines */
+ s_svsnoop(server, 1);
+
+ snprintf(reason, sizeof(reason), "NOOP command used by %s",
+ u->nick);
+ if (WallOSNoOp)
+ wallops(s_OperServ, "\2%s\2 used NOOP on \2%s\2", u->nick,
+ server);
+ notice_lang(s_OperServ, u, OPER_NOOP_SET, server);
+
+ /* Kill all the IRCops of the server */
+ for (u2 = firstuser(); u2; u2 = u3) {
+ u3 = nextuser();
+ if ((u2) && is_oper(u2) && (u2->server->name)
+ && !stricmp(u2->server->name, server)) {
+ kill_user(s_OperServ, u2->nick, reason);
+ }
+ }
+ } else if (!stricmp(cmd, "REVOKE")) {
+ s_svsnoop(server, 0);
+ notice_lang(s_OperServ, u, OPER_NOOP_REVOKE, server);
+ } else {
+ syntax_error(s_OperServ, u, "NOOP", OPER_NOOP_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_jupe(User * u)
+{
+ char *jserver = strtok(NULL, " ");
+ char *reason = strtok(NULL, "");
+ char rbuf[256];
+
+ if (!jserver) {
+ syntax_error(s_OperServ, u, "JUPE", OPER_JUPE_SYNTAX);
+ } else {
+ if (!isValidHost(jserver, 3)) {
+ notice_lang(s_OperServ, u, OPER_JUPE_HOST_ERROR);
+ } else {
+ snprintf(rbuf, sizeof(rbuf), "Juped by %s%s%s", u->nick,
+ reason ? ": " : "", reason ? reason : "");
+
+ send_cmd(NULL, "SQUIT %s :%s", jserver, rbuf);
+#ifdef IRC_PTLINK
+ send_cmd(NULL, "SERVER %s 1 Anope.Services%s :%s",
+ jserver, version_number, rbuf);
+#else
+ send_cmd(NULL, "SERVER %s 2 :%s", jserver, rbuf);
+#endif
+ new_server(me_server, jserver, rbuf, SERVER_JUPED);
+
+ if (WallOSJupe)
+ wallops(s_OperServ, "\2%s\2 used JUPE on \2%s\2", u->nick,
+ jserver);
+ }
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_raw(User * u)
+{
+
+ if (!DisableRaw) {
+
+ char *text = strtok(NULL, "");
+
+ if (!text)
+ syntax_error(s_OperServ, u, "RAW", OPER_RAW_SYNTAX);
+ else {
+ send_cmd(NULL, "%s", text);
+ if (WallOSRaw) {
+ char *kw = strtok(text, " ");
+ while (kw && *kw == ':')
+ kw = strtok(NULL, " ");
+ wallops(s_OperServ, "\2%s\2 used RAW command for \2%s\2",
+ u->nick,
+ (kw ? kw : "\2non RFC compliant message\2"));
+ }
+ alog("%s used RAW command for %s", u->nick, text);
+ }
+ } else {
+ notice_lang(s_OperServ, u, RAW_DISABLED);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_update(User * u)
+{
+ notice_lang(s_OperServ, u, OPER_UPDATING);
+ save_data = 1;
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_reload(User * u)
+{
+ if (!read_config(1)) {
+ quitmsg = calloc(28 + strlen(u->nick), 1);
+ if (!quitmsg)
+ quitmsg =
+ "Error during the reload of the configuration file, but out of memory!";
+ else
+ sprintf(quitmsg,
+ "Error during the reload of the configuration file!");
+ quitting = 1;
+ }
+
+ notice_lang(s_OperServ, u, OPER_RELOAD);
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_os_quit(User * u)
+{
+ quitmsg = calloc(28 + strlen(u->nick), 1);
+ if (!quitmsg)
+ quitmsg = "QUIT command received, but out of memory!";
+ else
+ sprintf(quitmsg, "QUIT command received from %s", u->nick);
+
+ if (GlobalOnCycle) {
+ oper_global(NULL, "%s", GlobalOnCycleMessage);
+ }
+ quitting = 1;
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_shutdown(User * u)
+{
+ quitmsg = calloc(32 + strlen(u->nick), 1);
+ if (!quitmsg)
+ quitmsg = "SHUTDOWN command received, but out of memory!";
+ else
+ sprintf(quitmsg, "SHUTDOWN command received from %s", u->nick);
+
+ if (GlobalOnCycle) {
+ oper_global(NULL, "%s", GlobalOnCycleMessage);
+ }
+ save_data = 1;
+ delayed_quit = 1;
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+static int do_restart(User * u)
+{
+#ifdef SERVICES_BIN
+ quitmsg = calloc(31 + strlen(u->nick), 1);
+ if (!quitmsg)
+ quitmsg = "RESTART command received, but out of memory!";
+ else
+ sprintf(quitmsg, "RESTART command received from %s", u->nick);
+
+ if (GlobalOnCycle) {
+ oper_global(NULL, "%s", GlobalOnCycleMessage);
+ }
+ /* raise(SIGHUP); */
+ do_restart_services();
+#else
+ notice_lang(s_OperServ, u, OPER_CANNOT_RESTART);
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+
+#ifdef DEBUG_COMMANDS
+
+static int do_matchwild(User * u)
+{
+ char *pat = strtok(NULL, " ");
+ char *str = strtok(NULL, " ");
+ if (pat && str)
+ notice(s_OperServ, u->nick, "%d", match_wild(pat, str));
+ else
+ notice(s_OperServ, u->nick, "Syntax error.");
+ return MOD_CONT;
+}
+
+#endif /* DEBUG_COMMANDS */
+
+/*************************************************************************/
+
+/* Kill all users matching a certain host. The host is obtained from the
+ * supplied nick. The raw hostmsk is not supplied with the command in an effort
+ * to prevent abuse and mistakes from being made - which might cause *.com to
+ * be killed. It also makes it very quick and simple to use - which is usually
+ * what you want when someone starts loading numerous clones. In addition to
+ * killing the clones, we add a temporary AKILL to prevent them from
+ * immediately reconnecting.
+ * Syntax: KILLCLONES nick
+ * -TheShadow (29 Mar 1999)
+ */
+
+static int do_killclones(User * u)
+{
+ char *clonenick = strtok(NULL, " ");
+ int count = 0;
+ User *cloneuser, *user, *tempuser;
+ char *clonemask, *akillmask;
+ char killreason[NICKMAX + 32];
+ char akillreason[] = "Temporary KILLCLONES akill.";
+
+ if (!clonenick) {
+ notice_lang(s_OperServ, u, OPER_KILLCLONES_SYNTAX);
+
+ } else if (!(cloneuser = finduser(clonenick))) {
+ notice_lang(s_OperServ, u, OPER_KILLCLONES_UNKNOWN_NICK,
+ clonenick);
+
+ } else {
+ clonemask = smalloc(strlen(cloneuser->host) + 5);
+ sprintf(clonemask, "*!*@%s", cloneuser->host);
+
+ akillmask = smalloc(strlen(cloneuser->host) + 3);
+ sprintf(akillmask, "*@%s", cloneuser->host);
+
+ user = firstuser();
+ while (user) {
+ if (match_usermask(clonemask, user) != 0) {
+ tempuser = nextuser();
+ count++;
+ snprintf(killreason, sizeof(killreason),
+ "Cloning [%d]", count);
+ kill_user(NULL, user->nick, killreason);
+ user = tempuser;
+ } else {
+ user = nextuser();
+ }
+ }
+
+ add_akill(u, akillmask, u->nick,
+ time(NULL) + KillClonesAkillExpire, akillreason);
+
+ wallops(s_OperServ, "\2%s\2 used KILLCLONES for \2%s\2 killing "
+ "\2%d\2 clones. A temporary AKILL has been added "
+ "for \2%s\2.", u->nick, clonemask, count, akillmask);
+
+ alog("%s: KILLCLONES: %d clone(s) matching %s killed.",
+ s_OperServ, count, clonemask);
+
+ free(akillmask);
+ free(clonemask);
+ }
+ return MOD_CONT;
+}
+
+/**
+ * Defcon - A method of impelemting various stages of securty, the hope is this will help serives
+ * protect a network during an attack, allowing admins to choose the precautions taken at each
+ * level.
+ *
+ * /msg OperServ DefCon [level]
+ *
+ **/
+
+static int do_defcon(User * u)
+{
+ char *lvl = strtok(NULL, " ");
+ int newLevel = 0;
+ char *langglobal;
+ langglobal = getstring(NULL, DEFCON_GLOBAL);
+
+ if (!DefConLevel) { /* If we dont have a .conf setting! */
+ notice_lang(s_OperServ, u, OPER_DEFCON_NO_CONF);
+ return MOD_CONT;
+ }
+
+ if (!lvl) {
+ notice_lang(s_OperServ, u, OPER_DEFCON_CHANGED, DefConLevel);
+ defcon_sendlvls(u);
+ return MOD_CONT;
+ }
+ newLevel = atoi(lvl);
+ if (newLevel < 1 || newLevel > 5) {
+ notice_lang(s_OperServ, u, OPER_DEFCON_SYNTAX);
+ return MOD_CONT;
+ }
+
+ DefConLevel = newLevel;
+ DefContimer = time(NULL);
+ notice_lang(s_OperServ, u, OPER_DEFCON_CHANGED, DefConLevel);
+ defcon_sendlvls(u);
+ alog("Defcon level changed to %d by Oper %s", newLevel, u->nick);
+ wallops(s_OperServ, getstring2(NULL, OPER_DEFCON_WALL), u->nick,
+ newLevel);
+ /* Global notice the user what is happening. Also any Message that
+ the Admin would like to add. Set in config file. */
+ if (GlobalOnDefcon) {
+ if ((DefConLevel == 5) && (DefConOffMessage)) {
+ oper_global(NULL, "%s", DefConOffMessage);
+ } else {
+ oper_global(NULL, langglobal, DefConLevel);
+ }
+ }
+ if (GlobalOnDefconMore) {
+ if ((DefConOffMessage) && DefConLevel == 5) {
+ } else {
+ oper_global(NULL, "%s", DefconMessage);
+ }
+ }
+ /* Run any defcon functions, e.g. FORCE CHAN MODE */
+ runDefCon();
+ return MOD_CONT;
+}
+
+/**
+ * Reverse the mode string, used for remove DEFCON chan modes.
+ **/
+char *defconReverseModes(const char *modes)
+{
+ char *newmodes = NULL;
+ int i = 0;
+ if (!modes) {
+ return NULL;
+ }
+ if (!(newmodes = malloc(sizeof(char) * strlen(modes) + 1))) {
+ return NULL;
+ }
+ for (i = 0; i < strlen(modes); i++) {
+ if (modes[i] == '+')
+ newmodes[i] = '-';
+ else if (modes[i] == '-')
+ newmodes[i] = '+';
+ else
+ newmodes[i] = modes[i];
+ }
+ newmodes[i] = '\0';
+ return newmodes;
+}
+
+/**
+ * Returns 1 if the passed level is part of the CURRENT defcon, else 0 is returned
+ **/
+int checkDefCon(int level)
+{
+ return DefCon[DefConLevel] & level;
+}
+
+/**
+ * Run DefCon level specific Functions.
+ **/
+void runDefCon(void)
+{
+ char *newmodes;
+ if (checkDefCon(DEFCON_FORCE_CHAN_MODES)) {
+ if (DefConChanModes && !DefConModesSet) {
+ if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
+ alog("DEFCON: setting %s on all chan's", DefConChanModes);
+ do_mass_mode(DefConChanModes);
+ DefConModesSet = 1;
+ }
+ }
+ } else {
+ if (DefConChanModes && (DefConModesSet != 0)) {
+ if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
+ if ((newmodes = defconReverseModes(DefConChanModes))) {
+ alog("DEFCON: setting %s on all chan's", newmodes);
+ do_mass_mode(newmodes);
+ }
+ DefConModesSet = 0;
+ }
+ }
+ }
+}
+
+/**
+ * Automaticaly re-set the DefCon level if the time limit has expired.
+ **/
+void resetDefCon(int level)
+{
+ if (DefConLevel != level) {
+ if ((DefContimer)
+ && (time(NULL) - DefContimer >= dotime(DefConTimeOut))) {
+ DefConLevel = level;
+ alog("Defcon level timeout, returning to lvl %d", level);
+ wallops(s_OperServ, getstring2(NULL, OPER_DEFCON_WALL),
+ s_OperServ, level);
+ if (GlobalOnDefcon) {
+ if (DefConOffMessage) {
+ oper_global(NULL, "%s", DefConOffMessage);
+ } else {
+ oper_global(NULL, getstring(NULL, DEFCON_GLOBAL),
+ DefConLevel);
+ }
+ }
+ if (GlobalOnDefconMore && !DefConOffMessage) {
+ oper_global(NULL, "%s", DefconMessage);
+ }
+ runDefCon();
+ }
+ }
+}
+
+/**
+ * Send a message to the oper about which precautions are "active" for this level
+ **/
+static void defcon_sendlvls(User * u)
+{
+ if (checkDefCon(DEFCON_NO_NEW_CHANNELS)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_CHANNELS);
+ }
+ if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_NICKS);
+ }
+ if (checkDefCon(DEFCON_NO_MLOCK_CHANGE)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_MLOCK_CHANGE);
+ }
+ if (checkDefCon(DEFCON_FORCE_CHAN_MODES) && (DefConChanModes)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_FORCE_CHAN_MODES,
+ DefConChanModes);
+ }
+ if (checkDefCon(DEFCON_REDUCE_SESSION)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_REDUCE_SESSION,
+ DefConSessionLimit);
+ }
+ if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_CLIENTS);
+ }
+ if (checkDefCon(DEFCON_OPER_ONLY)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_OPER_ONLY);
+ }
+ if (checkDefCon(DEFCON_SILENT_OPER_ONLY)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_SILENT_OPER_ONLY);
+ }
+ if (checkDefCon(DEFCON_AKILL_NEW_CLIENTS)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_AKILL_NEW_CLIENTS);
+ }
+ if (checkDefCon(DEFCON_NO_NEW_MEMOS)) {
+ notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_MEMOS);
+ }
+}
+
+/**
+ * ChanKill - Akill an entire channel (got botnet?)
+ *
+ * /msg OperServ ChanKill +expire #channel reason
+ *
+ **/
+
+static int do_chankill(User * u)
+{
+ char *expiry, *channel, *reason;
+ time_t expires;
+ char breason[BUFSIZE];
+ char mask[USERMAX + HOSTMAX + 2];
+ struct c_userlist *cu, *next;
+ Channel *c;
+
+ channel = strtok(NULL, " ");
+ if (channel && *channel == '+') {
+ expiry = channel;
+ channel = strtok(NULL, " ");
+ } else {
+ expiry = NULL;
+ }
+
+ expires = expiry ? dotime(expiry) : ChankillExpiry;
+ if (expiry && isdigit(expiry[strlen(expiry) - 1]))
+ expires *= 86400;
+ if (expires != 0 && expires < 60) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ if (channel && (reason = strtok(NULL, ""))) {
+
+ if (AddAkiller) {
+ snprintf(breason, sizeof(breason), "[%s] %s", u->nick, reason);
+ reason = sstrdup(breason);
+ }
+
+ if ((c = findchan(channel))) {
+ for (cu = c->users; cu; cu = next) {
+ next = cu->next;
+ if (is_oper(cu->user)) {
+ continue;
+ }
+ strncpy(mask, "*@", 3); /* Use *@" for the akill's, */
+ strncat(mask, cu->user->host, HOSTMAX);
+ add_akill(NULL, mask, s_OperServ, expires, reason);
+ check_akill(cu->user->nick, cu->user->username,
+ cu->user->host, NULL, NULL);
+ }
+ if (WallOSAkill) {
+ wallops(s_OperServ, "%s used CHANKILL on %s (%s)", u->nick,
+ channel, reason);
+ }
+ } else {
+ notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, channel);
+ }
+ } else {
+ syntax_error(s_OperServ, u, "CHANKILL", OPER_CHANKILL_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+#ifdef USE_MODULES
+
+int do_modload(User * u)
+{
+ char *name;
+ Module *m;
+
+ name = strtok(NULL, "");
+ if (!name) {
+ syntax_error(s_OperServ, u, "MODLOAD", OPER_MODULE_LOAD_SYNTAX);
+ return MOD_CONT;
+ }
+ m = findModule(name);
+ if (!m) {
+ m = createModule(name);
+ mod_current_module = m;
+ mod_current_user = u;
+ mod_current_op = 1;
+ } else {
+ notice_lang(s_OperServ, u, OPER_MODULE_LOAD_FAIL, name);
+ }
+ return MOD_CONT;
+}
+
+int do_modunload(User * u)
+{
+ char *name;
+ Module *m;
+
+ name = strtok(NULL, "");
+ if (!name) {
+ syntax_error(s_OperServ, u, "MODUNLOAD",
+ OPER_MODULE_UNLOAD_SYNTAX);
+ return MOD_CONT;
+ }
+ m = findModule(name);
+ if (m) {
+ mod_current_user = u;
+ mod_current_module = m;
+ mod_current_op = 2;
+ } else {
+ notice_lang(s_OperServ, u, OPER_MODULE_REMOVE_FAIL, name);
+ }
+ return MOD_CONT;
+}
+
+int do_modlist(User * u)
+{
+ int idx;
+ int count = 0;
+ ModuleHash *current = NULL;
+
+ notice_lang(s_OperServ, u, OPER_MODULE_LIST_HEADER);
+
+ for (idx = 0; idx != MAX_CMD_HASH; idx++) {
+ for (current = MODULE_HASH[idx]; current; current = current->next) {
+ notice_lang(s_OperServ, u, OPER_MODULE_LIST, current->name,
+ current->m->version);
+ count++;
+ }
+ }
+ if (count == 0) {
+ notice_lang(s_OperServ, u, OPER_MODULE_NO_LIST);
+ } else {
+ notice_lang(s_OperServ, u, OPER_MODULE_LIST_FOOTER, count);
+ }
+
+ return MOD_CONT;
+}
+
+int do_modinfo(User * u)
+{
+ char *file;
+ struct tm tm;
+ char timebuf[64];
+ Module *m;
+ int idx = 0;
+ int display = 0;
+
+ file = strtok(NULL, "");
+ if (!file) {
+ syntax_error(s_OperServ, u, "MODINFO", OPER_MODULE_INFO_SYNTAX);
+ return MOD_CONT;
+ }
+ m = findModule(file);
+ if (m) {
+ tm = *localtime(&m->time);
+ strftime_lang(timebuf, sizeof(timebuf), u,
+ STRFTIME_DATE_TIME_FORMAT, &tm);
+ notice_lang(s_OperServ, u, OPER_MODULE_INFO_LIST, m->name,
+ m->version ? m->version : "?",
+ m->author ? m->author : "?", timebuf);
+ for (idx = 0; idx < MAX_CMD_HASH; idx++) {
+ display += showModuleCmdLoaded(HOSTSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(OPERSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(NICKSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(CHANSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(BOTSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(MEMOSERV[idx], m->name, u);
+ display += showModuleCmdLoaded(HELPSERV[idx], m->name, u);
+ display += showModuleMsgLoaded(IRCD[idx], m->name, u);
+
+ }
+ }
+ if (display == 0) {
+ notice_lang(s_OperServ, u, OPER_MODULE_NO_INFO, file);
+ }
+ return MOD_CONT;
+}
+
+static int showModuleCmdLoaded(CommandHash * cmdList, char *mod_name,
+ User * u)
+{
+ Command *c;
+ CommandHash *current;
+ int display = 0;
+
+ for (current = cmdList; current; current = current->next) {
+ for (c = current->c; c; c = c->next) {
+ if ((c->mod_name) && (stricmp(c->mod_name, mod_name) == 0)) {
+ notice_lang(s_OperServ, u, OPER_MODULE_CMD_LIST,
+ c->service, c->name);
+ display++;
+ }
+ }
+ }
+ return display;
+}
+
+static int showModuleMsgLoaded(MessageHash * msgList, char *mod_name,
+ User * u)
+{
+ Message *msg;
+ MessageHash *mcurrent;
+ int display = 0;
+ for (mcurrent = msgList; mcurrent; mcurrent = mcurrent->next) {
+ for (msg = mcurrent->m; msg; msg = msg->next) {
+ if ((msg->mod_name) && (stricmp(msg->mod_name, mod_name) == 0)) {
+ notice_lang(s_OperServ, u, OPER_MODULE_MSG_LIST,
+ msg->name);
+ display++;
+ }
+ }
+ }
+ return display;
+}
+
+#endif
+
+/*************************************************************************/
diff --git a/src/process.c b/src/process.c
new file mode 100644
index 000000000..c917c9785
--- /dev/null
+++ b/src/process.c
@@ -0,0 +1,268 @@
+/* Main processing code for Services.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "messages.h"
+#include "modules.h"
+extern Module *mod_current_module;
+extern char *mod_current_module_name;
+extern User *mod_current_user;
+extern int mod_current_op;
+extern char *mod_current_buffer;
+/*************************************************************************/
+/*************************************************************************/
+
+/* Use ignore code? */
+int allow_ignore = 1;
+
+/* People to ignore (hashed by first character of nick). */
+IgnoreData *ignore[256];
+
+/*************************************************************************/
+
+/* add_ignore: Add someone to the ignorance list for the next `delta'
+ * seconds.
+ */
+
+void add_ignore(const char *nick, time_t delta)
+{
+ IgnoreData *ign;
+ char who[NICKMAX];
+ time_t now = time(NULL);
+ IgnoreData **whichlist = &ignore[tolower(nick[0])];
+
+ strscpy(who, nick, NICKMAX);
+ for (ign = *whichlist; ign; ign = ign->next) {
+ if (stricmp(ign->who, who) == 0)
+ break;
+ }
+ if (ign) {
+ if (ign->time > now)
+ ign->time += delta;
+ else
+ ign->time = now + delta;
+ } else {
+ ign = scalloc(sizeof(*ign), 1);
+ strscpy(ign->who, who, sizeof(ign->who));
+ ign->time = now + delta;
+ ign->next = *whichlist;
+ *whichlist = ign;
+ }
+}
+
+/*************************************************************************/
+
+/* get_ignore: Retrieve an ignorance record for a nick. If the nick isn't
+ * being ignored, return NULL and flush the record from the
+ * in-core list if it exists (i.e. ignore timed out).
+ */
+
+IgnoreData *get_ignore(const char *nick)
+{
+ IgnoreData *ign, *prev;
+ time_t now = time(NULL);
+ IgnoreData **whichlist = &ignore[tolower(nick[0])];
+ User *u = finduser(nick);
+ IgnoreData **whichlist2 = NULL;
+ // Bleah, this doesn't work. I need a way to get the first char of u->username.
+ //if (u) whichlist2 = &ignore[tolower(u->username[0])];
+ IgnoreData **whichlistast = &ignore[42]; /* * */
+ IgnoreData **whichlistqst = &ignore[63]; /* ? */
+ int finished = 0;
+ for (ign = *whichlist, prev = NULL; ign; prev = ign, ign = ign->next) {
+ if (stricmp(ign->who, nick) == 0) {
+ finished = 1;
+ break;
+ }
+ }
+ if (!finished && whichlist2) {
+ for (ign = *whichlist2, prev = NULL; ign;
+ prev = ign, ign = ign->next) {
+ if (match_usermask(ign->who, u)) {
+ finished = 1;
+ break;
+ }
+ }
+ }
+ if (!finished) {
+ for (ign = *whichlistast, prev = NULL; ign;
+ prev = ign, ign = ign->next) {
+ if (match_usermask(ign->who, u)) {
+ finished = 1;
+ break;
+ }
+ }
+ }
+ if (!finished) {
+ for (ign = *whichlistqst, prev = NULL; ign;
+ prev = ign, ign = ign->next) {
+ if (match_usermask(ign->who, u)) {
+ finished = 1;
+ break;
+ }
+ }
+ }
+ if (ign && ign->time <= now) {
+ if (prev)
+ prev->next = ign->next;
+ else
+ *whichlist = ign->next;
+ free(ign);
+ ign = NULL;
+ }
+ return ign;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* split_buf: Split a buffer into arguments and store the arguments in an
+ * argument vector pointed to by argv (which will be malloc'd
+ * as necessary); return the argument count. If colon_special
+ * is non-zero, then treat a parameter with a leading ':' as
+ * the last parameter of the line, per the IRC RFC. Destroys
+ * the buffer by side effect.
+ */
+
+int split_buf(char *buf, char ***argv, int colon_special)
+{
+ int argvsize = 8;
+ int argc;
+ char *s;
+
+ *argv = scalloc(sizeof(char *) * argvsize, 1);
+ argc = 0;
+ while (*buf) {
+ if (argc == argvsize) {
+ argvsize += 8;
+ *argv = srealloc(*argv, sizeof(char *) * argvsize);
+ }
+ if (*buf == ':') {
+ (*argv)[argc++] = buf + 1;
+ buf = "";
+ } else {
+ s = strpbrk(buf, " ");
+ if (s) {
+ *s++ = 0;
+ while (*s == ' ')
+ s++;
+ } else {
+ s = buf + strlen(buf);
+ }
+ (*argv)[argc++] = buf;
+ buf = s;
+ }
+ }
+ return argc;
+}
+
+/*************************************************************************/
+
+/* process: Main processing routine. Takes the string in inbuf (global
+ * variable) and does something appropriate with it. */
+
+void process()
+{
+ int retVal = 0;
+ Message *current = NULL;
+ char source[64];
+ char cmd[64];
+ char buf[512]; /* Longest legal IRC command line */
+ char *s;
+ int ac; /* Parameters for the command */
+ char **av;
+ Message *m;
+
+
+ /* If debugging, log the buffer */
+ if (debug)
+ alog("debug: Received: %s", inbuf);
+
+ /* First make a copy of the buffer so we have the original in case we
+ * crash - in that case, we want to know what we crashed on. */
+ strscpy(buf, inbuf, sizeof(buf));
+
+ doCleanBuffer((char *) buf);
+
+ /* Split the buffer into pieces. */
+ if (*buf == ':') {
+ s = strpbrk(buf, " ");
+ if (!s)
+ return;
+ *s = 0;
+ while (isspace(*++s));
+ strscpy(source, buf + 1, sizeof(source));
+ memmove(buf, s, strlen(s) + 1);
+ } else {
+ *source = 0;
+ }
+ if (!*buf)
+ return;
+ s = strpbrk(buf, " ");
+ if (s) {
+ *s = 0;
+ while (isspace(*++s));
+ } else
+ s = buf + strlen(buf);
+ strscpy(cmd, buf, sizeof(cmd));
+ ac = split_buf(s, &av, 1);
+ if (mod_current_buffer) {
+ free(mod_current_buffer);
+ }
+ if (av[1]) {
+ mod_current_buffer = sstrdup(av[1]);
+ } else {
+ mod_current_buffer = NULL;
+ }
+ /* Do something with the message. */
+ m = find_message(cmd);
+ if (m) {
+ if (m->func) {
+ mod_current_module_name = m->mod_name;
+ retVal = m->func(source, ac, av);
+ mod_current_module_name = NULL;
+ if (retVal == MOD_CONT) {
+ current = m->next;
+ while (current && current->func && retVal == MOD_CONT) {
+ mod_current_module_name = current->mod_name;
+ retVal = current->func(source, ac, av);
+ mod_current_module_name = NULL;
+ current = current->next;
+ }
+ }
+ }
+ } else {
+ if (debug)
+ alog("unknown message from server (%s)", inbuf);
+ }
+ if (mod_current_op == 1) {
+ alog("trying to load [%s]", mod_current_module->name);
+ alog("status: [%d]",
+ loadModule(mod_current_module, mod_current_user));
+ mod_current_module = NULL;
+ mod_current_user = NULL;
+ mod_current_op = 0;
+ } else if (mod_current_op == 2) {
+ alog("trying to unload [%s]", mod_current_module->name);
+ alog("status: [%d]",
+ unloadModule(mod_current_module, mod_current_user));
+ mod_current_module = NULL;
+ mod_current_user = NULL;
+ mod_current_op = 0;
+ }
+ /* Free argument list we created */
+ free(av);
+}
+
+/*************************************************************************/
diff --git a/src/protocol.c b/src/protocol.c
new file mode 100644
index 000000000..ba021bf3f
--- /dev/null
+++ b/src/protocol.c
@@ -0,0 +1,168 @@
+/* Simple interfaces to various protocols.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/* Makes an permanent ban from all the servers. Assumes that the matching clients are killed. */
+
+void s_akill(char *user, char *host, char *who, time_t when,
+ time_t expires, char *reason)
+{
+#if defined(IRC_BAHAMUT)
+ /* send_cmd(NULL, "AKILL %s %s %d %s %ld :%s", host, user, 86400*2, who, when, reason); */
+ send_cmd(NULL, "AKILL %s %s %d %s %ld :%s", host, user,
+ 86400 * 2, who, time(NULL), reason);
+
+#elif defined(IRC_UNREAL)
+ send_cmd(NULL, "TKL + G %s %s %s %ld %ld :%s", user, host, who, time(NULL) + 86400 * 2, /* Avoids filling the akill list of servers too much */
+ when, reason);
+#elif defined(IRC_DREAMFORGE)
+ send_cmd(NULL, "AKILL %s %s :%s", host, user, reason);
+#elif defined(IRC_PTLINK)
+ send_cmd(ServerName, "GLINE %s@%s %i %s :%s", user, host, 86400 * 2,
+ who, reason);
+#elif defined(IRC_HYBRID)
+ send_cmd(s_OperServ, "KLINE * %ld %s %s :%s",
+ (expires - (long) time(NULL)), user, host, reason);
+#endif
+}
+
+/*************************************************************************/
+
+/* Removes a permanent ban from all the servers. */
+
+void s_rakill(char *user, char *host)
+{
+#if defined(IRC_PTLINK)
+ send_cmd(NULL, "UNGLINE %s@%s", user, host);
+#elif defined(IRC_UNREAL)
+ send_cmd(NULL, "TKL - G %s %s %s", user, host, s_OperServ);
+#elif !defined(IRC_HYBRID)
+ send_cmd(NULL, "RAKILL %s %s", host, user);
+#endif
+}
+
+/*************************************************************************/
+
+void s_sgline(char *mask, char *reason)
+{
+#ifdef IRC_BAHAMUT
+ /* User *u; */
+
+ send_cmd(NULL, "SGLINE %d :%s:%s", strlen(mask), mask, reason);
+
+ /* Do things properly: kill all corresponding users as this is
+ unfortunately not done by the IRCds :/ */
+ /* Breaks things currently! */
+ /* for (u = firstuser(); u; u = nextuser())
+ if (match_wild_nocase(mask, u->realname))
+ send_cmd(NULL, "SVSKILL %s :G-Lined: %s", u->nick, reason); */
+#endif
+}
+
+/*************************************************************************/
+
+void s_sqline(char *mask, char *reason)
+{
+#ifdef IRC_BAHAMUT
+ if (*mask == '#') {
+ int i;
+ Channel *c, *next;
+
+ char *av[3];
+ struct c_userlist *cu, *cunext;
+
+ send_cmd(NULL, "SQLINE %s :%s", mask, reason);
+
+ for (i = 0; i < 1024; i++) {
+ for (c = chanlist[i]; c; c = next) {
+ next = c->next;
+
+ if (!match_wild_nocase(mask, c->name))
+ continue;
+
+ for (cu = c->users; cu; cu = cunext) {
+ cunext = cu->next;
+
+ if (is_oper(cu->user))
+ continue;
+
+ av[0] = c->name;
+ av[1] = cu->user->nick;
+ av[2] = reason;
+ send_cmd(s_OperServ, "KICK %s %s :Q-Lined: %s", av[0],
+ av[1], av[2]);
+ do_kick(s_ChanServ, 3, av);
+ }
+ }
+ }
+ } else {
+#endif
+ send_cmd(NULL, "SQLINE %s :%s", mask, reason);
+
+#ifdef IRC_BAHAMUT
+ }
+#endif
+}
+
+/*************************************************************************/
+
+void s_svsnoop(char *server, int set)
+{
+#ifndef IRC_HYBRID
+#ifdef IRC_PTLINK
+ send_cmd(NULL, "SVSADMIN %s :%s", server, set ? "noopers" : "rehash");
+#else
+ send_cmd(NULL, "SVSNOOP %s %s", server, (set ? "+" : "-"));
+#endif
+#endif
+}
+
+/*************************************************************************/
+
+void s_szline(char *mask, char *reason)
+{
+#ifdef IRC_BAHAMUT
+ send_cmd(NULL, "SZLINE %s :%s", mask, reason);
+#endif
+}
+
+/*************************************************************************/
+
+void s_unsgline(char *mask)
+{
+#ifdef IRC_BAHAMUT
+ send_cmd(NULL, "UNSGLINE 0 :%s", mask);
+#endif
+}
+
+/*************************************************************************/
+
+void s_unsqline(char *mask)
+{
+#ifdef IRC_BAHAMUT
+ send_cmd(NULL, "UNSQLINE 0 %s", mask);
+#else
+ send_cmd(NULL, "UNSQLINE %s", mask);
+#endif
+}
+
+/*************************************************************************/
+
+void s_unszline(char *mask)
+{
+#ifdef IRC_BAHAMUT
+ send_cmd(NULL, "UNSZLINE 0 %s", mask);
+#endif
+}
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 000000000..3c548e631
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,797 @@
+/* Proxy detector.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+#include <fcntl.h>
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xFFFFFFFF
+#endif
+
+/* Hashed list of HostCache; threads must not use it! */
+HostCache *hcache[1024];
+
+/*************************************************************************/
+
+/* Equivalent to inet_ntoa */
+
+void ntoa(struct in_addr addr, char *ipaddr, int len)
+{
+ unsigned char *bytes = (unsigned char *) &addr.s_addr;
+ snprintf(ipaddr, len, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2],
+ bytes[3]);
+}
+
+/*************************************************************************/
+
+#ifdef USE_THREADS
+
+/*************************************************************************/
+
+#define HASH(host) ((tolower((host)[0])&31)<<5 | (tolower((host)[1])&31))
+
+/* Proxy queue; access controlled by queuemut */
+SList pxqueue;
+
+pthread_mutex_t queuemut = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t queuecond = PTHREAD_COND_INITIALIZER;
+
+#if !defined(HAS_NICKIP) && !defined(HAVE_GETHOSTBYNAME_R6) && !defined(HAVE_GETHOSTBYNAME_R5) && !defined(HAVE_GETHOSTBYNAME_R3)
+pthread_mutex_t resmut = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static uint32 aton(char *ipaddr);
+static void proxy_akill(char *host);
+HostCache *proxy_cache_add(char *host);
+static void proxy_cache_del(HostCache * hc);
+static HostCache *proxy_cache_find(char *host);
+static int proxy_connect(unsigned long ip, unsigned short port);
+static void proxy_queue_cleanup_unlock(void *arg);
+static void proxy_queue_lock(void);
+static void proxy_queue_signal(void);
+static void proxy_queue_unlock(void);
+static void proxy_queue_wait(void);
+static int proxy_read(int s, char *buf, size_t buflen);
+#ifndef HAS_NICKIP
+static uint32 proxy_resolve(char *host);
+#endif
+static int proxy_scan(uint32 ip);
+static void *proxy_thread_main(void *arg);
+
+/*************************************************************************/
+
+/* Equivalent to inet_addr */
+
+static uint32 aton(char *ipaddr)
+{
+ int i;
+ long lv;
+ char *endptr;
+ uint32 res;
+ unsigned char *bytes = (unsigned char *) &res;
+
+ for (i = 0; i < 4; i++) {
+ if (!*ipaddr)
+ return INADDR_NONE;
+
+ lv = strtol(ipaddr, &endptr, 10);
+ if (lv < 0 || lv > 255 || (*endptr != 0 && *endptr != '.'))
+ return INADDR_NONE;
+
+ bytes[i] = (unsigned char) lv;
+ ipaddr = (!*endptr ? endptr : ++endptr);
+ }
+
+ if (*endptr)
+ return INADDR_NONE;
+
+ return res;
+}
+
+/*************************************************************************/
+
+void get_proxy_stats(long *nrec, long *memuse)
+{
+ int i;
+ long mem = 0, count = 0;
+ HostCache *hc;
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = hc->next) {
+ count += 1;
+ mem += sizeof(HostCache);
+ mem += strlen(hc->host) + 1;
+ }
+ }
+
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+
+/* Akills the given host, and issues a GLOBOPS if configured so */
+
+static void proxy_akill(char *host)
+{
+ s_akill("*", host, s_OperServ, time(NULL),
+ time(NULL) + (ProxyExpire ? ProxyExpire : 86400 * 2),
+ ProxyAkillReason);
+ if (WallProxy)
+ wallops(s_OperServ, "Insecure proxy \2%s\2 has been AKILLed.",
+ host);
+}
+
+/*************************************************************************/
+
+/* Adds a cache entry after having it allocated */
+
+HostCache *proxy_cache_add(char *host)
+{
+ HostCache *hc;
+ int index = HASH(host);
+
+ hc = scalloc(1, sizeof(HostCache));
+ hc->host = sstrdup(host);
+ hc->used = time(NULL);
+
+ hc->prev = NULL;
+ hc->next = hcache[index];
+ if (hc->next)
+ hc->next->prev = hc;
+ hcache[index] = hc;
+
+ if (debug)
+ alog("debug: Added %s to host cache", host);
+
+ return hc;
+}
+
+/*************************************************************************/
+
+/* Deletes and frees a proxy cache entry */
+
+static void proxy_cache_del(HostCache * hc)
+{
+ /* Just to be sure */
+ if (hc->status < 0)
+ return;
+
+ if (debug)
+ alog("debug: Deleting %s from host cache", hc->host);
+
+ if (hc->status > HC_NORMAL)
+ s_rakill("*", hc->host);
+
+ if (hc->next)
+ hc->next->prev = hc->prev;
+ if (hc->prev)
+ hc->prev->next = hc->next;
+ else
+ hcache[HASH(hc->host)] = hc->next;
+
+ if (hc->host)
+ free(hc->host);
+
+ free(hc);
+}
+
+/*************************************************************************/
+
+/* Finds a proxy cache entry */
+
+static HostCache *proxy_cache_find(char *host)
+{
+ HostCache *hc;
+
+ for (hc = hcache[HASH(host)]; hc; hc = hc->next) {
+ if (stricmp(hc->host, host) == 0)
+ return hc;
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Checks whether the specified host is in the cache.
+ * If so:
+ * * if it's a proxy, take the appropriate actions, including killing nick
+ * * if it's not a proxy, do nothing
+ * If not:
+ * * add the host to the cache
+ * * add the host to the queue
+ * * send a signal to a waiting thread (if any)
+ *
+ * Returns 0 if nick is to be added to internal list, 1 else
+ */
+
+int proxy_check(char *nick, char *host, uint32 ip)
+{
+ int i;
+ char **message;
+ HostCache *hc;
+
+ if ((hc = proxy_cache_find(host))) {
+ hc->used = time(NULL);
+
+ if (hc->status <= HC_NORMAL)
+ return 0;
+
+ proxy_akill(host);
+ return 0;
+ }
+
+ for (message = ProxyMessage, i = 0; i < 8 && *message && **message;
+ message++, i++)
+ notice(s_GlobalNoticer, nick, *message);
+
+ hc = proxy_cache_add(host);
+#ifdef HAS_NICKIP
+ hc->ip = htonl(ip);
+#endif
+ hc->status = HC_QUEUED;
+
+ proxy_queue_lock();
+ slist_add(&pxqueue, hc);
+ if (debug)
+ alog("debug: Added %s to proxy queue", hc->host);
+ proxy_queue_signal();
+ proxy_queue_unlock();
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Initiates a non-blocking connection */
+
+static int proxy_connect(unsigned long ip, unsigned short port)
+{
+ struct sockaddr_in sin;
+ int s;
+
+ fd_set fds;
+ struct timeval tv;
+ int error, errlen;
+
+ if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+ return -1;
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
+ close(s);
+ return -1;
+ }
+
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ip;
+ sin.sin_port = htons(port);
+
+ if (connect(s, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) ==
+ -1 && errno != EINPROGRESS) {
+ close(s);
+ return -1;
+ }
+
+ FD_ZERO(&fds);
+ FD_SET(s, &fds);
+
+ tv.tv_sec = ProxyTimeout;
+ tv.tv_usec = 0;
+
+ if (select(s + 1, NULL, &fds, NULL, &tv) <= 0) {
+ close(s);
+ return -1;
+ }
+
+ errlen = sizeof(int);
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &errlen) == -1
+ || error != 0) {
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+/*************************************************************************/
+
+/* Deletes expired cache entries */
+
+void proxy_expire()
+{
+ int i;
+ HostCache *hc, *next;
+ time_t t = time(NULL);
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = next) {
+ next = hc->next;
+
+ /* Don't expire not scanned yet entries */
+ if (hc->status < HC_NORMAL)
+ continue;
+
+ if (hc->status == HC_NORMAL
+ && t - hc->used >= ProxyCacheExpire) {
+ proxy_cache_del(hc);
+ continue;
+ }
+
+ if (ProxyExpire && hc->status > HC_NORMAL
+ && t - hc->used >= ProxyExpire) {
+ alog("proxy: Expiring proxy %s", hc->host);
+ proxy_cache_del(hc);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Initializes the proxy detector. Returns 1 on success, 0 on error. */
+
+int proxy_init(void)
+{
+ int i;
+ pthread_t th;
+
+ slist_init(&pxqueue);
+
+ for (i = 1; i <= ProxyThreads; i++) {
+ if (pthread_create(&th, NULL, proxy_thread_main, NULL))
+ return 0;
+ if (pthread_detach(th))
+ return 0;
+ if (debug)
+ alog("debug: Creating proxy thread %ld (%d of %d)", (long) th,
+ i, ProxyThreads);
+ }
+
+ alog("Proxy detector initialized");
+
+ return 1;
+}
+
+/*************************************************************************/
+
+static void proxy_queue_cleanup_unlock(void *arg)
+{
+ proxy_queue_unlock();
+}
+
+/*************************************************************************/
+
+static void proxy_queue_lock(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Locking proxy queue mutex",
+ (long) pthread_self());
+ pthread_mutex_lock(&queuemut);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_signal(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Signaling proxy queue condition",
+ (long) pthread_self());
+ pthread_cond_signal(&queuecond);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_unlock(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Unlocking proxy queue mutex",
+ (long) pthread_self());
+ pthread_mutex_unlock(&queuemut);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_wait(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: waiting proxy queue condition",
+ (long) pthread_self());
+ pthread_cond_wait(&queuecond, &queuemut);
+}
+
+/*************************************************************************/
+
+/* Reads from the socket, in a non-blocking manner */
+
+static int proxy_read(int s, char *buf, size_t buflen)
+{
+ fd_set fds;
+ struct timeval tv;
+
+ FD_ZERO(&fds);
+ FD_SET(s, &fds);
+
+ tv.tv_sec = ProxyTimeout;
+ tv.tv_usec = 0;
+
+ if (select(s + 1, &fds, NULL, NULL, &tv) <= 0)
+ return -1;
+
+ return recv(s, buf, buflen, 0);
+}
+
+/*************************************************************************/
+
+/* Resolves hostnames in a thread safe manner */
+
+#ifndef HAS_NICKIP
+
+static uint32 proxy_resolve(char *host)
+{
+ struct hostent *hentp = NULL;
+ uint32 ip = INADDR_NONE;
+#if defined(HAVE_GETHOSTBYNAME_R6)
+ struct hostent hent;
+ char hbuf[8192];
+ int herrno;
+
+ if (gethostbyname_r(host, &hent, hbuf, sizeof(hbuf), &hentp, &herrno) <
+ 0)
+ hentp = NULL;
+#elif defined(HAVE_GETHOSTBYNAME_R5)
+ struct hostent hent char hbuf[8192];
+ int herrno;
+ hentp = gethostbyname_r(host, &hent, hbuf, sizeof(hbuf), &herrno);
+#elif defined(HAVE_GETHOSTBYNAME_R3)
+ struct hostent hent;
+ struct hostent_data data;
+ hentp = gethostbyname_r(host, &hent, &data);
+#else
+ /* Make it safe that way */
+ pthread_mutex_lock(&resmut);
+ hentp = gethostbyname(host);
+#endif
+
+ if (hentp) {
+ memcpy(&ip, hentp->h_addr, sizeof(hentp->h_length));
+ if (debug) {
+ char ipbuf[16];
+ struct in_addr addr;
+ addr.s_addr = ip;
+ ntoa(addr, ipbuf, sizeof(ipbuf));
+ alog("debug: Thread %ld: resolved %s to %s",
+ (long) pthread_self(), host, ipbuf);
+ }
+ }
+#if !defined(HAVE_GETHOSTBYNAME_R6) && !defined(HAVE_GETHOSTBYNAME_R5) && !defined(HAVE_GETHOSTBYNAME_R3)
+ pthread_mutex_unlock(&resmut);
+#endif
+
+ return ip;
+}
+
+#endif
+
+/*************************************************************************/
+
+/* Scans the given host for proxy */
+
+static int proxy_scan(uint32 ip)
+{
+ int s; /* Socket */
+ int i;
+
+ if (ip == INADDR_NONE)
+ return HC_NORMAL;
+
+ /* Scan for SOCKS (4/5) */
+
+ for (i = 0; i < 2; i++) {
+ if ((s = proxy_connect(ip, 1080)) == -1)
+ break;
+
+ if (ProxyCheckSocks4 && i == 0) {
+ /* SOCKS4 */
+
+ char buf[9];
+ uint32 sip;
+
+ sip = aton(ProxyTestServer);
+ sip = htonl(sip);
+
+ buf[0] = 4;
+ buf[1] = 1;
+ buf[2] = (((unsigned short) ProxyTestPort) >> 8) & 0xFF;
+ buf[3] = ((unsigned short) ProxyTestPort) & 0xFF;
+ buf[4] = (sip >> 24) & 0xFF;
+ buf[5] = (sip >> 16) & 0xFF;
+ buf[6] = (sip >> 8) & 0xFF;
+ buf[7] = sip & 0xFF;
+ buf[8] = 0;
+
+ if (send(s, buf, 9, 0) != 9) {
+ close(s);
+ return HC_NORMAL;
+ }
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[1] == 90) {
+ close(s);
+ return HC_SOCKS4;
+ }
+
+ } else if (ProxyCheckSocks5 && i == 1) {
+ /* SOCKS5 */
+
+ char buf[10];
+ uint32 sip;
+
+ if (send(s, "\5\1\0", 3, 0) != 3) {
+ close(s);
+ continue;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[0] != 5 || buf[1] != 0) {
+ close(s);
+ continue;
+ }
+
+ sip = aton(ProxyTestServer);
+ sip = htonl(sip);
+
+ buf[0] = 5;
+ buf[1] = 1;
+ buf[2] = 0;
+ buf[3] = 1;
+ buf[4] = (sip >> 24) & 0xFF;
+ buf[5] = (sip >> 16) & 0xFF;
+ buf[6] = (sip >> 8) & 0xFF;
+ buf[7] = sip & 0xFF;
+ buf[8] = (((unsigned short) ProxyTestPort) >> 8) & 0xFF;
+ buf[9] = ((unsigned short) ProxyTestPort) & 0xFF;
+
+ if (send(s, buf, 10, 0) != 10) {
+ close(s);
+ continue;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[0] == 5 && buf[1] == 0) {
+ close(s);
+ return HC_SOCKS5;
+ }
+ }
+
+ close(s);
+ }
+
+ /* Scan for HTTP proxy */
+ for (i = 0; i < 3; i++) {
+ if ((i ==
+ 0 ? ProxyCheckHTTP2 : (i ==
+ 1 ? ProxyCheckHTTP1 : ProxyCheckHTTP3))
+ && (s =
+ proxy_connect(ip,
+ (i == 0 ? 8080 : (i == 1 ? 3128 : 80)))) !=
+ -1) {
+ int bread;
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\n\n",
+ ProxyTestServer, ProxyTestPort);
+ if (send(s, buf, strlen(buf), 0) == strlen(buf)) {
+ if ((bread = proxy_read(s, buf, 15)) >= 12) {
+ buf[bread] = 0;
+
+ if (!strnicmp(buf, "HTTP/1.0 200", 12) || !stricmp(buf, "HTTP/1.1 200 Co")) { /* Apache may return 200 OK
+ even if it's not processing
+ the CONNECT request. :/ */
+ close(s);
+ return HC_HTTP;
+ }
+ }
+ }
+ close(s);
+ }
+ }
+
+ /* Scan for Wingate */
+ if (ProxyCheckWingate && (s = proxy_connect(ip, 23)) != -1) {
+ char buf[9];
+
+ if (proxy_read(s, buf, 8) == 8) {
+ buf[8] = '\0';
+ if (!stricmp(buf, "Wingate>") || !stricmp(buf, "Too many")) {
+ close(s);
+ return HC_WINGATE;
+ }
+ }
+ close(s);
+ }
+
+ return HC_NORMAL;
+}
+
+/*************************************************************************/
+
+/* Proxy detector threads entry point */
+
+static void *proxy_thread_main(void *arg)
+{
+ while (1) {
+ pthread_cleanup_push(&proxy_queue_cleanup_unlock, NULL);
+ proxy_queue_lock();
+ proxy_queue_wait();
+ pthread_cleanup_pop(1);
+
+ /* We loop until there is no more host to check in the list */
+ while (1) {
+ HostCache *hc = NULL;
+ int status;
+
+ pthread_cleanup_push(&proxy_queue_cleanup_unlock, NULL);
+ proxy_queue_lock();
+ if (pxqueue.count > 0) {
+ hc = pxqueue.list[0];
+ hc->status = HC_PROGRESS;
+ slist_delete(&pxqueue, 0);
+ }
+ pthread_cleanup_pop(1);
+
+ if (!hc)
+ break;
+
+ if (debug) {
+ if (hc->ip) {
+ char ipbuf[16];
+ struct in_addr in;
+ in.s_addr = hc->ip;
+ ntoa(in, ipbuf, sizeof(ipbuf));
+ alog("debug: Scanning host %s [%s] for proxy",
+ hc->host, ipbuf);
+ } else {
+ alog("debug: Scanning host %s for proxy", hc->host);
+ }
+ }
+#ifndef HAS_NICKIP
+ /* Test if it's an IP, and if not try to resolve the hostname */
+ if ((hc->ip = aton(hc->host)) == INADDR_NONE)
+ hc->ip = proxy_resolve(hc->host);
+#endif
+ status = proxy_scan(hc->ip);
+
+ if (debug) {
+ char ipbuf[16];
+ struct in_addr in;
+ in.s_addr = hc->ip;
+ ntoa(in, ipbuf, sizeof(ipbuf));
+ alog("debug: Scan for %s [%s] complete, result: %d",
+ hc->host, ipbuf, status);
+ }
+
+ if (status > HC_NORMAL)
+ proxy_akill(hc->host);
+
+ hc->status = status;
+ }
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+#endif
+
+/*************************************************************************/
+
+/* OperServ CACHE */
+
+int do_cache(User * u)
+{
+#ifdef USE_THREADS
+ char *cmd = strtok(NULL, " ");
+ char *pattern = strtok(NULL, " ");
+
+ if (!ProxyDetect) {
+ notice_lang(s_OperServ, u, OPER_CACHE_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!cmd || !pattern) {
+ syntax_error(s_OperServ, u, "CACHE", OPER_CACHE_SYNTAX);
+ } else if (!stricmp(cmd, "DEL")) {
+ HostCache *hc;
+
+ if (!(hc = proxy_cache_find(pattern))) {
+ notice_lang(s_OperServ, u, OPER_CACHE_NOT_FOUND, pattern);
+ return MOD_CONT;
+ }
+
+ proxy_cache_del(hc);
+ notice_lang(s_OperServ, u, OPER_CACHE_REMOVED, pattern);
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "LIST")) {
+ char *option = strtok(NULL, " ");
+ int i, restrict = 0, count = 0, total = 0;
+ HostCache *hc;
+
+ static int statusdesc[7] = {
+ OPER_CACHE_QUEUED,
+ OPER_CACHE_PROGRESS,
+ OPER_CACHE_NORMAL,
+ OPER_CACHE_WINGATE,
+ OPER_CACHE_SOCKS4,
+ OPER_CACHE_SOCKS5,
+ OPER_CACHE_HTTP
+ };
+
+ if (option && !stricmp(option, "QUEUED"))
+ restrict = 1;
+ else if (option && !stricmp(option, "ALL"))
+ restrict = 2;
+
+ notice_lang(s_OperServ, u, OPER_CACHE_HEADER);
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = hc->next) {
+ if (!match_wild_nocase(pattern, hc->host))
+ continue;
+ if ((restrict == 0 && hc->status <= HC_NORMAL)
+ || (restrict == 1 && hc->status >= HC_NORMAL))
+ continue;
+ total++;
+ if (count >= ProxyMax)
+ continue;
+ notice_lang(s_OperServ, u, OPER_CACHE_LIST, hc->host,
+ getstring(u->na, statusdesc[hc->status + 2]));
+ count++;
+ }
+ }
+
+ notice_lang(s_OperServ, u, OPER_CACHE_FOOTER, count, total);
+
+ } else {
+ syntax_error(s_OperServ, u, "CACHE", OPER_CACHE_SYNTAX);
+ }
+#else
+ notice_lang(s_OperServ, u, OPER_CACHE_DISABLED);
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/
diff --git a/src/rdb.c b/src/rdb.c
new file mode 100644
index 000000000..5876273dd
--- /dev/null
+++ b/src/rdb.c
@@ -0,0 +1,466 @@
+/* RDB functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+#include "services.h"
+
+/*************************************************************************/
+
+int rdb_init()
+{
+
+#ifdef USE_MYSQL
+ return db_mysql_init();
+#endif
+
+}
+
+/*************************************************************************/
+
+int rdb_open()
+{
+
+#ifdef USE_MYSQL
+ return do_mysql; // db_mysql_open();
+#endif
+
+}
+
+/*************************************************************************/
+
+int rdb_close()
+{
+
+#ifdef USE_MYSQL
+ return 1; // db_mysql_close();
+#endif
+
+}
+
+/*************************************************************************/
+
+int rdb_tag_table(char *table)
+{
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf), "UPDATE %s SET active='0'", table);
+ return db_mysql_query(buf);
+#endif
+
+ return 0;
+
+}
+
+/*************************************************************************/
+
+int rdb_clear_table(char *table)
+{
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf), "TRUNCATE TABLE %s", table);
+ return db_mysql_query(buf);
+#endif
+
+ return 0;
+
+}
+
+/*************************************************************************/
+
+int rdb_scrub_table(char *table, char *clause)
+{
+
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf), "DELETE FROM %s WHERE %s", table, clause);
+ return db_mysql_query(buf);
+#endif
+
+ return 0;
+
+}
+
+/*************************************************************************/
+
+int rdb_direct_query(char *query)
+{
+
+#ifdef USE_MYSQL
+ alog("Direct Query: %s", query);
+ return db_mysql_query(query);
+#endif
+
+ return 0;
+
+}
+
+/*************************************************************************/
+
+/* I still don't really like doing it this way, it should really be done
+ * inside mysql.c and not here. So I'll revisit this later
+ */
+int rdb_ns_set_display(char *newnick, char *oldnick)
+{
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ /* Change the display on NS_CORE */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_ns_core SET display='%s' WHERE display='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Change the display on NS_ALIAS for all grouped nicks */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_ns_alias SET display='%s' WHERE display='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Change the display on ChanServ ACCESS list */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_cs_access SET display='%s' WHERE display='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Change the display on ChanServ AKICK list */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_cs_access SET creator='%s' WHERE creator='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Change the display on MemoServ sent memos */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_ms_info SET sender='%s' WHERE sender='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Change the display on MemoServ received memos */
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_ms_info SET receiver='%s' WHERE receiver='%s'",
+ newnick, oldnick);
+ db_mysql_query(buf);
+
+ /* Need to do bwords and akills */
+
+#endif
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int rdb_cs_deluser(char *nick)
+{
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_cs_info SET successor=NULL WHERE successor='%s'",
+ nick);
+ db_mysql_query(buf);
+
+ snprintf(buf, sizeof(buf), "display='%s'", nick);
+ rdb_scrub_table("anope_cs_access", buf);
+ snprintf(buf, sizeof(buf), "creator='%s'", nick);
+ rdb_scrub_table("anope_cs_akicks", buf);
+
+ return 1;
+#endif
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int rdb_cs_delchan(ChannelInfo * ci)
+{
+ static char buf[1024];
+ char *channel = ci->name;
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_cs_info SET successor=NULL WHERE name='%s'",
+ channel);
+ db_mysql_query(buf);
+
+ snprintf(buf, sizeof(buf), "name='%s'", channel);
+ rdb_scrub_table("anope_cs_info", buf);
+ snprintf(buf, sizeof(buf), "receiver='%s' AND serv='CHAN'", channel);
+ rdb_scrub_table("anope_ms_info", buf);
+ snprintf(buf, sizeof(buf), "channel='%s'", channel);
+ rdb_scrub_table("anope_cs_access", buf);
+ rdb_scrub_table("anope_cs_akicks", buf);
+ rdb_scrub_table("anope_cs_levels", buf);
+ rdb_scrub_table("anope_cs_badwords", buf);
+ if (ci->founder) {
+ snprintf(buf, sizeof(buf),
+ "update anope_ns_core set channelcount=channelcount-1 where display='%s'",
+ ci->founder->display);
+ db_mysql_query(buf);
+ }
+
+ return 1;
+#endif
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int rdb_cs_set_founder(char *channel, char *founder)
+{
+ static char buf[1024];
+
+#ifdef USE_MYSQL
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_cs_info SET founder='%s', successor=NULL WHERE name='%s'",
+ founder, channel);
+ db_mysql_query(buf);
+
+ snprintf(buf, sizeof(buf),
+ "UPDATE anope_ns_core SET channelcount=channelcount+1 WHERE display='%s'",
+ founder);
+ db_mysql_query(buf);
+
+ /* Do i need to scrub the access list for this channel ? */
+ snprintf(buf, sizeof(buf), "display='%s' AND channel='%s'", founder,
+ channel);
+ rdb_scrub_table("anope_cs_access", buf);
+
+ return 1;
+#endif
+
+ return 0;
+}
+
+/*************************************************************************/
+
+void rdb_save_ns_core(NickCore * nc)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_ns_core(nc);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_ns_alias(NickAlias * na)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_ns_alias(na);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_ns_req(NickRequest * nr)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_ns_req(nr);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_cs_info(ChannelInfo * ci)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_cs_info(ci);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_bs_core(BotInfo * bi)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_bs_core(bi);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_hs_core(HostCore * hc)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_hs_core(hc);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_os_db(unsigned int maxucnt, unsigned int maxutime,
+ SList * ak, SList * sgl, SList * sql, SList * szl,
+ HostCache * hc)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_os_db(maxusercnt, maxusertime, ak, sgl, sql, szl, hc);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_save_news(NewsItem * ni)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_news(ni);
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_load_bs_dbase(void)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_load_bs_dbase();
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_load_hs_dbase(void)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_load_hs_dbase();
+#endif
+
+}
+
+/*************************************************************************/
+
+void rdb_load_ns_dbase(void)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_load_ns_dbase();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_news(void)
+{
+#ifdef USE_MYSQL
+ db_mysql_load_news();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_exceptions(void)
+{
+#ifdef USE_MYSQL
+ db_mysql_load_exceptions();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_cs_dbase(void)
+{
+#ifdef USE_MYSQL
+ db_mysql_load_cs_dbase();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_os_dbase(void)
+{
+#ifdef USE_MYSQL
+ db_mysql_load_os_dbase();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_ns_req_dbase(void)
+{
+#ifdef USE_MYSQL
+ db_mysql_load_ns_req_dbase();
+#endif
+}
+
+/*************************************************************************/
+
+void rdb_load_dbases(void)
+{
+ if (!skeleton) {
+ rdb_load_ns_dbase();
+ if (debug)
+ alog("RDB: Loaded NickServ DataBase (1/8)");
+ if (s_HostServ) {
+ rdb_load_hs_dbase();
+ if (debug)
+ alog("RDB: Loaded HostServ DataBase (2/8)");
+ }
+ if (s_BotServ) {
+ rdb_load_bs_dbase();
+ if (debug)
+ alog("RDB: Loaded BotServ DataBase (3/8)");
+ }
+ rdb_load_cs_dbase();
+ if (debug)
+ alog("RDB: Loaded ChanServ DataBase (4/8)");
+ }
+ rdb_load_os_dbase();
+ if (debug)
+ alog("RDB: Loaded OperServ DataBase (5/8)");
+ rdb_load_news();
+ if (debug)
+ alog("RDB: Loaded News DataBase (6/8)");
+ rdb_load_exceptions();
+ if (debug)
+ alog("RDB: Loaded Exception Database (7/8)");
+ if (PreNickDBName) {
+ rdb_load_ns_req_dbase();
+ if (debug)
+ alog("RDB: Loaded PreNick DataBase (8/8)");
+ } else {
+ if (debug)
+ alog("RDB: No need to load PreNickDB (8/8)");
+ }
+ alog("RDB: All DataBases loaded.");
+}
+
+/*************************************************************************/
+
+void rdb_save_exceptions(Exception * e)
+{
+
+#ifdef USE_MYSQL
+ db_mysql_save_exceptions(e);
+#endif
+
+}
diff --git a/src/send.c b/src/send.c
new file mode 100644
index 000000000..0c21b65d3
--- /dev/null
+++ b/src/send.c
@@ -0,0 +1,240 @@
+/* Routines for sending stuff to the network.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+
+/* Send a command to the server. The two forms here are like
+ * printf()/vprintf() and friends. */
+
+void send_cmd(const char *source, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vsend_cmd(source, fmt, args);
+ va_end(args);
+}
+
+void vsend_cmd(const char *source, const char *fmt, va_list args)
+{
+ char buf[BUFSIZE];
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ if (source) {
+ sockprintf(servsock, ":%s %s\r\n", source, buf);
+ if (debug)
+ alog("debug: Sent: :%s %s", source, buf);
+ } else {
+ sockprintf(servsock, "%s\r\n", buf);
+ if (debug)
+ alog("debug: Sent: %s", buf);
+ }
+}
+
+/*************************************************************************/
+
+/* Send out a WALLOPS (a GLOBOPS on ircd.dal). */
+
+void wallops(const char *source, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+#ifdef IRC_HYBRID
+ send_cmd(source ? source : ServerName, "WALLOPS :%s", buf);
+#else
+ send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
+#endif
+}
+
+/*************************************************************************/
+
+/* Send a NOTICE from the given source to the given nick. */
+void notice(const char *source, const char *dest, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ send_cmd(source, "%s %s :%s", (UsePrivmsg ? "PRIVMSG" : "NOTICE"),
+ dest, buf);
+}
+
+/*************************************************************************/
+
+void notice_server(const char *source, Server * s, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+#ifdef IRC_HYBRID
+ send_cmd(source, "%s $$%s :%s", (UsePrivmsg ? "PRIVMSG" : "NOTICE"),
+ s->name, buf);
+#else
+ send_cmd(source, "%s $%s :%s", (UsePrivmsg ? "PRIVMSG" : "NOTICE"),
+ s->name, buf);
+#endif
+}
+
+/*************************************************************************/
+
+void notice_user(const char *source, User * u, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ send_cmd(source, "%s %s :%s",
+ (UsePrivmsg && (!u->na || (u->na->nc->flags & NI_MSG)) ?
+ "PRIVMSG" : "NOTICE"), u->nick, buf);
+}
+
+/*************************************************************************/
+
+/* Send a NULL-terminated array of text as NOTICEs. */
+void notice_list(const char *source, const char *dest, const char **text)
+{
+ while (*text) {
+ /* Have to kludge around an ircII bug here: if a notice includes
+ * no text, it is ignored, so we replace blank lines by lines
+ * with a single space.
+ */
+ if (**text)
+ notice(source, dest, *text);
+ else
+ notice(source, dest, " ");
+ text++;
+ }
+}
+
+/*************************************************************************/
+
+/* Send a message in the user's selected language to the user using NOTICE. */
+void notice_lang(const char *source, User * dest, int message, ...)
+{
+ va_list args;
+ char buf[4096]; /* because messages can be really big */
+ char *s, *t;
+ const char *fmt;
+ if (!dest)
+ return;
+ va_start(args, message);
+ fmt = getstring(dest->na, message);
+ if (!fmt)
+ return;
+ memset(buf, 0, 4096);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ s = buf;
+ while (*s) {
+ t = s;
+ s += strcspn(s, "\n");
+ if (*s)
+ *s++ = 0;
+ send_cmd(source, "%s %s :%s", (UsePrivmsg
+ && (!dest->na || (dest->na->nc->
+ flags &
+ NI_MSG)) ?
+ "PRIVMSG" : "NOTICE"),
+ dest->nick, *t ? t : " ");
+ }
+}
+
+/*************************************************************************/
+
+/* Like notice_lang(), but replace %S by the source. This is an ugly hack
+ * to simplify letting help messages display the name of the pseudoclient
+ * that's sending them.
+ */
+void notice_help(const char *source, User * dest, int message, ...)
+{
+ va_list args;
+ char buf[4096], buf2[4096], outbuf[BUFSIZE];
+ char *s, *t;
+ const char *fmt;
+
+ if (!dest)
+ return;
+ va_start(args, message);
+ fmt = getstring(dest->na, message);
+ if (!fmt)
+ return;
+ /* Some sprintf()'s eat %S or turn it into just S, so change all %S's
+ * into \1\1... we assume this doesn't occur anywhere else in the
+ * string. */
+ strscpy(buf2, fmt, sizeof(buf2));
+ strnrepl(buf2, sizeof(buf2), "%S", "\1\1");
+ vsnprintf(buf, sizeof(buf), buf2, args);
+ s = buf;
+ while (*s) {
+ t = s;
+ s += strcspn(s, "\n");
+ if (*s)
+ *s++ = 0;
+ strscpy(outbuf, t, sizeof(outbuf));
+ strnrepl(outbuf, sizeof(outbuf), "\1\1", source);
+ send_cmd(source, "%s %s :%s",
+ (UsePrivmsg
+ && (!dest->na
+ || (dest->na->nc->
+ flags & NI_MSG)) ? "PRIVMSG" : "NOTICE"),
+ dest->nick, *outbuf ? outbuf : " ");
+ }
+}
+
+/*************************************************************************/
+
+/* Send a PRIVMSG from the given source to the given nick. */
+void privmsg(const char *source, const char *dest, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ send_cmd(source, "PRIVMSG %s :%s", dest, buf);
+}
+
+/*************************************************************************/
+
+/* Sends a MODE from the given source on the given nick */
+void send_mode(const char *source, const char *on, const char *fmt, ...)
+{
+ va_list args;
+ char buf[BUFSIZE];
+
+ va_start(args, fmt);
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+#ifdef IRC_BAHAMUT
+ if (uplink_capab & CAPAB_TSMODE)
+ send_cmd(source, "MODE %s 0 %s", on, buf);
+ else
+#endif
+ send_cmd(source, "MODE %s %s", on, buf);
+}
+
+/*************************************************************************/
diff --git a/src/servers.c b/src/servers.c
new file mode 100644
index 000000000..71e6ce92a
--- /dev/null
+++ b/src/servers.c
@@ -0,0 +1,259 @@
+/* Routines to maintain a list of connected servers
+ *
+ * (C) 2004 Anope Team / GeniusDex
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+Server *servlist = NULL;
+Server *me_server = NULL;
+#ifdef IRC_BAHAMUT
+uint16 uplink_capab;
+#endif
+
+/* For first_server / next_server */
+static Server *server_cur;
+
+/*************************************************************************/
+
+/* Walk through the servers list */
+Server *first_server(int flags)
+{
+ server_cur = servlist;
+ if (flags > -1) {
+ while (server_cur && (server_cur->flags != flags))
+ server_cur = next_server(flags);
+ }
+ return server_cur;
+}
+
+Server *next_server(int flags)
+{
+ if (!server_cur)
+ return NULL;
+
+ do {
+ if (server_cur->links) {
+ server_cur = server_cur->links;
+ } else if (server_cur->next) {
+ server_cur = server_cur->next;
+ } else {
+ do {
+ server_cur = server_cur->uplink;
+ if (server_cur && server_cur->next) {
+ server_cur = server_cur->next;
+ break;
+ }
+ } while (server_cur);
+ }
+ } while (server_cur && ((flags > -1) || (server_cur->flags != flags)));
+
+ return server_cur;
+}
+
+/*************************************************************************/
+
+/* This function makes a new Server structure and links it in the right
+ * places in the linked list if a Server struct to it's uplink if provided.
+ * It can also be NULL to indicate it's the uplink and should be first in
+ * the server list.
+ */
+
+Server *new_server(Server * uplink, const char *name, const char *desc,
+ uint16 flags)
+{
+ Server *serv;
+
+ serv = scalloc(sizeof(Server), 1);
+ if (!name)
+ name = "";
+ serv->name = sstrdup(name);
+ serv->desc = sstrdup(desc);
+ serv->flags = flags;
+ serv->uplink = uplink;
+ serv->links = NULL;
+ serv->prev = NULL;
+
+ if (!uplink) {
+ serv->hops = 0;
+ serv->next = servlist;
+ if (servlist)
+ servlist->prev = serv;
+ servlist = serv;
+ } else {
+ serv->hops = uplink->hops + 1;
+ serv->next = uplink->links;
+ if (uplink->links)
+ uplink->links->prev = serv;
+ uplink->links = serv;
+ }
+
+ return serv;
+}
+
+/*************************************************************************/
+
+/* Remove and free a Server structure. This function is the most complete
+ * remove treatment a server can get, as it first quits all clients which
+ * still pretend to be on this server, then it walks through all connected
+ * servers and disconnects them too. If all mess is cleared, the server
+ * itself will be too.
+ */
+
+static void delete_server(Server * serv, const char *quitreason)
+{
+ Server *s, *snext;
+#ifdef IRC_BAHAMUT
+ User *u, *unext;
+ NickAlias *na;
+#endif
+
+ if (!serv) {
+ alog("delete_server() called with NULL arg!");
+ return;
+ }
+
+ if (debug)
+ alog("delete_server() called for %s", serv->name);
+
+#ifdef IRC_BAHAMUT
+ if (uplink_capab & CAPAB_NOQUIT) {
+ u = firstuser();
+ while (u) {
+ unext = nextuser();
+ if (u->server == serv) {
+ if ((na = u->na) && !(na->status & NS_VERBOTEN)
+ && (na->status & (NS_IDENTIFIED | NS_RECOGNIZED))) {
+ na->last_seen = time(NULL);
+ if (na->last_quit)
+ free(na->last_quit);
+ na->last_quit =
+ (quitreason ? sstrdup(quitreason) : NULL);
+ }
+#ifndef STREAMLINED
+ if (LimitSessions)
+ del_session(u->host);
+#endif
+ delete_user(u);
+ }
+ u = unext;
+ }
+ if (debug >= 2)
+ alog("delete_server() cleared all users");
+ }
+#endif
+
+ s = serv->links;
+ while (s) {
+ snext = s->next;
+ delete_server(s, quitreason);
+ s = snext;
+ }
+
+ if (debug >= 2)
+ alog("delete_server() cleared all servers");
+
+ free(serv->name);
+ free(serv->desc);
+ if (serv->prev)
+ serv->prev->next = serv->next;
+ if (serv->next)
+ serv->next->prev = serv->prev;
+ if (serv->uplink->links == serv)
+ serv->uplink->links = serv->next;
+
+ if (debug)
+ alog("delete_server() completed");
+}
+
+/*************************************************************************/
+
+/* Find a server by name, returns NULL if not found */
+
+Server *findserver(Server * s, const char *name)
+{
+ Server *sl;
+
+ if (debug >= 3)
+ alog("debug: findserver(%p)", name);
+ while (s && (stricmp(s->name, name) != 0)) {
+ if (s->links) {
+ sl = findserver(s->links, name);
+ if (sl)
+ s = sl;
+ else
+ s = s->next;
+ } else {
+ s = s->next;
+ }
+ }
+ if (debug >= 3)
+ alog("debug: findserver(%s) -> %p", name, s);
+ return s;
+}
+
+/*************************************************************************/
+/* :<introducing server> SERVER <servername> <hops> :<description>
+ */
+void do_server(const char *source, int ac, char **av)
+{
+ Server *s;
+
+ if (debug)
+ alog("debug: Server introduced (%s) from %s", av[0], source);
+
+ if (source[0] == '\0')
+ s = me_server;
+ else
+ s = findserver(servlist, source);
+#ifdef IRC_PTLINK
+ if (ac < 4)
+ alog("Malformed SERVER received (less than 4 params)");
+ else
+ new_server(s, av[0], av[3], 0);
+#else
+ if (ac < 3)
+ alog("Malformed SERVER received (less than 3 params)");
+ else
+ new_server(s, av[0], av[2], 0);
+#endif
+}
+
+/*************************************************************************/
+/* SQUIT <server> :<comment>
+ */
+void do_squit(const char *source, int ac, char **av)
+{
+ char buf[BUFSIZE];
+ Server *s;
+
+ s = findserver(servlist, av[0]);
+ if (!s) {
+ alog("SQUIT for nonexistent server (%s)!!", av[0]);
+ return;
+ }
+
+ snprintf(buf, sizeof(buf), "%s %s", s->name,
+ (s->uplink ? s->uplink->name : ""));
+
+#ifdef IRC_BAHAMUT
+ if ((s->uplink == me_server) && (uplink_capab & CAPAB_UNCONNECT)) {
+ if (debug)
+ alog("debuf: Sending UNCONNECT SQUIT for %s", s->name);
+ send_cmd(ServerName, "SQUIT %s :%s", s->name, buf);
+ }
+#endif
+
+ delete_server(s, buf);
+}
+
+/* EOF */
diff --git a/src/sessions.c b/src/sessions.c
new file mode 100644
index 000000000..1620a2e15
--- /dev/null
+++ b/src/sessions.c
@@ -0,0 +1,834 @@
+/* Session Limiting functions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "pseudo.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)
+ */
+
+/*************************************************************************/
+
+typedef struct session_ Session;
+struct session_ {
+ Session *prev, *next;
+ char *host;
+ int count; /* Number of clients with this host */
+ int hits; /* Number of subsequent kills for a host */
+};
+
+/* I'm sure there is a better way to hash the list of hosts for which we are
+ * storing session information. This should be sufficient for the mean time.
+ * -TheShadow */
+
+#define HASH(host) (((host)[0]&31)<<5 | ((host)[1]&31))
+
+static Session *sessionlist[1024];
+static int32 nsessions = 0;
+
+Exception *exceptions = NULL;
+int16 nexceptions = 0;
+
+/*************************************************************************/
+
+static Session *findsession(const char *host);
+
+static Exception *find_host_exception(const char *host);
+static int exception_add(const char *mask, const int limit,
+ const char *reason, const char *who,
+ const time_t expires);
+
+/*************************************************************************/
+/****************************** Statistics *******************************/
+/*************************************************************************/
+
+void get_session_stats(long *nrec, long *memuse)
+{
+ Session *session;
+ long mem;
+ int i;
+
+ mem = sizeof(Session) * nsessions;
+ for (i = 0; i < 1024; i++) {
+ for (session = sessionlist[i]; session; session = session->next) {
+ mem += strlen(session->host) + 1;
+ }
+ }
+
+ *nrec = nsessions;
+ *memuse = mem;
+}
+
+void get_exception_stats(long *nrec, long *memuse)
+{
+ long mem;
+ int i;
+
+ mem = sizeof(Exception) * nexceptions;
+ for (i = 0; i < nexceptions; i++) {
+ mem += strlen(exceptions[i].mask) + 1;
+ mem += strlen(exceptions[i].reason) + 1;
+ }
+ *nrec = nexceptions;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/************************* 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.
+ */
+
+int do_session(User * u)
+{
+ Session *session;
+ Exception *exception;
+ char *cmd = strtok(NULL, " ");
+ char *param1 = strtok(NULL, " ");
+ int mincount;
+ int i;
+
+ if (!LimitSessions) {
+ notice_lang(s_OperServ, u, OPER_SESSION_DISABLED);
+ return MOD_CONT;
+ }
+
+ 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 (i = 0; i < 1024; i++) {
+ for (session = sessionlist[i]; session;
+ session = session->next) {
+ 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 = findsession(param1);
+ if (!session) {
+ notice_lang(s_OperServ, u, OPER_SESSION_NOT_FOUND, param1);
+ } else {
+ exception = find_host_exception(param1);
+
+ notice_lang(s_OperServ, u, OPER_SESSION_VIEW_FORMAT,
+ param1, session->count,
+ exception ? exception->
+ limit : DefSessionLimit);
+ }
+ }
+
+ } else {
+ syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
+/********************* Internal Session Functions ************************/
+/*************************************************************************/
+
+static Session *findsession(const char *host)
+{
+ Session *session;
+ int i;
+
+ if (!host)
+ return NULL;
+
+ for (i = 0; i < 1024; i++) {
+ for (session = sessionlist[i]; session; session = session->next) {
+ if (stricmp(host, session->host) == 0) {
+ return session;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* 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.
+ */
+
+int add_session(const char *nick, const char *host)
+{
+ Session *session, **list;
+ Exception *exception;
+ int sessionlimit = 0;
+
+ session = findsession(host);
+
+ if (session) {
+ exception = find_host_exception(host);
+ if (checkDefCon(DEFCON_REDUCE_SESSION)) {
+ sessionlimit =
+ exception ? exception->limit : DefConSessionLimit;
+ } else {
+ sessionlimit = exception ? exception->limit : DefSessionLimit;
+ }
+
+ if (sessionlimit != 0 && session->count >= sessionlimit) {
+ if (SessionLimitExceeded)
+ notice(s_OperServ, nick, SessionLimitExceeded, host);
+ if (SessionLimitDetailsLoc)
+ notice(s_OperServ, nick, SessionLimitDetailsLoc);
+
+ /* We don't use kill_user() because a user stucture has not yet
+ * been created. Simply kill the user. -TheShadow
+ */
+#ifdef IRC_BAHAMUT
+ send_cmd(NULL, "SVSKILL %s :Session limit exceeded", nick);
+#else
+ send_cmd(s_OperServ, "KILL %s :%s (Session limit exceeded)",
+ nick, s_OperServ);
+#endif
+ session->hits++;
+ if (MaxSessionKill && session->hits >= MaxSessionKill) {
+ char akillmask[BUFSIZE];
+ snprintf(akillmask, sizeof(akillmask), "*@%s", host);
+ add_akill(NULL, akillmask, s_OperServ,
+ time(NULL) + SessionAutoKillExpiry,
+ "Session limit exceeded");
+ wallops(s_OperServ,
+ "Added a temporary AKILL for \2%s\2 due to excessive connections",
+ akillmask);
+ }
+ return 0;
+ } else {
+ session->count++;
+ return 1;
+ }
+ }
+
+ nsessions++;
+ session = scalloc(sizeof(Session), 1);
+ session->host = sstrdup(host);
+ list = &sessionlist[HASH(session->host)];
+ session->next = *list;
+ if (*list)
+ (*list)->prev = session;
+ *list = session;
+ session->count = 1;
+
+ return 1;
+}
+
+void del_session(const char *host)
+{
+ Session *session;
+
+ if (debug >= 2)
+ alog("debug: del_session() called");
+
+ session = findsession(host);
+
+ if (!session) {
+ wallops(s_OperServ,
+ "WARNING: Tried to delete non-existant session: \2%s",
+ host);
+ alog("session: Tried to delete non-existant session: %s", host);
+ return;
+ }
+
+ if (session->count > 1) {
+ session->count--;
+ return;
+ }
+
+ if (session->prev)
+ session->prev->next = session->next;
+ else
+ sessionlist[HASH(session->host)] = session->next;
+ if (session->next)
+ session->next->prev = session->prev;
+
+ if (debug >= 2)
+ alog("debug: del_session(): free session structure");
+
+ free(session->host);
+ free(session);
+
+ nsessions--;
+
+ if (debug >= 2)
+ alog("debug: del_session() done");
+}
+
+
+/*************************************************************************/
+/********************** Internal Exception Functions *********************/
+/*************************************************************************/
+
+void expire_exceptions(void)
+{
+ int i;
+ time_t now = time(NULL);
+
+ for (i = 0; i < nexceptions; i++) {
+ if (exceptions[i].expires == 0 || exceptions[i].expires > now)
+ continue;
+ if (WallExceptionExpire)
+ wallops(s_OperServ,
+ "Session limit exception for %s has expired.",
+ exceptions[i].mask);
+ free(exceptions[i].mask);
+ free(exceptions[i].reason);
+ nexceptions--;
+ memmove(exceptions + i, exceptions + i + 1,
+ sizeof(Exception) * (nexceptions - i));
+ exceptions = srealloc(exceptions, sizeof(Exception) * nexceptions);
+ i--;
+ }
+}
+
+/* Find the first exception this host matches and return it. */
+
+Exception *find_host_exception(const char *host)
+{
+ int i;
+
+ for (i = 0; i < nexceptions; i++) {
+ if (match_wild_nocase(exceptions[i].mask, host)) {
+ return &exceptions[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+/*********************** Exception Load/Save *****************************/
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", ExceptionDBName); \
+ nexceptions = i; \
+ break; \
+ } \
+} while (0)
+
+void load_exceptions()
+{
+ dbFILE *f;
+ int i;
+ int16 n;
+ int16 tmp16;
+ int32 tmp32;
+
+ if (!
+ (f = open_db(s_OperServ, ExceptionDBName, "r", EXCEPTION_VERSION)))
+ return;
+ switch (i = get_file_version(f)) {
+ case 9:
+ case 8:
+ case 7:
+ SAFE(read_int16(&n, f));
+ nexceptions = n;
+ exceptions = scalloc(sizeof(Exception) * nexceptions, 1);
+ if (!nexceptions) {
+ close_db(f);
+ return;
+ }
+ for (i = 0; i < nexceptions; i++) {
+ SAFE(read_string(&exceptions[i].mask, f));
+ SAFE(read_int16(&tmp16, f));
+ exceptions[i].limit = tmp16;
+ SAFE(read_buffer(exceptions[i].who, f));
+ SAFE(read_string(&exceptions[i].reason, f));
+ SAFE(read_int32(&tmp32, f));
+ exceptions[i].time = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ exceptions[i].expires = tmp32;
+ }
+ break;
+
+ default:
+ fatal("Unsupported version (%d) on %s", i, ExceptionDBName);
+ } /* switch (ver) */
+
+ close_db(f);
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", ExceptionDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ wallops(NULL, "Write error on %s: %s", ExceptionDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_exceptions()
+{
+ dbFILE *f;
+ int i;
+ static time_t lastwarn = 0;
+
+ if (!
+ (f = open_db(s_OperServ, ExceptionDBName, "w", EXCEPTION_VERSION)))
+ return;
+ SAFE(write_int16(nexceptions, f));
+ for (i = 0; i < nexceptions; i++) {
+ SAFE(write_string(exceptions[i].mask, f));
+ SAFE(write_int16(exceptions[i].limit, f));
+ SAFE(write_buffer(exceptions[i].who, f));
+ SAFE(write_string(exceptions[i].reason, f));
+ SAFE(write_int32(exceptions[i].time, f));
+ SAFE(write_int32(exceptions[i].expires, f));
+ }
+ close_db(f);
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+void save_rdb_exceptions()
+{
+#ifdef USE_RDB
+ int i;
+ Exception *e;
+
+ if (!rdb_open())
+ return;
+ rdb_clear_table("anope_os_exceptions");
+ for (i = 0; i < nexceptions; i++) {
+ e = &exceptions[i];
+ rdb_save_exceptions(e);
+ }
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+/************************ Exception Manipulation *************************/
+/*************************************************************************/
+
+static int exception_add(const char *mask, const int limit,
+ const char *reason, const char *who,
+ const time_t expires)
+{
+ int i;
+
+ /* Check if an exception already exists for this mask */
+ for (i = 0; i < nexceptions; i++)
+ if (stricmp(mask, exceptions[i].mask) == 0)
+ return 0;
+
+ nexceptions++;
+ exceptions = srealloc(exceptions, sizeof(Exception) * nexceptions);
+
+ exceptions[nexceptions - 1].mask = sstrdup(mask);
+ exceptions[nexceptions - 1].limit = limit;
+ exceptions[nexceptions - 1].reason = sstrdup(reason);
+ exceptions[nexceptions - 1].time = time(NULL);
+ strscpy(exceptions[nexceptions - 1].who, who, NICKMAX);
+ exceptions[nexceptions - 1].expires = expires;
+ exceptions[nexceptions - 1].num = nexceptions - 1;
+
+ return 1;
+}
+
+/*************************************************************************/
+
+static int exception_del(const int index)
+{
+ if (index < 0 || index >= nexceptions)
+ return 0;
+
+ free(exceptions[index].mask);
+ free(exceptions[index].reason);
+ nexceptions--;
+ memmove(exceptions + index, exceptions + index + 1,
+ sizeof(Exception) * (nexceptions - index));
+ exceptions = srealloc(exceptions, sizeof(Exception) * nexceptions);
+
+ return 1;
+}
+
+/* We use the "num" property to keep track of the position of each exception
+ * when deleting using ranges. This is because an exception's position changes
+ * as others are deleted. The positions will be recalculated once the process
+ * is complete. -TheShadow
+ */
+
+static int exception_del_callback(User * u, int num, va_list args)
+{
+ int i;
+ int *last = va_arg(args, int *);
+
+ *last = num;
+ for (i = 0; i < nexceptions; i++) {
+ if (num - 1 == exceptions[i].num)
+ break;
+ }
+ if (i < nexceptions)
+ return exception_del(i);
+ else
+ return 0;
+}
+
+static int exception_list(User * u, const int index, int *sent_header)
+{
+ if (index < 0 || index >= nexceptions)
+ return 0;
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_HEADER);
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_COLHEAD);
+ *sent_header = 1;
+ }
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_FORMAT, index + 1,
+ exceptions[index].limit, exceptions[index].mask);
+ return 1;
+}
+
+static int exception_list_callback(User * u, int num, va_list args)
+{
+ int *sent_header = va_arg(args, int *);
+
+ return exception_list(u, num - 1, sent_header);
+}
+
+static int exception_view(User * u, const int index, int *sent_header)
+{
+ char timebuf[32], expirebuf[256];
+ struct tm tm;
+ time_t t = time(NULL);
+
+ if (index < 0 || index >= nexceptions)
+ return 0;
+ if (!*sent_header) {
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_HEADER);
+ *sent_header = 1;
+ }
+
+ tm = *localtime(exceptions[index].time ? &exceptions[index].time : &t);
+ strftime_lang(timebuf, sizeof(timebuf),
+ u, STRFTIME_SHORT_DATE_FORMAT, &tm);
+
+ expire_left(u->na, expirebuf, sizeof(expirebuf),
+ exceptions[index].expires);
+
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_VIEW_FORMAT,
+ index + 1, exceptions[index].mask,
+ *exceptions[index].who ?
+ exceptions[index].who : "<unknown>",
+ timebuf, expirebuf, exceptions[index].limit,
+ exceptions[index].reason);
+ return 1;
+}
+
+static int exception_view_callback(User * u, int num, va_list args)
+{
+ int *sent_header = va_arg(args, int *);
+
+ return exception_view(u, num - 1, sent_header);
+}
+
+/*************************************************************************/
+
+/* 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 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 MOVE num position
+ * Moves the exception at position num to position.
+ */
+
+int do_exception(User * u)
+{
+ char *cmd = strtok(NULL, " ");
+ char *mask, *reason, *expiry, *limitstr;
+ int limit, expires;
+ int i;
+
+ if (!LimitSessions) {
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!cmd)
+ cmd = "";
+
+ if (stricmp(cmd, "ADD") == 0) {
+ if (nexceptions >= 32767) {
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_TOO_MANY);
+ return MOD_CONT;
+ }
+
+ mask = strtok(NULL, " ");
+ if (mask && *mask == '+') {
+ expiry = mask;
+ mask = strtok(NULL, " ");
+ } else {
+ expiry = NULL;
+ }
+ limitstr = strtok(NULL, " ");
+ reason = strtok(NULL, "");
+
+ if (!reason) {
+ syntax_error(s_OperServ, u, "EXCEPTION",
+ OPER_EXCEPTION_ADD_SYNTAX);
+ return MOD_CONT;
+ }
+
+ expires = expiry ? dotime(expiry) : ExceptionExpiry;
+ if (expires < 0) {
+ notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
+ return MOD_CONT;
+ } else if (expires > 0) {
+ expires += time(NULL);
+ }
+
+ limit = (limitstr && isdigit(*limitstr)) ? atoi(limitstr) : -1;
+
+ if (limit < 0 || limit > MaxSessionLimit) {
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_INVALID_LIMIT,
+ MaxSessionLimit);
+ return MOD_CONT;
+
+ } else {
+ if (strchr(mask, '!') || strchr(mask, '@')) {
+ notice_lang(s_OperServ, u,
+ OPER_EXCEPTION_INVALID_HOSTMASK);
+ return MOD_CONT;
+ }
+
+ if (exception_add(mask, limit, reason, u->nick, expires))
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_ADDED, mask,
+ limit);
+ else
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_ALREADY_PRESENT,
+ mask, limit);
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ }
+ } else if (stricmp(cmd, "DEL") == 0) {
+ mask = strtok(NULL, " ");
+
+ if (!mask) {
+ syntax_error(s_OperServ, u, "EXCEPTION",
+ OPER_EXCEPTION_DEL_SYNTAX);
+ return MOD_CONT;
+ }
+
+ if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+ int count, deleted, last = -1;
+ deleted =
+ process_numlist(mask, &count, exception_del_callback, u,
+ &last);
+ if (!deleted) {
+ 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 {
+ int deleted = 0;
+
+ for (i = 0; i < nexceptions; i++) {
+ if (stricmp(mask, exceptions[i].mask) == 0) {
+ exception_del(i);
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED,
+ mask);
+ deleted = 1;
+ break;
+ }
+ }
+ if (!deleted && i == nexceptions)
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_NOT_FOUND, mask);
+ }
+
+ /* 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 */
+
+ for (i = 0; i < nexceptions; i++)
+ exceptions[i].num = i;
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+
+ } else if (stricmp(cmd, "MOVE") == 0) {
+ Exception *exception;
+ char *n1str = strtok(NULL, " "); /* From position */
+ char *n2str = strtok(NULL, " "); /* To position */
+ int n1, n2;
+
+ if (!n2str) {
+ syntax_error(s_OperServ, u, "EXCEPTION",
+ OPER_EXCEPTION_MOVE_SYNTAX);
+ return MOD_CONT;
+ }
+
+ n1 = atoi(n1str) - 1;
+ n2 = atoi(n2str) - 1;
+
+ if ((n1 >= 0 && n1 < nexceptions) && (n2 >= 0 && n2 < nexceptions)
+ && (n1 != n2)) {
+ exception = scalloc(sizeof(Exception), 1);
+ memcpy(exception, &exceptions[n1], sizeof(Exception));
+
+ if (n1 < n2) {
+ /* Shift upwards */
+ memmove(&exceptions[n1], &exceptions[n1 + 1],
+ sizeof(Exception) * (n2 - n1));
+ memmove(&exceptions[n2], exception, sizeof(Exception));
+ } else {
+ /* Shift downwards */
+ memmove(&exceptions[n2 + 1], &exceptions[n2],
+ sizeof(Exception) * (n1 - n2));
+ memmove(&exceptions[n2], exception, sizeof(Exception));
+ }
+
+ free(exception);
+
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_MOVED,
+ exceptions[n1].mask, n1 + 1, n2 + 1);
+
+ /* Renumber the exception list. See the DEL block above for why. */
+ for (i = 0; i < nexceptions; i++)
+ exceptions[i].num = i;
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else {
+ syntax_error(s_OperServ, u, "EXCEPTION",
+ OPER_EXCEPTION_MOVE_SYNTAX);
+ }
+ } else if (stricmp(cmd, "LIST") == 0) {
+ int sent_header = 0;
+ expire_exceptions();
+ mask = strtok(NULL, " ");
+ if (mask && strspn(mask, "1234567890,-") == strlen(mask)) {
+ process_numlist(mask, NULL, exception_list_callback, u,
+ &sent_header);
+ } else {
+ for (i = 0; i < nexceptions; i++) {
+ if (!mask || match_wild_nocase(mask, exceptions[i].mask))
+ exception_list(u, i, &sent_header);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_MATCH);
+
+ } else if (stricmp(cmd, "VIEW") == 0) {
+ int sent_header = 0;
+ expire_exceptions();
+ mask = strtok(NULL, " ");
+ if (mask && strspn(mask, "1234567890,-") == strlen(mask)) {
+ process_numlist(mask, NULL, exception_view_callback, u,
+ &sent_header);
+ } else {
+ for (i = 0; i < nexceptions; i++) {
+ if (!mask || match_wild_nocase(mask, exceptions[i].mask))
+ exception_view(u, i, &sent_header);
+ }
+ }
+ if (!sent_header)
+ notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_MATCH);
+
+ } else {
+ syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_SYNTAX);
+ }
+ return MOD_CONT;
+}
+
+/*************************************************************************/
diff --git a/src/slist.c b/src/slist.c
new file mode 100644
index 000000000..5b2c0e774
--- /dev/null
+++ b/src/slist.c
@@ -0,0 +1,325 @@
+/* Services list handler implementation.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "slist.h"
+
+static SListOpts slist_defopts = { 0, NULL, NULL, NULL };
+
+/*************************************************************************/
+
+/* Adds a pointer to the list. Returns the index of the new item.
+ Returns -2 if there are too many items in the list, -3 if the
+ item already exists when the flags of the list contain SLISTF_NODUP. */
+
+int slist_add(SList * slist, void *item)
+{
+ if (slist->limit != 0 && slist->count >= slist->limit)
+ return -2;
+ if (slist->opts && (slist->opts->flags & SLISTF_NODUP)
+ && slist_indexof(slist, item) != -1)
+ return -3;
+ if (slist->capacity == slist->count)
+ slist_setcapacity(slist, slist->capacity + 1);
+
+ if (slist->opts && (slist->opts->flags & SLISTF_SORT)
+ && slist->opts->compareitem) {
+ int i;
+
+ for (i = 0; i < slist->count; i++) {
+ if (slist->opts->compareitem(slist, item, slist->list[i]) <= 0) {
+ memmove(&slist->list[i + 1], &slist->list[i],
+ sizeof(void *) * (slist->count - i));
+ slist->list[i] = item;
+ break;
+ }
+ }
+
+ if (i == slist->count)
+ slist->list[slist->count] = item;
+ } else {
+ slist->list[slist->count] = item;
+ }
+
+ return slist->count++;
+}
+
+/*************************************************************************/
+
+/* Clears the list. If free is 1, the freeitem function will be called
+ * for each item before clearing.
+ */
+
+void slist_clear(SList * slist, int mustfree)
+{
+ if (mustfree && slist->opts && slist->opts->freeitem && slist->count) {
+ int i;
+
+ for (i = 0; i < slist->count; i++)
+ if (slist->list[i])
+ slist->opts->freeitem(slist, slist->list[i]);
+ }
+
+ if (slist->list) {
+ free(slist->list);
+ slist->list = NULL;
+ }
+ slist->capacity = 0;
+ slist->count = 0;
+}
+
+/*************************************************************************/
+
+/* Deletes an item from the list, by index. Returns 1 if successful,
+ 0 otherwise. */
+
+int slist_delete(SList * slist, int index)
+{
+ /* Range check */
+ if (index >= slist->count)
+ return 0;
+
+ if (slist->list[index] && slist->opts && slist->opts->freeitem)
+ slist->opts->freeitem(slist, slist->list[index]);
+
+ slist->list[index] = NULL;
+ slist->count--;
+
+ if (index < slist->count)
+ memmove(&slist->list[index], &slist->list[index + 1],
+ sizeof(void *) * (slist->count - index));
+
+ slist_setcapacity(slist, slist->capacity - 1);
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Deletes a range of entries. Return -1 if the permission was denied,
+ * 0 if no records were deleted, or the number of records deleted
+ */
+
+int slist_delete_range(SList * slist, char *range, slist_delcheckcb_t cb,
+ ...)
+{
+ int count = 0, i, n1, n2;
+ va_list args;
+
+ va_start(args, cb);
+
+ for (;;) {
+ n1 = n2 = strtol(range, (char **) &range, 10);
+ range += strcspn(range, "0123456789,-");
+
+ if (*range == '-') {
+ range++;
+ range += strcspn(range, "0123456789,");
+ if (isdigit(*range)) {
+ n2 = strtol(range, (char **) &range, 10);
+ range += strcspn(range, "0123456789,-");
+ }
+ }
+
+ for (i = n1; i <= n2 && i > 0 && i <= slist->count; i++) {
+ if (!slist->list[i - 1])
+ continue;
+ if (cb && !cb(slist, slist->list[i - 1], args))
+ return -1;
+
+ if (slist->opts && slist->opts->freeitem)
+ slist->opts->freeitem(slist, slist->list[i - 1]);
+ slist->list[i - 1] = NULL;
+
+ count++;
+ }
+
+ range += strcspn(range, ",");
+ if (*range)
+ range++;
+ else
+ break;
+ }
+
+ /* We only really delete the items from the list after having processed
+ * everything because it would change the position of the items in the
+ * list otherwise.
+ */
+ slist_pack(slist);
+
+ va_end(args);
+ return count;
+}
+
+/*************************************************************************/
+
+/* Enumerates all entries of the list. If range is not NULL, will only
+ * enumerate entries that are in the range. Returns the total number
+ * of entries enumerated.
+ */
+
+int slist_enum(SList * slist, char *range, slist_enumcb_t cb, ...)
+{
+ int count = 0, i, res;
+ va_list args;
+
+ va_start(args, cb);
+
+ if (!range) {
+ for (i = 0; i < slist->count; i++) {
+ if (!slist->list[i]) {
+ alog("SList: warning: NULL pointer in the list (?)");
+ continue;
+ }
+
+ res = cb(slist, i + 1, slist->list[i], args);
+ if (res < 0)
+ break;
+ count += res;
+ }
+ } else {
+ int n1, n2;
+
+ for (;;) {
+ res = 0;
+ n1 = n2 = strtol(range, (char **) &range, 10);
+ range += strcspn(range, "0123456789,-");
+ if (*range == '-') {
+ range++;
+ range += strcspn(range, "0123456789,");
+ if (isdigit(*range)) {
+ n2 = strtol(range, (char **) &range, 10);
+ range += strcspn(range, "0123456789,-");
+ }
+ }
+ for (i = n1; i <= n2 && i > 0 && i <= slist->count; i++) {
+ if (!slist->list[i - 1]) {
+ alog("SList: warning: NULL pointer in the list (?)");
+ continue;
+ }
+
+ res = cb(slist, i, slist->list[i - 1], args);
+ if (res < 0)
+ break;
+ count += res;
+ }
+ if (res < -1)
+ break;
+ range += strcspn(range, ",");
+ if (*range)
+ range++;
+ else
+ break;
+ }
+ }
+
+ va_end(args);
+
+ return count;
+}
+
+/*************************************************************************/
+
+/* Determines whether the list is full. */
+
+int slist_full(SList * slist)
+{
+ if (slist->limit != 0 && slist->count >= slist->limit)
+ return 1;
+ else
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Initialization of the list. */
+
+void slist_init(SList * slist)
+{
+ memset(slist, 0, sizeof(SList));
+ slist->limit = SLIST_DEFAULT_LIMIT;
+ slist->opts = &slist_defopts;
+}
+
+/*************************************************************************/
+
+/* Returns the index of an item in the list, -1 if inexistant. */
+
+int slist_indexof(SList * slist, void *item)
+{
+ int16 i;
+ void *entry;
+
+ if (slist->count == 0)
+ return -1;
+
+ for (i = 0, entry = slist->list[0]; i < slist->count;
+ i++, entry = slist->list[i]) {
+ if ((slist->opts
+ && slist->opts->isequal) ? (slist->opts->isequal(slist, item,
+ entry))
+ : (item == entry))
+ return i;
+ }
+
+ return -1;
+}
+
+/*************************************************************************/
+
+/* Removes all NULL pointers from the list. */
+
+void slist_pack(SList * slist)
+{
+ int i;
+
+ for (i = slist->count - 1; i >= 0; i--)
+ if (!slist->list[i])
+ slist_delete(slist, i);
+}
+
+/*************************************************************************/
+
+/* Removes a specific item from the list. Returns the old index of the
+ deleted item, or -1 if the item was not found. */
+
+int slist_remove(SList * slist, void *item)
+{
+ int index = slist_indexof(slist, item);
+ if (index == -1)
+ return -1;
+ slist_delete(slist, index);
+ return index;
+}
+
+/*************************************************************************/
+
+/* Sets the maximum capacity of the list */
+
+int slist_setcapacity(SList * slist, int16 capacity)
+{
+ if (slist->capacity == capacity)
+ return 1;
+ slist->capacity = capacity;
+ if (slist->capacity)
+ slist->list =
+ srealloc(slist->list, sizeof(void *) * slist->capacity);
+ else {
+ free(slist->list);
+ slist->list = NULL;
+ }
+ if (slist->capacity < slist->count)
+ slist->count = slist->capacity;
+ return 1;
+}
diff --git a/src/sockutil.c b/src/sockutil.c
new file mode 100644
index 000000000..2ce091c2d
--- /dev/null
+++ b/src/sockutil.c
@@ -0,0 +1,549 @@
+/* Socket utility routines.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Read from a socket with buffering. */
+
+static char read_netbuf[NET_BUFSIZE];
+static char *read_curpos = read_netbuf; /* Next byte to return */
+static char *read_bufend = read_netbuf; /* Next position for data from socket */
+static char *const read_buftop = read_netbuf + NET_BUFSIZE;
+int32 total_read = 0;
+
+
+/* Return amount of data in read buffer. */
+
+int32 read_buffer_len()
+{
+ if (read_bufend >= read_curpos)
+ return read_bufend - read_curpos;
+ else
+ return (read_bufend + NET_BUFSIZE) - read_curpos;
+}
+
+
+/* Read data. */
+
+static int buffered_read(int fd, char *buf, int len)
+{
+ int nread, left = len;
+ fd_set fds;
+ struct timeval tv = { 0, 0 };
+ int errno_save = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+ while (left > 0) {
+ struct timeval *tvptr = (read_bufend == read_curpos ? NULL : &tv);
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ while (read_bufend != read_curpos - 1
+ && !(read_curpos == read_netbuf
+ && read_bufend == read_buftop - 1)
+ && select(fd + 1, &fds, 0, 0, tvptr) == 1) {
+ int maxread;
+ tvptr = &tv; /* don't wait next time */
+ if (read_bufend < read_curpos) /* wrapped around? */
+ maxread = (read_curpos - 1) - read_bufend;
+ else if (read_curpos == read_netbuf)
+ maxread = read_buftop - read_bufend - 1;
+ else
+ maxread = read_buftop - read_bufend;
+ nread = read(fd, read_bufend, maxread);
+ errno_save = errno;
+ if (debug >= 3)
+ alog("debug: buffered_read wanted %d, got %d", maxread,
+ nread);
+ if (nread <= 0)
+ break;
+ read_bufend += nread;
+ if (read_bufend == read_buftop)
+ read_bufend = read_netbuf;
+ }
+ if (read_curpos == read_bufend) /* No more data on socket */
+ break;
+ /* See if we can gobble up the rest of the buffer. */
+ if (read_curpos + left >= read_buftop && read_bufend < read_curpos) {
+ nread = read_buftop - read_curpos;
+ memcpy(buf, read_curpos, nread);
+ buf += nread;
+ left -= nread;
+ read_curpos = read_netbuf;
+ }
+ /* Now everything we need is in a single chunk at read_curpos. */
+ if (read_bufend > read_curpos && read_bufend - read_curpos < left)
+ nread = read_bufend - read_curpos;
+ else
+ nread = left;
+ if (nread) {
+ memcpy(buf, read_curpos, nread);
+ buf += nread;
+ left -= nread;
+ read_curpos += nread;
+ }
+ }
+ total_read += len - left;
+ if (debug >= 4) {
+ alog("debug: buffered_read(%d,%p,%d) returning %d",
+ fd, buf, len, len - left);
+ }
+ errno = errno_save;
+ return len - left;
+}
+
+/* Optimized version of the above for reading a single character; returns
+ * the character in an int or EOF, like fgetc(). */
+
+static int buffered_read_one(int fd)
+{
+ int nread;
+ fd_set fds;
+ struct timeval tv = { 0, 0 };
+ char c;
+ struct timeval *tvptr = (read_bufend == read_curpos ? NULL : &tv);
+ int errno_save = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ while (read_bufend != read_curpos - 1
+ && !(read_curpos == read_netbuf
+ && read_bufend == read_buftop - 1)
+ && select(fd + 1, &fds, 0, 0, tvptr) == 1) {
+ int maxread;
+ tvptr = &tv; /* don't wait next time */
+ if (read_bufend < read_curpos) /* wrapped around? */
+ maxread = (read_curpos - 1) - read_bufend;
+ else if (read_curpos == read_netbuf)
+ maxread = read_buftop - read_bufend - 1;
+ else
+ maxread = read_buftop - read_bufend;
+ nread = read(fd, read_bufend, maxread);
+ errno_save = errno;
+ if (debug >= 3)
+ alog("debug: buffered_read_one wanted %d, got %d", maxread,
+ nread);
+ if (nread <= 0)
+ break;
+ read_bufend += nread;
+ if (read_bufend == read_buftop)
+ read_bufend = read_netbuf;
+ }
+ if (read_curpos == read_bufend) { /* No more data on socket */
+ if (debug >= 4)
+ alog("debug: buffered_read_one(%d) returning %d", fd, EOF);
+ errno = errno_save;
+ return EOF;
+ }
+ c = *read_curpos++;
+ if (read_curpos == read_buftop)
+ read_curpos = read_netbuf;
+ total_read++;
+ if (debug >= 4)
+ alog("debug: buffered_read_one(%d) returning %d", fd, c);
+ return (int) c & 0xFF;
+}
+
+/*************************************************************************/
+
+/* Write to a socket with buffering. Note that this assumes only one
+ * socket. */
+
+static char write_netbuf[NET_BUFSIZE];
+static char *write_curpos = write_netbuf; /* Next byte to write to socket */
+static char *write_bufend = write_netbuf; /* Next position for data to socket */
+static char *const write_buftop = write_netbuf + NET_BUFSIZE;
+static int write_fd = -1;
+int32 total_written;
+
+
+/* Return amount of data in write buffer. */
+
+int32 write_buffer_len()
+{
+ if (write_bufend >= write_curpos)
+ return write_bufend - write_curpos;
+ else
+ return (write_bufend + NET_BUFSIZE) - write_curpos;
+}
+
+
+/* Helper routine to try and write up to one chunk of data from the buffer
+ * to the socket. Return how much was written. */
+
+static int flush_write_buffer(int wait)
+{
+ fd_set fds;
+ struct timeval tv = { 0, 0 };
+ int errno_save = errno;
+
+ if (write_bufend == write_curpos || write_fd == -1)
+ return 0;
+ FD_ZERO(&fds);
+ FD_SET(write_fd, &fds);
+ if (select(write_fd + 1, 0, &fds, 0, wait ? NULL : &tv) == 1) {
+ int maxwrite, nwritten;
+ if (write_curpos > write_bufend) /* wrapped around? */
+ maxwrite = write_buftop - write_curpos;
+ else if (write_bufend == write_netbuf)
+ maxwrite = write_buftop - write_curpos - 1;
+ else
+ maxwrite = write_bufend - write_curpos;
+ nwritten = write(write_fd, write_curpos, maxwrite);
+ errno_save = errno;
+ if (debug >= 3)
+ alog("debug: flush_write_buffer wanted %d, got %d", maxwrite,
+ nwritten);
+ if (nwritten > 0) {
+ write_curpos += nwritten;
+ if (write_curpos == write_buftop)
+ write_curpos = write_netbuf;
+ total_written += nwritten;
+ return nwritten;
+ }
+ }
+ errno = errno_save;
+ return 0;
+}
+
+
+/* Write data. */
+
+static int buffered_write(int fd, char *buf, int len)
+{
+ int nwritten, left = len;
+ int errno_save = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+ write_fd = fd;
+
+ while (left > 0) {
+
+ /* Don't try putting anything in the buffer if it's full. */
+ if (write_curpos != write_bufend + 1 &&
+ (write_curpos != write_netbuf
+ || write_bufend != write_buftop - 1)) {
+ /* See if we need to write up to the end of the buffer. */
+ if (write_bufend + left >= write_buftop
+ && write_curpos <= write_bufend) {
+ nwritten = write_buftop - write_bufend;
+ memcpy(write_bufend, buf, nwritten);
+ buf += nwritten;
+ left -= nwritten;
+ write_bufend = write_netbuf;
+ }
+ /* Now we can copy a single chunk to write_bufend. */
+ if (write_curpos > write_bufend
+ && write_curpos - write_bufend - 1 < left)
+ nwritten = write_curpos - write_bufend - 1;
+ else
+ nwritten = left;
+ if (nwritten) {
+ memcpy(write_bufend, buf, nwritten);
+ buf += nwritten;
+ left -= nwritten;
+ write_bufend += nwritten;
+ }
+ }
+
+ /* Now write to the socket as much as we can. */
+ if (write_curpos == write_bufend + 1 ||
+ (write_curpos == write_netbuf
+ && write_bufend == write_buftop - 1))
+ flush_write_buffer(1);
+ else
+ flush_write_buffer(0);
+ errno_save = errno;
+ if (write_curpos == write_bufend + 1 ||
+ (write_curpos == write_netbuf
+ && write_bufend == write_buftop - 1)) {
+ /* Write failed on full buffer */
+ break;
+ }
+ }
+
+ if (debug >= 4) {
+ alog("debug: buffered_write(%d,%p,%d) returning %d",
+ fd, buf, len, len - left);
+ }
+ errno = errno_save;
+ return len - left;
+}
+
+/* Optimized version of the above for writing a single character; returns
+ * the character in an int or EOF, like fputc(). Commented out because it
+ * isn't currently used. */
+
+#if 0
+static int buffered_write_one(int c, int fd)
+{
+ struct timeval tv = { 0, 0 };
+
+ if (fd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+ write_fd = fd;
+
+ /* Try to flush the buffer if it's full. */
+ if (write_curpos == write_bufend + 1 ||
+ (write_curpos == write_netbuf
+ && write_bufend == write_buftop - 1)) {
+ flush_write_buffer(1);
+ if (write_curpos == write_bufend + 1 ||
+ (write_curpos == write_netbuf
+ && write_bufend == write_buftop - 1)) {
+ /* Write failed */
+ if (debug >= 4)
+ alog("debug: buffered_write_one(%d) returning %d", fd,
+ EOF);
+ return EOF;
+ }
+ }
+
+ /* Write the character. */
+ *write_bufend++ = c;
+ if (write_bufend == write_buftop)
+ write_bufend = write_netbuf;
+
+ /* Move it to the socket if we can. */
+ flush_write_buffer(0);
+
+ if (debug >= 4)
+ alog("debug: buffered_write_one(%d) returning %d", fd, c);
+ return (int) c & 0xFF;
+}
+#endif /* 0 */
+
+/*************************************************************************/
+/*************************************************************************/
+
+static int lastchar = EOF;
+
+int sgetc(int s)
+{
+ int c;
+
+ if (lastchar != EOF) {
+ c = lastchar;
+ lastchar = EOF;
+ return c;
+ }
+ return buffered_read_one(s);
+}
+
+int sungetc(int c, int s)
+{
+ return lastchar = c;
+}
+
+/*************************************************************************/
+
+/* If connection was broken, return NULL. If the read timed out, return
+ * (char *)-1.
+ */
+
+char *sgets(char *buf, int len, int s)
+{
+ int c = 0;
+ struct timeval tv;
+ fd_set fds;
+ char *ptr = buf;
+
+ if (len == 0)
+ return NULL;
+ FD_SET(s, &fds);
+ tv.tv_sec = ReadTimeout;
+ tv.tv_usec = 0;
+ while (read_buffer_len() == 0 &&
+ (c = select(s + 1, &fds, NULL, NULL, &tv)) < 0) {
+ if (errno != EINTR)
+ break;
+ }
+ if (read_buffer_len() == 0 && c == 0)
+ return (char *) -1;
+ c = sgetc(s);
+ while (--len && (*ptr++ = c) != '\n' && (c = sgetc(s)) >= 0);
+ if (c < 0)
+ return NULL;
+ *ptr = 0;
+ return buf;
+}
+
+/*************************************************************************/
+
+/* sgets2: Read a line of text from a socket, and strip newline and
+ * carriage return characters from the end of the line.
+ */
+
+char *sgets2(char *buf, int len, int s)
+{
+ char *str = sgets(buf, len, s);
+
+ if (!str || str == (char *) -1)
+ return str;
+ str = buf + strlen(buf) - 1;
+ if (*str == '\n')
+ *str-- = 0;
+ if (*str == '\r')
+ *str = 0;
+ return buf;
+}
+
+/*************************************************************************/
+
+/* Read from a socket. (Use this instead of read() because it has
+ * buffering.) */
+
+int sread(int s, char *buf, int len)
+{
+ return buffered_read(s, buf, len);
+}
+
+/*************************************************************************/
+
+int sputs(char *str, int s)
+{
+ return buffered_write(s, str, strlen(str));
+}
+
+/*************************************************************************/
+
+int sockprintf(int s, char *fmt, ...)
+{
+ va_list args;
+ char buf[16384]; /* Really huge, to try and avoid truncation */
+
+ va_start(args, fmt);
+ return buffered_write(s, buf, vsnprintf(buf, sizeof(buf), fmt, args));
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+#if !HAVE_GETHOSTBYNAME
+
+/* Translate an IP dotted-quad address to a 4-byte character string.
+ * Return NULL if the given string is not in dotted-quad format.
+ */
+
+static char *pack_ip(const char *ipaddr)
+{
+ static char ipbuf[4];
+ int tmp[4], i;
+
+ if (sscanf(ipaddr, "%d.%d.%d.%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3])
+ != 4)
+ return NULL;
+ for (i = 0; i < 4; i++) {
+ if (tmp[i] < 0 || tmp[i] > 255)
+ return NULL;
+ ipbuf[i] = tmp[i];
+ }
+ return ipbuf;
+}
+
+#endif
+
+/*************************************************************************/
+
+/* lhost/lport specify the local side of the connection. If they are not
+ * given (lhost==NULL, lport==0), then they are left free to vary.
+ */
+
+int conn(const char *host, int port, const char *lhost, int lport)
+{
+#if HAVE_GETHOSTBYNAME
+ struct hostent *hp;
+#else
+ char *addr;
+#endif
+ struct sockaddr_in sa, lsa;
+ int sock;
+
+ memset(&lsa, 0, sizeof(lsa));
+ if (lhost) {
+#if HAVE_GETHOSTBYNAME
+ if ((hp = gethostbyname(lhost)) != NULL) {
+ memcpy((char *) &lsa.sin_addr, hp->h_addr, hp->h_length);
+ lsa.sin_family = hp->h_addrtype;
+#else
+ if (addr = pack_ip(lhost)) {
+ memcpy((char *) &lsa.sin_addr, addr, 4);
+ lsa.sin_family = AF_INET;
+#endif
+ } else {
+ lhost = NULL;
+ }
+ }
+ if (lport)
+ lsa.sin_port = htons((unsigned short) lport);
+
+ memset(&sa, 0, sizeof(sa));
+#if HAVE_GETHOSTBYNAME
+ if (!(hp = gethostbyname(host)))
+ return -1;
+ memcpy((char *) &sa.sin_addr, hp->h_addr, hp->h_length);
+ sa.sin_family = hp->h_addrtype;
+#else
+ if (!(addr = pack_ip(host))) {
+ alog("conn(): `%s' is not a valid IP address", host);
+ errno = EINVAL;
+ return -1;
+ }
+ memcpy((char *) &sa.sin_addr, addr, 4);
+ sa.sin_family = AF_INET;
+#endif
+ sa.sin_port = htons((unsigned short) port);
+
+ if ((sock = socket(sa.sin_family, SOCK_STREAM, 0)) < 0)
+ return -1;
+
+ if ((lhost || lport)
+ && bind(sock, (struct sockaddr *) &lsa, sizeof(lsa)) < 0) {
+ int errno_save = errno;
+ close(sock);
+ errno = errno_save;
+ return -1;
+ }
+
+ if (connect(sock, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+ int errno_save = errno;
+ close(sock);
+ errno = errno_save;
+ return -1;
+ }
+
+ return sock;
+}
+
+/*************************************************************************/
+
+void disconn(int s)
+{
+ shutdown(s, 2);
+ close(s);
+}
diff --git a/src/timeout.c b/src/timeout.c
new file mode 100644
index 000000000..fd3c8c983
--- /dev/null
+++ b/src/timeout.c
@@ -0,0 +1,130 @@
+/* Routines for time-delayed actions.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+#include "timeout.h"
+
+static Timeout *timeouts = NULL;
+
+/*************************************************************************/
+
+#ifdef DEBUG_COMMANDS
+
+/* Send the timeout list to the given user. */
+
+void send_timeout_list(User * u)
+{
+ Timeout *to, *last;
+
+ notice(s_OperServ, u->nick, "Now: %ld", time(NULL));
+ for (to = timeouts, last = NULL; to; last = to, to = to->next) {
+ notice(s_OperServ, u->nick, "%p: %ld: %p (%p)",
+ to, to->timeout, to->code, to->data);
+ if (to->prev != last)
+ notice(s_OperServ, u->nick,
+ " to->prev incorrect! expected=%p seen=%p",
+ last, to->prev);
+ }
+}
+
+#endif /* DEBUG_COMMANDS */
+
+/*************************************************************************/
+
+/* Check the timeout list for any pending actions. */
+
+void check_timeouts(void)
+{
+ Timeout *to, *to2;
+ time_t t = time(NULL);
+
+ if (debug >= 2)
+ alog("debug: Checking timeouts at %ld", t);
+
+ to = timeouts;
+ while (to) {
+ if (t < to->timeout) {
+ to = to->next;
+ continue;
+ }
+ if (debug >= 4) {
+ alog("debug: Running timeout %p (code=%p repeat=%d)",
+ to, to->code, to->repeat);
+ }
+ to->code(to);
+ if (to->repeat) {
+ to = to->next;
+ continue;
+ }
+ to2 = to->next;
+ if (to->next)
+ to->next->prev = to->prev;
+ if (to->prev)
+ to->prev->next = to->next;
+ else
+ timeouts = to->next;
+ free(to);
+ to = to2;
+ }
+ if (debug >= 2)
+ alog("debug: Finished timeout list");
+}
+
+/*************************************************************************/
+
+/* Add a timeout to the list to be triggered in `delay' seconds. If
+ * `repeat' is nonzero, do not delete the timeout after it is triggered.
+ * This must maintain the property that timeouts added from within a
+ * timeout routine do not get checked during that run of the timeout list.
+ */
+
+Timeout *add_timeout(int delay, void (*code) (Timeout *), int repeat)
+{
+ Timeout *t = scalloc(sizeof(Timeout), 1);
+ t->settime = time(NULL);
+ t->timeout = t->settime + delay;
+ t->code = code;
+ t->repeat = repeat;
+ t->next = timeouts;
+ t->prev = NULL;
+ if (timeouts)
+ timeouts->prev = t;
+ timeouts = t;
+ return t;
+}
+
+/*************************************************************************/
+
+/* Remove a timeout from the list (if it's there). */
+
+void del_timeout(Timeout * t)
+{
+ Timeout *ptr;
+
+ for (ptr = timeouts; ptr; ptr = ptr->next) {
+ if (ptr == t)
+ break;
+ }
+ if (!ptr)
+ return;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ timeouts = t->next;
+ if (t->next)
+ t->next->prev = t->prev;
+ free(t);
+}
+
+/*************************************************************************/
diff --git a/src/users.c b/src/users.c
new file mode 100644
index 000000000..abf4b4b29
--- /dev/null
+++ b/src/users.c
@@ -0,0 +1,1161 @@
+/* Routines to maintain a list of online users.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include "services.h"
+
+#define HASH(nick) (((nick)[0]&31)<<5 | ((nick)[1]&31))
+User *userlist[1024];
+
+int32 usercnt = 0, opcnt = 0, maxusercnt = 0;
+time_t maxusertime;
+
+static unsigned long umodes[128] = {
+ 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, 0,
+ 0, UMODE_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ UMODE_P,
+#else
+ 0,
+#endif
+ 0,
+#if defined(IRC_BAHAMUT) || defined(IRC_ULTIMATE)
+ UMODE_R,
+#else
+ 0,
+#endif
+ 0, 0, 0, 0, 0, 0, 0,
+#ifdef IRC_ULTIMATE3
+ UMODE_Z,
+#else
+ 0,
+#endif
+ 0, 0, 0, 0, 0,
+ 0, UMODE_a, 0, 0, 0, 0, 0,
+#ifdef IRC_DREAMFORGE
+ UMODE_g,
+#else
+ 0,
+#endif
+ UMODE_h, UMODE_i, 0, 0, 0, 0, 0, UMODE_o,
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ UMODE_p,
+#else
+ 0,
+#endif
+ 0, UMODE_r, 0, 0, 0, 0, UMODE_w,
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_RAGE2)
+ UMODE_x,
+#else
+ 0,
+#endif
+ 0,
+ 0,
+ 0, 0, 0, 0, 0
+};
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Allocate a new User structure, fill in basic values, link it to the
+ * overall list, and return it. Always successful.
+ */
+
+static User *new_user(const char *nick)
+{
+ User *user, **list;
+
+ user = scalloc(sizeof(User), 1);
+ if (!nick)
+ nick = "";
+ strscpy(user->nick, nick, NICKMAX);
+ list = &userlist[HASH(user->nick)];
+ user->next = *list;
+ if (*list)
+ (*list)->prev = user;
+ *list = user;
+ user->na = findnick(nick);
+ if (user->na)
+ user->na->u = user;
+ usercnt++;
+ if (usercnt > maxusercnt) {
+ maxusercnt = usercnt;
+ maxusertime = time(NULL);
+ if (LogMaxUsers)
+ alog("user: New maximum user count: %d", maxusercnt);
+ }
+ user->isSuperAdmin = 0; /* always set SuperAdmin to 0 for new users */
+ return user;
+}
+
+/*************************************************************************/
+
+/* Change the nickname of a user, and move pointers as necessary. */
+
+static void change_user_nick(User * user, const char *nick)
+{
+ User **list;
+ int is_same = (!stricmp(user->nick, nick) ? 1 : 0);
+
+ if (user->prev)
+ user->prev->next = user->next;
+ else
+ userlist[HASH(user->nick)] = user->next;
+ if (user->next)
+ user->next->prev = user->prev;
+ user->nick[1] = 0; /* paranoia for zero-length nicks */
+ strscpy(user->nick, nick, NICKMAX);
+ list = &userlist[HASH(user->nick)];
+ user->next = *list;
+ user->prev = NULL;
+ if (*list)
+ (*list)->prev = user;
+ *list = user;
+
+ /* Only if old and new nick aren't the same; no need to waste time */
+ if (!is_same) {
+ if (user->na)
+ user->na->u = NULL;
+ user->na = findnick(nick);
+ if (user->na)
+ user->na->u = user;
+ }
+}
+
+/*************************************************************************/
+
+#ifdef HAS_VHOST
+
+static void update_host(User * user)
+{
+ if (user->na && (nick_identified(user)
+ || (!(user->na->nc->flags & NI_SECURE)
+ && nick_recognized(user)))) {
+ if (user->na->last_usermask)
+ free(user->na->last_usermask);
+
+ user->na->last_usermask =
+ smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) + 2);
+ sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
+ GetHost(user));
+ }
+
+ if (debug)
+ alog("debug: %s changes its host to %s", user->nick,
+ GetHost(user));
+}
+
+#endif
+
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_PTLINK) || defined(IRC_RAGE2)
+
+/* Change the (virtual) hostname of a user. */
+
+void change_user_host(User * user, const char *host)
+{
+ if (user->vhost)
+ free(user->vhost);
+ user->vhost = sstrdup(host);
+
+ if (debug)
+ alog("debug: %s changes its vhost to %s", user->nick, host);
+
+
+
+ update_host(user);
+}
+
+#endif
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_PTLINK)
+/* Change the realname of a user. */
+
+void change_user_realname(User * user, const char *realname)
+{
+ if (user->realname)
+ free(user->realname);
+ user->realname = sstrdup(realname);
+
+ if (user->na && (nick_identified(user)
+ || (!(user->na->nc->flags & NI_SECURE)
+ && nick_recognized(user)))) {
+ if (user->na->last_realname)
+ free(user->na->last_realname);
+ user->na->last_realname = sstrdup(realname);
+ }
+
+ if (debug)
+ alog("debug: %s changes its realname to %s", user->nick, realname);
+}
+
+
+/*************************************************************************/
+
+/* Change the username of a user. */
+
+void change_user_username(User * user, const char *username)
+{
+ if (user->username)
+ free(user->username);
+ user->username = sstrdup(username);
+ if (user->na && (nick_identified(user)
+ || (!(user->na->nc->flags & NI_SECURE)
+ && nick_recognized(user)))) {
+ if (user->na->last_usermask)
+ free(user->na->last_usermask);
+
+ user->na->last_usermask =
+ smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) + 2);
+ sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
+ GetHost(user));
+ }
+ if (debug)
+ alog("debug: %s changes its username to %s", user->nick, username);
+}
+
+#endif
+
+/*************************************************************************/
+
+void set_umode(User * user, int ac, char **av)
+{
+ int add = 1; /* 1 if adding modes, 0 if deleting */
+ char *modes = av[0];
+
+ ac--;
+
+ if (debug)
+ alog("debug: Changing mode for %s to %s", user->nick, modes);
+
+ while (*modes) {
+
+ add ? (user->mode |= umodes[(int) *modes]) : (user->mode &=
+ ~umodes[(int)
+ *modes]);
+
+ switch (*modes++) {
+ case '+':
+ add = 1;
+ break;
+ case '-':
+ add = 0;
+ break;
+#if defined(IRC_BAHAMUT) && !defined(IRC_ULTIMATE3) && !defined(IRC_VIAGRA) && !defined(IRC_RAGE2)
+ case 'a':
+ if (add && !is_services_admin(user)) {
+ send_cmd(ServerName, "SVSMODE %s -a", user->nick);
+ user->mode &= ~UMODE_a;
+ }
+ break;
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ case 'a':
+ if (add && !is_services_oper(user)) {
+ send_cmd(ServerName, "SVSMODE %s -a", user->nick);
+ user->mode &= ~UMODE_a;
+ }
+ break;
+ case 'P':
+ if (add && !is_services_admin(user)) {
+ send_cmd(ServerName, "SVSMODE %s -P", user->nick);
+ user->mode &= ~UMODE_P;
+ }
+ break;
+#endif
+#if defined(IRC_ULTIMATE)
+ case 'R':
+ if (add && !is_services_root(user)) {
+ send_cmd(ServerName, "SVSMODE %s -R", user->nick);
+ user->mode &= ~UMODE_R;
+ }
+ break;
+#endif
+#if defined(IRC_ULTIMATE3)
+ case 'Z':
+ if (add && !is_services_root(user)) {
+ send_cmd(ServerName, "SVSMODE %s -Z", user->nick);
+ user->mode &= ~UMODE_Z;
+ }
+ break;
+#endif
+ case 'd':
+ if (ac == 0) {
+#if !defined(IRC_ULTIMATE) && !defined(IRC_UNREAL)
+ alog("user: umode +d with no parameter (?) for user %s",
+ user->nick);
+#endif
+ break;
+ }
+
+ ac--;
+ av++;
+ user->svid = strtoul(*av, NULL, 0);
+ break;
+ case 'o':
+ if (add) {
+ opcnt++;
+
+ if (WallOper)
+ wallops(s_OperServ, "\2%s\2 is now an IRC operator.",
+ user->nick);
+ display_news(user, NEWS_OPER);
+#if defined(IRC_PTLINK)
+ if (is_services_admin(user)) {
+ send_cmd(ServerName, "SVSMODE %s +a", user->nick);
+ user->mode |= UMODE_a;
+ }
+#endif
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_RAGE2)
+ if (is_services_oper(user)) {
+ send_cmd(ServerName, "SVSMODE %s +a", user->nick);
+ user->mode |= UMODE_a;
+ }
+#endif
+
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+ if (is_services_admin(user)) {
+ send_cmd(ServerName, "SVSMODE %s +P", user->nick);
+ user->mode |= UMODE_P;
+ }
+#endif
+
+#ifdef IRC_ULTIMATE
+ if (is_services_root(user)) {
+ send_cmd(ServerName, "SVSMODE %s +R", user->nick);
+ user->mode |= UMODE_R;
+ }
+#endif
+#ifdef IRC_ULTIMATE3
+ if (is_services_root(user)) {
+ send_cmd(ServerName, "SVSMODE %s +Z", user->nick);
+ user->mode |= UMODE_Z;
+ }
+#endif
+ } else {
+ opcnt--;
+ }
+ break;
+ case 'r':
+ if (add && !nick_identified(user)) {
+ send_cmd(ServerName, "SVSMODE %s -r", user->nick);
+ user->mode &= ~UMODE_r;
+ }
+ break;
+#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_RAGE2)
+ case 'x':
+ update_host(user);
+ break;
+#endif
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Remove and free a User structure. */
+
+void delete_user(User * user)
+{
+ struct u_chanlist *c, *c2;
+ struct u_chaninfolist *ci, *ci2;
+
+ if (LogUsers) {
+#ifdef HAS_VHOST
+ alog("LOGUSERS: %s (%s@%s => %s) (%s) left the network (%s).",
+ user->nick, user->username, user->host,
+ (user->vhost ? user->vhost : "(none)"), user->realname,
+ user->server->name);
+#else
+ alog("LOGUSERS: %s (%s@%s) (%s) left the network (%s).",
+ user->nick, user->username, user->host,
+ user->realname, user->server->name);
+#endif
+ }
+
+ if (debug >= 2)
+ alog("debug: delete_user() called");
+ usercnt--;
+ if (is_oper(user))
+ opcnt--;
+ if (debug >= 2)
+ alog("debug: delete_user(): free user data");
+ free(user->username);
+ free(user->host);
+#ifdef HAS_VHOST
+ if (user->vhost)
+ free(user->vhost);
+#endif
+ free(user->realname);
+ if (debug >= 2)
+ alog("debug: delete_user(): remove from channels");
+ c = user->chans;
+ while (c) {
+ c2 = c->next;
+ chan_deluser(user, c->chan);
+ free(c);
+ c = c2;
+ }
+ /* This called only here now */
+ cancel_user(user);
+ if (user->na)
+ user->na->u = NULL;
+ if (debug >= 2)
+ alog("debug: delete_user(): free founder data");
+ ci = user->founder_chans;
+ while (ci) {
+ ci2 = ci->next;
+ free(ci);
+ ci = ci2;
+ }
+
+ moduleCleanStruct(user->moduleData);
+
+ if (debug >= 2)
+ alog("debug: delete_user(): delete from list");
+ if (user->prev)
+ user->prev->next = user->next;
+ else
+ userlist[HASH(user->nick)] = user->next;
+ if (user->next)
+ user->next->prev = user->prev;
+ if (debug >= 2)
+ alog("debug: delete_user(): free user structure");
+ free(user);
+ if (debug >= 2)
+ alog("debug: delete_user() done");
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Return statistics. Pointers are assumed to be valid. */
+
+void get_user_stats(long *nusers, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i;
+ User *user;
+ struct u_chanlist *uc;
+ struct u_chaninfolist *uci;
+
+ for (i = 0; i < 1024; i++) {
+ for (user = userlist[i]; user; user = user->next) {
+ count++;
+ mem += sizeof(*user);
+ if (user->username)
+ mem += strlen(user->username) + 1;
+ if (user->host)
+ mem += strlen(user->host) + 1;
+#ifdef HAS_VHOST
+ if (user->vhost)
+ mem += strlen(user->vhost) + 1;
+#endif
+ if (user->realname)
+ mem += strlen(user->realname) + 1;
+ if (user->server->name)
+ mem += strlen(user->server->name) + 1;
+ for (uc = user->chans; uc; uc = uc->next)
+ mem += sizeof(*uc);
+ for (uci = user->founder_chans; uci; uci = uci->next)
+ mem += sizeof(*uci);
+ }
+ }
+ *nusers = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+
+/* Find a user by nick. Return NULL if user could not be found. */
+
+User *finduser(const char *nick)
+{
+ User *user;
+
+ if (debug >= 3)
+ alog("debug: finduser(%p)", nick);
+ user = userlist[HASH(nick)];
+ while (user && stricmp(user->nick, nick) != 0)
+ user = user->next;
+ if (debug >= 3)
+ alog("debug: finduser(%s) -> %p", nick, user);
+ return user;
+}
+
+/*************************************************************************/
+
+/* Iterate over all users in the user list. Return NULL at end of list. */
+
+static User *current;
+static int next_index;
+
+User *firstuser(void)
+{
+ next_index = 0;
+ while (next_index < 1024 && current == NULL)
+ current = userlist[next_index++];
+ if (debug)
+ alog("debug: firstuser() returning %s",
+ current ? current->nick : "NULL (end of list)");
+ return current;
+}
+
+User *nextuser(void)
+{
+ if (current)
+ current = current->next;
+ if (!current && next_index < 1024) {
+ while (next_index < 1024 && current == NULL)
+ current = userlist[next_index++];
+ }
+ if (debug)
+ alog("debug: nextuser() returning %s",
+ current ? current->nick : "NULL (end of list)");
+ return current;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* Handle a server NICK command. */
+
+User *do_nick(const char *source, char *nick, char *username, char *host,
+ char *server, char *realname, time_t ts, uint32 svid, ...)
+{
+ User *user;
+
+ char *tmp = NULL;
+ NickAlias *old_na; /* Old nick rec */
+ int nc_changed = 1; /* Did nick core change? */
+ int status = 0; /* Status to apply */
+ char mask[USERMAX + HOSTMAX + 2];
+
+ if (!*source) {
+#ifdef HAS_NICKIP
+ char ipbuf[16];
+ struct in_addr addr;
+ uint32 ip;
+#endif
+#ifdef HAS_VHOST
+ char *vhost = NULL;
+#endif
+#if defined(HAS_NICKIP) || defined(HAS_NICKVHOST)
+ va_list args;
+ va_start(args, svid);
+#endif
+
+#ifdef HAS_NICKIP
+ ip = va_arg(args, uint32);
+#endif
+
+#ifdef HAS_NICKVHOST
+ vhost = va_arg(args, char *);
+ if (!strcmp(vhost, "*")) {
+ vhost = NULL;
+ if (debug)
+ alog("debug: new user with no vhost in NICK command: %s",
+ nick);
+ }
+#endif
+
+ /* This is a new user; create a User structure for it. */
+ if (debug)
+ alog("debug: new user: %s", nick);
+
+ if (LogUsers) {
+ /**
+ * Ugly swap routine for Flop's bug :)
+ **/
+ tmp = strchr(realname, '%');
+ while (tmp) {
+ *tmp = '-';
+ tmp = strchr(realname, '%');
+ }
+ /**
+ * End of ugly swap
+ **/
+
+#ifdef HAS_NICKIP
+ addr.s_addr = htonl(ip);
+ ntoa(addr, ipbuf, sizeof(ipbuf));
+#endif
+
+#ifdef HAS_NICKVHOST
+# ifdef HAS_NICKIP
+ alog("LOGUSERS: %s (%s@%s => %s) (%s) [%s] connected to the network (%s).", nick, username, host, vhost, realname, ipbuf, server);
+# else
+ alog("LOGUSERS: %s (%s@%s => %s) (%s) connected to the network (%s).", nick, username, host, vhost, realname, server);
+# endif
+#else
+# ifdef HAS_NICKIP
+ alog("LOGUSERS: %s (%s@%s) (%s) [%s] connected to the network (%s).", nick, username, host, realname, ipbuf, server);
+# else
+ alog("LOGUSERS: %s (%s@%s) (%s) connected to the network (%s).", nick, username, host, realname, server);
+# endif
+#endif
+ }
+
+ /* We used to ignore the ~ which a lot of ircd's use to indicate no
+ * identd response. That caused channel bans to break, so now we
+ * just take what the server gives us. People are still encouraged
+ * to read the RFCs and stop doing anything to usernames depending
+ * on the result of an identd lookup.
+ */
+
+ /* First check for AKILLs. */
+ /* DONT just return null if its an akill match anymore - yes its more efficent to, however, now that ircd's are
+ * starting to use things like E/F lines, we cant be 100% sure the client will be removed from the network :/
+ * as such, create a user_struct, and if the client is removed, we'll delete it again when the QUIT notice
+ * comes in from the ircd.
+ **/
+ if (check_akill(nick, username, host,
+#ifdef HAS_NICKVHOST
+ vhost,
+#else
+ NULL,
+#endif
+#ifdef HAS_NICKIP
+ ipbuf)) {
+#else
+ NULL)) {
+#endif
+/* return NULL; */
+ }
+
+/**
+ * DefCon AKILL system, if we want to akill all connecting user's here's where to do it
+ * then force check_akill again on them...
+ **/
+ if (checkDefCon(DEFCON_AKILL_NEW_CLIENTS)) {
+ strncpy(mask, "*@", 3);
+ strncat(mask, host, HOSTMAX);
+ alog("DEFCON: adding akill for %s", mask);
+ add_akill(NULL, mask, s_OperServ,
+ time(NULL) + dotime(DefConAKILL),
+ DefConAkillReason ? DefConAkillReason :
+ "DEFCON AKILL");
+ if (check_akill(nick, username, host,
+#ifdef HAS_NICKVHOST
+ vhost,
+#else
+ NULL,
+#endif
+#ifdef HAS_NICKIP
+ ipbuf)) {
+#else
+ NULL)) {
+#endif
+/* return NULL; */
+ }
+ }
+#ifdef IRC_BAHAMUT
+ /* Next for SGLINEs */
+ if (check_sgline(nick, realname))
+ return NULL;
+#endif
+
+ /* And for SQLINEs */
+ if (check_sqline(nick, 0))
+ return NULL;
+
+#ifndef STREAMLINED
+ /* Now check for session limits */
+ if (LimitSessions && !add_session(nick, host))
+ return NULL;
+#endif
+
+ /* And finally, for proxy ;) */
+#ifdef USE_THREADS
+# ifdef HAS_NICKIP
+ if (ProxyDetect && proxy_check(nick, host, ip))
+# else
+ if (ProxyDetect && proxy_check(nick, host, 0))
+# endif
+ return NULL;
+#endif
+
+ /* Allocate User structure and fill it in. */
+ user = new_user(nick);
+ user->username = sstrdup(username);
+ user->host = sstrdup(host);
+ user->server = findserver(servlist, server);
+ user->realname = sstrdup(realname);
+ user->timestamp = ts;
+ user->my_signon = time(NULL);
+
+#ifdef HAS_VHOST
+ user->vhost = vhost ? sstrdup(vhost) : sstrdup(host);
+#endif
+
+ if (CheckClones) {
+ /* Check to see if it looks like clones. */
+ check_clones(user);
+ }
+
+ if (svid == 0) {
+ display_news(user, NEWS_LOGON);
+ display_news(user, NEWS_RANDOM);
+ }
+
+ if (svid == ts && user->na) {
+ /* Timestamp and svid match, and nick is registered; automagically identify the nick */
+ user->svid = svid;
+ user->na->status |= NS_IDENTIFIED;
+ check_memos(user);
+ nc_changed = 0;
+
+ /* Start nick tracking if available */
+ if (NSNickTracking)
+ nsStartNickTracking(user);
+
+ } else if (svid != 1) {
+ /* Resets the svid because it doesn't match */
+ user->svid = 1;
+#ifdef IRC_BAHAMUT
+ send_cmd(ServerName, "SVSMODE %s %lu +d 1", user->nick,
+ user->timestamp);
+#else
+#ifndef IRC_PTLINK
+ send_cmd(ServerName, "SVSMODE %s +d 1", user->nick);
+#endif
+#endif
+ } else {
+ user->svid = 1;
+ }
+
+ } else {
+ /* An old user changing nicks. */
+ user = finduser(source);
+ if (!user) {
+ alog("user: NICK from nonexistent nick %s", source);
+ return NULL;
+ }
+ user->isSuperAdmin = 0; /* Dont let people nick change and stay SuperAdmins */
+ if (debug)
+ alog("debug: %s changes nick to %s", source, nick);
+
+ if (LogUsers) {
+#ifdef HAS_VHOST
+ alog("LOGUSERS: %s (%s@%s => %s) (%s) changed his nick to %s (%s).", user->nick, user->username, user->host, (user->vhost ? user->vhost : "(none)"), user->realname, nick, user->server->name);
+#else
+ alog("LOGUSERS: %s (%s@%s) (%s) changed his nick to %s (%s).",
+ user->nick, user->username, user->host,
+ user->realname, nick, user->server->name);
+#endif
+ }
+
+ user->timestamp = ts;
+
+ if (stricmp(nick, user->nick) == 0) {
+ /* No need to redo things */
+ change_user_nick(user, nick);
+ nc_changed = 0;
+ } else {
+ /* Update this only if nicks aren't the same */
+ user->my_signon = time(NULL);
+
+ old_na = user->na;
+ if (old_na) {
+ if (nick_recognized(user))
+ user->na->last_seen = time(NULL);
+ status = old_na->status & NS_TRANSGROUP;
+ cancel_user(user);
+ }
+
+ change_user_nick(user, nick);
+
+ if ((old_na ? old_na->nc : NULL) ==
+ (user->na ? user->na->nc : NULL))
+ nc_changed = 0;
+
+ if (!nc_changed && (user->na))
+ user->na->status |= status;
+ else {
+#if !defined(IRC_BAHAMUT) && !defined(IRC_PTLINK)
+ /* Because on Bahamut it would be already -r */
+ change_user_mode(user, "-r+d", "1");
+#else
+ change_user_mode(user, "+d", "1");
+#endif
+ }
+ }
+
+ if (!is_oper(user) && check_sqline(user->nick, 1))
+ return NULL;
+
+ } /* if (!*source) */
+
+ /* Check for nick tracking to bypass identification */
+ if (NSNickTracking && nsCheckNickTracking(user)) {
+ user->na->status |= NS_IDENTIFIED;
+ nc_changed = 0;
+ }
+
+ if (nc_changed || !nick_recognized(user)) {
+ if (validate_user(user))
+ check_memos(user);
+ } else {
+ if (nick_identified(user)) {
+ user->na->last_seen = time(NULL);
+
+ if (user->na->last_usermask)
+ free(user->na->last_usermask);
+ user->na->last_usermask =
+ smalloc(strlen(GetIdent(user)) + strlen(GetHost(user)) +
+ 2);
+ sprintf(user->na->last_usermask, "%s@%s", GetIdent(user),
+ GetHost(user));
+
+#ifdef IRC_PTLINK
+ change_user_mode(user, "+r", NULL);
+#endif
+
+#if !defined(IRC_BAHAMUT) && !defined(IRC_PTLINK)
+ if (user->svid != user->timestamp) {
+ char tsbuf[16];
+ snprintf(tsbuf, sizeof(tsbuf), "%lu", user->timestamp);
+ change_user_mode(user, "+rd", tsbuf);
+ } else {
+ change_user_mode(user, "+r", NULL);
+ }
+#endif
+
+
+ alog("%s: %s!%s@%s automatically identified for nick %s",
+ s_NickServ, user->nick, user->username, GetHost(user),
+ user->nick);
+ }
+ }
+
+/* Bahamut sets -r on every nick changes, so we must test it even if nc_changed == 0 */
+#ifdef IRC_BAHAMUT
+ if (nick_identified(user)) {
+ if (user->svid != user->timestamp) {
+ char tsbuf[16];
+ snprintf(tsbuf, sizeof(tsbuf), "%lu", user->timestamp);
+ change_user_mode(user, "+rd", tsbuf);
+ } else {
+ change_user_mode(user, "+r", NULL);
+ }
+ }
+#endif
+
+ return user;
+}
+
+/*************************************************************************/
+
+/* Handle a MODE command for a user.
+ * av[0] = nick to change mode for
+ * av[1] = modes
+ */
+
+void do_umode(const char *source, int ac, char **av)
+{
+ User *user;
+
+ if (stricmp(source, av[0]) != 0) {
+ alog("user: MODE %s %s from different nick %s!", av[0], av[1],
+ source);
+ wallops(NULL, "%s attempted to change mode %s for %s", source,
+ av[1], av[0]);
+ return;
+ }
+
+ user = finduser(source);
+ if (!user) {
+ alog("user: MODE %s for nonexistent nick %s: %s", av[1], source,
+ merge_args(ac, av));
+ return;
+ }
+
+ set_umode(user, ac - 1, &av[1]);
+}
+
+/*************************************************************************/
+
+/* Handle a QUIT command.
+ * av[0] = reason
+ */
+
+void do_quit(const char *source, int ac, char **av)
+{
+ User *user;
+ NickAlias *na;
+
+ user = finduser(source);
+ if (!user) {
+ alog("user: QUIT from nonexistent user %s: %s", source,
+ merge_args(ac, av));
+ return;
+ }
+ if (debug)
+ alog("debug: %s quits", source);
+ if ((na = user->na) && (!(na->status & NS_VERBOTEN))
+ && (na->status & (NS_IDENTIFIED | NS_RECOGNIZED))) {
+ na->last_seen = time(NULL);
+ if (na->last_quit)
+ free(na->last_quit);
+ na->last_quit = *av[0] ? sstrdup(av[0]) : NULL;
+ }
+#ifndef STREAMLINED
+ if (LimitSessions)
+ del_session(user->host);
+#endif
+ delete_user(user);
+}
+
+/*************************************************************************/
+
+/* Handle a KILL command.
+ * av[0] = nick being killed
+ * av[1] = reason
+ */
+
+void do_kill(const char *source, int ac, char **av)
+{
+ User *user;
+ NickAlias *na;
+
+ user = finduser(av[0]);
+ if (!user)
+ return;
+ if (debug)
+ alog("debug: %s killed", av[0]);
+ if ((na = user->na) && (!(na->status & NS_VERBOTEN))
+ && (na->status & (NS_IDENTIFIED | NS_RECOGNIZED))) {
+ na->last_seen = time(NULL);
+ if (na->last_quit)
+ free(na->last_quit);
+ na->last_quit = *av[1] ? sstrdup(av[1]) : NULL;
+
+ }
+#ifndef STREAMLINED
+ if (LimitSessions)
+ del_session(user->host);
+#endif
+ delete_user(user);
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
+
+/* Is the given user protected from kicks and negative mode changes? */
+
+int is_protected(User * user)
+{
+ return (user->mode & UMODE_p);
+}
+#endif
+
+/*************************************************************************/
+
+/* Is the given nick an oper? */
+
+int is_oper(User * user)
+{
+ return (user->mode & UMODE_o);
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+#ifdef HAS_EXCEPT
+/* Is the given user ban-excepted? */
+int is_excepted(ChannelInfo * ci, User * user)
+{
+ int count, i;
+ int isexcepted = 0;
+ char **excepts;
+
+ if (!ci->c)
+ return 0;
+
+ count = ci->c->exceptcount;
+ excepts = scalloc(sizeof(char *) * count, 1);
+ memcpy(excepts, ci->c->excepts, sizeof(char *) * count);
+
+ for (i = 0; i < count; i++) {
+ if (match_usermask(excepts[i], user)) {
+ isexcepted = 1;
+ }
+ }
+ free(excepts);
+ return isexcepted;
+}
+
+/*************************************************************************/
+
+/* Is the given MASK ban-excepted? */
+int is_excepted_mask(ChannelInfo * ci, char *mask)
+{
+ int count, i;
+ int isexcepted = 0;
+ char **excepts;
+
+ if (!ci->c)
+ return 0;
+
+ count = ci->c->exceptcount;
+ excepts = scalloc(sizeof(char *) * count, 1);
+ memcpy(excepts, ci->c->excepts, sizeof(char *) * count);
+
+ for (i = 0; i < count; i++) {
+ if (match_wild_nocase(excepts[i], mask)) {
+ isexcepted = 1;
+ }
+ }
+ free(excepts);
+ return isexcepted;
+}
+
+#endif
+/*************************************************************************/
+
+/* Does the user's usermask match the given mask (either nick!user@host or
+ * just user@host)?
+ */
+
+int match_usermask(const char *mask, User * user)
+{
+ char *mask2 = sstrdup(mask);
+ char *nick, *username, *host;
+ int result;
+
+ if (strchr(mask2, '!')) {
+ nick = strtok(mask2, "!");
+ username = strtok(NULL, "@");
+ } else {
+ nick = NULL;
+ username = strtok(mask2, "@");
+ }
+ host = strtok(NULL, "");
+ if (!username || !host) {
+ free(mask2);
+ return 0;
+ }
+
+ if (nick) {
+ result = match_wild_nocase(nick, user->nick)
+ && match_wild_nocase(username, user->username)
+ && (match_wild_nocase(host, user->host)
+#ifdef HAS_VHOST
+ || match_wild_nocase(host, user->vhost)
+#endif
+ );
+ } else {
+ result = match_wild_nocase(username, user->username)
+ && (match_wild_nocase(host, user->host)
+#ifdef HAS_VHOST
+ || match_wild_nocase(host, user->vhost)
+#endif
+ );
+ }
+
+ free(mask2);
+ return result;
+}
+
+/*************************************************************************/
+
+/* Split a usermask up into its constitutent parts. Returned strings are
+ * malloc()'d, and should be free()'d when done with. Returns "*" for
+ * missing parts.
+ */
+
+void split_usermask(const char *mask, char **nick, char **user,
+ char **host)
+{
+ char *mask2 = sstrdup(mask);
+
+ *nick = strtok(mask2, "!");
+ *user = strtok(NULL, "@");
+ *host = strtok(NULL, "");
+ /* Handle special case: mask == user@host */
+ if (*nick && !*user && strchr(*nick, '@')) {
+ *nick = NULL;
+ *user = strtok(mask2, "@");
+ *host = strtok(NULL, "");
+ }
+ if (!*nick)
+ *nick = "*";
+ if (!*user)
+ *user = "*";
+ if (!*host)
+ *host = "*";
+ *nick = sstrdup(*nick);
+ *user = sstrdup(*user);
+ *host = sstrdup(*host);
+ free(mask2);
+}
+
+/*************************************************************************/
+
+/* Given a user, return a mask that will most likely match any address the
+ * user will have from that location. For IP addresses, wildcards the
+ * appropriate subnet mask (e.g. 35.1.1.1 -> 35.*; 128.2.1.1 -> 128.2.*);
+ * for named addresses, wildcards the leftmost part of the name unless the
+ * name only contains two parts. If the username begins with a ~, delete
+ * it. The returned character string is malloc'd and should be free'd
+ * when done with.
+ */
+
+char *create_mask(User * u)
+{
+ char *mask, *s, *end;
+ int ulen = strlen(GetIdent(u));
+
+ /* Get us a buffer the size of the username plus hostname. The result
+ * will never be longer than this (and will often be shorter), thus we
+ * can use strcpy() and sprintf() safely.
+ */
+ end = mask = smalloc(ulen + strlen(GetHost(u)) + 3);
+ end += sprintf(end, "%s%s@",
+ (ulen <
+ (*(GetIdent(u)) ==
+ '~' ? USERMAX + 1 : USERMAX) ? "*" : ""),
+ (*(GetIdent(u)) ==
+ '~' ? GetIdent(u) + 1 : GetIdent(u)));
+
+ if (strspn(GetHost(u), "0123456789.") == strlen(GetHost(u))
+ && (s = strchr(GetHost(u), '.'))
+ && (s = strchr(s + 1, '.'))
+ && (s = strchr(s + 1, '.'))
+ && (!strchr(s + 1, '.'))) { /* IP addr */
+ s = sstrdup(GetHost(u));
+ *strrchr(s, '.') = 0;
+
+ sprintf(end, "%s.*", s);
+ free(s);
+ } else {
+ if ((s = strchr(GetHost(u), '.')) && strchr(s + 1, '.')) {
+ s = sstrdup(strchr(GetHost(u), '.') - 1);
+ *s = '*';
+ strcpy(end, s);
+ free(s);
+ } else {
+ strcpy(end, GetHost(u));
+ }
+ }
+ return mask;
+}
+
+/*************************************************************************/
diff --git a/src/vsnprintf.c b/src/vsnprintf.c
new file mode 100644
index 000000000..7c6c212e3
--- /dev/null
+++ b/src/vsnprintf.c
@@ -0,0 +1,518 @@
+/* An implementation of vsnprintf() for systems that don't have it.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+typedef int (*_pfmt_writefunc_t)
+ (const char *buf, size_t len, void *arg1, void *arg2);
+
+int my_vsnprintf(char *string, size_t size, const char *format,
+ va_list args);
+
+/*************************************************************************/
+
+/* Basic format routine for *printf() interfaces. Takes a writing function
+ * and two (optional) parameters, one pointer and one integer, for that
+ * function which are passed on unmodified. The function should return the
+ * number of bytes written, and 0 (not -1!) on write failure.
+ */
+
+static int _pfmt(const char *format, va_list args,
+ _pfmt_writefunc_t writefunc, void *arg1, void *arg2)
+{
+ int total = 0; /* Total bytes written */
+ const char *startptr; /* Beginning of non-token text in format string.
+ * Used for writing in bulk instead of
+ * character-at-a-time. */
+ int n; /* Bytes written in last writefunc() call */
+ int valid; /* Was this a valid %-token? */
+ int alt_form; /* "Alternate form"? (# flag) */
+ int zero_pad; /* Zero-pad value? */
+ int left_justify; /* Left-justify? (0 means right-justify) */
+ int always_sign; /* Always add sign value? */
+ int width; /* Field width */
+ int precision; /* Precision */
+ int argsize; /* Size of argument: 0=normal, 1=short, 2=long,
+ * 3=long long */
+ int what; /* What are we working on? 0=flags, 1=width,
+ * 2=precision, 3=argsize, 4=argtype */
+ long intval; /* Integer value */
+ char *strval; /* String value */
+ void *ptrval; /* Pointer value */
+ char numbuf[64]; /* Temporary buffer for printing numbers */
+ char *numptr; /* Pointer to start of printed number in numbuf */
+
+
+ intval = 0;
+ strval = NULL;
+ ptrval = NULL;
+
+ startptr = format;
+ while (*format) {
+ if (*format != '%') {
+ format++;
+ continue;
+ }
+ if (startptr != format) {
+ /* Write out accumulated text */
+ n = writefunc(startptr, format - startptr, arg1, arg2);
+ total += n;
+ /* Abort on short write */
+ if (n != format - startptr)
+ break;
+ /* Point to this token, in case it's a bad one */
+ startptr = format;
+ }
+
+ valid = 0; /* 1 if valid, -1 if known not valid (syntax error) */
+ alt_form = 0;
+ left_justify = 0;
+ always_sign = 0;
+ zero_pad = 0;
+ width = -1;
+ precision = -1;
+ argsize = 0;
+ what = 0;
+
+ while (!valid && *++format) { /* Broken out of by terminal chars */
+ switch (*format) {
+
+ /* Flags */
+ case '#':
+ if (what != 0) {
+ valid = -1;
+ break;
+ }
+ alt_form = 1;
+ break;
+ case '-':
+ if (what != 0) {
+ valid = -1;
+ break;
+ }
+ left_justify = 1;
+ break;
+ case '+':
+ if (what != 0) {
+ valid = -1;
+ break;
+ }
+ always_sign = 1;
+ break;
+ case '0':
+ if (what == 0) {
+ zero_pad = 1;
+ break;
+ }
+ /* else fall through */
+
+ /* Field widths */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (what == 0)
+ what = 1;
+ else if (what > 2) {
+ valid = -1;
+ break;
+ }
+ if (what == 1) {
+ if (width < 0)
+ width = 0;
+ width = width * 10 + (*format) - '0';
+ } else {
+ if (precision < 0)
+ precision = 0;
+ precision = precision * 10 + (*format) - '0';
+ }
+ break;
+ case '*':
+ if (what == 0)
+ what = 1;
+ else if (what >= 2) {
+ valid = -1;
+ break;
+ }
+ if (what == 1) {
+ width = va_arg(args, int);
+ if (width < 0) {
+ width = -width;
+ left_justify = 1;
+ }
+ } else {
+ precision = va_arg(args, int);
+ }
+ break;
+ case '.':
+ if (what >= 2) {
+ valid = -1;
+ break;
+ }
+ what = 2;
+ break;
+
+ /* Argument sizes */
+ case 'h':
+ if (what > 3) {
+ valid = -1;
+ break;
+ }
+ argsize = 1;
+ what = 4;
+ break;
+ case 'l':
+ if (what > 3) {
+ valid = -1;
+ break;
+ }
+ argsize = 2;
+ what = 4;
+ break;
+ case 'L':
+ if (what > 3) {
+ valid = -1;
+ break;
+ }
+ argsize = 3;
+ what = 4;
+ break;
+
+ /* Argument types */
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ if (argsize == 1)
+ intval = va_arg(args, short);
+ else if (argsize == 2)
+ intval = va_arg(args, long);
+ else if (argsize == 3)
+ /* XXX we don't handle long longs yet */
+ intval = va_arg(args, long);
+ else
+ intval = va_arg(args, int);
+ valid = 1;
+ break;
+ case 'c':
+ intval = va_arg(args, unsigned char);
+ valid = 1;
+ break;
+ case 's':
+ strval = va_arg(args, char *);
+ valid = 1;
+ break;
+ case 'p':
+ ptrval = va_arg(args, void *);
+ valid = 1;
+ break;
+ case 'n':
+ *((int *) va_arg(args, int *)) = total;
+ valid = 1;
+ break;
+
+ /* All other characters--this neatly catches "%%" too */
+ default:
+ valid = -1;
+ break;
+ } /* switch (*format) */
+ }
+ if (valid != 1) {
+ /* Not a valid %-token; start loop over (token will get printed
+ * out next time through). */
+ continue;
+ }
+
+ /* Don't zero-pad if a precision was given or left-justifying */
+ if (precision != -1 || left_justify)
+ zero_pad = 0;
+
+ /* For numbers, limit precision to the size of the print buffer */
+ if ((*format == 'd' || *format == 'i' || *format == 'o'
+ || *format == 'u' || *format == 'x' || *format == 'X')
+ && precision > (signed) sizeof(numbuf)) {
+ precision = sizeof(numbuf);
+ }
+
+ switch (*format++) { /* Do something with this token */
+ case 'p':
+ /* Print the NULL value specially */
+ if (ptrval == NULL) {
+ total += writefunc("(null)", 6, arg1, arg2);
+ break;
+ }
+ /* For all other values, pretend it's really %#.8x */
+ alt_form = 1;
+ zero_pad = 0;
+ precision = 8;
+ intval = (long) ptrval;
+ /* Fall through */
+
+ case 'x':
+ case 'X':{
+ static const char x_chars[] = "0123456789abcdef0x";
+ static const char X_chars[] = "0123456789ABCDEF0X";
+ const char *chars =
+ (format[-1] == 'X') ? X_chars : x_chars;
+ const char *padstr = zero_pad ? "0" : " ";
+ unsigned long uintval;
+ int len;
+
+ uintval = (unsigned long) intval;
+ if (alt_form && uintval != 0) {
+ n = writefunc(chars + 16, 2, arg1, arg2);
+ total += n;
+ if (n != 2)
+ break;
+ width -= 2;
+ }
+ if (precision < 1)
+ precision = 1;
+ numptr = numbuf + sizeof(numbuf);
+ for (len = 0; len < precision || uintval != 0; len++) {
+ *--numptr = chars[uintval % 16];
+ uintval /= 16;
+ }
+ if (left_justify) {
+ n = writefunc(numptr, len, arg1, arg2);
+ total += n;
+ if (n != len)
+ break;
+ }
+ while (len < width) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify)
+ total += writefunc(numptr, len, arg1, arg2);
+ break;
+ } /* case 'x', 'X' */
+
+ case 'o':{
+ const char *padstr = zero_pad ? "0" : " ";
+ unsigned long uintval;
+ int len;
+
+ uintval = (unsigned long) intval;
+ if (precision < 1)
+ precision = 1;
+ numptr = numbuf + sizeof(numbuf);
+ for (len = 0; len < precision || uintval != 0; len++) {
+ *--numptr = '0' + uintval % 8;
+ uintval /= 8;
+ }
+ if (alt_form && *numptr != '0') {
+ *--numptr = '0';
+ len++;
+ }
+ if (left_justify) {
+ n = writefunc(numptr, len, arg1, arg2);
+ total += n;
+ if (n != len)
+ break;
+ }
+ while (len < width) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify)
+ total += writefunc(numptr, len, arg1, arg2);
+ break;
+ } /* case 'o' */
+
+ if (alt_form && *numptr != '0')
+ *--numptr = '0';
+ case 'u':{
+ const char *padstr = zero_pad ? "0" : " ";
+ unsigned long uintval;
+ int len;
+
+ uintval = (unsigned long) intval;
+ if (precision < 1)
+ precision = 1;
+ numptr = numbuf + sizeof(numbuf);
+ for (len = 0; len < precision || uintval != 0; len++) {
+ *--numptr = '0' + uintval % 10;
+ uintval /= 10;
+ }
+ if (left_justify) {
+ n = writefunc(numptr, len, arg1, arg2);
+ total += n;
+ if (n != len)
+ break;
+ }
+ while (len < width) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify)
+ total += writefunc(numptr, len, arg1, arg2);
+ break;
+ } /* case 'u' */
+
+ case 'd':
+ case 'i':{
+ const char *padstr = zero_pad ? "0" : " ";
+ int len;
+
+ numptr = numbuf + sizeof(numbuf);
+ len = 0;
+ if (intval < 0) {
+ if (1 != writefunc("-", 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ intval = -intval;
+ if (intval < 0) { /* true for 0x800...0 */
+ *numptr-- = '0' - intval % 10;
+ len++;
+ intval /= 10;
+ intval = -intval;
+ }
+ } else if (intval >= 0 && always_sign) {
+ if (1 != writefunc("+", 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (precision < 1)
+ precision = 1;
+ for (; len < precision || intval != 0; len++) {
+ *--numptr = '0' + intval % 10;
+ intval /= 10;
+ }
+ if (left_justify) {
+ n = writefunc(numptr, len, arg1, arg2);
+ total += n;
+ if (n != len)
+ break;
+ }
+ while (len < width) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify)
+ total += writefunc(numptr, len, arg1, arg2);
+ break;
+ } /* case 'd', 'i' */
+
+ case 'c':{
+ const char *padstr = zero_pad ? "0" : " ";
+ unsigned char c = (unsigned char) intval;
+
+ if (left_justify) {
+ if (1 != writefunc(&c, 1, arg1, arg2))
+ break;
+ total++;
+ }
+ while (width > 1) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify)
+ total += writefunc(&c, 1, arg1, arg2);
+ break;
+ } /* case 'c' */
+
+ case 's':{
+ const char *padstr = zero_pad ? "0" : " ";
+ int len;
+
+ /* Catch null strings */
+ if (strval == NULL) {
+ total += writefunc("(null)", 6, arg1, arg2);
+ break;
+ }
+ len = strlen(strval);
+ if (precision < 0 || precision > len)
+ precision = len;
+ if (left_justify && len > 0) {
+ n = writefunc(strval, precision, arg1, arg2);
+ total += n;
+ if (n != precision)
+ break;
+ }
+ while (width > precision) {
+ if (1 != writefunc(padstr, 1, arg1, arg2))
+ break;
+ total++;
+ width--;
+ }
+ if (!left_justify && len > 0)
+ total += writefunc(strval, precision, arg1, arg2);
+ break;
+ } /* case 's' */
+
+ } /* switch (*format++) */
+
+ startptr = format; /* Start again after this %-token */
+ } /* while (*format) */
+
+ /* Write anything left over. */
+ if (startptr != format)
+ total += writefunc(startptr, format - startptr, arg1, arg2);
+
+ /* Return total bytes written. */
+ return total;
+}
+
+/*************************************************************************/
+
+static int writefunc(const char *buf, size_t len, char **string,
+ size_t * size)
+{
+ if (*size <= 0)
+ return 0;
+ if (len > (*size) - 1)
+ len = (*size) - 1;
+ if (len == 0)
+ return 0;
+ memcpy(*string, buf, len);
+ (*string) += len;
+ (*string)[0] = 0;
+ return len;
+}
+
+int my_vsnprintf(char *string, size_t size, const char *format,
+ va_list args)
+{
+ int ret;
+
+ if (size <= 0)
+ return 0;
+ ret =
+ _pfmt(format, args, (_pfmt_writefunc_t) writefunc, &string, &size);
+ return ret;
+}
+
+/*************************************************************************/