diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 107 | ||||
-rw-r--r-- | src/actions.c | 95 | ||||
-rwxr-xr-x | src/bin/am | 420 | ||||
-rwxr-xr-x | src/bin/anoperc | 136 | ||||
-rwxr-xr-x | src/bin/cp-recursive | 21 | ||||
-rwxr-xr-x | src/bin/langtool | 239 | ||||
-rwxr-xr-x | src/bin/mydbgen | 207 | ||||
-rwxr-xr-x | src/bin/register | 101 | ||||
-rw-r--r-- | src/botserv.c | 2593 | ||||
-rw-r--r-- | src/channels.c | 1633 | ||||
-rw-r--r-- | src/chanserv.c | 6257 | ||||
-rw-r--r-- | src/commands.c | 178 | ||||
-rw-r--r-- | src/compat.c | 212 | ||||
-rw-r--r-- | src/config.c | 1333 | ||||
-rw-r--r-- | src/converter.c | 398 | ||||
-rw-r--r-- | src/datafiles.c | 510 | ||||
-rw-r--r-- | src/encrypt.c | 434 | ||||
-rw-r--r-- | src/helpserv.c | 83 | ||||
-rw-r--r-- | src/hostserv.c | 1107 | ||||
-rw-r--r-- | src/init.c | 849 | ||||
-rw-r--r-- | src/language.c | 265 | ||||
-rw-r--r-- | src/list.c | 178 | ||||
-rw-r--r-- | src/log.c | 298 | ||||
-rw-r--r-- | src/mail.c | 246 | ||||
-rw-r--r-- | src/main.c | 537 | ||||
-rw-r--r-- | src/memory.c | 96 | ||||
-rw-r--r-- | src/memoserv.c | 1382 | ||||
-rw-r--r-- | src/messages.c | 1284 | ||||
-rw-r--r-- | src/misc.c | 711 | ||||
-rw-r--r-- | src/modules.c | 2023 | ||||
-rw-r--r-- | src/modules/Makefile | 32 | ||||
-rw-r--r-- | src/modules/README | 1 | ||||
-rwxr-xr-x | src/modules/compile.sh | 37 | ||||
-rwxr-xr-x | src/modules/configure | 21 | ||||
-rw-r--r-- | src/modules/hs_moo.c | 104 | ||||
-rw-r--r-- | src/modules/ircd_catserv.c | 128 | ||||
-rw-r--r-- | src/modules/module.h | 9 | ||||
-rw-r--r-- | src/mysql.c | 1638 | ||||
-rw-r--r-- | src/news.c | 541 | ||||
-rw-r--r-- | src/nickserv.c | 4169 | ||||
-rw-r--r-- | src/operserv.c | 5064 | ||||
-rw-r--r-- | src/process.c | 268 | ||||
-rw-r--r-- | src/protocol.c | 168 | ||||
-rw-r--r-- | src/proxy.c | 797 | ||||
-rw-r--r-- | src/rdb.c | 466 | ||||
-rw-r--r-- | src/send.c | 240 | ||||
-rw-r--r-- | src/servers.c | 259 | ||||
-rw-r--r-- | src/sessions.c | 834 | ||||
-rw-r--r-- | src/slist.c | 325 | ||||
-rw-r--r-- | src/sockutil.c | 549 | ||||
-rw-r--r-- | src/timeout.c | 130 | ||||
-rw-r--r-- | src/users.c | 1161 | ||||
-rw-r--r-- | src/vsnprintf.c | 518 |
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(¤t->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(¤t->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; +} + +/*************************************************************************/ |