]> jfr.im git - irc/SurrealServices/srsv.git/commitdiff
Caught up the mus-0.4.x-devel branch with 0.4.3
authormusashix90 <redacted>
Tue, 15 Jan 2013 21:54:20 +0000 (21:54 +0000)
committermusashix90 <redacted>
Tue, 15 Jan 2013 21:54:20 +0000 (21:54 +0000)
git-svn-id: http://svn.tabris.net/repos/srsv@3599 70d4eda1-72e9-0310-a436-91e5bd24443c

208 files changed:
branches/mus-0.4.x-devel/CREDITS
branches/mus-0.4.x-devel/INSTALL
branches/mus-0.4.x-devel/README2
branches/mus-0.4.x-devel/SQLserv.README [new file with mode: 0644]
branches/mus-0.4.x-devel/SpamServ.README [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/64bit.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/Agent.pm
branches/mus-0.4.x-devel/SrSv/ChanReg/Flags.pm
branches/mus-0.4.x-devel/SrSv/Conf/main.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/Conf/services.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/Conf/sql.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/Hash/SaltedHash.pm
branches/mus-0.4.x-devel/SrSv/HostMask.pm
branches/mus-0.4.x-devel/SrSv/IPv6.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/IRCd/Event.pm
branches/mus-0.4.x-devel/SrSv/IRCd/IO.pm
branches/mus-0.4.x-devel/SrSv/IRCd/Queue.pm
branches/mus-0.4.x-devel/SrSv/IRCd/State.pm
branches/mus-0.4.x-devel/SrSv/Insp/decodeUUID.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/SrSv/Log.pm
branches/mus-0.4.x-devel/SrSv/MySQL.pm
branches/mus-0.4.x-devel/SrSv/MySQL/Stub.pm
branches/mus-0.4.x-devel/SrSv/NickReg/NickText.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/NickReg/User.pm
branches/mus-0.4.x-devel/SrSv/Process/Worker.pm
branches/mus-0.4.x-devel/SrSv/Shared/Hash.pm
branches/mus-0.4.x-devel/SrSv/SimpleHash.pm
branches/mus-0.4.x-devel/SrSv/TOR.pm
branches/mus-0.4.x-devel/SrSv/Text/Format.pm
branches/mus-0.4.x-devel/SrSv/Time.pm
branches/mus-0.4.x-devel/SrSv/Unreal/Base64.pm
branches/mus-0.4.x-devel/SrSv/Unreal/Parse.pm
branches/mus-0.4.x-devel/SrSv/Unreal/Send.pm
branches/mus-0.4.x-devel/SrSv/Unreal/Tokens.pm
branches/mus-0.4.x-devel/SrSv/Unreal/Validate.pm
branches/mus-0.4.x-devel/SrSv/User.pm
branches/mus-0.4.x-devel/SrSv/User/Tags.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/SrSv/Util.pm
branches/mus-0.4.x-devel/UPGRADING
branches/mus-0.4.x-devel/addroot.pl
branches/mus-0.4.x-devel/config-example/connectserv.conf [new file with mode: 0644]
branches/mus-0.4.x-devel/config-example/main.conf
branches/mus-0.4.x-devel/config-example/securitybot/sb.conf
branches/mus-0.4.x-devel/config-example/services.conf
branches/mus-0.4.x-devel/config-example/spamserv/nicklist.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/config-example/spamserv/spamserv.conf [new file with mode: 0644]
branches/mus-0.4.x-devel/db-setup.pl
branches/mus-0.4.x-devel/delroot.pl
branches/mus-0.4.x-devel/help/adminserv.txt
branches/mus-0.4.x-devel/help/adminserv/staff.txt
branches/mus-0.4.x-devel/help/adminserv/svsop.txt
branches/mus-0.4.x-devel/help/adminserv/whois.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/botserv/act.txt
branches/mus-0.4.x-devel/help/botserv/add.txt
branches/mus-0.4.x-devel/help/botserv/assign.txt
branches/mus-0.4.x-devel/help/botserv/say.txt
branches/mus-0.4.x-devel/help/botserv/set.txt
branches/mus-0.4.x-devel/help/chanbot.txt
branches/mus-0.4.x-devel/help/chanbot/abbreviations.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv.txt
branches/mus-0.4.x-devel/help/chanserv/admin.txt
branches/mus-0.4.x-devel/help/chanserv/akick.txt
branches/mus-0.4.x-devel/help/chanserv/aop.txt
branches/mus-0.4.x-devel/help/chanserv/auth.txt
branches/mus-0.4.x-devel/help/chanserv/ban.txt
branches/mus-0.4.x-devel/help/chanserv/banlist.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/cf.txt
branches/mus-0.4.x-devel/help/chanserv/close.txt
branches/mus-0.4.x-devel/help/chanserv/copy.txt
branches/mus-0.4.x-devel/help/chanserv/drone.txt
branches/mus-0.4.x-devel/help/chanserv/halfop.txt
branches/mus-0.4.x-devel/help/chanserv/hop.txt
branches/mus-0.4.x-devel/help/chanserv/join.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/kick.txt
branches/mus-0.4.x-devel/help/chanserv/kickban.txt
branches/mus-0.4.x-devel/help/chanserv/kickbanmask.txt
branches/mus-0.4.x-devel/help/chanserv/kickmask.txt
branches/mus-0.4.x-devel/help/chanserv/levels.txt
branches/mus-0.4.x-devel/help/chanserv/list.txt
branches/mus-0.4.x-devel/help/chanserv/mlock.txt
branches/mus-0.4.x-devel/help/chanserv/mode.txt
branches/mus-0.4.x-devel/help/chanserv/op.txt
branches/mus-0.4.x-devel/help/chanserv/resync.txt
branches/mus-0.4.x-devel/help/chanserv/set.txt
branches/mus-0.4.x-devel/help/chanserv/set/bantime.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/set/bantype.txt
branches/mus-0.4.x-devel/help/chanserv/set/desc.txt
branches/mus-0.4.x-devel/help/chanserv/set/founder.txt
branches/mus-0.4.x-devel/help/chanserv/set/freeze.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/set/hold.txt
branches/mus-0.4.x-devel/help/chanserv/set/neverop.txt
branches/mus-0.4.x-devel/help/chanserv/set/noclones.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/set/opguard.txt
branches/mus-0.4.x-devel/help/chanserv/set/password.txt
branches/mus-0.4.x-devel/help/chanserv/set/splitops.txt
branches/mus-0.4.x-devel/help/chanserv/set/successor.txt
branches/mus-0.4.x-devel/help/chanserv/set/topiclock.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/set/verbose.txt
branches/mus-0.4.x-devel/help/chanserv/set/welcomeinchan.txt
branches/mus-0.4.x-devel/help/chanserv/sop.txt
branches/mus-0.4.x-devel/help/chanserv/tempban.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/topic.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/topicappend.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/topicprepend.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/chanserv/unban.txt
branches/mus-0.4.x-devel/help/chanserv/uop.txt
branches/mus-0.4.x-devel/help/chanserv/voice.txt
branches/mus-0.4.x-devel/help/chanserv/vop.txt
branches/mus-0.4.x-devel/help/chanserv/welcome.txt
branches/mus-0.4.x-devel/help/chanserv/why.txt
branches/mus-0.4.x-devel/help/hostserv/sethost.txt
branches/mus-0.4.x-devel/help/memoserv/csend.txt
branches/mus-0.4.x-devel/help/memoserv/del.txt
branches/mus-0.4.x-devel/help/memoserv/ignore.txt
branches/mus-0.4.x-devel/help/memoserv/read.txt
branches/mus-0.4.x-devel/help/memoserv/send.txt
branches/mus-0.4.x-devel/help/nickserv.txt
branches/mus-0.4.x-devel/help/nickserv/acc.txt.
branches/mus-0.4.x-devel/help/nickserv/ajoin.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/nickserv/auth.txt
branches/mus-0.4.x-devel/help/nickserv/authcode.txt
branches/mus-0.4.x-devel/help/nickserv/drop.txt
branches/mus-0.4.x-devel/help/nickserv/dropgroup.txt
branches/mus-0.4.x-devel/help/nickserv/emailreg.txt
branches/mus-0.4.x-devel/help/nickserv/ghost.txt
branches/mus-0.4.x-devel/help/nickserv/gidentify.txt
branches/mus-0.4.x-devel/help/nickserv/identify.txt
branches/mus-0.4.x-devel/help/nickserv/info.txt
branches/mus-0.4.x-devel/help/nickserv/link.txt
branches/mus-0.4.x-devel/help/nickserv/listemail.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/nickserv/profile.txt
branches/mus-0.4.x-devel/help/nickserv/recover.txt
branches/mus-0.4.x-devel/help/nickserv/register.txt
branches/mus-0.4.x-devel/help/nickserv/release.txt
branches/mus-0.4.x-devel/help/nickserv/set.txt
branches/mus-0.4.x-devel/help/nickserv/set/auth.txt
branches/mus-0.4.x-devel/help/nickserv/set/email.txt
branches/mus-0.4.x-devel/help/nickserv/set/greet.txt
branches/mus-0.4.x-devel/help/nickserv/set/hidemail.txt
branches/mus-0.4.x-devel/help/nickserv/set/hold.txt
branches/mus-0.4.x-devel/help/nickserv/set/neverop.txt
branches/mus-0.4.x-devel/help/nickserv/set/noacc.txt
branches/mus-0.4.x-devel/help/nickserv/set/nomemo.txt
branches/mus-0.4.x-devel/help/nickserv/set/password.txt
branches/mus-0.4.x-devel/help/nickserv/set/protect.txt
branches/mus-0.4.x-devel/help/nickserv/set/umode.txt
branches/mus-0.4.x-devel/help/nickserv/set/vacation.txt
branches/mus-0.4.x-devel/help/nickserv/sidentify.txt
branches/mus-0.4.x-devel/help/nickserv/silence.txt
branches/mus-0.4.x-devel/help/nickserv/unlink.txt
branches/mus-0.4.x-devel/help/nickserv/watch.txt
branches/mus-0.4.x-devel/help/operserv.txt
branches/mus-0.4.x-devel/help/operserv/chankill.txt
branches/mus-0.4.x-devel/help/operserv/clones.txt
branches/mus-0.4.x-devel/help/operserv/kill.txt
branches/mus-0.4.x-devel/help/operserv/killnew.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/operserv/logonnews.txt
branches/mus-0.4.x-devel/help/operserv/loners.txt
branches/mus-0.4.x-devel/help/operserv/qline.txt
branches/mus-0.4.x-devel/help/operserv/svskill.txt
branches/mus-0.4.x-devel/help/operserv/svsnick.txt
branches/mus-0.4.x-devel/help/spamserv.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/listconf.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/rehash.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/save.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/set.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/watch.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/watch/add.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/watch/del.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/help/spamserv/watch/list.txt [new file with mode: 0644]
branches/mus-0.4.x-devel/killservices.sh
branches/mus-0.4.x-devel/libs/modes.pm
branches/mus-0.4.x-devel/modules/connectserv.pm
branches/mus-0.4.x-devel/modules/echoserv.pm
branches/mus-0.4.x-devel/modules/geoip.pm
branches/mus-0.4.x-devel/modules/securitybot.pm
branches/mus-0.4.x-devel/modules/services.pm
branches/mus-0.4.x-devel/modules/serviceslibs/adminserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/botserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/chanserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/hostserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/memoserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/nickserv.pm
branches/mus-0.4.x-devel/modules/serviceslibs/operserv.pm
branches/mus-0.4.x-devel/modules/spamserv.pm [new file with mode: 0644]
branches/mus-0.4.x-devel/modules/sql.pm
branches/mus-0.4.x-devel/services.pl
branches/mus-0.4.x-devel/sql/004003000.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/004003001.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/004003002.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/004003003.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/004003004.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/004003005.sql [new file with mode: 0644]
branches/mus-0.4.x-devel/sql/services.sql
branches/mus-0.4.x-devel/tests/inspConnect.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/tests/ipv6.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/tests/seqTest.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/tests/testHash.pl [moved from branches/mus-0.4.x-devel/testHash.pl with 91% similarity]
branches/mus-0.4.x-devel/tests/testTime.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/utils/archivelogs.pl
branches/mus-0.4.x-devel/utils/blacklistLoader.pl
branches/mus-0.4.x-devel/utils/country-table.pl
branches/mus-0.4.x-devel/utils/country-table2.pl
branches/mus-0.4.x-devel/utils/country-table3.pl
branches/mus-0.4.x-devel/utils/db-dump.pl
branches/mus-0.4.x-devel/utils/geoip-slower.pl [new file with mode: 0755]
branches/mus-0.4.x-devel/utils/geoip.pl
branches/mus-0.4.x-devel/utils/parse-msg_h.pl [moved from branches/mus-0.4.x-devel/parse-msg_h.pl with 100% similarity]

index 0a79cff58477e2dab9c401ac9cc457008271a0fa..2c479dcfca68e548df202e030ec3e032157d4640 100644 (file)
@@ -4,9 +4,10 @@ into our tree, and/or modified for our use.
 
 SurrealServices has the following copyrights:
 
-Copyright tabris@surrealchat.net (tabris@tabris.net) 2004, 2005, 2006, 2007, 2008
-Copyright saturn@surrealchat.net 2004, 2005, 2006, 2007, 2008
+Copyright tabris@surrealchat.net (tabris@tabris.net) 2004, 2005, 2006, 2007, 2008, 2009, 2010
+Copyright saturn@surrealchat.net 2004, 2005, 2006, 2007, 2008, 2009
 Copyright errietta@hotmail.com 2008
+Copyright musashix90@gmail.com 2009, 2010
 
 We do not claim ownership of the following modules which we have
 imported into our tree:
index 830db943ab438eaa2614d1ac474519bf42fad583..3aeedf37b0f83ea56d8e825fa0cee67b9b4128f7 100644 (file)
@@ -3,7 +3,8 @@ You will need to have all of these installed:
 * Perl 5.8 and standard modules. (threads no longer required)
        NOTE: Many linux distros place Perl's standard modules in a separate
        package from Perl itself.
-* MySQL 4.1 or 5.0. 5.1 and up are not supported.
+* MySQL 5.0 or 5.1. 5.2 is not well tested; 5.3 and up are not
+supported.
 * Event module from CPAN
 * Date::Parse module from CPAN
 Both the Event and Date::Parse modules should be available
index a9c088f25f7689e34be8ef0ec6f8785fc8f323fc..1d334f40ddef12a512263175bb56035293c2ac73 100644 (file)
@@ -17,16 +17,25 @@ uniform throughout your irc-network. We believe that this is a
 necessary aspect of a properly maintained network, and as such
 should not be an undue burden.
 
+       Definition of 'unsyncserver': This means a server that is
+not 100% conformant to the UnrealIRCd Server Protocol. Basically
+most services servers (NeoStats, denora, janus, etc) don't send an
+EOS at the end of their netburst. It seems that Unreal is perfectly
+happy with this, but SrSv isn't. When we don't receive an
+End-of-Sync message, we don't know if they're done announcing
+everything, and thus whether to start re-mangling channel modes.
+
        Further of note is that SrSv does not have a full
 capability list for ircds and such may be necessary for
 portability (if your ircd does not support things like WATCH,
 SILENCE, etc).
 
        IRC Networks known to be using SrSv as of this date
-(20060904):
-       irc.talkingirc.net
+(20100506):
        irc.surrealchat.net (duh)
-       irc.webchatting.com
+       irc.CrystalNET.eu
+       irc.lucidchat.net
+       irc.pokebeach.com
 
        We would appreciate any success reports from other networks,
 contact us on irc.surrealchat.net #dev.lounge or via email:
diff --git a/branches/mus-0.4.x-devel/SQLserv.README b/branches/mus-0.4.x-devel/SQLserv.README
new file mode 100644 (file)
index 0000000..a055032
--- /dev/null
@@ -0,0 +1,34 @@
+       SQLserv is a bot intended to make direct query of the database
+possible. It is not considered 'stable', and it barely works at all
+right now.
+
+       First, this service is potentially dangerous. At present only
+read-only commands are possible, but it is capable of being extended to
+allow modification of the database. Doing so without knowledge of the
+workings of the program may BREAK the program. If you do so you get to
+keep all the pieces. The coders of this module cannot be held
+responsible for what you do with it.
+
+       Second, at present it requires the 'services' module to be
+loaded, and the user to have ROOT access. This is for your protection.
+Modifying this module to allow regular opers to use this module MAY
+BREAK the app, and/or expose them to information that they are otherwise
+not supposed to have. Again, the coders of this module cannot be held
+responsible for what you do with it.
+
+       Third, this module does not protect you from doing invalid
+queries. This module does not prevent you from doing queries that may
+take 5 minutes to complete. Since the module has to run everything in
+the parent process, this may BREAK YOUR APP. As usual, we are not
+responsible for what you do with it.
+ADDENDUM: SQL queries are no longer executed in the parent, but the
+disclaimer still applies.
+
+       Fourth, there is no documentation for this module, not that much
+is necessary. You submit SQL queries to it, as if you were using the
+MySQL shell. It attempts to present the result back to you, much as the
+MySQL shell would. Embedded newlines in the returned data MAY BREAK. Not
+that there should be many cases of this in this program. You cannot run
+dependent queries (LOCK first, then SELECT, then UNLOCK), you cannot
+instantiate TEMPORARY tables. You cannot start a transaction. One-shot
+queries are all that is safe.
diff --git a/branches/mus-0.4.x-devel/SpamServ.README b/branches/mus-0.4.x-devel/SpamServ.README
new file mode 100644 (file)
index 0000000..1a1e385
--- /dev/null
@@ -0,0 +1,12 @@
+       SpamServ is a module written to watch channels for on-join private
+messages, which might be an indication of a spam bot.  This module is not
+considered to be stable, and does not take any action for private messages
+received, only reports the private messages to the diagnostics channel.
+
+       This service requires the 'services' module to be loaded, as well
+as a population of nicknames in the 'config/spamserv/nicklist.txt' directory.
+The module itself assumes that there are nicknames supplied in the .txt file,
+delimited by a new line.
+
+       There is some documentation of this module, in the form of 
+/MSG SpamServ HELP
diff --git a/branches/mus-0.4.x-devel/SrSv/64bit.pm b/branches/mus-0.4.x-devel/SrSv/64bit.pm
new file mode 100644 (file)
index 0000000..466baf8
--- /dev/null
@@ -0,0 +1,29 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::64bit;
+
+use strict;
+use Exporter qw( import );
+BEGIN {
+       require Config;
+
+       require constant;
+       import constant { HAS_64BIT_INT => ($Config::Config{use64bitint} eq 'define'), };
+       our @EXPORT = qw( HAS_64BIT_INT );
+}
+
+1;
index 9abd71664e37be3d1e43827e66877f6994641e7b..2856bdfcb1516e97dacc9276b347d223bc998e81 100644 (file)
@@ -36,7 +36,7 @@ use SrSv::Process::InParent qw(
 use SrSv::Conf2Consts qw(main);
 
 use SrSv::Debug;
-use SrSv::Unreal::Tokens;
+use SrSv::Unreal::Tokens qw( :tokens );
 use SrSv::Unreal::Base64 qw(itob64);
 use SrSv::IRCd::State qw(synced $ircd_ready %IRCd_capabilities);
 use SrSv::IRCd::IO qw(ircsend ircsendimm);
@@ -81,12 +81,12 @@ sub agent_connect($$$$$) {
        $agents{lc $nick}{PARMS} = [ @_ ];
 
        $host = main_conf_local unless $host;
-       ircsend($tkn{NICK}[$tkn]." $nick 1 $time $ident $host ".
+       ircsend("@{[TOK_NICK]} $nick 1 $time $ident $host ".
                (SJB64 ? itob64(main_conf_numeric) : main_conf_local).
                " 1 $modes * :$gecos");
 
        foreach my $chan (@chans) {
-               ircsend(":$nick ".$tkn{JOIN}[$tkn]." $chan");
+               ircsend(":$nick @{[TOK_JOIN]} $chan");
                # If we tracked chanmodes for agents, that would go here as well.
        }
 }
@@ -97,7 +97,7 @@ sub agent_quit($$) {
        delete($agents{lc $nick}{CHANS});
        delete($agents{lc $nick});
 
-       ircsendimm(":$nick ".$tkn{QUIT}[$tkn]." :$msg");
+       ircsendimm(":$nick @{[TOK_QUIT]} :$msg");
 }
 
 sub agent_quit_all($) {
@@ -134,7 +134,7 @@ sub agent_join($$) {
 
        if($agents{lc $agent}) {
                $agents{lc $agent}{CHANS}{lc $chan} = 1;
-               ircsend(":$agent ".$tkn{JOIN}[$tkn]." $chan");
+               ircsend(":$agent @{[TOK_JOIN]} $chan");
        } else {
                if($ircd_ready) {
                        print "Tried to make nonexistent agent ($agent) join channel ($chan)" if DEBUG;
@@ -149,13 +149,13 @@ sub agent_part($$$) {
        my ($agent, $chan, $reason) = @_;
 
        delete($agents{lc $agent}{CHANS}{lc $chan});
-       ircsend(":$agent $tkn{PART}[$tkn] $chan :$reason");
+       ircsend(":$agent @{[TOK_PART]} $chan :$reason");
 }
 
 sub set_agent_umode($$) {
        my ($src, $modes) = @_;
 
-       ircsend(":$src $tkn{UMODE2}[$tkn] $modes");
+       ircsend(":$src @{[TOK_UMODE2]} $modes");
 }
 
 sub agent_sync() {
@@ -232,7 +232,7 @@ sub kill_callback($$$$) {
 
                if($src =~ /\./) {
                        # let's NOT loopback this event
-                       ircsendimm(':'.main_conf_local.' '.$tkn{KILL}[$tkn]." $dst :Nick Collision");
+                       ircsendimm(':'.main_conf_local.' '."@{[TOK_KILL]} $dst :Nick Collision");
                } elsif (defined($agents{lc $src})) {
                        # Do Nothing.
                } else {
index fbc331b30d0a474e785cbda14930096f39c02534..e42bfbfdbf48d85dcbcd40578a63b9961fad8dc2 100644 (file)
@@ -42,6 +42,7 @@ BEGIN {
                CRF_AUTOVOICE => 1024,
                CRF_WELCOMEINCHAN => 2048,
                CRF_NEVEROP => 4096,
+               CRF_NOCLONES => 8192,
        );
 
        our @EXPORT = (qw(cr_chk_flag cr_set_flag), keys(%constants));
diff --git a/branches/mus-0.4.x-devel/SrSv/Conf/main.pm b/branches/mus-0.4.x-devel/SrSv/Conf/main.pm
new file mode 100644 (file)
index 0000000..1748a09
--- /dev/null
@@ -0,0 +1,38 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::Conf::main;
+
+use SrSv::Conf::Parameters main => [
+       qw(local remote port numeric pass load email replyto),
+       [info => 'SurrealServices'],
+       [procs => 4],
+       [diag => '#Diagnostics'],
+       [netname => 'Network'],
+       [sig => 'Thank you for chatting with us.'],
+       [unsyncserver => undef],
+       [nomail => undef],
+       [logmail => undef],
+       [hashed_passwords => undef],
+       [ban_webchat_prefixes => 'java|htIRC'],
+       [ipv6 => 0], # not enabled by default as not all systems support it
+       [tokens => 1], # turn off for debugging, so debug-output is easier to read
+       [queue_lowwater => 30],
+       [queue_highwater => 50],
+       [operchan => undef],
+];
+
+1;
diff --git a/branches/mus-0.4.x-devel/SrSv/Conf/services.pm b/branches/mus-0.4.x-devel/SrSv/Conf/services.pm
new file mode 100644 (file)
index 0000000..2802021
--- /dev/null
@@ -0,0 +1,48 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::Conf::services;
+
+use SrSv::Conf::Parameters services => [
+       [noexpire => undef],
+       [nickexpire => 21],
+       [vacationexpire => 90],
+       [nearexpire => 7],
+       [chanexpire => 21],
+       [validate_email => undef],
+       [validate_expire => 1],
+       [clone_limit => 3],
+       [chankilltime => 86400],
+
+       [default_protect => 'normal'],
+       [default_chanbot => undef],
+       [default_channel_mlock => '+nrt'],
+       [old_user_age => 300],
+       [chanreg_needs_oper => 0],
+
+       [log_overrides => 0],
+
+       [botserv => undef],
+       [nickserv => undef],
+       [chanserv => undef],
+       [memoserv => undef],
+       [adminserv => undef],
+       [operserv => undef],
+       [hostserv => undef],
+
+];
+
+1;
diff --git a/branches/mus-0.4.x-devel/SrSv/Conf/sql.pm b/branches/mus-0.4.x-devel/SrSv/Conf/sql.pm
new file mode 100644 (file)
index 0000000..ed4964d
--- /dev/null
@@ -0,0 +1,24 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::Conf::sql;
+
+use SrSv::Conf::Parameters sql => [
+       qw(mysql_user mysql_pass mysql_db),
+       [server_prepare => 0],
+];
+
+1;
index 78889fdd812214bf8011bad8cf54346d20ee2570..c0c4f26a033e5e51b0790c9b923a6367a3ea5f65 100644 (file)
@@ -58,6 +58,7 @@ BEGIN {
                die "Unable to find a suitable SHA implementation\n";
        }
 }
+use Digest::MD5;
 
 =item Hash Notes
 
@@ -126,6 +127,17 @@ sub makeHash_v1($;$$$) {
        return $string;
 }
 
+sub makeHash_vBulletin($;$$$) {
+       my ($secret, $salt, $algorithm, $version) = @_;
+       $algorithm = 'md5' unless $algorithm;
+       $salt = makeBinSalt(3) unless $salt;
+       $version = 2 unless $version;
+       my $string = "$algorithm:$version:";
+       $string .= encode_base64($salt, '').':';
+       $string .= md5_base64(md5_hex($secret) . $salt);
+       return $string;
+}
+
 sub __makeHash($$) {
        my ($plaintext, $algorithm) = @_;
        $algorithm = 'sha256';
@@ -172,6 +184,8 @@ sub verifyHash($$) {
        my $hash2;
        if($version eq 'v0') {
                $hash2 = makeHash_v0($plain, $salt, $algorithm);
+       } elsif($version eq 'vBulletin') {
+               $hash2 = makeHash_vBulletin($plain, $salt, $algorithm);
        } else {
                $hash2 = makeHash_v1($plain, $salt, $algorithm, $version);
        }
index c6c6e9baa8b63841d09ebd68c6dc9c94fb2254b7..a2512f780291f5274c0f6cb08c65980f088788d4 100644 (file)
@@ -173,13 +173,15 @@ sub parse_hostmask($) {
   our java iframe is _not_ open source [yet]. I do not know if it will be either.
 
 =cut
+use SrSv::Conf2Consts qw( main );
+our $ident_regexp = qr/^(@{[main_conf_ban_webchat_prefixes]})-/;
 
 sub make_hostmask($$$$) {
        my ($type, $nick, $ident, $host) = @_;
        no warnings 'prototype'; #we call ourselves
 
        if($type == 10) {
-               if ($ident =~ /^java-/) {
+               if ($ident =~ $ident_regexp) {
                        return make_hostmask(3, $nick, $ident, $host);
                }
                else {
diff --git a/branches/mus-0.4.x-devel/SrSv/IPv6.pm b/branches/mus-0.4.x-devel/SrSv/IPv6.pm
new file mode 100644 (file)
index 0000000..741c423
--- /dev/null
@@ -0,0 +1,62 @@
+package SrSv::IPv6;
+
+use Exporter qw( import );
+use SrSv::Conf2Consts qw( main );
+
+use SrSv::64bit;
+BEGIN {
+       our @EXPORT = qw( is_ipv6 get_ipv6_net get_ipv6_64 );
+       if(main_conf_ipv6) {
+               require Socket; import Socket;
+               require Socket6; import Socket6;
+               if(!HAS_64BIT_INT) {
+                       eval {
+                               require Math::BigInt;
+                               import Math::BigInt try => 'GMP';
+                       };
+                       if($@) {
+                               print STDERR "Running old version of perl/Math::BigInt.\n", $@, "Trying again.\n";
+                               require Math::BigInt;
+                               import Math::BigInt;
+                       }
+               }
+               push @EXPORT, qw( AF_INET6 );
+       }
+}
+
+sub is_ipv6($) {
+       my ($addr) = @_;
+       if($addr =~ /^((?:\d{1,3}\.){3}\d{1,3})$/) {
+               return 0 unless wantarray;
+               return (0, $addr);
+       }
+       elsif($addr =~ /:ffff:((?:\d{1,3}\.){3}\d{1,3})$/) {
+               return 0 unless wantarray;
+               return (0, $1);
+       } else {
+               return 1 unless wantarray;
+               return (1, $addr);
+       }
+}
+
+
+sub get_ipv6_net($) {
+# grabs the top 64bits of the IPv6 addr.
+       my ($addr) = @_;
+       my $str = Socket6::inet_pton(AF_INET6, $addr);
+       my (@words) = unpack('H4H4H4H4H4H4H4H4', $str);
+       my $int = ( !HAS_64BIT_INT ? Math::BigInt->bzero() : 0 );
+       for(0..3) {
+               $int <<= 16;
+               $int |= hex($words[$_]);
+       }
+       return $int;
+}
+
+sub get_ipv6_64($) {
+       my ($addr) = @_;
+       my $str = Socket6::inet_pton(AF_INET6, $addr);
+       return join(":", unpack("H4H4H4H4", $str))."::/64";
+}
+
+1;
index 93829e565ec797f7b6cfb82c57fa23ce7b78ad50..8866b8d5949ff021a3a06bd2b08161e27fbb9bb2 100644 (file)
@@ -23,19 +23,14 @@ BEGIN { our @EXPORT_OK = qw(addhandler callfuncs) }
 
 use SrSv::Debug;
 
+use SrSv::Conf2Consts qw( main );
+
 use SrSv::IRCd::Queue qw(ircd_enqueue);
 use SrSv::IRCd::State qw($ircline $ircline_real synced initial_synced);
 
 use SrSv::Message qw(add_callback message);
 
-# FIXME
-use constant {
-       # Wait For
-       WF_NONE => 0,
-       WF_NICK => 1,
-       WF_CHAN => 2,
-       WF_ALL => 3,
-};
+use SrSv::Constants;
 
 sub addhandler($$$$;$) {
        my ($type, $src, $dst, $cb, $po) = @_;
@@ -59,6 +54,7 @@ sub addhandler($$$$;$) {
        });
 }
 
+our $last_highqueue = time();
 sub callfuncs {
        my ($args, $sync, $wf, $message);
 
@@ -83,7 +79,16 @@ sub callfuncs {
                ARGS => $args,
                ON_FINISH => ($sync ? undef : 'SrSv::IRCd::Queue::finished'), # FIXME
                SYNCED => [synced, initial_synced],
+               QUEUE_DEPTH_HIGHPRIO => SrSv::IRCd::Queue::queue_size(WF_ALL),
+               QUEUE_DEPTH => SrSv::IRCd::Queue::queue_size(WF_MAX), # but not WF_MSG
        };
+       if(initial_synced && ($message->{QUEUE_DEPTH_HIGHPRIO} > main_conf_queue_lowwater) && ($last_highqueue < time()-5)) {
+               ircd::privmsg_noloop(main_conf_local, main_conf_operchan, "HIGH TRAFFIC WARNING",
+                       "Queue depth exceeded @{[main_conf_queue_lowwater]}") if defined(main_conf_operchan);
+               ircd::privmsg_noloop(main_conf_local, main_conf_diag, "HIGH TRAFFIC WARNING",
+                       "Queue depth exceeded @{[main_conf_queue_lowwater]}");
+               $last_highqueue = time();
+       }
 
        if($sync) {
                message($message);
@@ -98,10 +103,11 @@ sub _realcall($$) {
        my ($message, $callback) = @_;
 
        print "Calling ", $callback->{REALCALL}, " ", join(',', @{$message->{ARGS}}), "\n" if DEBUG();
-       $ircline = $message->{IRCLINE};
+       local $ircline = $message->{IRCLINE};
 
        local $SrSv::IRCd::State::synced = $message->{SYNCED}[0]; # XXX This is questionable.
        local $SrSv::IRCd::State::initial_synced = $message->{SYNCED}[1];
+       local $SrSv::IRCd::State::queue_depth = $message->{QUEUE_DEPTH};
 
        print "IRCLINE is $ircline  synced is $SrSv::IRCd::State::synced  initial_synced is $SrSv::IRCd::State::initial_synced\n" if DEBUG();
 
index 98681a430b3fd1139e640746f61e7cee43e9b78d..0d7263ade4daf1172351446c9f3473752f58ac75 100644 (file)
@@ -33,7 +33,7 @@ use SrSv::Process::Worker qw(ima_worker);
 use SrSv::Debug;
 use SrSv::IRCd::State qw($ircline $ircline_real $ircd_ready);
 use SrSv::IRCd::Event qw(callfuncs);
-use SrSv::Unreal::Tokens;
+use SrSv::Unreal::Tokens qw( :tokens );
 use SrSv::IRCd::Parse qw(parse_line);
 use SrSv::RunLevel qw(emerg_shutdown);
 use SrSv::Log qw( write_log );
@@ -173,7 +173,7 @@ sub ircsend {
                ircsendimm(@_);
        } else {
                foreach my $x (@_) {
-                       if($x =~ /^$tkn{NICK}[$tkn]/) {
+                       if($x =~ /^@{[TOK_NICK]}/) {
                                unshift @queue, $x;
                        } else {
                                push @queue, $x;
index 5185b316691f674eccfc2b28e566f4a8801344dc..454c841ea14fa06619a18746e65c70202197347d 100644 (file)
@@ -31,8 +31,9 @@ BEGIN { our @EXPORT_OK = qw(ircd_enqueue queue_size) }
 
 use SrSv::Debug;
 use SrSv::Message qw(message);
+use SrSv::Constants qw( WF_MAX );
 
-our @queue = map [], 0..3; # 3 is the maximum WF value
+our @queue = map [], 0..WF_MAX;
 
 sub ircd_enqueue($) {
        my ($message) = @_;
@@ -52,9 +53,15 @@ sub ircd_enqueue($) {
        }
 }
 
-sub queue_size() {
+sub queue_size(;$) {
+       my ($depth) = @_;
+       if(!$depth) {
+               $depth = WF_MAX;
+       }
        my $r;
-       foreach (@queue) { $r += @$_ }
+       for(my $i = 0; $i < $depth; ++$i) {
+               $r += scalar @{$queue[$i]};
+       }
        return $r;
 }
 
index c0b2288a1cbe2731b138b5539a19d517225f4158..8e47ab34a7ed194f6c62817da3506347661941c8 100644 (file)
@@ -23,7 +23,12 @@ use Exporter 'import';
 our @EXPORT_OK = qw($ircline $ircline_real $remoteserv $ircd_ready synced initial_synced create_server get_server_children set_server_state set_server_juped get_server_state get_online_servers %IRCd_capabilities);
 
 # FIXME - synced() is called very often and should be cached locally
-use SrSv::Process::InParent qw(calc_synced create_server get_server_children set_server_state set_server_juped get_server_state get_online_servers);
+use SrSv::Process::InParent qw(
+       calc_synced
+       __initial_synced_inparent __synced_inparent
+       create_server get_server_children
+       set_server_state set_server_juped
+       get_server_state get_online_servers);
 
 use SrSv::Conf 'main';
 
@@ -40,13 +45,22 @@ our %servers;
 our %juped_servers;
 our $synced;
 our $initial_synced;
+our $queue_depth;
 
-sub synced {
+sub __initial_synced_inparent {
+       return $initial_synced;
+}
+sub __synced_inparent {
        return $synced;
 }
 
+sub synced {
+# $ircline is zero if running in a timer context (among other possibilities)
+       return ($ircline ? $synced : __synced_inparent());
+}
+
 sub initial_synced {
-       return $initial_synced;
+       return ($ircline ? $initial_synced : __synced_inparent());
 }
 
 sub calc_synced {
@@ -84,6 +98,7 @@ sub create_server($$) {
                PARENT => $parent,
                CHILDREN => [],
                SYNCED => 0,
+               NONCONFORMANT => isNonconformant($parent, $child),
        };
 
        push @{$servers{$parent}{CHILDREN}}, $child if $parent;
@@ -127,11 +142,31 @@ sub set_server_juped($) {
        $juped_servers{$server} = 1;
 }
 
+sub isNonconformant(@) {
+       my (@serverList) = @_;
+       foreach my $server (@serverList) {
+               if(defined($servers{$server}) && $servers{$server}->{NONCONFORMANT}) {
+                       return 1;
+               }
+               if(defined $main_conf{'unsyncserver'}) {
+                       my @list;
+                       if(ref($main_conf{'unsyncserver'}) eq 'ARRAY') {
+                               @list = @{$main_conf{'unsyncserver'}};
+                       } else {
+                               @list = ($main_conf{'unsyncserver'});
+                       }
+                       if(grep (m/^$server$/i, @list) ) {
+                               return 1;
+                       }
+               }
+       }
+       return 0;
+}
+
 sub get_server_state {
        my ($server) = @_;
 
-       my $badserver = $main_conf{'unsyncserver'};
-       return 1 if($badserver and lc $server eq lc $badserver); # I HATE NEOSTATS
+       return 1 if isNonconformant($server);
 
        return $servers{$server}{SYNCED};
 }
diff --git a/branches/mus-0.4.x-devel/SrSv/Insp/decodeUUID.pl b/branches/mus-0.4.x-devel/SrSv/Insp/decodeUUID.pl
new file mode 100755 (executable)
index 0000000..c74bba1
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+
+#       This file is part of SurrealServices.
+#
+#       SurrealServices is free software; you can redistribute it and/or modify
+#       it under the terms of the GNU Lesser General Public License version 2,
+#       as published by the Free Software Foundation.
+#
+#       SurrealServices 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 SurrealServices; if not, write to the Free Software
+#       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+=cut
+
+THIS CODE IS alpha only, and untested. Don't just trust it blindly.
+
+=cut
+
+use strict;
+use warnings;
+
+sub isAlpha($) {
+       my ($char) = @_;
+       return ($char =~ /[A-Z]/);
+}
+
+sub getBase36($) {
+       my ($char) = @_;
+       if(isAlpha($char)) {
+               my $val = (ord($char) - ord('A')) + 10;
+               #print "$val\n";
+               return $val;
+       } else {
+               return int($char);
+       }
+}
+
+sub decodeUUID($) {
+       my ($UUID) = @_;
+       my @chars = split(//, $UUID);
+       my @sidC = @chars[0..2];
+       my @uidC = @chars[3..8];
+       my $sidN = int($sidC[0]) << (4 + (6 * 2));
+       $sidN |= getBase36($sidC[1]) << (4 + (6 * 1));
+       $sidN |= getBase36($sidC[2]) << (4 + (6 * 0));
+       my $uidN = 0;
+       foreach my $char (@uidC) {
+               #print "$char\n";
+               $uidN <<= 6;
+               $uidN |= getBase36($char);
+       }
+       return (($sidN << 48) | $uidN);
+}
+
+my $int = decodeUUID('751AAAAAA');
+print "$int\n";
+print log($int)/log(2), "\n";
index e22561d89946699526c1f06e5a374584ae400746..e27d853ab0667f766d99b63e4a5dfd3e20ed9ecd 100644 (file)
@@ -17,11 +17,13 @@ package SrSv::Log;
 
 use strict;
 use IO::Handle;
+use English qw(-no_match_var);
 
 use SrSv::Debug;
 use SrSv::Timer qw(add_timer);
 use SrSv::Time;
 use SrSv::Process::InParent qw(write_log open_log close_log rotate_logs close_all_logs);
+use IO::File;
 
 use SrSv::Text::Codes qw( strip_codes );
 
@@ -95,10 +97,17 @@ sub open_log($$) {
        }
        my ($year, $month, undef, $mday) = gmt_date();
        my $filename2 = $filename.'-'.sprintf('%04d-%02d-%02d', $year, $month, $mday);
-       
-       open $log_handles{lc $handle}, '>>', $path.'/'.$filename2;
+
+       my $fh;
+       if($fh = IO::File->new($path.'/'.$filename2, '>>')) {
+       } else {
+               use SrSv::RunLevel qw( main_shutdown );
+               ircd::debug_nolog(qq(Unable to open "$path/$filename2": $OS_ERROR}));
+               main_shutdown();
+       }
+       $fh->autoflush(1);
+       $log_handles{lc $handle} = $fh;
        $file_handles{lc $handle} = { BASENAME => $filename, FILENAME => $filename2 };
-       $log_handles{lc $handle}->autoflush(1);
 }
 
 sub close_log($) {
@@ -107,29 +116,39 @@ sub close_log($) {
                ircd::debug_nolog("undefined log-handle $handle, aborting close()");
                return undef;
        }
-       close $log_handles{lc $handle};
+       $log_handles{lc $handle}->close();
+       delete($log_handles{lc $handle});
        delete($log_handles{lc $handle});
 }
 
 sub rotate_logs() {
        foreach my $handle (keys(%file_handles)) {
-               close $log_handles{$handle};
+               $log_handles{lc $handle}->close();
                my ($year, $month, undef, $mday) = gmt_date();
-               $file_handles{lc $handle}{FILENAME} = $file_handles{lc $handle}{BASENAME}.'-'.sprintf('%04d-%02d-%02d', $year, $month, $mday);
-               open $log_handles{$handle}, '>>', $path.'/'.$file_handles{lc $handle}{FILENAME};
+               $file_handles{lc $handle}{FILENAME} =
+                       $file_handles{lc $handle}{BASENAME}.'-'.sprintf('%04d-%02d-%02d', $year, $month, $mday);
+               my $new_fh;
+               if($new_fh = IO::File->new($path.'/'.$file_handles{lc $handle}{FILENAME}, '>>')) {
+               } else {
+                       use SrSv::RunLevel qw( main_shutdown );
+                       my $new_path = "$path/".$file_handles{lc $handle}{FILENAME};
+                       ircd::debug_nolog(qq(Unable to open "$new_path": $OS_ERROR}));
+                       main_shutdown();
+               }
+               $log_handles{lc $handle} = $new_fh;
        }
-       
-       add_timer('', get_nextday_time()-time(), __PACKAGE__, 'SrSv::Log::rotate_logs');
+
+       #add_timer('', get_nextday_time()-time(), __PACKAGE__, 'SrSv::Log::rotate_logs');
+       Event->timer( at => get_nextday_time(), cb => \&SrSv::Log::rotate_logs );
 }
 
 sub close_all_logs() {
        foreach my $handle (keys(%file_handles)) {
-               close $log_handles{$handle};
-               $file_handles{lc $handle} = undef;
+               close_log($handle);
        }
 }
 
 # set a timer to rotate logs on day-change
-add_timer('', get_nextday_time()-time(), __PACKAGE__, 'SrSv::Log::rotate_logs');
+Event->timer( at => get_nextday_time(), cb => \&SrSv::Log::rotate_logs );
 
 1;
index c1fd4807eea40c4020958bc3c10a34aed4309935..f09797dc51776235a2730e248a74594f5df6a44e 100644 (file)
@@ -18,23 +18,26 @@ package SrSv::MySQL;
 
 use strict;
 
-use Exporter 'import';
-BEGIN { our @EXPORT_OK = qw($dbh) }
+use DBI qw( :sql_types );
 
-use DBI;
+use Exporter 'import';
+BEGIN {
+       our @EXPORT_OK = (qw( $dbh connectDB disconnectDB ), @{$DBI::EXPORT_TAGS{'sql_types'}} );
+       our %EXPORT_TAGS = ( sql_types => $DBI::EXPORT_TAGS{'sql_types'} );
+}
 
 use SrSv::Process::Init;
 
-use SrSv::Conf::Parameters sql => [
-       qw(mysql_user mysql_pass mysql_db),
-       [server_prepare => 0],
-];
+use SrSv::Conf::sql;
 
 use SrSv::Conf 'sql';
 
 our $dbh;
 
 proc_init {
+       connectDB();
+};
+sub connectDB() {
        $dbh = DBI->connect(
                "DBI:mysql:".$sql_conf{'mysql-db'}.($sql_conf{server_prepare} ? ":mysql_server_prepare=1" : ''),
                $sql_conf{'mysql-user'},
@@ -47,6 +50,11 @@ proc_init {
        );
        # Prevent timeout
        $dbh->do("SET wait_timeout=(86400*365)");
-};
+}
+
+sub disconnectDB() {
+       $dbh->disconnect();
+       $dbh = undef;
+}
 
 1;
index b5d50e619a29bd34af3d705bbc91197eb6fc712b..81292105e5e2c34b2f6c3a652dd6f2abb940adef 100644 (file)
@@ -28,9 +28,8 @@ use Symbol 'delete_package';
 use Carp qw( confess );
 
 use SrSv::Debug;
-use SrSv::MySQL '$dbh';
+use SrSv::MySQL qw( $dbh :sql_types );
 use SrSv::Process::Init;
-use DBI qw(:sql_types);
 
 our %types;
 
diff --git a/branches/mus-0.4.x-devel/SrSv/NickReg/NickText.pm b/branches/mus-0.4.x-devel/SrSv/NickReg/NickText.pm
new file mode 100644 (file)
index 0000000..d774f01
--- /dev/null
@@ -0,0 +1,39 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::NickReg::NickText;
+
+use strict;
+
+use Exporter 'import';
+
+BEGIN {
+       my %constants = (
+               NTF_QUIT        => 1,
+               NTF_GREET       => 2,
+               NTF_JOIN        => 3,
+               NTF_AUTH        => 4,
+               NTF_UMODE       => 5,
+               NTF_VACATION    => 6,
+               NTF_AUTHCODE    => 7,
+               NTF_PROFILE     => 8,
+               NTF_VHOST_REQ   => 9,
+       );
+       require constant; import constant \%constants;
+       our @EXPORT = keys(%constants);
+}
+
+1;
index e07e6673f06cd2c12749dcb117c77ef0a9fedc40..7d8dae5a68d76aa5d2344371ce98d8b78f1eab6c 100644 (file)
@@ -25,7 +25,13 @@ SrSv::NickReg::User - Determine which users are identified to which nicks
 use strict;
 
 use Exporter 'import';
-BEGIN { our @EXPORT_OK = qw(is_identified chk_identified get_id_nicks get_nick_user_nicks get_nick_users) }
+BEGIN {
+       our @EXPORT_OK = qw(
+               is_identified chk_identified
+               get_id_nicks
+               get_nick_user_nicks get_nick_users get_nick_users_all
+       );
+}
 
 use SrSv::Process::Init;
 use SrSv::MySQL '$dbh';
@@ -34,64 +40,64 @@ use SrSv::User::Notice;
 use SrSv::NickReg::Flags;
 use SrSv::Errors;
 
-our ($is_identified, $get_id_nicks, $get_nick_users);
-
-proc_init {
-       $is_identified = $dbh->prepare("SELECT 1 FROM user, nickid, nickalias WHERE user.id=nickid.id AND user.nick=? AND nickid.nrid=nickalias.nrid AND nickalias.alias=?");
-       $get_id_nicks = $dbh->prepare("SELECT nickreg.nick FROM nickid, nickreg WHERE nickid.nrid=nickreg.id AND nickid.id=?");
-       $get_nick_users = $dbh->prepare("SELECT user.nick, user.id FROM user, nickid, nickalias WHERE user.id=nickid.id AND nickid.nrid=nickalias.nrid AND nickalias.alias=? AND user.online=1");
+my $find_user_tables = 'user JOIN nickid ON (user.id=nickid.id) JOIN nickalias ON (nickid.nrid=nickalias.nrid)';
+require SrSv::MySQL::Stub;
+import SrSv::MySQL::Stub {
+       __get_nick_users => ['ARRAY', "SELECT user.nick, user.id
+               FROM $find_user_tables WHERE nickalias.alias=? AND user.online=1"],
+       __get_nick_users_all => ['ARRAY', "SELECT user.nick, user.id, user.online
+               FROM $find_user_tables WHERE nickalias.alias=?"],
+       __is_identified => ['SCALAR', "SELECT 1
+               FROM $find_user_tables WHERE user.nick=? AND nickalias.alias=?"],
+       __get_id_nicks => ['COLUMN', "SELECT nickreg.nick
+               FROM nickid JOIN nickreg ON (nickid.nrid=nickreg.id) WHERE nickid.id=?"],
 };
 
 sub is_identified($$) {
        my ($user, $rnick) = @_;
        my $nick = get_user_nick($user);
-       
-       $is_identified->execute($nick, $rnick);
-       return scalar $is_identified->fetchrow_array;
+
+       return __is_identified($nick, $rnick) ? 1 : 0;
 }
 
 sub chk_identified($;$) {
        my ($user, $nick) = @_;
 
        $nick = get_user_nick($user) unless $nick;
-       
+
        nickserv::chk_registered($user, $nick) or return 0;
 
        unless(is_identified($user, $nick)) {
                notice($user, $err_deny);
                return 0;
        }
-       
+
        return 1;
 }
 
 sub get_id_nicks($) {
        my ($user) = @_;
        my $id = get_user_id($user);
-       my @nicks;
-
-       $get_id_nicks->execute($id);
-       my $ref = $get_id_nicks->fetchall_arrayref;
 
-       return map $_->[0], @$ref;
+       return __get_id_nicks($id);
 }
 
 sub get_nick_user_nicks($) {
        my ($nick) = @_;
 
-       $get_nick_users->execute($nick);
-       my $ref = $get_nick_users->fetchall_arrayref;
-
-       return map $_->[0], @$ref;
+       return map $_->[0], __get_nick_users($nick);
 }
 
 sub get_nick_users($) {
        my ($nick) = @_;
 
-       $get_nick_users->execute($nick);
-       my $ref = $get_nick_users->fetchall_arrayref;
-       
-       return map +{NICK => $_->[0], ID => $_->[1]}, @$ref;
+       return map +{ NICK => $_->[0], ID => $_->[1], ONLINE => 1 }, __get_nick_users($nick);
+}
+
+sub get_nick_users_all($) {
+       my ($nick) = @_;
+
+       return map +{ NICK => $_->[0], ID => $_->[1], ONLINE => $_->[2] }, __get_nick_users_all($nick);
 }
 
 1;
index 17dd8e039253936dc993462084d70479e5913131..c311ca79d13a751887a2a5d2084b66452f53bdf8 100644 (file)
@@ -21,13 +21,24 @@ use strict;
 use Carp 'croak';
 
 use Exporter 'import';
-BEGIN { our @EXPORT_OK = qw(spawn ima_worker $ima_worker multi get_socket call_in_parent call_all_child do_callback_in_child shutdown_worker shutdown_all_workers kill_all_workers) }
+BEGIN {
+       our @EXPORT_OK = qw(spawn write_pidfiles
+               ima_worker $ima_worker
+               multi get_socket
+               call_in_parent call_all_child do_callback_in_child
+               shutdown_worker shutdown_all_workers kill_all_workers)
+       }
 
 use Event;
+use English qw( -no_match_vars );
 use IO::Socket;
+use IO::File;
 use Storable qw(fd_retrieve store_fd);
 
 use SrSv::Debug;
+
+sub PREFIX() { return main::PREFIX }
+
 BEGIN {
        if(DEBUG) {
                require Data::Dumper; import Data::Dumper ();
@@ -77,6 +88,15 @@ sub spawn() {
        }
 }
 
+sub write_pidfiles() {
+       my $fh = IO::File->new("@{[PREFIX]}/data/worker.pids", 'w', '0600');
+       for(my $i = scalar(@workers); $i; $i--) {
+               my $pid = $workers[$i-1]->{PID};
+               print $fh $pid,"\n";
+       }
+       print $fh $PID,"\n";
+}
+
 sub ima_worker {
        return $ima_worker;
 }
@@ -161,6 +181,18 @@ sub kill_all_workers() {
 sub do_callback_in_child {
        my ($callback, $message) = @_;
 
+       # this whole thing is a workaround for perl 5.12's Storable.
+       # Can't pass a regexp through Storable.
+       if(ref($callback->{TRIGGER_COND}->{DST}) || ref($callback->{TRIGGER_COND}->{SRC})) {
+               foreach my $k (qw(DST SRC)) {
+                       next unless defined $callback->{TRIGGER_COND}->{$k};
+                       my $v = $callback->{TRIGGER_COND}->{$k};
+                       $v = "$v"; # convert regexp to string
+                       $callback->{TRIGGER_COND}->{$k} = $v;
+               }
+               #use Data::Dumper;
+               #ircd::debug( split($/, Data::Dumper::Dumper($worker->{UNIT})) );
+       }
        if(my $worker = pop @free_workers) {
                print "Asking worker ".$worker->{NUMBER}." to call ".$callback->{CALL}."\n" if DEBUG;
                #store_fd([$unit], $worker->{SOCKET});
index 908066bb7b98ced95d44d974db410a2ad1af9c7c..36aef05ba7505be32032fb4ef79b2a9dd2f80a7e 100644 (file)
@@ -38,27 +38,35 @@ sub TIEHASH {
 sub STORE {
        my ($self, $key, $value) = @_;
 
-       print "Store \%" . $$self . "\n" if SrSv::Shared::DEBUG;
+#      print "Store \%" . $$self . "\n" if SrSv::Shared::DEBUG;
        return ${$$self}{$key} = $value;
 }
 
 sub FETCH {
        my ($self, $key) = @_;
 
-       print "Fetch \%" . $$self . "\n" if SrSv::Shared::DEBUG;
+#      print "Fetch \%" . $$self . "\n" if SrSv::Shared::DEBUG;
        return ${$$self}{$key};
 }
 
 sub DELETE {
        my ($self, $key) = @_;
 
+       print "DELETE \%" . $$self . "{$key}\n" if SrSv::Shared::DEBUG;
        return delete(${$$self}{$key});
 }
 
 sub CLEAR {
        my ($self) = @_;
-
+       print "CLEAR \%" . $$self . "\n" if SrSv::Shared::DEBUG;
+=cut
+       foreach my $key (keys %{$$self}) {
+               delete ($$self->{$key});
+       }
        return %{$$self} = ();
+=cut
+       $$self = {};
+       return %{$$self};
 }
 
 sub EXISTS {
index 61cc9df036722e113b8c7c6880bf5504a5ae4f36..5589fea9e8e8174b5e67c6d28054138568b09d7d 100644 (file)
@@ -22,29 +22,29 @@ use Exporter 'import';
 BEGIN { our @EXPORT_OK = qw(read_hash readHash write_hash writeHash) }
 
 sub writeHash {
-        my $hash = $_[0];
-        my $file = $_[1];
+       my $hash = $_[0];
+       my $file = $_[1];
 
        my $fh;
-        open $fh, '>', $file;
+       open $fh, '>', $file;
 
-        my @keys = keys(%$hash); my @values = values(%$hash);
+       my @keys = keys(%$hash); my @values = values(%$hash);
 
-        for(my $i=0; $i<@keys; $i++) {
-                if(ref($values[$i]) eq 'ARRAY') {
-                        chomp $keys[$i];
-                        print $fh $keys[$i], " =[ ";
-                        foreach my $atom (@{$values[$i]}) {
-                                print $fh $atom, ", ";
-                        }
-                        print $fh "\n";
-                } else {
-                        chomp $keys[$i]; chomp $values[$i];
-                        print $fh $keys[$i], " = ", $values[$i], "\n";
-                }
-        }
+       for(my $i=0; $i<@keys; $i++) {
+               if(ref($values[$i]) eq 'ARRAY') {
+                       chomp $keys[$i];
+                       print $fh $keys[$i], " =[ ";
+                       foreach my $atom (@{$values[$i]}) {
+                               print $fh $atom, ", ";
+                       }
+                       print $fh "\n";
+               } else {
+                       chomp $keys[$i]; chomp $values[$i];
+                       print $fh $keys[$i], " = ", $values[$i], "\n";
+               }
+       }
 
-        close $fh;
+       close $fh;
 }
 
 sub readHash {
@@ -57,15 +57,15 @@ sub readHash {
 
        while(my $line = <$fh>) {
                if($line =~ /^#|^\s*$/) { }
-               elsif($line =~ /^\S+ ?=\[ /) {
-                       my ($key, $value) = split(/ =\[ /, $line);
+               elsif($line =~ /^(\S+) ?= ?\[ ?(.*) ?]$/) {
+                       my ($key, $value) = ($1, $2);
                        chomp $key; chomp $value;
                        $key =~ s/(^\s+|\s+$)//g;
                        $value =~ s/(^\s+|\s+$)//g;
                        $hash{$key} = [ split(/, /, $value) ];
                }
                elsif($line =~ /^\S+ ?= ?/) {
-                       my ($key, $value) = split(/ = /, $line);
+                       my ($key, $value) = split(/ ?= ?/, $line, 2);
                        chomp $key; chomp $value;
                        if($value eq 'undef') {
                                $value = undef;
@@ -77,10 +77,10 @@ sub readHash {
                else {
                        die "Malformed config file: $file\n";
                }
-        }
-        close $fh;
+       }
+       close $fh;
 
-        return (%hash);
+       return (%hash);
 }
 
 BEGIN { # The same functions, now with less camelCase
index 41390939252dbb18ce4f0c0b74bc94be716570c6..a6c46b57d04f0554f5e21288a95e3e3d82dda064 100644 (file)
@@ -31,47 +31,85 @@ BEGIN { our @EXPORT = qw( getTorRouters ); }
 
 sub openURI($) {
        my ($URI) = @_;
-       my $fh;
+       my $data;
        if($URI =~ s/^file:\/\///i) {
-               open($fh, '<', $URI) or die;
+               use IO::File;
+               my $fh = IO::File->new($URI, 'r') or die;
+               return $fh;
        } else {
        # assume HTTP/FTP URI
-               open($fh, '-|', ('wget -q -O - ' . $URI)) or die;
+=cut           use IO::Pipe;
+               my $fh = IO::Pipe->new();
+               $fh->reader(qq(wget -q -O - $URI)) or die;
+=cut
+               use WWW::Mechanize;
+               my $mech = WWW::Mechanize->new();
+               $mech->get($URI) or die $!;
+               my $content = $mech->content;
+               return $content;
        }
-       return $fh;
+}
+
+our %TOR_cmdhash;
+BEGIN {
+%TOR_cmdhash = (
+       'r'             => \&TOR_r,
+       's'             => \&TOR_s,
+       'router'        => \&TOR_router,
+       'reject'        => \&TOR_reject,
+       'accept'        => \&TOR_accept,
+);
 }
 
 sub parseTorRouterList($) {
        my ($fh) = @_;
-       my (%currentRouter, @routerList);
-       while (my $l = <$fh>) {
+       our (%currentRouter, @routerList);
+       foreach my $l (ref($fh) ? <$fh> : split($/, $fh)) {
+               my ($tok, undef) = split(' ', $l, 2);
                #print "$l";
                chomp $l;
-               if($l =~ /^r (\S)+ (?:[a-zA-Z0-9+\/]+) (?:[a-zA-Z0-9+\/]+) (?:\d{4}-\d{2}-\d{2} \d\d:\d\d:\d\d) (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}) (\d+) (\d+)/) {
+               if(my $code = $TOR_cmdhash{$tok}) {
+                       &$code($l);
+               }
+       }
+       sub TOR_r {
+               my ($l) = @_;
                #r atari i2i65Qm8DXfRpHVk6N0tcT0fxvs djULF2FbASFyIzuSpH1Zit9cYFc 2007-10-07 00:19:17 85.31.187.200 9001 9030
-                       #print "( NAME => $1, IP => \"$2.$3.$4.$5\", IN_PORT => $6, DIR_PORT => $7 )\n";
-                       %currentRouter = ( NAME => $1, IP => "$2.$3.$4.$5", IN_PORT => $6, DIR_PORT => $7 );
-               } 
-               elsif($l =~ /^s (.*)/) {
+               my (undef, $name, undef, undef, undef, $ip, $in_port, $dir_port) = split(' ', $l);
+               %currentRouter = ( NAME => $name, IP => $ip, IN_PORT => $in_port, DIR_PORT => $dir_port );
+               return;
+       }
+       sub TOR_s {
+               my ($l) = @_;
+               if($l =~ /^s (.*)/) {
                #s Exit Fast Guard Stable Running V2Dir Valid
                        my $tokens = $1;
                        # uncomment the conditional if you trust the router status flags
-#                      if($tokens =~ /Exit/) {
+                       #if($tokens =~ /Exit/) {
                                push @routerList, $currentRouter{IP};
-#                      }
-               }
-               elsif($l =~ /router (\S+) (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}) (\d+) (\d+) (\d+)/) {
-                       push @routerList, processTorRouter(%currentRouter) if scalar(%currentRouter);
-                       %currentRouter = ( NAME => $1, IP => "$2.$3.$4.$5", IN_PORT => $6, DIR_PORT => $8 );
-               } elsif($l =~ /reject (\S+):(\S+)/) {
-                       #print STDERR "$currentRouter{IP} reject $1:$2\n";
-                       push @{$currentRouter{REJECT}}, "$1:$2";
-               } elsif($l =~ /accept (\S+):(\S+)/) {
-                       #print STDERR "$currentRouter{IP} accept $1:$2\n";
-                       push @{$currentRouter{ACCEPT}}, "$1:$2";
+                       #}
                }
        }
-       close $fh;
+       sub TOR_router {
+               my ($l) = @_;
+               my (undef, $name, $ip, $in_port, undef, $dir_port) = split(' ', $l);
+               push @routerList, processTorRouter(%currentRouter) if scalar(%currentRouter);
+               %currentRouter = ( NAME => $name, IP => $ip, IN_PORT => $in_port, DIR_PORT => $dir_port );
+               return;
+       }
+       sub TOR_reject {
+               my ($l) = @_;
+               my ($tok, $tuple) = split(' ', $l);
+               my ($ip, $ports) = split(':', $tuple);
+               push @{$currentRouter{REJECT}}, "$ip:$ports";
+       }
+       sub TOR_accept {
+               my ($l) = @_;
+               my ($tok, $tuple) = split(' ', $l);
+               my ($ip, $ports) = split(':', $tuple);
+               push @{$currentRouter{ACCEPT}}, "$ip:$ports";
+       }
+       #close $fh;
        return @routerList;
 }
 
index 2f2fe8d6fbf2821745dc56f1f0856c2414fa44aa..510e09eb7276d764df2ffc0cd3df1678e84f4d3e 100644 (file)
@@ -21,7 +21,7 @@ use strict;
 use Encode 'encode';
 
 use constant {
-       MAX_WIDTH       => 60,
+       MAX_WIDTH       => 96,
        COLORS          => 1,
        BULLET          => encode('utf8', "\x{2022} "),
 };
@@ -38,7 +38,7 @@ BEGIN { if(COLORS) {
                $t =~ s/^(.{60}.*?)\s*$/$1  / if length $t > 60;
                $t = "\0031,15" . $t if $bg;
 
-               return $t;
+               return split($/, $t);
        }
 } else {
        *line_post = sub ($$) {
@@ -47,17 +47,19 @@ BEGIN { if(COLORS) {
                $t =~ s/ +$//;
                $t = ' ' unless $t;
 
-               return $t;
+               return split($/, $t);
        }
 } }
 
 sub columnar(@) {
-       my $opts = shift if ref($_[0]) eq 'HASH';
+       my $opts;
+       $opts = shift if ref($_[0]) eq 'HASH';
        my (@mlen, @out);
 
        $opts->{DOUBLE} = 0 if $opts->{NOHIGHLIGHT};
        my $double = $opts->{DOUBLE};
        my $border = $opts->{BORDER};
+       my $justified = $opts->{JUSTIFIED};
 
        foreach my $x (@_) {
                next unless ref($x) eq 'ARRAY';
@@ -89,11 +91,14 @@ sub columnar(@) {
        }
 
        my ($bg, $collapsed);
+       my $headerBorder = 0;
        foreach my $x (@_) {
                if(ref $x eq 'HASH') {
                        if(my $t = $x->{COLLAPSE}) {
                                next unless @$t;
-                               push @out, $borderLine if $border;
+                               if($border) {
+                                       push @out, $borderLine;
+                               }
                                push @out, ' ' unless $collapsed;
                                @$t = map BULLET . $_, @$t if($x->{BULLET});
                                push @out, @$t;
@@ -113,10 +118,22 @@ sub columnar(@) {
                #my $border = '+'.'-'x($width+1).'+';
                for(my $i; $i<@mlen; $i++) {
                        my $nc = strip_codes($x->[$i]);
-                       $str .= $x->[$i] .' ' x (($mlen[$i] - length($nc) + ($mlen[$i] ? 2 : 0))). ($border ? '| ' : '  ');
+                       if($justified && $i == 0) {
+                               $str .= ' ' x (($mlen[$i] - length($nc) + ($mlen[$i] ? 2 : 0))).
+                                       $x->[$i] . ($border ? '| ' : '  ');
+                       } else {
+                               $str .= $x->[$i] .' ' x (($mlen[$i] - length($nc) + ($mlen[$i] ? 2 : 0))).
+                                       ($border ? '| ' : '  ');
+                       }
                }
 
-               push @out, $borderLine if $border;
+               if($border) {
+                       if($headerBorder >= 2) {
+                       } else {
+                               push @out, $borderLine;
+                               $headerBorder++
+                       }
+               }
                push @out, line_post $bg, $str;
 
                if($double and $x->[-1]) {
@@ -127,7 +144,7 @@ sub columnar(@) {
        continue {
                $bg = !$bg unless $opts->{NOHIGHLIGHT};
        }
-       push @out, $borderLine if $border && !$collapsed;
+       push @out, $borderLine if $border && !$collapsed && scalar(@_)!=1;
 
        push @out, '  (empty list)' unless @out;
        push @out, ' --';
index 6168362f873d723fa6677b380dc41f07003d5acd..9625f34d44430207e6fc6797f8fcb61ecb783a0e 100644 (file)
@@ -101,7 +101,7 @@ sub split_time($) {
        no integer; # We might want to pass in a float value for $difference
        my ($difference) = @_;
        my ($weeks, $days, $hours, $minutes, $seconds);
-       $seconds        =  $difference % 60;
+       $seconds        =  $difference % 60 + ($difference - int($difference));
        $difference     = ($difference - $seconds) / 60;
        $minutes        =  $difference % 60;
        $difference     = ($difference - $minutes) / 60;
@@ -207,6 +207,9 @@ sub time_rel($;$) {
                ($seconds!=1 ? 's' : '');
 =cut
        }
+       if(!($weeks || $days || $hours || $minutes || $seconds) ) {
+               return '0 seconds';
+       }
        return $text;
 }
 
index e48e9212f028d549be01c06100829dd68f0bc0c0..871cea3d2f30ef060e855b27893a4f275dd1e613 100644 (file)
@@ -23,6 +23,20 @@ SrSv::Unreal::Base64 - Implementation of the UnrealIRCd Base64 encoding
 =cut
 
 use strict;
+use SrSv::64bit;
+BEGIN {
+       if(!HAS_64BIT_INT) {
+               eval {
+                       require Math::BigInt;
+                       import Math::BigInt try => 'GMP';
+               };
+               if($@) {
+                       print STDERR "Running old version of perl/Math::BigInt.\n", $@, "Trying again.\n";
+                       require Math::BigInt;
+                       import Math::BigInt;
+               }
+       }
+}
 
 use Exporter 'import';
 BEGIN { our @EXPORT_OK = qw(b64toi itob64); }
@@ -62,8 +76,15 @@ our @base64_to_int6_map = (
 *b64toi = \&base64_to_int;
 sub base64_to_int($) {
        my ($base64) = @_;
-
        my $val = 0;
+       #wKgIAw==
+       if(length($base64) > 8) {
+               warn "greater-than-32bit base64($base64) in base64_to_int";
+               $val = (HAS_64BIT_INT ? 0 : Math::BigInt->bzero());
+       } else {
+               $val = 0;
+       }
+
        foreach my $ch (split(//, $base64)) {
                $val <<= 6;
                $val += $base64_to_int6_map[ord($ch)];
index 5b949f3ece4bad9235a879646991331510c0e6a8..875c578be94f7542365f6b0effff691066d609f0 100644 (file)
@@ -27,6 +27,14 @@ BEGIN { our @EXPORT_OK = qw(parse_line parse_tkl) }
 BEGIN { *SJB64 = \&ircd::SJB64; *CLK = \&ircd::CLK; *NICKIP = \&ircd::NICKIP; }
 
 use SrSv::Conf 'main';
+use SrSv::Conf2Consts 'main';
+
+use Socket;
+BEGIN {
+       if(main_conf_ipv6) {
+               require Socket6; import Socket6;
+       }
+}
 
 use SrSv::Debug;
 use SrSv::IRCd::State qw($ircline $remoteserv create_server get_server_children set_server_state get_server_state %IRCd_capabilities);
@@ -42,14 +50,7 @@ use SrSv::Unreal::Base64 qw(b64toi itob64);
 # tho MIME's is probably faster
 use MIME::Base64;
 
-# FIXME
-use constant {
-       # Wait For
-       WF_NONE => 0,
-       WF_NICK => 1,
-       WF_CHAN => 2,
-       WF_ALL => 3,
-};
+use SrSv::Constants;
 
 use SrSv::Shared qw(@servernum);
 
@@ -360,11 +361,15 @@ sub MESSAGE($) {
        }
        $args[1] =~ s/\@${main_conf{local}}.*//io;
 
-       if(queue_size > 50 and $event eq 'PRIVMSG' and $args[1] !~ /^#/ and $args[2] =~ /^\w/) {
-               ircd::notice($args[1], $args[0], "It looks like the system is busy. You don't need to do your command again, just hold on a minute...");
+       if(queue_size(WF_MAX) > main_conf_queue_highwater) {
+               if($event eq 'PRIVMSG' and $args[1] !~ m'^#' and $args[2] =~ /^\w/) {
+                       ircd::notice($args[1], $args[0],
+                               "It looks like the system is busy. ".
+                               "You don't need to do your command again, just hold on a minute...");
+               }
        }
 
-       return ($event, 0, 1, WF_ALL, @args);
+       return ($event, 0, 1, WF_MSG, @args);
 }
 
 sub AWAY($) {
@@ -383,6 +388,7 @@ sub NICK($) {
        }
        elsif(CLK && NICKIP && $_[0] =~ /^(?:NICK|\&) (\S+) (\d+) (\S+) (\S+) (\S+) (\S+) (\d+) (\S+) (\S+) (\S+) (\S+) :(.*)$/) {
 #NICK Guest57385 1 !14b7t0 northman tabriel.tabris.net 38 0 +iowghaAxNWzt netadmin.SCnet.ops SCnet-3B0714C4.tabris.net CgECgw== :Sponsored By Skuld
+#NICK outis 1 !14corv northman localhost 38 0 +iowghaAxNWzt tabris.netadmin.SCnet.ops SCnet-D8C01838 AAAAAAAAAAAAAAAAAAAAAQ== :Sponsored By Skuld
                my ($nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost, $cloakhost, $IP, $gecos) =
                        ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
                if ($ts =~ s/^!//) {
@@ -392,8 +398,13 @@ sub NICK($) {
                        $server = $servernum[b64toi($server)];
 
                }
-               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost, $gecos,
-                       join('.', unpack('C4', MIME::Base64::decode($IP))), $cloakhost
+               if(main_conf_ipv6 && (length($IP) > 8)) {
+                       $IP = Socket6::inet_ntop(AF_INET6, MIME::Base64::decode($IP));
+               } else {
+                       $IP = join('.', unpack('C4', MIME::Base64::decode($IP)));
+               }
+               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost,
+                       $gecos, $IP, $cloakhost
                );
        }
        elsif(!CLK && NICKIP && $_[0] =~ /^(?:NICK|\&) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) :(.*)$/) {
@@ -407,8 +418,13 @@ sub NICK($) {
                        $server = $servernum[b64toi($server)];
 
                }
-               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost, $gecos,
-                       join('.', unpack('C4', MIME::Base64::decode($IP)))
+               if(main_conf_ipv6 && length($IP) > 8) {
+                       $IP = Socket6::inet_ntop(AF_INET6, MIME::Base64::decode($IP));
+               } else {
+                       $IP = join('.', unpack('C4', MIME::Base64::decode($IP)));
+               }
+               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost,
+                       $gecos, $IP
                );
        }
        elsif(!CLK && !NICKIP && $_[0] =~ /^(?:NICK|\&) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) :(.*)$/) {
@@ -422,7 +438,8 @@ sub NICK($) {
                        $server = $servernum[b64toi($server)];
 
                }
-               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes, $vhost, $gecos);
+               return ('NICKCONN', undef, undef, WF_NICK, $nick, $hops, $ts, $ident, $host, $server, $stamp, $modes,
+                       $vhost, $gecos);
        }
 }
 
@@ -498,18 +515,26 @@ sub IDENT($) {
 sub TOPIC($) {
        if($_[0] =~ /^(@|:)(\S+) (?:TOPIC|\)) (\S+) (\S+) (\S+) :(.*)$/) {
        #:tabris TOPIC #the_lounge tabris 1089336598 :Small Channel in search of Strong Founder for long term relationship, growth, and great conversation.
-       my $name;
-       if ($1 eq '@') {
-               $name = $servernum[b64toi($2)];
-       }
-       else {
-               $name = $2;
-       }
-               return ('TOPIC', 0, 1, WF_ALL, $name, $3, $4, $5, $6);
+               my $name;
+               my ($name, $cn, $setter, $ts, $topic) = ($2, $3, $4, $5, $6);
+               if ($1 eq '@') {
+                       $name = $servernum[b64toi($2)];
+               }
+               else {
+                       $name = $2;
+               }
+               if ($ts =~ s/^!//) {
+                       $ts = b64toi($ts);
+               }
+               return ('TOPIC', 0, 1, WF_ALL, $name, $cn, $setter, $ts, $topic);
        }
        elsif($_[0] =~ /^(?:TOPIC|\)) (\S+) (\S+) (\S+) :(.*)$/) {
+               my ($cn, $setter, $ts, $topic) = ($1, $2, $3, $4);
+               if ($ts =~ s/^!//) {
+                       $ts = b64toi($ts);
+               }
        # src, channel, setter, timestamp, topic
-               return ('TOPIC', 0, 1, WF_ALL, undef, $1, $2, $3, $4);
+               return ('TOPIC', 0, 1, WF_ALL, undef, $cn, $setter, $ts, $topic);
        }
 }
 
index 04996facffdd12a59a48e01e0cd14027c1d70caf..4102cea259beae528a55646284d8ca7a8c370345 100644 (file)
@@ -23,6 +23,8 @@ use Carp;
 use MIME::Base64;
 
 use SrSv::Conf 'main';
+use SrSv::Conf::main;
+use SrSv::Conf2Consts qw( main );
 
 use SrSv::Debug;
 use SrSv::Log;
@@ -56,7 +58,7 @@ use SrSv::IRCd::Event qw(addhandler callfuncs);
 use SrSv::IRCd::State qw($ircline $remoteserv $ircd_ready synced initial_synced set_server_state set_server_juped get_server_state get_online_servers);
 
 use SrSv::Unreal::Modes qw(@opmodes %opmodes $scm $ocm $acm);
-use SrSv::Unreal::Tokens;
+use SrSv::Unreal::Tokens qw( :tokens );
 use SrSv::IRCd::Parse qw(parse_tkl);
 use SrSv::Unreal::Base64 qw(itob64 b64toi);
 
@@ -77,18 +79,18 @@ addhandler('VERSION', undef(), undef(), 'ircd::version', 1);
 addhandler('SERVER', undef(), undef(), 'ircd::handle_server', 1);
 
 sub serv_connect() {
-       my $remote = $main_conf{remote};
-       my $port = $main_conf{port};
+       my $remote = main_conf_remote;
+       my $port = main_conf_port;
 
        ircd_connect($remote, $port);
        
-       ircsendimm('PROTOCTL '.($tkn ? 'TOKEN ' : '').'NICKv2 UMODE2 TKLEXT'.
+       ircsendimm('PROTOCTL '.(main_conf_tokens ? 'TOKEN ' : '').'NICKv2 UMODE2 TKLEXT'.
                (CLK ? ' CLK' : ' VHP'). # CLK obsoletes VHP. Plus if you leave VHP on, CLK doesn't work.
                (NOQUIT ? ' NOQUIT' : '').(SJ3 ? ' SJOIN SJOIN2 SJ3' : '').
                (NICKIP ? ' NICKIP' : '').
                (SJB64 ? ' SJB64 NS VL' : ''),
-               'PASS :'.$main_conf{pass},
-               'SERVER '.$main_conf{local}.' 1 '.$main_conf{numeric}.(SJB64 ? ( ':U*-*-'.$main_conf{numeric}.' ') : ' :').$main_conf{info});
+               'PASS :'.main_conf_pass,
+               'SERVER '.main_conf_local.' 1 '.main_conf_numeric.(SJB64 ? ( ':U*-*-'.main_conf_numeric.' ') : ' :').main_conf_info);
        
        %preconnect_defer_mode = %defer_mode;
        %defer_mode = ();
@@ -114,10 +116,10 @@ sub pong($$$) {
                # $dst is always $main_conf{local} anyway...
                # this is only valid b/c we never have messages routed THROUGH us
                # we are always an end point.
-               ircsendimm(":$dst $tkn{PONG}[$tkn] $src :$cookie");
+               ircsendimm(":$dst @{[TOK_PONG]} $src :$cookie");
        }
        else {
-               ircsendimm("$tkn{PONG}[$tkn] :$src");
+               ircsendimm("@{[TOK_PONG]} :$src");
         }
 }
 
@@ -130,7 +132,7 @@ sub eos {
        #print "Synced: ", synced(), "\n\n";
        #exit;
        
-       ircsendimm(':'.$main_conf{local}.' '.$tkn{EOS}[$tkn], 'VERSION');
+       ircsendimm(':'.main_conf_local.' '.TOK_EOS, 'VERSION');
 
        agent_sync();
        flushmodes(\%preconnect_defer_mode);
@@ -140,12 +142,12 @@ sub eos {
 }
 
 sub netinfo($$$$$$$$) {
-       ircsendimm($tkn{NETINFO}[$tkn].' 0 '.time." $_[2] $_[3] 0 0 0 :$_[7]");
+       ircsendimm(TOK_NETINFO.' 0 '.time." $_[2] $_[3] 0 0 0 :$_[7]");
        $main_conf{network} = $_[7];
 }
 
 sub tssync {
-       ircsendimm((SJB64 ? '@'.itob64($main_conf{numeric}) : ':'.$main_conf{local})." $tkn{TSCTL}[$tkn] SVSTIME ".time);
+       ircsendimm((SJB64 ? '@'.itob64(main_conf_numeric) : ':'.main_conf_local)." @{[TOK_TSCTL]} SVSTIME ".time);
 }
 
 sub parse_sjoin($$$$) {
@@ -192,27 +194,27 @@ sub parse_sjoin($$$$) {
 
 sub kick($$$$) {
        my ($src, $chan, $target, $reason) = @_;
-       $src = $main_conf{local} unless initial_synced();
-       ircsend(":$src $tkn{KICK}[$tkn] $chan $target :$reason");
-#      thread::ircrecv(":$src $tkn{KICK}[$tkn] $chan $target :$reason");
+       $src = main_conf_local unless initial_synced();
+       ircsend(":$src @{[TOK_KICK]} $chan $target :$reason");
+#      thread::ircrecv(":$src @{[TOK_KICK]} $chan $target :$reason");
        callfuncs('KICK', 0, 2, [$src, $chan, $target, $reason]);
 }
 
 sub invite($$$) {
        my ($src, $chan, $target) = @_;
        #:SecurityBot INVITE tabris #channel
-       ircsend(":$src $tkn{INVITE}[$tkn] $target $chan");
+       ircsend(":$src @{[TOK_INVITE]} $target $chan");
 }
 
 sub ping {
 #      if(@_ == 1) {
-               ircsend(':'.$main_conf{local}.' '.$tkn{PING}[$tkn].' :'.$main_conf{local});
+               ircsend(':'.main_conf_local.' '.TOK_PING.' :'.main_conf_local);
 #      } else {
-#              ircsend(':'.$_[2].' '.$tkn{PONG}[$tkn].' '.$_[0].' :'.$_[1]);
+#              ircsend(':'.$_[2].' '.TOK_PONG.' '.$_[0].' :'.$_[1]);
 #      }
 }
 
-sub privmsg($$@) {
+sub __privmsg($$@) {
        my ($src, $dst, @msgs) = @_;
 
        my @bufs;
@@ -221,42 +223,36 @@ sub privmsg($$@) {
                # Length restrictions are for CLIENT Protocol
                # hence the (MASKLEN - (NICKLEN + 1))
                # Technically optimizable if we use $agent{lc $src}'s ident and host
-               my $buflen = length($src) + length($dst) + 12 + (MASKLEN - (NICKLEN + 1));
+               my $buflen = length($src) + length($dst) + 5 + length(TOK_PRIVMSG) + (MASKLEN - (NICKLEN + 1));
                push @bufs, wordwrap($buf, (MAXBUFLEN - $buflen));
        }
 
        # submit a list of messages as a single packet to the server
-       ircsend(":$src $tkn{PRIVMSG}[$tkn] $dst :".join("\r\n".":$src $tkn{PRIVMSG}[$tkn] $dst :", @bufs));
-       callfuncs('LOOP_PRIVMSG', 0, 1, [$src, $dst, \@bufs]);
+       ircsend(":$src @{[TOK_PRIVMSG]} $dst :".join("\r\n".":$src @{[TOK_PRIVMSG]} $dst :", @bufs));
+       return \@bufs;
+}
+sub privmsg($$@) {
+       my ($src, $dst, @msgs) = @_;
+       my $bufs = __privmsg($src, $dst, @msgs);
+       callfuncs('LOOP_PRIVMSG', 0, 1, [$src, $dst, $bufs]);
+}
+sub privmsg_noloop($$@) {
+       my ($src, $dst, @msgs) = @_;
+       __privmsg($src, $dst, @msgs);
+       return;
 }
 
 sub debug(@) {
        my (@msgs) = @_;
-       debug_privmsg($main_conf{local}, $main_conf{diag}, @msgs);
-       write_log('diag', '<'.$main_conf{local}.'>', @msgs);
+       privmsg(main_conf_local, main_conf_diag, @msgs);
+       write_log('diag', '<'.main_conf_local.'>', @msgs);
 }
 
 sub debug_nolog(@) {
        my (@msgs) = @_;
-       debug_privmsg($main_conf{local}, $main_conf{diag}, @msgs);
+       privmsg(main_conf_local, main_conf_diag, @msgs);
 }
 
-sub debug_privmsg($$@) {
-       my ($src, $dst, @msgs) = @_;
-
-       my @bufs;
-       foreach my $buf (@msgs) {
-               # 3 spaces, two colons, PRIVMSG=7
-               # Length restrictions are for CLIENT Protocol
-               # hence the (MASKLEN - (NICKLEN + 1))
-               my $buflen = length($src) + length($dst) + 12 + (MASKLEN - (NICKLEN + 1));
-               push @bufs, wordwrap($buf, (MAXBUFLEN - $buflen));
-       }
-
-       # submit a list of messages as a single packet to the server
-       ircsendimm(":$src $tkn{PRIVMSG}[$tkn] $dst :".join("\r\n".":$src $tkn{PRIVMSG}[$tkn] $dst :", @bufs));
-       callfuncs('LOOP_PRIVMSG', 0, 1, [$src, $dst, \@bufs]);
-}
 
 sub notice($$@) {
        my ($src, $dst, @msgs) = @_;
@@ -266,12 +262,12 @@ sub notice($$@) {
                # 3 spaces, two colons, NOTICE=6
                # Length restrictions are for CLIENT Protocol
                # hence the (MASKLEN - (NICKLEN + 1))
-               my $buflen = length($src) + length($dst) + 12 + (MASKLEN - (NICKLEN + 1));
+               my $buflen = length($src) + length($dst) + 5 + length(TOK_NOTICE) + (MASKLEN - (NICKLEN + 1));
                push @bufs, wordwrap($buf, (MAXBUFLEN - $buflen));
        }
 
        # submit a list of notices as a single packet to the server
-       ircsend(":$src $tkn{NOTICE}[$tkn] $dst :".join("\r\n".":$src $tkn{NOTICE}[$tkn] $dst :", @bufs));
+       ircsend(":$src @{[TOK_NOTICE]} $dst :".join("\r\n".":$src @{[TOK_NOTICE]} $dst :", @bufs));
        callfuncs('LOOP_NOTICE', 0, 1, [$src, $dst, \@bufs]);
 }
 
@@ -290,14 +286,14 @@ sub ctcp_reply($$@) {
 sub setumode($$$) {
        my ($src, $dst, $modes) = @_;
 
-       ircsend(":$src $tkn{SVS2MODE}[$tkn] $dst $modes");
+       ircsend(":$src @{[TOK_SVS2MODE]} $dst $modes");
        callfuncs('UMODE', 0, undef, [$dst, $modes]);
 }
 
 sub setsvsstamp($$$) {
        my ($src, $dst, $stamp) = @_;
 
-       ircsend(":$src $tkn{SVS2MODE}[$tkn] $dst +d $stamp");
+       ircsend(":$src @{[TOK_SVS2MODE]} $dst +d $stamp");
        # This function basically set the svsstamp to
        # be the same as the userid. Not all ircd will
        # support this function.
@@ -309,7 +305,7 @@ sub setsvsstamp($$$) {
 sub setagent_umode($$) {
        my ($src, $modes) = @_;
 
-       ircsend(":$src $tkn{UMODE2}[$tkn] $modes");
+       ircsend(":$src @{[TOK_UMODE2]} $modes");
 }
 
 sub setmode2($$@) {
@@ -331,7 +327,7 @@ sub ban_list($$$$@) {
 
 sub setmode($$$;$) {
        my ($src, $dst, $modes, $parms) = @_;
-       $src = $main_conf{local} unless initial_synced();
+       $src = main_conf_local unless initial_synced();
 
        callfuncs('MODE', undef, 1, [$src, $dst, $modes, $parms]);
        
@@ -377,30 +373,30 @@ sub setmode_real($$$;$) {
        print "$ircline -- setmode_real($src, $dst, $modes, $parms)\n" if DEBUG;
        # for server sources, there must be a timestamp. but you can put 0 for unspecified.
        $parms =~ s/\s+$//; #trim any trailing whitespace, as it might break the simple parser in the ircd.
-       ircsend(":$src ".$tkn{MODE}[$tkn]." $dst $modes".($parms?" $parms":'').($src =~ /\./ ? ' 0' : ''));
+       ircsend(":$src @{[TOK_MODE]} $dst $modes".($parms?" $parms":'').($src =~ /\./ ? ' 0' : ''));
 }
 
 sub settopic($$$$$) {
        my ($src, $chan, $setter, $time, $topic) = @_;
-       $src = $main_conf{local} unless initial_synced();
+       $src = main_conf_local unless initial_synced();
        
-       ircsend(":$src ".$tkn{TOPIC}[$tkn]." $chan $setter $time :$topic");
+       ircsend(":$src @{[TOK_TOPIC]} $chan $setter $time :$topic");
        callfuncs('TOPIC', undef, undef, [$src, $chan, $setter, $time, $topic]);
 }
 
 sub wallops ($$) {
        my ($src, $message) = @_;
-       ircsend(":$src $tkn{WALLOPS}[$tkn] :$message");
+       ircsend(":$src @{[TOK_WALLOPS]} :$message");
 }
 
 sub globops ($$) {
        my ($src, $message) = @_;
-       ircsend(":$src $tkn{GLOBOPS}[$tkn] :$message");
+       ircsend(":$src @{[TOK_GLOBOPS]} :$message");
 }
 
 sub kline ($$$$$) {
         my ($setter, $ident, $host, $expiry, $reason) = @_;
-       $setter=$main_conf{local} unless defined($setter);
+       $setter=main_conf_local unless defined($setter);
        $ident = '*' unless defined($ident);
 
 
@@ -429,7 +425,7 @@ sub unkline ($$$) {
 
 sub zline ($$$$) {
         my ($setter, $host, $expiry, $reason) = @_;
-       $setter=$main_conf{local} unless defined($setter);
+       $setter=main_conf_local unless defined($setter);
 
        #foreach my $ex (@except) { return 1 if $mask =~ /\Q$ex\E/i; }
        
@@ -473,12 +469,12 @@ sub update_userkill($) {
 
 sub irckill($$$) {
        my ($src, $targetlist, $reason) = @_;
-       $src = $main_conf{local} unless initial_synced();
+       $src = main_conf_local unless initial_synced();
        
        foreach my $target (split(',', $targetlist)) {
                next unless update_userkill($target);
        
-               ircsendimm(":$src ".$tkn{KILL}[$tkn]." $target :$src ($reason)");
+               ircsendimm(":$src @{[TOK_KILL]} $target :$src ($reason)");
        
                callfuncs('KILL', 0, 1, [$src, $target, $src, $reason]);
        }
@@ -486,7 +482,7 @@ sub irckill($$$) {
 
 sub svssno($$$) {
     my ($src, $target, $snomasks) = @_;
-    $src=$main_conf{local} unless defined($src);
+    $src=main_conf_local unless defined($src);
     # TODO:
     # None, this doesn't affect us.
 
@@ -496,22 +492,22 @@ sub svssno($$$) {
 
 sub svsnick($$$) {
     my ($src, $oldnick, $newnick) = @_;
-    $src=$main_conf{local} unless defined($src);
+    $src=main_conf_local unless defined($src);
     # note: we will get a NICK cmd back after a 
     # successful nick change.
     # warning, if misused, this can KILL the user
     # with a collision
     
-#    ircsend(":$src ".$tkn{SVSNICK}[$tkn]." $oldnick $newnick ".time);
-    ircsend($tkn{SVSNICK}[$tkn]." $oldnick $newnick :".time);
+#    ircsend(":$src @{[TOK_SVSNICK]} $oldnick $newnick ".time);
+    ircsend("@{[TOK_SVSNICK]} $oldnick $newnick :".time);
 }
 
 sub svsnoop($$$) {
     my ($targetserver, $bool, $src) = @_;
-    $src = $main_conf{local} unless defined($src);
+    $src = main_conf_local unless defined($src);
     if ($bool > 0) { $bool = '+'; } else { $bool = '-'; }
 #this is SVS NO-OP not SVS SNOOP
-    ircsend(":$main_conf{local} $tkn{SVSNOOP}[$tkn] $targetserver $bool");
+    ircsend(":@{[main_conf_local]} @{[TOK_SVSNOOP]} $targetserver $bool");
 }
 
 sub svswatch ($$@) {
@@ -520,7 +516,7 @@ sub svswatch ($$@) {
 # Example: SVSWATCH Blah :+Blih!*@* -Bluh!*@* +Bleh!*@*.com
 # *** We do not track this info nor care.
        my ($src, $target, @watchlist) = @_;
-       my $base_str = ":$src ".$tkn{SVSWATCH}[$tkn]." $target :";
+       my $base_str = ":$src @{[TOK_SVSWATCH]} $target :";
        my $send_str = $base_str;
        while (@watchlist) {
                my $watch = shift @watchlist;
@@ -539,7 +535,7 @@ sub svssilence ($$@) {
 # Example: SVSSILENCE Blah :+Blih!*@* -Bluh!*@* +Bleh!*@*.com
 # *** We do not track this info nor care.
        my ($src, $target, @silencelist) = @_;
-       my $base_str = ":$src ".$tkn{SVSSILENCE}[$tkn]." $target :";
+       my $base_str = ":$src @{[TOK_SVSSILENCE]} $target :";
        my $send_str = $base_str;
        while (@silencelist) {
                my $silence = shift @silencelist;
@@ -562,16 +558,16 @@ sub svso($$$) {
 # *** this cmd does not change any umodes!
 
     my ($src, $target, $oflags) = @_;
-    $src = $main_conf{local} unless defined($src);
-    ircsend(":$src $tkn{SVSO}[$tkn] $target $oflags");
+    $src = main_conf_local unless defined($src);
+    ircsend(":$src @{[TOK_SVSO]} $target $oflags");
 
 }
 
 sub swhois($$$) {
 # *** We do not track this info nor care.
     my ($src, $target, $swhois) = @_;
-    $src = $main_conf{local} unless defined($src);
-    ircsend(":$src $tkn{SWHOIS}[$tkn] $target :$swhois");
+    $src = main_conf_local unless defined($src);
+    ircsend(":$src @{[TOK_SWHOIS]} $target :$swhois");
 }
 
 sub svsjoin($$@) {
@@ -587,12 +583,12 @@ sub __svsjoin($$@) {
     # a note. a JOIN is returned back to us on success
     # so no need to process this command.
     # similar for svspart.
-    ircsend(($src?":$src":'')." $tkn{SVSJOIN}[$tkn] $target ".join(',', @chans));
+    ircsend(($src?":$src":'')." @{[TOK_SVSJOIN]} $target ".join(',', @chans));
 }
 
 sub svspart($$$@) {
     my ($src, $target, $reason, @chans) = @_;
-    ircsend(($src ? ":$src" : '')." $tkn{SVSPART}[$tkn] $target ".join(',', @chans).
+    ircsend(($src ? ":$src" : '')." @{[TOK_SVSPART]} $target ".join(',', @chans).
        ($reason ? " :$reason" : ''));
 }
 
@@ -601,7 +597,7 @@ sub sqline ($;$) {
 # tho whether we want to put it in agent_connect
 # or leave it to the module to call it...
        my ($nickmask, $reason) = @_;
-       #ircsend("$tkn{S1QLINE}[$tkn] $nickmask".($reason?" :$reason":''));
+       #ircsend("@{[TOK_SQLINE]} $nickmask".($reason?" :$reason":''));
        qline($nickmask, 0, $reason);
 }
 
@@ -610,7 +606,7 @@ sub svshold($$$) {
        my ($nickmask, $expiry, $reason) = @_;
 # TKL version - Allows timed qlines.
 # TKL + Q * test services.SC.net 0 1092179497 :test
-       my $line = 'TKL + Q H '.$nickmask.' '.$main_conf{local}.' '.($expiry ? $expiry+time() : 0).' '.time().' :'.$reason;
+       my $line = 'TKL + Q H '.$nickmask.' '.main_conf_local.' '.($expiry ? $expiry+time() : 0).' '.time().' :'.$reason;
        ircsend($line);
 
        # at startup we send these too early,
@@ -623,7 +619,7 @@ sub svsunhold($) {
        my ($nickmask) = @_;
 # TKL version
 # TKL - Q * test services.SC.net
-       my $line = 'TKL - Q H '.$nickmask.' '.$main_conf{local};
+       my $line = 'TKL - Q H '.$nickmask.' '.main_conf_local;
        ircsend($line);
        callfuncs('TKL', undef, undef, [parse_tkl($line)]);
 }
@@ -632,7 +628,7 @@ sub qline($$$) {
        my ($nickmask, $expiry, $reason) = @_;
 # TKL version - Allows timed qlines.
 # TKL + Q * test services.SC.net 0 1092179497 :test
-       my $line = 'TKL + Q * '.$nickmask.' '.$main_conf{local}.' '.($expiry ? $expiry+time() : 0).' '.time().' :'.$reason;
+       my $line = 'TKL + Q * '.$nickmask.' '.main_conf_local.' '.($expiry ? $expiry+time() : 0).' '.time().' :'.$reason;
        ircsend($line);
 
        # at startup we send these too early,
@@ -653,7 +649,7 @@ sub unqline($) {
        my ($nickmask) = @_;
 # TKL version
 # TKL - Q * test services.SC.net
-       my $line = 'TKL - Q * '.$nickmask.' '.$main_conf{local};
+       my $line = 'TKL - Q * '.$nickmask.' '.main_conf_local;
        ircsend($line);
        callfuncs('TKL', undef, undef, [parse_tkl($line)]);
 }
@@ -664,19 +660,19 @@ sub svskill($$$) {
        # not sure if it'll accept a servername or not.
        # consider defaulting to ServServ
        die('svskill called w/o $src') unless $src;
-       ircsend(':'.$src.' '.$tkn{SVSKILL}[$tkn].' '.$target.' :'.$reason);
+       ircsend(':'.$src.' '.TOK_SVSKILL.' '.$target.' :'.$reason);
        callfuncs('QUIT', 0, undef, [$target, $reason]);
 }
 
 sub version($) {
        my ($src) = @_;
-       ircsend(":$main_conf{local} 351 $src $main::progname ver $main::version $main_conf{local} ".
+       ircsend(":@{[main_conf_local]} 351 $src $main::progname ver $main::version @{[main_conf_local]} ".
                $main::extraversion);
 }
 
 sub userhost($) {
        my ($target) = @_;
-       ircsend($tkn{USERHOST}[$tkn]." $target");
+       ircsend("@{[TOK_USERHOST]} $target");
 }
 
 sub userip($) {
@@ -687,13 +683,13 @@ sub userip($) {
 
 sub chghost($$$) {
        my ($src, $target, $vhost) = @_;
-       ircsend(($src?":$src ":'').$tkn{CHGHOST}[$tkn]." $target $vhost");
+       ircsend(($src?":$src ":'')."@{[TOK_CHGHOST]} $target $vhost");
         callfuncs('CHGHOST', 0, 1, [$src, $target, $vhost]);
 }
 
 sub chgident($$$) {
        my ($src, $target, $ident) = @_;
-       ircsend(($src?":$src ":'').$tkn{CHGIDENT}[$tkn]." $target $ident");
+       ircsend(($src?":$src ":'')."@{[TOK_CHGIDENT]} $target $ident");
         callfuncs('CHGIDENT', 0, 1, [$src, $target, $ident]);
 }
 
@@ -702,9 +698,9 @@ sub jupe_server($$) {
 
        # :nascent.surrealchat.net SERVER wyvern.surrealchat.net 2 :SurrealChat
        die "You can't jupe $server"
-               if ((lc($server) eq lc($remoteserv)) or (lc($server) eq lc($main_conf{local})));
-       ircsend(':'.$main_conf{local}.' '.$tkn{SQUIT}[$tkn]." $server :");
-       ircsend(':'.$main_conf{local}.' '.$tkn{SERVER}[$tkn]." $server 2 :$reason");
+               if ((lc($server) eq lc($remoteserv)) or (lc($server) eq lc(main_conf_local)));
+       ircsend(':'.main_conf_local.' '."@{[TOK_SQUIT]} $server :");
+       ircsend(':'.main_conf_local.' '."@{[TOK_SERVER]} $server 2 :$reason");
 
        set_server_juped($server);
 }
@@ -717,7 +713,7 @@ sub rehash_all_servers(;$) {
        $type = undef() if(defined($type) && !($type =~ /^\-(motd|botmotd|opermotd|garbage)$/i));
 
        foreach my $server (get_online_servers()) {
-               ircsend(':'.$main::rsnick.' '.$tkn{REHASH}[$tkn].' '.$server.(defined($type) ? ' '.$type : '') );
+               ircsend(':'.$main::rsnick.' '.TOK_REHASH.' '.$server.(defined($type) ? ' '.$type : '') );
        }
 }
 
@@ -731,12 +727,12 @@ sub unban_nick($$@) {
        while(my $nick = shift @nicks) {
                push @nicklist, $nick;
                if(++$i >= 10) {
-                       ircsend(($src ? ":$src " : '' ).$tkn{SVSMODE}[$tkn]." $cn -".'b'x($i).' '.join(' ', @nicklist));
+                       ircsend(($src ? ":$src " : '' )."@{[TOK_SVSMODE]} $cn -".'b'x($i).' '.join(' ', @nicklist));
                        $i = 0; @nicklist = ();
                }
        }
        
-       ircsend(($src ? ":$src " : '' ).$tkn{SVSMODE}[$tkn]." $cn -".'b'x($i).' '.join(' ', @nicklist));
+       ircsend(($src ? ":$src " : '' )."@{[TOK_SVSMODE]} $cn -".'b'x($i).' '.join(' ', @nicklist));
        # We don't loopback this, as we'll receive back the list
        # of removed bans.
 }
@@ -747,7 +743,7 @@ sub clear_bans($$) {
 # Similar concepts may exist in other ircd implementations
        my ($src, $cn) = @_;
        
-       ircsend(($src ? ":$src " : '' ).$tkn{SVSMODE}[$tkn]." $cn -b");
+       ircsend(($src ? ":$src " : '' )."@{[TOK_SVSMODE]} $cn -b");
        # We don't loopback this, as we'll receive back the list
        # of removed bans.
 }
@@ -773,9 +769,9 @@ sub enable_cloakhost($$) {
 
 sub nolag($$@) {
        my ($src, $sign, @targets) = @_;
-       $src = $main_conf{local} unless $src;
+       $src = main_conf_local unless $src;
        foreach my $target (@targets) {
-               ircsend(':'.$src .' '.$tkn{SVS2NOLAG}[$tkn].' '.$sign.' '.$target);
+               ircsend(':'.$src .' '.TOK_SVS2NOLAG.' '.$sign.' '.$target);
        }
 }
 
index ad5e66f04fff9ca9dbfa296210bd4286d1724c63..ed81216b7fa7119e3b4f4e05e87089bd1f0d88bd 100644 (file)
@@ -18,13 +18,19 @@ package SrSv::Unreal::Tokens;
 
 use strict;
 
+use SrSv::Conf::main;
+use SrSv::Conf2Consts qw( main );
+
 use Exporter 'import';
-BEGIN { our @EXPORT = qw($tkn %tkn) }
 
-our $tkn = 0;
+#=cut
+our $tkn = main_conf_tokens;
 
+our %tkn;
+#=cut
+BEGIN {
 # TODO: Turn these into constants.
-our %tkn = (
+my %unrealTokens = (
        PRIVMSG         => ['PRIVMSG',  '!'],
        WHOIS           => ['WHOIS',    '#'],
        WHOWAS          => ['WHOWAS',   '$'],
@@ -94,4 +100,27 @@ our %tkn = (
        SVS2NOLAG       => ['SVS2NOLAG', 'SL'],
 );
 
+       %tkn = %unrealTokens;
+       my %msgs; map { $msgs{"MSG_$_"} = $unrealTokens{$_}->[0] } keys(%unrealTokens);
+       my %toks;
+       if(main_conf_tokens) {
+               map { $toks{"TOK_$_"} = $unrealTokens{$_}->[1] } keys(%unrealTokens);
+       } else {
+               map { $toks{"TOK_$_"} = $unrealTokens{$_}->[0] } keys(%unrealTokens);
+       }
+       require constant;
+       import constant \%toks;
+       import constant \%msgs;
+
+       our @EXPORT_OK = (
+               keys(%toks),
+               keys(%msgs),
+               qw( %tkn $tkn )
+       );
+       our %EXPORT_TAGS = (
+               tokens => [keys(%toks)],
+               messages => [keys(%msgs)],
+       );
+}
+
 1;
index e690cc9a2edf3fc3df84b32ead8abdddd2f9f212..66dc14430439a4666e17f51debf26a6b2aea347e 100644 (file)
@@ -109,13 +109,13 @@ sub validate_extban($) {
        my ($parm) = @_;
        my ($type, $payload) = split(':', $parm, 2);
        $type =~ s/^\~//;
-       if(lc $type eq 'q' or lc $type eq 'n') {
+       if($type eq 'q' or $type eq 'n') {
                return 1 if($payload =~ /^(.+)!(.+)@(.+)$/);
-       } elsif(lc $type eq 'c') {
+       } elsif($type eq 'c') {
                return 1 if($payload =~ /^[~&@%+]?#.{0,29}$/);
-       } elsif(lc $type eq 'r') {
+       } elsif($type eq 'r') {
                return 1; # how can this be invalid anyway?
-       } elsif(uc $type eq 'T') {
+       } elsif($type eq 'T') {
                my ($action, $mask) = split(':', $payload);
                return 1 if ($action =~ /^(block|censor)$/i);
        }
index a3f683977d85f9aeed19047129e9443c5661fda8..24eed2ab2eff343bfd7901819f4cf5329de93f1d 100644 (file)
@@ -32,7 +32,7 @@ use Exporter 'import';
 BEGIN {
        my %constants = (
                UF_FINISHED => 1,
-               UF_ONLINE => 2, # not used yet
+               UF_GUEST => 2,
        );
 
        our @EXPORT_OK = (qw(get_user_id get_user_nick get_user_agent is_online chk_online 
@@ -41,6 +41,7 @@ BEGIN {
                get_user_flags set_user_flag chk_user_flag set_user_flag_all 
                get_host get_vhost get_cloakhost get_user_info
                flood_inc flood_check get_flood_level
+               kill_user kline_user
                __flood_expire
                ),
                keys(%constants));
@@ -56,11 +57,12 @@ BEGIN {
 }
 
 use SrSv::MySQL::Stub {
-        __getIPV4 => ['SCALAR', "SELECT INET_NTOA(ip) FROM user WHERE id=?"],
+        __getIP => ['ROW', "SELECT INET_NTOA(ip), ipv6 FROM user WHERE id=?"],
 };
        
        
 
+use SrSv::IRCd::Send; #package ircd
 use SrSv::Process::Init;
 use SrSv::MySQL '$dbh';
 use SrSv::NickControl::Enforcer qw(%enforcers);
@@ -68,6 +70,12 @@ use SrSv::IRCd::State qw(synced);
 use SrSv::Agent qw(is_agent);
 use SrSv::User::Notice;
 
+use SrSv::Conf::services;
+use SrSv::Conf::main;
+use SrSv::Conf2Consts qw( main services );
+
+use SrSv::IPv6;
+
 use SrSv::Log;
 
 our (
@@ -75,7 +83,7 @@ our (
 
        $get_user_flags, $set_user_flag, $unset_user_flag, $set_user_flag_all,
 
-       $get_host, $get_vhost, $get_cloakhost, $get_ip, $get_user_info,
+       $get_host, $get_vhost, $get_cloakhost,
 );
 
 proc_init {
@@ -92,13 +100,15 @@ proc_init {
        $get_host = $dbh->prepare("SELECT ident, host FROM user WHERE id=?");
        $get_vhost = $dbh->prepare("SELECT ident, vhost FROM user WHERE id=?");
        $get_cloakhost = $dbh->prepare("SELECT 1, cloakhost FROM user WHERE id=?");
-       $get_user_info  = $dbh->prepare("SELECT ident, host, vhost, gecos, server FROM user WHERE id=?");
 };
 require SrSv::MySQL::Stub;
 import SrSv::MySQL::Stub {
        __flood_check => ['SCALAR', "SELECT flood FROM user WHERE id=?"],
        __flood_inc => ['NULL', "UPDATE user SET flood = flood + ? WHERE id=?"],
        __flood_expire => ['NULL', "UPDATE user SET flood = flood >> 1"], # shift is faster than mul
+
+       __get_user_info => ['ROW', "SELECT ident, host, vhost, gecos, server, time, quittime
+               FROM user WHERE id=?"],
 };
 
 sub get_flood_level($) {
@@ -372,13 +382,10 @@ sub get_user_info($) {
        my $uid = get_user_id($user);
        return undef() unless $uid;
 
-       $get_user_info->execute($uid);
-       my ($ident, $host, $vhost, $gecos, $server) = $get_user_info->fetchrow_array();
-       $get_user_info->finish;
-
-       return ($ident, $host, $vhost, $gecos, $server);
+       return __get_user_info($uid);
 }
 
+=cut
 sub get_user_ipv4($) {
        my ($user) = @_;
 
@@ -400,9 +407,44 @@ sub get_user_ipv4($) {
                return $ip;
        }
 }
+=cut
 
 sub get_user_ip($) {
-       return get_user_ipv4(@_);
+       my ($user) = @_;
+
+       my $id;
+       if (ref($user)) {
+               if(exists $user->{IP}) {
+                       return $user->{IP};
+               }
+               $id = get_user_id($user);
+       } else {
+               $id = get_user_id({ NICK => $user});
+       }
+       return undef unless $id;
+
+       my ($ipv4,$ipv6) = __getIP($id);
+       if (defined $ipv6) {
+               return $user->{IP} = $ipv6 unless !ref($user);
+               return $ipv6;
+       } else {
+               return $user->{IP} = $ipv4 unless !ref($user);
+               return $ipv4;
+       }
+}
+
+sub kill_user($$) {
+       my ($user, $reason) = @_;
+
+       ircd::irckill(get_user_agent($user) || main_conf_local, get_user_nick($user), $reason);
+}
+
+sub kline_user($$$) {
+       my ($user, $time, $reason) = @_;
+       my $agent = get_user_agent($user);
+       my ($ident, $host) = get_host($user);
+
+       ircd::kline($agent, '*', $host, $time, $reason);
 }
 
 1;
diff --git a/branches/mus-0.4.x-devel/SrSv/User/Tags.pm b/branches/mus-0.4.x-devel/SrSv/User/Tags.pm
new file mode 100644 (file)
index 0000000..183bc8e
--- /dev/null
@@ -0,0 +1,45 @@
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package SrSv::User::Notice;
+
+use strict;
+
+use Exporter 'import';
+BEGIN { our @EXPORT = qw(add_user_tag get_user_tags check_user_tags) }
+
+use SrSv::User qw(get_user_nick get_user_id);
+
+use SrSv::MySQL::Stub (
+       __add_user_tag => ['INSERT', "INSERT IGNORE INTO usertags (userid, tag) VALUES (?,?)"],
+       __get_user_tags => ['COLUMN', 'SELECT tag FROM usertags WHERE userid=?'],
+       __check_user_tags => ['SCALAR', 'SELECT 1 FROM usertags WHERE userid=? AND tag=?'],
+);
+
+sub add_user_tag($$) {
+       my ($user, $tag) = @_;
+       return __add_user_tag(get_user_id($user), $tag);
+}
+sub get_user_tags($$) {
+       my ($user, $tag) = @_;
+       return __get_user_tags(get_user_id($user));
+}
+sub check_user_tags($$) {
+       my ($user, $tag) = @_;
+       return __check_user_tag(get_user_id($user), $tag);
+}
+
+1;
index fb9ed505e872b83cf288156d8c574b3c2d80ff83..30eace98afe50b4f2817e1e82c6aad8081e00982 100644 (file)
@@ -20,11 +20,12 @@ use strict;
 
 use Exporter 'import';
 BEGIN {
-       our @EXPORT = qw(min max makeSeqList);
+       our @EXPORT = qw(min max makeSeqList seqifyList);
        our @EXPORT_OK = qw(
                say say2 say3 sayFH sayERR
                slurpFile dumpFile
                interpretSuffixes humanizeBigNums
+               unique countUnique
        );
        our %EXPORT_TAGS = (
                say => [qw( say say2 say3 sayFH sayERR )],
@@ -39,6 +40,16 @@ sub max($$) {
        return ($_[0] > $_[1] ? $_[0] : $_[1]);
 }
 
+# This one only exists b/c it should be faster/simpler
+# than the unique() below
+sub __uniq(@) {
+       return keys %{{ map { $_ => 1 } @_ }}
+}
+# the sort is modified to sort numerically rather than by string.
+sub __numSort(@) {
+       return sort( {$a <=> $b} @_ );
+}
+
 sub makeSeqList(@) { 
        my @nums;
        foreach my $arg (@_) {
@@ -53,8 +64,36 @@ sub makeSeqList(@) {
                }
        }
        # map is a uniqify in case of duplicates
-       # the sort is modified to sort numerically rather than by string.
-       return sort( {$a <=> $b}  keys %{{ map { $_ => 1 } @nums }} );
+       return __numSort(  __uniq(@nums) );
+}
+
+sub __seqify($$) {
+       my ($lowNum, $highNum) = @_;
+       if($lowNum == $highNum) {
+               return $lowNum;
+       } else {
+               return "${lowNum}..${highNum}";
+       }
+}
+sub seqifyList(@) {
+       my @nums = __numSort( __uniq(@_) );
+       my $lowNum = shift @nums;
+       my $highNum = $lowNum;
+       my @seqs;
+       foreach my $num (@nums) {
+               if($num == ($highNum + 1)) {
+                       # one could also $highNum++
+                       # which would on a register-based CPU/VM be potentially faster
+                       # and only use one register, assuming it implemented it via inc(reg)
+                       # otoh, $num is already loaded into a reg, right?
+                       $highNum = $num;
+               } else {
+                       push @seqs, __seqify($lowNum, $highNum);
+                       $lowNum = $highNum = $num;
+               }
+       }
+       push @seqs, __seqify($lowNum, $highNum);
+       return @seqs;
 }
 
 sub __say($@) {
@@ -72,7 +111,7 @@ sub sayFH($@) {
        print $fh _say(@list);
 }
 sub sayERR(@) {
-       sayFH STDERR, @_;
+       sayFH(*STDERR, @_);
 }
 sub say2(@) {
        say( __say( ' ', @_) );
@@ -130,4 +169,25 @@ sub humanizeBigNums($;$) {
        }
 }
 
+sub __unique($) {
+       my ($input_arrayRef) = @_;
+       my %seen; keys(%seen) = scalar(@$input_arrayRef) / 2;
+       no warnings 'uninitialized';
+       foreach my $item (@$input_arrayRef) {
+               $seen{$item}++;
+       }
+       return %seen;
+}
+sub unique(@) {
+       my (@input_array) = @_;
+       my %seen = __unique(\@input_array);
+       return sort(keys(%seen));
+}
+sub countUnique(@) {
+       my (@input_array) = @_;
+       my %seen = __unique(\@input_array);
+       return map("$_($seen{$_})", sort(keys(%seen)));
+}
+
+
 1;
index a997db36d67195f0760a5b95dc098359c68b24d2..69604dafa8fec326936ae3d2a27d1bdb571af295 100644 (file)
@@ -7,6 +7,11 @@ You must move all mysql settings from services.conf to sql.conf
 0.4.2: Adds support for password-hashing, and makes a lot of changes to
 the database schema. running db-setup.pl is required.
 
+0.4.3: Changes the SQL schema upgrading method to be safer and simpler.
+Changes the schema quite a bit even so. You MUST run the upgrade of
+0.4.2 before you can run the upgrade on 0.4.3. After 0.4.3 you should be
+able to skip versions if desired.
+
 -----------------------------------------------------------------------
 
 The db-setup.pl script should perform any database changes or
index 09877cdbc9774844e3d97f09fc35bb6c84fe9cf8..b6f86f7d6da8b1c00cc58ece895ef1c4506eefa2 100755 (executable)
@@ -28,8 +28,8 @@ BEGIN {
        require constant; import constant(\%constants);
        chdir $constants{PREFIX};
 }
-use lib PREFIX;
-               
+use lib PREFIX, "@{[PREFIX]}/CPAN";
+
 use SrSv::Conf 'sql';
 
 $dbh = DBI->connect('DBI:mysql:'.$sql_conf{'mysql-db'}, $sql_conf{'mysql-user'}, $sql_conf{'mysql-pass'}, 
diff --git a/branches/mus-0.4.x-devel/config-example/connectserv.conf b/branches/mus-0.4.x-devel/config-example/connectserv.conf
new file mode 100644 (file)
index 0000000..97bcd86
--- /dev/null
@@ -0,0 +1,2 @@
+# whether ConnectServ should list JOIN/PART events
+joinpart = 0
index df400c8903d0b5ba715a6dfe6c1ebc1217ca99b6..170e3b81bb9b1183c200117e46d0a5f8ed451345 100644 (file)
@@ -15,6 +15,12 @@ port = 6667
 # a unique value.
 numeric = 123
 
+# This is for enabling IPv6 usage.  You must set this to
+# 1 for true, or 0 for false.
+# IPv6 support isn't known to be bug-free, but it is running on SCnet.
+# If you see errors about 'Socket6' you have to disable this.
+ipv6 = 0
+
 # You can set this to anything you want, it shows up in
 # /whois and /links
 info = SurrealServices
@@ -51,7 +57,7 @@ sig = Thank you for chatting with us.
 
 # If you use NeoStats, set this to the name of your NeoStats
 # server.  Otherwise, leave it commented out.
-#unsyncserver = neostats.example.com
+#unsyncserver = [neostats.example.com, denora.example.com]
 
 # Uncomment this if you don't want any emails to be sent.
 #nomail = 1
@@ -72,3 +78,31 @@ sig = Thank you for chatting with us.
 # a backup copy of your database. Keeping the backup for any period of time
 # violates the point of hashing your passwords.
 hashed-passwords = 0
+
+# Used for the special channel bantype 10 
+
+# The assumption is that anything that starts with one of these prefixes
+# and a hyphen has a per-user ident that is maintained by a cookie and
+# should be reasonably persistent.
+#ban_webchat_prefixes = java|htIRC
+
+# Using tokens uses less bandwidth for processing commands
+# The TOK_FOO are shorter than CMD_FOO and can also allow faster hash matching
+# for command dispatch.
+# DON'T CHANGE UNLESS YOU'RE A DEVELOPER
+tokens = 1
+
+# If the queue size goes over queue_highwater,
+# a) low priority user commands are ignored and user is asked to come back later.
+# b) services notifies opers that this has occurred
+# c) svsop are exempt.
+# below are defaults
+#queue_highwater = 50
+
+# If the queue size goes over the queue_lowwater,
+# a notice/warning is put in the operchan (if defined) and the diagchan
+# However, this only applies to regular events (JOIN, NICK, et cetera),
+# not PRIVMSG in chans or to agents.
+#queue_lowwater = 30
+
+operchan = #opers
index 230d921ee778de8b9d321f4da7095ba9b10f4cda..395b081c4381d6a507551ff38b7bb63ce814bf2c 100644 (file)
@@ -24,6 +24,6 @@ EnableOPM = 0
 OPMZlineReason = Open proxy - see http://opm.blitzed.org/$
 ProxyZlineTime = 21600
 TorZlineReason = You are not permitted to connect to this network using anonymous proxies.
-TorServer = http://moria.seul.org:9031/tor/status/all
+TorServer =  http://tor.noreply.org/tor/status/all
 EnableTor = 1
 CountryZlineReason = Due to persistent abuse, access to this network from $ is denied.
index cfdf58d6a68902c378cbd8d2458924ab874eadc4..58c69a9a15f255e86a045300adef68526c8bcb82 100644 (file)
@@ -37,13 +37,14 @@ default-protect = normal
 log-overrides = 0
 
 # Default channel bot for all registrations
-#default-chanbot=ChanBot
+#default-chanbot = ChanBot
 
-# How many seconds to wait before assuming that a user
-# won't be coming back from a netsplit.
-# default is 5 minutes.
-# More than this hasn't been tested yet, but it should be ok.
-#old_user_age = 300
+# default mlock for channels when registered
+#default_channel_mlock = +nrt
+
+# Restricts channel registration to network staff
+# (anyone with helpop or higher)
+#chanreg-needs-oper = 0
 
 # Allows you to add secondary names for the agents
 botserv = undef
@@ -62,3 +63,10 @@ hostserv = undef
 #adminserv = Secretary
 #operserv = Bouncer
 #hostserv = Butler
+
+# How long after signoff (or netsplit) a user entry is deleted/expired from.
+# the table in case they come back with the same userid & timestamp.
+# It also determines how long OS UINFO will be able to retrieve information
+# about a user assuming that no one has used that nick since.
+# The value is in seconds. Default is 300 (5 minutes)
+#old_user_age = 300
diff --git a/branches/mus-0.4.x-devel/config-example/spamserv/nicklist.txt b/branches/mus-0.4.x-devel/config-example/spamserv/nicklist.txt
new file mode 100644 (file)
index 0000000..5005d48
--- /dev/null
@@ -0,0 +1,8 @@
+pikachu
+charmander
+jynx
+blastoise
+typhlosion
+squirtle
+Raichu
+giratinaorigin
diff --git a/branches/mus-0.4.x-devel/config-example/spamserv/spamserv.conf b/branches/mus-0.4.x-devel/config-example/spamserv/spamserv.conf
new file mode 100644 (file)
index 0000000..e1ebd80
--- /dev/null
@@ -0,0 +1,15 @@
+# This sets the maximum amount of time, in seconds,
+# for pseudoclients to idle in a given channel, 
+# before cycling over to a new pseudoclient
+#
+# Default is 14400 seconds, or 4 hours
+#
+idlemax = 14400
+
+# Same as above, except this sets the minimum
+# amount of time a pseudoclient will be in
+# a channel.
+#
+# Default is 3600 seconds, or 1 hour
+#
+idlemin = 3600
index db7eedf752545d2d899b80b208d03dac81bcecfe..9db34cf0cad86707e49d56b41c58c0d20e110469 100755 (executable)
@@ -28,8 +28,9 @@ BEGIN {
        );
        require constant; import constant(\%constants);
 }
+
 chdir PREFIX;
-use lib PREFIX;
+use lib PREFIX, "@{[PREFIX]}/CPAN";
 
 our ($delete_db, $skip_backup, $auto_backup, $restore, $help);
 BEGIN {
@@ -42,20 +43,21 @@ BEGIN {
        );
 
        if($help) {
-               print <<EOF;
+               print qq"
 Options:
        --delete        Delete entire database
        --skip-backup   Don't nag about making a backup
        --backup        Make backup without upgrading
        --restore FILE  Restore the database from a backup
        --help          Show this message
-EOF
-               
+";
+
                exit 1;
        }
 }
 
 use SrSv::Conf qw(main sql);
+use SrSv::DB::Schema;
 
 BEGIN {
        if($restore) {
@@ -83,16 +85,6 @@ sub ask($) {
        }
 }
 
-sub do_sql_file($) {
-       my $file = shift;
-       open ((my $SQL), $file) or die "$file: $!\n";
-       my $sql;
-       while(my $x = <$SQL>) { $sql .= $x unless $x =~ /^#/ or $x eq '\n'}
-       foreach my $line (split(/;/s, $sql)) {
-               $dbh->do($line);
-       }
-}
-
 unless($skip_backup) {
        if($auto_backup or ask "Would you like to make a backup of your database: $sql_conf{'mysql-db'}? (Y/n) ") {
                my @lt = localtime();
@@ -115,12 +107,18 @@ if($delete_db) {
        }
 }
 
-print "Creating tables...\n";
-
 $dbh->{RaiseError} = 0;
 $dbh->{PrintError} = 0;
 
-do_sql_file("sql/services.sql");
+my ($ver) = check_schema();
+#print "$ver\n";
+if($ver == 0) {
+       print "Creating tables...\n";
+       do_sql_file("sql/services.sql");
+       upgrade_schema(0);
+} elsif($ver) {
+       upgrade_schema($ver);
+}
 
 print "Updating chanperm...\n";
 
@@ -145,8 +143,8 @@ my @perms = (
        ['INVITE', 4, 0],
        ['InviteSelf', 1, 0],
        ['TOPIC', 5, 0],
-       ['UnbanSelf', 4, 0],
-       ['UNBAN', 5, 0],
+       ['UnbanSelf', 2, 0],
+       ['UNBAN', 4, 0],
        ['VOICE', 2, 0],
        ['HALFOP', 3, 0],
        ['OP', 4, 0],
index a170cc6f589cdb06e94f90dce5e2646f499fb7e3..148e9015cc796dc8366f86eb4574433a5a77e1b2 100755 (executable)
@@ -28,8 +28,8 @@ BEGIN {
        require constant; import constant(\%constants);
 }
 chdir PREFIX;
-use lib PREFIX;
-               
+use lib PREFIX, "@{[PREFIX]}/CPAN";
+
 use SrSv::Conf 'sql';
 
 $dbh = DBI->connect('DBI:mysql:'.$sql_conf{'mysql-db'}, $sql_conf{'mysql-user'}, $sql_conf{'mysql-pass'}, 
index 04708dce41833364bc88c5cb95a0d9ad51439e39..73379e19d918c22cba2460d92c48889cea08659d 100644 (file)
@@ -1,5 +1,7 @@
-AdminServ Commands
+%BAdminServ%B allows services administrators or higher
+to list, modify, or look up users on the staff lists.
 
-SVSOP     - Modify Services Operator
-WHOIS     - Check rank of an individual
-STAFF     - List all Services Ops
+Commands:
+  SVSOP    Modify Services Operator
+  WHOIS    Check rank of an individual
+  STAFF    List all Services Ops
index 40006912a6a470ee5bd90edf6b5603df21672b5a..1bead4c43efa66494026879c3498bd98b8435089 100644 (file)
@@ -1,2 +1,4 @@
 %BAdminServ STAFF%B lists all services staff-members, in rank
-order.
\ No newline at end of file
+order.
+
+Syntax: %BSTAFF%B
index 9b048fe4c9aeb9151a3e8851d4fea9f9815274ea..338086bac360ddaea74a19ed4659350e2f1a82c4 100644 (file)
@@ -1,10 +1,10 @@
-Modify Services Operator
+%BAdminServ SVSOP%B modifies the rank of a user and lists 
+the services operators.
  
-Syntax: SVSOP <ADD|DEL|LIST> <nick> <H|O|A|R>
+Syntax: %BSVSOP%B <%UADD|DEL|LIST%U> %Unick%U <%UH|O|A|R%U>
  
 Services Access:
-H       HelpOp
-O       [Services] Operator
-A       [Services] Admin
-R       Services Root
+  H       HelpOp
+  O       [Services] Operator
+  A       [Services] Admin
+  R       Services Root
diff --git a/branches/mus-0.4.x-devel/help/adminserv/whois.txt b/branches/mus-0.4.x-devel/help/adminserv/whois.txt
new file mode 100644 (file)
index 0000000..e75f619
--- /dev/null
@@ -0,0 +1,3 @@
+%BAdminServ WHOIS%B displays the services rank of a given user.
+
+Syntax: %BWHOIS%B %Unick%U
index 6f268c921173ec52f0e705089f245ecfcbb7fff1..bc791e63685a89cb913292a0de92e64dd4fe1b7b 100644 (file)
@@ -1,3 +1,3 @@
 %BBotServ ACT%B allows you to make a bot perform an action in a channel.
 
-Syntax: %BACT%B %U#channel%U %Uaction%U
+Syntax: %BACT%B %U#channel%U <%Uaction%U>
index c11f1b78d4057b23df3a4c8a3106fd0187af9349..c9e08d7f7b4c1684342f0d3c113137e2feb4a168 100644 (file)
@@ -1,3 +1,3 @@
 %BBotServ ADD%B allows you to create a new services bot.
 
-Syntax: %BADD%B %Unick%U %Uident%U %Uvhost%U %Urealname%U
+Syntax: %BADD%B <%Unick%U> <%Uident%U> <%Uvhost%U> <%Urealname%U>
index b3f1b06e9d2a2ed5f392fc4a4100ddcd21ae8216..1ec981ed05dc68ac9d6df4f312133d4f73ddc13a 100644 (file)
@@ -1,4 +1,4 @@
 %BBotServ ASSIGN%B allows you to assign a bot to a channel.  Once assigned,
 the bot will join the channel and accept commands.
 
-Syntax: %BASSIGN%B %U#channel%U %Ubot%U
+Syntax: %BASSIGN%B %U#channel%U <%Ubot%U>
index 7affde36c12af1cfd54f5e21fadfcdbc1abc08af..711b8aca598897ebec86292da1b23990ee2ff445 100644 (file)
@@ -1,3 +1,3 @@
 %BBotServ SAY%B allows you to make a bot send a message to a channel.
 
-Syntax: %BSAY%B %U#channel%U %Umessage%U
+Syntax: %BSAY%B %U#channel%U <%Umessage%U>
index a41959bb2d50065759e9b1d53b08bcc5ecefab1f..654c0a8b3d7dc7ec3690de9b555177f9dd9c605b 100644 (file)
@@ -6,4 +6,4 @@
               Deaf bots will not be able to be used for
               any kind of badword kicking.
 
-Syntax: %BSET%B %Ubot%U %Uflag%U %UOn/Off%U
+Syntax: %BSET%B %Ubot%U <%Uflag%U> <%UON/OFF%U>
index 9fbda4430a0bc774ba65c7c7ed50c3a46e120e77..bb46788b473b5872e4ac28f9e380d9a13177cc3c 100644 (file)
@@ -3,6 +3,10 @@ Commands:
   !down          Removes all channel status.
   !invite        Invites a user to the channel.
   !ban           Bans a user or mask from the channel.
+  !banlist       Lists all bans in the channel, suitable for using the 
+                     numbers with !unban
+  !qban          Places a quiet ban on the user in the channel.
+  !nban          Places a nick-change ban on the user in the channel.
   !kick          Kicks a user from the channel.
   !kickban       Kicks and bans a user from the channel.
   !kickmask      Kicks users matching a mask from the channel.
@@ -13,6 +17,11 @@ Commands:
   !dice          Rolls dice, !d 2d4 rolls 2 4 sided dice.
   !mode          Sets modes in a channel.
   !resync        Gives everyone the precise chan-ops they're supposed to have.
+  !topic         Sets the topic of the channel.
+
+  !abbreviations Shows all short command aliases.
+  !abbrev
+  !abbrevs
 
 Commands to set channel status:
   !voice    !halfop    !op    !admin
diff --git a/branches/mus-0.4.x-devel/help/chanbot/abbreviations.txt b/branches/mus-0.4.x-devel/help/chanbot/abbreviations.txt
new file mode 100644 (file)
index 0000000..cb81dd0
--- /dev/null
@@ -0,0 +1,11 @@
+Commands:
+  !b             !ban
+  !k             !kick
+  !kb            !kickban
+  !kbm           !kickbanmask
+  !km            !kickmask
+  !kbmask        !kickbanmask
+  !d             !dice
+  !m             !mode
+  !blist         !banlist
+  !t             !topic
index a70e238dd392633a8399438621de03fd506a76dc..3ad6eb9e583b913079cc52c3e66365cd7d1bf761 100644 (file)
@@ -1,8 +1,8 @@
 %BChanServ%B allows you to register and control various aspects of
 channels. ChanServ can prevent malicious users from "taking
 over" channels by limiting who is allowed channel operator
-priviliges.
-
+privileges.
 Commands:
   REGISTER  Register a channel
   SET       Change various channel configuration settings
@@ -11,20 +11,27 @@ Commands:
   INFO      Information about a channel
   DROP      Drop a registered channel
   MODE      Change channel modes.
-
 Commands to manipulate access lists:
    CF  SOP  AOP  HOP  VOP  UOP  AUTH
-
 Commands to change or check channel status:
    VOICE    OP     HALFOP    PROTECT    UP
    DEVOICE  DEOP   DEHALFOP  DEPROTECT  DOWN
    WHY      COUNT  ALIST     RESYNC
-
+Commands for moderating a channel's users
+   KICK    KICKBAN  KICKMASK KICKBANMASK
+   BAN     UNBAN    BANLIST  TEMPBAN
+Network Admin Commands:
+   GETKEY   CLOSE    DRONE
 Other available commands:
-   DICE   INVITE   GETKEY  CLOSE     COPY
-   CLEAR  WELCOME  DRONE   KICKMASK  
-   KICK   KICKBAN  MLOCK   KICKBANMASK
-
+   DICE     JOIN     INVITE
+   WELCOME  CLEAR    MLOCK
+   COPY     TOPIC    TOPICAPPEND
 Note that channels will be dropped after 21 days of inactivity.
  
 For more help on a specific command, type: %B/cs help%B %Ucommand%U
index 2edd6b931f70ab4d47361abd429c227caf29c1cb..814e55d2663731a118228c87c66ad06020646956 100644 (file)
@@ -1,6 +1,5 @@
 %BChanServ ADMIN%B allows you to set channel-admin mode on
 either yourself or on other people in a channel.
 
-Syntax:
-        ADMIN <channel> [nick [nick ...]]
-        ADMIN <channel> [channel [channel]]
+Syntax: %BADMIN%B %U#channel%U [%Unick%U [%Unick%U ...]]
+        %BADMIN%B %U#channel%U [%Uchannel%U [%Uchannel%U]]
index 23bb7ee47be374a63e8cf737771764e06ff24fd1..215a447c60e4dd3f1d9308e4790e977540984cbc 100644 (file)
@@ -2,8 +2,8 @@
 If a user on the AutoKick list attempts to join the channel,
 ChanServ will ban that user from the channel, then kick the user.
  
-Syntax: %BAKICK%B %U#channel%U %BADD%B %Unick/mask%U %Ureason%U
-        %BAKICK%B %U#channel%U %BDEL%B %Unick/mask/list%U
+Syntax: %BAKICK%B %U#channel%U %BADD%B <%Unick/mask%U> [%Ureason%U]
+        %BAKICK%B %U#channel%U %BDEL%B <%Unick/mask/list%U>
         %BAKICK%B %U#channel%U %BLIST%B
  
 The %BAKICK ADD%B command adds the given nick or hostmask to
@@ -21,4 +21,9 @@ be removed manually.
 The %BAKICK LIST%B command displays the AutoKick list.
 #or
 #optionally only those AutoKick entries which match the given
-#mask. 
+#mask.
+
+The reason is used when kicking and is visible in AKICK LIST. If
+the reason contains a '|' character everything after it does not
+appear in bans placed by an AutoKick; but does appear in AKICK
+LIST.
index 14d71c5045eecf4f45c165d539b73a41b4026f14..d21e116a341b7faf93cd74e44000109465f3f000 100644 (file)
@@ -2,8 +2,8 @@
 Users on this list are given op status upon joining
 the channel.
 
-Syntax: %BAOP%B %U#channel%U %BADD%B %Unick%U
-        %BAOP%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BAOP%B %U#channel%U %BADD%B <%Unick%U>
+        %BAOP%B %U#channel%U %BDEL%B <%Unick%U>
         %BAOP%B %U#channel%U %BLIST%B [%Umask%U]
         %BAOP%B %U#channel%U %BWIPE%B
 
index 6d67762e42b18dbd25e02709f9746955e8c8776f..8cb6d30b89a29d43818f090270af3f7d514a15e1 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ AUTH%B allows channel SOps to delete old/stale
 entries from the pending access list.
 
-Syntax: %BAUTH %U<#channel>%U <LIST|DELETE> %U[number|name]%U%B
+Syntax: %BAUTH%B %U#channel%U <%BLIST|DELETE%B> [%Unumber|name%U]
 
 Examples:
 /msg ChanServ AUTH #SurrealChat LIST
index 292f677bbb71d9839d815079aa783b6439a81edd..be45d075cfdc8852032ee2ea3e2b1c00679cdffd 100644 (file)
@@ -1,6 +1,6 @@
 %BChanServ BAN%B Tells ChanServ to set a ban on a person or
 mask. It can also remove bans, if you prefix the ban with a -
 
-Syntax: %BBAN %U#channel%U <nick|mask>%B
+Syntax: %BBAN%B %U#channel%U <%Bnick|mask%B>
 
 By default limited to %BHOP%B
diff --git a/branches/mus-0.4.x-devel/help/chanserv/banlist.txt b/branches/mus-0.4.x-devel/help/chanserv/banlist.txt
new file mode 100644 (file)
index 0000000..45a35ce
--- /dev/null
@@ -0,0 +1,4 @@
+%BChanServ BANLIST%B asks ChanServ for the list of bans in
+a channel.
+
+Syntax: %BBANLIST%B %U#channel%U
index 202dbcc52b98f37d429ce6fa07e15489db1ad206..ac81076a970f4e511714c7c34043886224495b34 100644 (file)
@@ -1,8 +1,8 @@
 %BChanServ CF%B maintains the cofounder list for a channel.
 Users on this list are allowed to do anything the founder can do.
 
-Syntax: %BCF%B %U#channel%U %BADD%B %Unick%U
-        %BCF%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BCF%B %U#channel%U %BADD%B <%Unick%U>
+        %BCF%B %U#channel%U %BDEL%B <%Unick%U>
         %BCF%B %U#channel%U %BLIST%B [%Umask%U]
         %BCF%B %U#channel%U %BWIPE%B
 
index 730cf3b04978ea0e457c9a19dbb5a0b5fccb4a79..4cb1a4e815ecfd8dfa27038ab0dac9190d17256a 100644 (file)
@@ -7,6 +7,6 @@ The founder is unchanged, and the oper who closes the
 channel becomes successor, in case the founder nick is
 dropped.
 
-Syntax: %BCLOSE %U#channel%U Reason%B
+Syntax: %BCLOSE%B %U#channel%U <%Ureason%U>
 
 Requires SERVOP.
index 8528306125e3fc1c3fb446db242dbf830201c57c..5c9250bc24078f9534e390f79279645a6f1aa3ea 100644 (file)
@@ -18,8 +18,7 @@ Available properties are:
   May only copy one particular xOp/rank list.
 * LEVELS
 
-Syntax:
-  %BCOPY #chan1 [%Utype [rank]%U] #chan2%B
+Syntax: %BCOPY%B %U#chan1%U [%Utype [rank]%U] %U#chan2%%U
 
 Examples:
   COPY #chan1 #chan2
index 407f31da6f6dede180e7c59ee6c4a714303fa560..75dcb131460f597818af21702112eec5090d6dc6 100644 (file)
@@ -3,6 +3,6 @@ using kick it uses G:line or GZ:line.
 
 For more information, see ChanServ CLOSE.
 
-Syntax: %BDRONE %U#channel%U Reason%B
+Syntax: %BDRONE%B %U#channel%U <%Ureason%U>
 
 Requires SERVOP.
index 8de0ce92b443acc503c5be8ee6b7087a3ab5a92c..a7b9ab1c4736c39506c85d7148c3d452806c72ac 100644 (file)
@@ -1,6 +1,5 @@
 %BChanServ HALFOP%B allows you to set halfop mode on either
 yourself or on other people in a channel.
 
-Syntax:
-        HALFOP <channel> [nick [nick ...]]
-        HALFOP <channel> [channel [channel]]
+Syntax: %BHALFOP%B %U#channel%U [%Unick%U [%Unick%U ...]]
+        %BHALFOP%B %U#channel%U [%Uchannel%U [%Uchannel%U]]
index eb3c811defa84ab90c4543ce48f9a44c3b23860b..df8cfe18022b0311341a94165cbf96f1941e3aaa 100644 (file)
@@ -2,8 +2,8 @@
 Users on this list are given half-op status upon joining
 the channel.
 
-Syntax: %BHOP%B %U#channel%U %BADD%B %Unick%U
-        %BHOP%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BHOP%B %U#channel%U %BADD%B <%Unick%U>
+        %BHOP%B %U#channel%U %BDEL%B <%Unick%U>
         %BHOP%B %U#channel%U %BLIST%B [%Umask%U]
         %BHOP%B %U#channel%U %BWIPE%B
 
diff --git a/branches/mus-0.4.x-devel/help/chanserv/join.txt b/branches/mus-0.4.x-devel/help/chanserv/join.txt
new file mode 100644 (file)
index 0000000..b00e253
--- /dev/null
@@ -0,0 +1,9 @@
+%BChanServ JOIN%B requests ChanServ to join you to the
+channel. If you have channel access, the bot will also give
+you an invite, to bypass any bans or other restrictive
+channel modes.
+
+If you are not allowed to join the channel, you will not
+join.
+
+Syntax: %BJOIN%B %U#channel%U [%U#channel%U [%U#channel%U]]
index 39720affd5c74d9563e80ca95081caa332dc5ac9..46352ed29dabcee09ac4f0f1549f2fdd3a520926 100644 (file)
@@ -1,3 +1,3 @@
 %BChanServ KICK%B allows you to kick a user from a channel.
 
-Syntax: %BKICK%B %U#channel%U %Unick%U [%Ureason%U]
+Syntax: %BKICK%B %U#channel%U <%Unick%U> [%Ureason%U]
index 6044243f30b8e4c7fa39cf49c9696a2cc085ddb1..247f4c0292ce673c993d86fbf55db32770363dbb 100644 (file)
@@ -1,4 +1,4 @@
 %BChanServ KICKBAN%B allows you to kick and ban a user from
 a channel.
 
-Syntax: %BKICKBAN%B %U#channel%U %Unick%U [%Ureason%U]
+Syntax: %BKICKBAN%B %U#channel%U <%Unick%U> [%Ureason%U]
index a4671f4ab54a341bc7466114367506ef80df38bf..fb9b3d85c94ef54cefd76170b7b93545fca46964 100644 (file)
@@ -2,4 +2,4 @@
 hostmask from the channel. It does not affect users with
 channel access.
 
-Syntax: %BKICKBANMASK%B %U#channel%U %Umask%U [%Ureason%U]
+Syntax: %BKICKBANMASK%B %U#channel%U <%Umask%U> [%Ureason%U]
index 72a1b33b29bf3eaac0d124a2a79663aae7082ee5..55b2336a2d266b0e73ae604e858cbcd65acb271f 100644 (file)
@@ -2,4 +2,4 @@
 hostmask from the channel. It does not affect users with
 channel access.
 
-Syntax: %BKICKMASK%B %U#channel%U %Umask%U [%Ureason%U]
+Syntax: %BKICKMASK%B %U#channel%U <%Umask%U> [%Ureason%U]
index ff281dc5872c1d9ca9870fab7490c67aea8113cc..c19950f0cdce80839d4ed4de5ec511e1799297f5 100644 (file)
@@ -1,8 +1,8 @@
 %BChanServ LEVELS%B allows you to adjust the minimum access levels
 required to do certain commands.
 
-Syntax: %BLEVELS%B %U#channel%U %BSET%B %Ucommand%U %Ulevel%U
-        %BLEVELS%B %U#channel%U %BRESET%B %Ucommand%U
+Syntax: %BLEVELS%B %U#channel%U %BSET%B <%Ucommand%U> <%Ulevel%U>
+        %BLEVELS%B %U#channel%U %BRESET%B <%Ucommand%U>
         %BLEVELS%B %U#channel%U %BLIST%B
         %BLEVELS%B %U#channel%U %BCLEAR%B
 
index 39abe77753dcc423e7687c6169397496d6d0cb0f..92960ce3a5592dae0672d25089c00572564b7a82 100644 (file)
@@ -2,4 +2,4 @@
 (Channels with the PRIVATE option set are not listed, of
 course.)
 
-Syntax: %BLIST %Umask%U%B
+Syntax: %BLIST%B <%Umask%U>
index c015bca83d3bd33430b0aafc690f08af4b7d4dcb..3187f6a2dc83114f9020d1b07b3a8162fd0f119a 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ MLOCK%B allows you to lock channel modes either
 on or off.
 
-Syntax: %BMLOCK%B %U#channel%U %B<SET|ADD|DEL>%B %Umodes%U
+Syntax: %BMLOCK%B %U#channel%U <%BADD|DEL|SET|RESET%B> %Umodes%U
 
 The %Umodes%U parameter is constructed exactly the same way as
 a %B/MODE%B command; that is, modes preceded by a %B+%B are locked
@@ -20,12 +20,17 @@ Examples:
     %BMLOCK%B %U#channel%U %BDEL%B %UQ%U
        Removes Q from your mlock, it may be +Q or -Q.
 
+    %BMLOCK%B %U#channel%U %BRESET%B
+       Resets the mode lock to default.
+
     %BMLOCK%B %U#channel%U %BSET%B %U+nt-iklps%U
+       DON'T USE %USET%U. USE %UADD%U OR %UDEL%U.
        Forces modes n and t on, and modes i, k, l, p, and
        s off.  Mode m (and others) are left free to be either
        on or off.
 
     %BMLOCK%B %U#channel%U %BSET%B %U+knst-ilmp%U %Umy-key%U
+       DON'T USE %USET%U. USE %UADD%U OR %UDEL%U.
        Forces modes k, n, s, and t on, and modes i, l, m,
        and p off.  Also forces the channel key to be
        "my-key".
index ce1034af12393436948c22cdb38f8166bdb8fdb6..a4b7db78112cdc5c55efe0d1d7910469eb93873f 100644 (file)
@@ -2,7 +2,7 @@
 command does, but allows you to do so if you're not currently
 opped in the channel.
 
-Syntax: %BMODE%B %U#channel%U %U+modes-modes%U %U%B[%Bparams%B]%B%U
+Syntax: %BMODE%B %U#channel%U <%U+modes-modes%U> [%Uparams%U]
 
 Example: MODE #Support +tn
          MODE #Support +ootn hAtbLaDe XYZ
index 3ed11bbe300925978fd283a7962b92faae91e03a..57f750fd7a2d40aa397738539d90f9eb50c83cb6 100644 (file)
@@ -1,6 +1,5 @@
 %BChanServ OP%B allows you to set channel op mode on either
 yourself or on other people in a channel.
 
-Syntax:
-        OP <channel> [nick [nick ...]]
-        OP <channel> [channel [channel]]
+Syntax: %BOP%B %U#channel%U [%Unick%U [%Unick%U ...]]
+        %BOP%B %U#channel%U [%Uchannel%U [%Uchannel%U]]
index b30d5771462fa33ed4354a516d64d8ee82f09ae7..79f7f16162ba7ff86879e1357cb56b842ace9f52 100644 (file)
@@ -8,4 +8,4 @@ voice nor ops.
 This is actually implemented via CS CLEAR <#chan> OPS, and a
 CS UP #chan <all users>
 
-Syntax: %BRESYNC%B %U#channel [#channel [#channel]]%U
+Syntax: %BRESYNC%B %U#channel%U [%U#channel%U [%U#channel%U]]
index bdb6516faaa21f033ec81e806af1c9522df06345..4a31ed645eb3165620dc43aa4acd11f1a9ba139a 100644 (file)
@@ -16,7 +16,9 @@ Settings:
                      Don't use it.
   AUTOVOICE      Voices everyone who joins the channel.
   BANTYPE        Selects the ban-type to be used for KICKBAN and AKICKs
-
+  TOPICLOCK      Restricts who can change the topic in the channel.
+  NOCLONES       Bans people who bring clones into the channel.
+  BANTIME               Time until bans are automatically removed.
 Oper only flags:
   HOLD         Prevent channel from expiring
   FREEZE       Suspend access in this channel
diff --git a/branches/mus-0.4.x-devel/help/chanserv/set/bantime.txt b/branches/mus-0.4.x-devel/help/chanserv/set/bantime.txt
new file mode 100644 (file)
index 0000000..8f35fff
--- /dev/null
@@ -0,0 +1,6 @@
+%BChanServ SET BANTIME%B sets the default ban time for /cs (kick)ban and /cs tempban.
+Default is 0 (permanent unless manually removed)
+Examples:
+/cs set #pokemonlake bantime +24h
+/cs set #pokemondeluge bantime +12h
+Syntax: %BSET%B %U#channel%U %BBANTIME%B +<%Utime%U>
index 3b2feb1e6c2b9801540e5669279164e22cfb4f76..4b0a06f71fdb80aa742ec5b7aac311d0ec6c0e9c 100644 (file)
@@ -12,3 +12,6 @@ Default is 2.
   7 - nick!*@host.domain
   8 - nick!*user@*.domain
   9 - nick!*@*.domain
+  10 - cross btwn 2 and 3, depending on if is a java-abcd1 ident or not
+
+Syntax: %BSET%B %U#channel%U %BBANTYPE%B <%Unumber%U>
index 8421fde63978ba17590086522358cbbf74c34b0d..f70f9a0d8afb47e17bb3e7544d93833a600065cf 100644 (file)
@@ -1,4 +1,4 @@
 %BChanServ SET DESC%B sets the description for the channel,
 which can be seen with the %BChanServ INFO%B command.
  
-Syntax: %BSET%B %U#channel%U %BDESC%B %Udescription%U
+Syntax: %BSET%B %U#channel%U %BDESC%B <%Udescription%U>
index 7d43ca28dbf2b14718347fec1339f279bbbeeb75..2241ef223329515fb01e260f57ead6c4e67300cf 100644 (file)
@@ -2,7 +2,7 @@
 You must be the current founder of the channel, and the new
 founder must have a registered nick.
 
-Syntax: %BSET%B %U#channel%U %BFOUNDER%B %Unick%U
+Syntax: %BSET%B %U#channel%U %BFOUNDER%B <%Unick%U>
 
 Once you have used this command, you will be automatically
 moved to the channel's co-founder list; you may then remove
diff --git a/branches/mus-0.4.x-devel/help/chanserv/set/freeze.txt b/branches/mus-0.4.x-devel/help/chanserv/set/freeze.txt
new file mode 100644 (file)
index 0000000..32b41be
--- /dev/null
@@ -0,0 +1,7 @@
+%BChanServ SET FREEZE%B sets whether the given channel
+will be suspended, preventing users in that channel
+from being granted their access.
+
+Syntax: %BSET%B %U#channel%U %BFREEZE%B <%UON/OFF%U>
+
+Requires SERVOP.
index 660fb3dd397432abeecfafbfdc16e1fd572d5955..c67c913f1ac704cd03201972b30376fb3cc3c885 100644 (file)
@@ -1,5 +1,7 @@
-%BChanServ SET HOLD%B Sets whether the given channel will
+%BChanServ SET HOLD%B sets whether the given channel will
 expire.  Setting this to ON prevents the channel from
 expiring.
 
-Syntax: ChanServ SET #channel HOLD <ON/OFF>
+Syntax: %BSET%B %U#channel%U %BHOLD%B <%UON/OFF%U>
+
+Requires SERVOP.
index 173b3f0f166d766049182d05946049c9570dc736..49a0c4cd6076105dc3c41ceddfdcd0414138c925 100644 (file)
@@ -2,4 +2,4 @@
 in the channel. Users may still use the UP command to gain
 chanop.
  
-Syntax: %BSET #chan NEVEROP %U{ON|OFF}%U%B
+Syntax: %BSET%B %U#channel%U %BNEVEROP%B <%UON/OFF%U>
diff --git a/branches/mus-0.4.x-devel/help/chanserv/set/noclones.txt b/branches/mus-0.4.x-devel/help/chanserv/set/noclones.txt
new file mode 100644 (file)
index 0000000..0a75743
--- /dev/null
@@ -0,0 +1,4 @@
+%BChanServ SET NOCLONES%B Kicks and bans users who bring clones (multiple connections) to a channel.
+Users in the channel's access lists are excempt.
+Syntax: %BSET%B %U#channel%U %BNOCLONES%B <%UON/OFF%U>
index 6d4b9f2cf11df69c1e0d612b480d80e0b145a6b0..7389500761ce4c8adf3f2c694f50be64610c4156 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ SET OPGUARD%B makes ChanServ strictly
 control channel status.
 
-Syntax: %BSET%B %U#channel%U %BOPGUARD%B %UON/OFF%U
+Syntax: %BSET%B %U#channel%U %BOPGUARD%B <%UON/OFF%U>
   
 When OpGuard is set to ON, only people with the
 appropriate permission will be allowed to grant
index d3200d153ad9d169a0bb31ac77b600a6150c192a..6ea6d449d2b3f15eac2c47829633994ce3d84a1b 100644 (file)
@@ -1,6 +1,6 @@
 %BChanServ SET PASSWORD%B changes the password of a channel.
  
-Syntax: %BSET%B %U#channel%U %BPASSWORD%B %Upassword%U
+Syntax: %BSET%B %U#channel%U %BPASSWORD%B <%Upassword%U>
 
 Note: Channel passwords cannot be used; this command
 exists only for completeness.
index e1539992a09a7bc5a43be6f4bdbef0fc922c51f9..b729a6abf047f9de9deb0b83c54c54aa902888bc 100644 (file)
@@ -4,4 +4,4 @@ avoid mass-deops in a channel where not everyone is
 identified to NickServ, but may make channel takeovers
 easier.
 
-Syntax: SET <channel> SPLITOPS {ON|OFF}
+Syntax: %BSET%B %U#channel%U %BSPLITOPS%B <%UON/OFF%U>
index cd58df98bfb93da960d566c790c34f69527c3e5f..26ef93390d0d0fb8ee4fdf50f12c50e2d9192b42 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ SET SUCCESSOR%B changes the successor of a channel.
 The new successor must have a registered nick.
 
-Syntax: %BSET%B %U#channel%U %BSUCCESSOR%B %Unick%U
+Syntax: %BSET%B %U#channel%U %BSUCCESSOR%B <%Unick%U>
 
 The channel successor will be made founder in case the original
 founder's nick is expired or dropped.  A channel with no
diff --git a/branches/mus-0.4.x-devel/help/chanserv/set/topiclock.txt b/branches/mus-0.4.x-devel/help/chanserv/set/topiclock.txt
new file mode 100644 (file)
index 0000000..f73b365
--- /dev/null
@@ -0,0 +1,5 @@
+Syntax: SET #CHANNEL TOPICLOCK <UOP|VOP|AOP|SOP|CFOUNDER|FOUNDER|OFF>
+
+Enables or disables the TOPICLOCK option for a channel.
+When TOPICLOCK is set, ChanServ will not allow the
+channel topic to be changed unless access permits.
index d0f62689e6547278d96838722454dd85837b53e9..a3bcd1f17e9ceec7038ef4f0b65193f67f95a3d5 100644 (file)
@@ -5,3 +5,5 @@ ChanServ and BotServ commands.
   AKick                     - Add/Delete/Move/Wipe
   VOp/HOp/AOp/SOp/CoFounder - Add/Delete/Move/Wipe
   BotSay                    - Usage
+
+Syntax: %BSET% %U#channel%U %BVERBOSE%B <%UON/OFF%U>
index ca14f2058f6a89be88a9f264f76b1ce50d54aa09..bc73708c4ba88fa02b857404bdccc999d7384229 100644 (file)
@@ -5,4 +5,4 @@ than to the user as a private NOTICE.
 This flag probably doesn't do what you think it does.
 Don't use it.
 
-Syntax: %BSET #chan WelcomeInChan %U{ON|OFF}%U%B
+Syntax: %BSET%B %U#channel%U %BWelcomeInChan%B <%UON/OFF%U>
index 747ffc84990d4f64aab4e42c06cab9bc150c83c9..4af6d3037648821189c1210a929758687b0d4a7a 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ SOP%B maintains the super-op list for a channel.
 
-Syntax: %BSOP%B %U#channel%U %BADD%B %Unick%U
-        %BSOP%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BSOP%B %U#channel%U %BADD%B <%Unick%U>
+        %BSOP%B %U#channel%U %BDEL%B <%Unick%U>
         %BSOP%B %U#channel%U %BLIST%B [%Umask%U]
         %BSOP%B %U#channel%U %BWIPE%B
 
diff --git a/branches/mus-0.4.x-devel/help/chanserv/tempban.txt b/branches/mus-0.4.x-devel/help/chanserv/tempban.txt
new file mode 100644 (file)
index 0000000..ffe5cac
--- /dev/null
@@ -0,0 +1,9 @@
+%BChanServ TEMPBAN%B Tells ChanServ to set a ban on a person or
+mask for a set amount of time. The ban will be removed automatically after that time has elapsed.
+
+Syntax: %BTEMPBAN%B %U#channel%U %B<nick|mask>%B <%B+TIME%B>
+
+Examples: TEMPBAN #pokemoncrater erry +1d Enough already!
+TEMPBAN #pokemonlake mario121 Marcus +7d
+
+By default limited to %BHOP%B
diff --git a/branches/mus-0.4.x-devel/help/chanserv/topic.txt b/branches/mus-0.4.x-devel/help/chanserv/topic.txt
new file mode 100644 (file)
index 0000000..6adee5c
--- /dev/null
@@ -0,0 +1,4 @@
+%BChanServ TOPIC%B sets, or unsets, the current topic of the channel.
+To unset the topic, use NONE as the message.
+
+Syntax: %BTOPIC%B %U#channel%U <message|NONE>
diff --git a/branches/mus-0.4.x-devel/help/chanserv/topicappend.txt b/branches/mus-0.4.x-devel/help/chanserv/topicappend.txt
new file mode 100644 (file)
index 0000000..5d4c203
--- /dev/null
@@ -0,0 +1,5 @@
+%BChanServ TOPICAPPEND%B appends a phrase to the current topic, or sets the topic if no topic is set yet.
+
+Syntax: %BTOPICAPPEND%B %U#channel%U <message>
+Examples: /cs TOPICAPPEND #erry COOKIES!
+-!- Eustace has changed the topic of #erry to: SLINKIES | COOKIES!
diff --git a/branches/mus-0.4.x-devel/help/chanserv/topicprepend.txt b/branches/mus-0.4.x-devel/help/chanserv/topicprepend.txt
new file mode 100644 (file)
index 0000000..a8bb0c1
--- /dev/null
@@ -0,0 +1,5 @@
+%BChanServ TOPICAPPEND%B prepends a phrase to the current topic, or sets the topic if no topic is set yet.
+
+Syntax: %BTOPICPREPEND%B %U#channel%U <message>
+Examples: /cs TOPICPREPEND #erry COLORSS!!!
+-!- Eustace has changed the topic of #erry to: COLORSS!!! | SLINKIES | COOKIES!
index 87e9656408b063577799e8d715b8764cafb6321e..2d64935b184c17d5df422a4b8e626e133ef1e711 100644 (file)
@@ -2,7 +2,7 @@
 preventing you or another person from entering the given
 channel, or remove particular ban-masks.
 
-Syntax: %BUNBAN %U#channel%U [nick|mask]%B
+Syntax: %BUNBAN%B %U#channel%U [%Unick|mask%U]
 
 By default limited to %BAOP%B
 
index 0e2a08647b8206d6f4a2becebd906fc1acda20a6..850d89b442f88efe1b82977e040bb948283a8bc0 100644 (file)
@@ -1,7 +1,7 @@
 %BChanServ UOP%B maintains the user list for a channel.
 
-Syntax: %BUOP%B %U#channel%U %BADD%B %Unick%U
-        %BUOP%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BUOP%B %U#channel%U %BADD%B <%Unick%U>
+        %BUOP%B %U#channel%U %BDEL%B <%Unick%U>
         %BUOP%B %U#channel%U %BLIST%B [%Umask%U]
         %BUOP%B %U#channel%U %BWIPE%B
 
index 976ac7e1b245b0bf05db6ff3e95f949d36d176cc..f512a2896ba5d00264aefc91e2594a7f4797909d 100644 (file)
@@ -1,6 +1,5 @@
 %BChanServ VOICE%B allows you to set channel-voice mode on either
 yourself or on other people in a channel.
 
-Syntax:
-        VOICE <channel> [nick [nick ...]]
-        VOICE <channel> [channel [channel]]
+Syntax: %BVOICE%B %U#channel%U [%Unick%U [%Unick%U ...]]
+        %BVOICE%B %U#channel%U [%Uchannel%U [%Uchannel%U]]
index 9a06d87792959d722b6f9bdb6a593bd28138b449..06266b90725a574376165116f13045336f6fd8f7 100644 (file)
@@ -2,8 +2,8 @@
 Users on this list are given voice status upon joining
 the channel.
 
-Syntax: %BVOP%B %U#channel%U %BADD%B %Unick%U
-        %BVOP%B %U#channel%U %BDEL%B %Unick%U
+Syntax: %BVOP%B %U#channel%U %BADD%B <%Unick%U>
+        %BVOP%B %U#channel%U %BDEL%B <%Unick%U>
         %BVOP%B %U#channel%U %BLIST%B [%Umask%U]
         %BVOP%B %U#channel%U %BWIPE%B
 
index be076346395970527c0ff735ccd7c87f45d14e31..0cd9fc6237a2d8d836c8db507438cf71ce5e389a 100644 (file)
@@ -2,8 +2,8 @@
 list.  The contents of this list will be sent to every user who
 joins your channel.
 
-Syntax: %BWELCOME%B %U#channel%U %BADD%B %Umessage%U
-        %BWELCOME%B %U#channel%U %BDEL%B %Unumber%U
+Syntax: %BWELCOME%B %U#channel%U %BADD%B <%Umessage%U>
+        %BWELCOME%B %U#channel%U %BDEL%B <%Unumber%U>
         %BWELCOME%B %U#channel%U %BLIST%B
 
 The %BWELCOME ADD%B command adds a message to the welcome list.
index 59477749c9dd715274990c7070e0aaab7817c729..0e46642d403a455a3c57e3b76ccdbe684f223f1a 100644 (file)
@@ -1,3 +1,3 @@
 %BChanServ WHY%B tells what status a user has in a channel.
 
-Syntax: %BWHY%B %U#channel%U %Unick%U
+Syntax: %BWHY%B %U#channel%U [%Unick%U [%Unick%U ...]]
index f7d8c7f86e93dcef916a1d0c68f2e09a7b878513..a0c01225cd78e37e5efee496dbd2af7b2d918645 100644 (file)
@@ -3,4 +3,4 @@ of the given hostmask.  If your IRCD supports vIdents, then
 using SET <nick> <ident>@<hostmask> set idents for users as well
 as vhosts.
  
-Syntax: %BSET%B %Unick%U %Uvhost%U
+Syntax: %BSET%B %Unick%U <%Uvhost%U>
index f5e231389233b0549ff9ceb6cd908c5dc2c4fc56..c3cc7a8400b40c07be7947b8c524cc99c733a988 100644 (file)
@@ -1,6 +1,6 @@
 %BMemoServ CSEND%B allows you to send a channel memo
 to users of a certain rank or higher.
 
-Syntax: %BCSEND%B %U#channel%U %Urank%U %Umessage%U
+Syntax: %BCSEND%B %U#channel%U <%Urank%U> <%Umessage%U>
 
 Valid ranks include: UOP, VOP, HOP, AOP, SOP, CF, FOUNDER
index 01d63f4ad72bd91930ea26278f294bb394cc2c59..41fc78e9261fa5a27b3cd12ea7f44f30876cddc6 100644 (file)
@@ -1,6 +1,6 @@
 %BMemoServ DEL%B deletes a memo, a series of memos, or all memos.
 
-Syntax: %BDEL%B %Unumber%U
-        %BDEL%B %Unumber1-number2%U
-        %BDEL%B %Unumber1..number2%U
+Syntax: %BDEL%B <%Unumber%U>
+        %BDEL%B <%Unumber1-number2%U>
+        %BDEL%B <%Unumber1..number2%U>
         %BDEL ALL%B
index c70a40df62d0b818a91bad9fae56ce9a56117da0..cf6facd8dacfda3b7c737c70291a4ef64a11c0fa 100644 (file)
@@ -5,7 +5,7 @@ nickname on your ignore list tries to send you a memo, the
 ignored person will be notified that they are on your ignore
 list and you do not wish to receive any memos from them.
 
-%BSyntax: IGNORE [ADD|DEL|LIST] [<nick>]%B
+Syntax: %BIGNORE%B <%UADD|DEL|LIST%U> [<%Unick%U>]
 
 This is a good way to avoid memos from people who spam you.
 They can get around an ignore by registering a different
index e215c0e8ea02982f821a281c39ed2b7cd3f7c70d..f829fe62c006d6808648a65bf7d00fdd34f6bef2 100644 (file)
@@ -1,4 +1,7 @@
-Syntax: %BREAD {%Bnum%B|%BLAST%B}%B
+%BMemoServ READ%B displays a specified memo that you have
+received.
+
+Syntax: %BREAD <%Unum|LAST%U>
 
 Sends you the text of memo number %Bnum%B, or of the last
 (i.e. most recently received) memo if LAST is given
index 34ea85d13ee2e2f2a7b1b47bfe3c27f81f96a12b..c2e343c142a8bb9d6523cfa5a194885709241ba1 100644 (file)
@@ -1,3 +1,3 @@
 %BMemoServ SEND%B allows you to send a memo to a nick or channel.
 
-Syntax: %BSEND%B %Unick/#chan%U %Umessage%U
+Syntax: %BSEND%B <%Unick/#chan%U> <%Umessage%U>
index a4c61a946208beec89ddeb43e9f7dc7d7e7861d8..57d4b1db7f940ef99b36c92f300b923428cbcd18 100644 (file)
@@ -12,15 +12,15 @@ Commands:
   RELEASE   Releases your nick from services custody
   INFO      Get information about a nick
   DROPGROUP Delete a registered nickname and all aliases
+
   LINK      Make an alias of your nick
   UNLINK    Remove an alias
   DROP      Same as UNLINK
   CHGROOT   Change your root nick
 
 Additional Commands:
-  GLIST  ALIST  WATCH  SILENCE  ACC  SEEN  
-  LOGOUT  LIST   AUTH  AUTHCODE SENDPASS  PROFILE
+  GLIST  ALIST  WATCH  SILENCE  ACC  SEEN  AJOIN   LISTEMAIL
+  LOGOUT  LIST   AUTH  AUTHCODE SENDPASS   PROFILE
 
 %BNOTICE:%B This service is intended to provide a way for IRC users to
 ensure their identity is not compromised. It is NOT intended to
index 62529d03affd71dbfda8364f72bd1be8724610ed..6a9bd7dbd9badabb54130f67f32ada1868244bec 100644 (file)
@@ -13,5 +13,5 @@ response has this format:
   4 - Identified via access list
   5 - Forbidden Nickname
 
-Syntax: %BACC%B <%Bnickname%B>
+Syntax: %BACC%B %Unickname%U
 
diff --git a/branches/mus-0.4.x-devel/help/nickserv/ajoin.txt b/branches/mus-0.4.x-devel/help/nickserv/ajoin.txt
new file mode 100644 (file)
index 0000000..2960f93
--- /dev/null
@@ -0,0 +1,30 @@
+Syntax: %BAJOIN%B [%Unick%U] %BADD%B <%Uchannel%U>
+        %BAJOIN%B [%Unick%U] %BDEL%B <%Uchannel|entry-nr|list%U>
+        %BAJOIN%B [%Unick%U] %BLIST%B [%Umask|list%U]
+        %BAJOIN%B [%Unick%U] %BJOIN%B
+        %BAJOIN%B [%Unick%U] %BCLEAR%B
+        %BAJOIN%B [%Unick%U] %BWIPE%B
+
+Maintains the %BAutoJoin list%B for nick group.
+If a user identifies to his nickname, he will
+automatically join the listed channels.
+
+The %BAJOIN ADD%B command adds the given channel
+to the AutoJoin list.
+
+The %BAJOIN DEL%B command removes the given channel
+from the AutoJoin list. If a list of entry numbers is given,
+those entries are deleted.
+
+The %BAJOIN LIST%B command displays the AutoJoin list.
+If a wildcard mask is given, only those entries matching the
+mask are displayed. If a list of entry numbers is given, only
+those entries are shown.
+
+The %BAJOIN JOIN%B attempts to join you to all of the channels
+in your list.
+NOTE: %BAJOIN%B does not [attempt to] bypass bans, chmode +i,
+or any other such thing.
+
+The %BAJOIN WIPE%B command clears all entries on the
+AutoJoin list.
index 4d457364b0d1f7533f0753c7d55280fdf4f40e8f..d563038c9f2da19fb1c002552cd93e978f33017c 100644 (file)
@@ -8,7 +8,7 @@ DECLINE - Decline the authorization request and memo the person
 REJECT  - Same as decline
 LIST    - List auth requests.
 
-Syntax: AUTH [nick] <LIST|ACCEPT|DECLINE> [num|chan]
+Syntax: AUTH [%Unick%U] <%ULIST|ACCEPT|DECLINE%U> [%Unum|chan%U]
 
 If you do not want to be added to that channel list, use decline or reject.
 Demotions are handled by deleting the target's access, so they may accept
index a4e65efbae71c5bbe6b91cf4f7447d250416fba7..759dd8121b400c72078962d94d17cb3f214aa9f7 100644 (file)
@@ -4,4 +4,4 @@ is enabled.
 
 More information on its use should be in the email you receive.
 
-Syntax: %BAUTHCODE%B %Unickname%U %Ucode%U [%Unewpassword%U]
+Syntax: %BAUTHCODE%B %Unickname%U <%Ucode%U> [%Unewpassword%U]
index 6594c0eac3152d5cbd305aa5597fda5312920c57..606ce4dc27624aca789cc9c9e3914f3afb34ca22 100644 (file)
@@ -1,4 +1,4 @@
 %BNickServ DROP%B allows you to relinquish a previously registered
 nickname.
  
-Syntax: %BDROP%B %Unick%U %Upassword%U
+Syntax: %BDROP%B %Unick%U <%Upassword%U>
index d2f4eb9fc9134c7f161e9d253bcf2785472f2af0..7e3033e139a6c439d8f017a4db863ad72dd5b9d2 100644 (file)
@@ -1,4 +1,4 @@
 %BNickServ DROPGROUP%B allows you to delete a whole group of
 nicks/aliases
  
-Syntax: %BDROPGROUP%B %Unick%U %Upassword%U
+Syntax: %BDROPGROUP%B %Unick%U <%Upassword%U>
index b59d108e9593134241c6c185512556f5576618e8..ef0787f22ab28f5e6aad0a761d3acb8580ac5a01 100644 (file)
@@ -3,4 +3,4 @@ verification is enabled.
 
 More information on its use should be in the email you receive.
 
-Syntax: %BEMAILREG%B %Unickname%U %Ucode%U
+Syntax: %BEMAILREG%B %Unickname%U <%Ucode%U>
index 7fc5701af77036015a8cf588620d849e56ced8d7..ea472024dfd02e0f58563aafab39626cfb883317 100644 (file)
@@ -4,7 +4,7 @@ connected, but which the IRC server believes is still online.
 Typically, this happens when your Internet connection goes down
 while you're on IRC.
 
-Syntax: %BGHOST%B %Unickname%U [%Upassword%U]
+Syntax: %BGHOST%B %Unick%U [%Upassword%U]
 #
 #In order to use the GHOST command for a nick, your current
 #address as shown in /WHOIS must be on that nick's access
index dc6d4b9ce2c7d1cc6085fa27b98bb9d621278091..32262792c4ab8660e6c163e4a88d27880eb11038 100644 (file)
@@ -3,9 +3,8 @@ difference is GIDENITFY changes your nickname while
 identifying, and will use GHOST if the target nick is
 currently online.
 
-Syntax: GIDENTIFY <nick> <password>
+Syntax: %BGIDENTIFY%B %Unick%U <%Upassword%U>
 
 Examples:
-    IDENTIFY n00b
-    IDENTIFY Soulja soulseeker
-    ID bob thebuilder
+    GIDENTIFY Soulja soulseeker
+    GID bob thebuilder
index 77cda5fcfc1a5c1eacdbf23842a95e2aab814dca..fab8930786e24cc2d49c0cdcea09d2f1df48b93b 100644 (file)
@@ -4,7 +4,7 @@ authenticate yourself with this command before you use them.
 The password should be the same one you sent with the
 %BREGISTER%B command.
 
-Syntax: %BIDENTIFY%B [%Unick%U] %Upassword%U
+Syntax: %BIDENTIFY%B [%Unick%U] <%Upassword%U>
 
 Examples:
     IDENTIFY n00b
index 37e8effc387c15ed518318e13cfefaac967cd986..10dd9faf7ad282c712535fd27b5f3a7cf9076915 100644 (file)
@@ -2,4 +2,4 @@
 nicknames, such as the nick's owner, last seen address and
 time, and nick options. 
 
-Syntax: %BINFO%B %Unickname%U [%Unickname%U ...]
+Syntax: %BINFO%B %Unick%U [%Unick%U ...]
index 7ebe30461f7bc86137bb5e3b1e3d05ebc7369330..6cda6360a7c7b6ab1f00dee27e9188187aa12465 100644 (file)
@@ -2,7 +2,7 @@
 nickname.  Linked nicknames share everything from access
 lists and settings to memos.
 
-Syntax: %BLINK%B %Unick%U %Upassword%U
+Syntax: %BLINK%B %Unick%U <%Upassword%U>
 
 This command does NOT support linking two groups together.
 
diff --git a/branches/mus-0.4.x-devel/help/nickserv/listemail.txt b/branches/mus-0.4.x-devel/help/nickserv/listemail.txt
new file mode 100644 (file)
index 0000000..0b91556
--- /dev/null
@@ -0,0 +1,4 @@
+%BNickServ LISTEMAIL%B is used by opers to find registered nicks
+whose email addresses match a specified pattern.
+
+Syntax: %BLISTEMAIL%B %Uemail@address.tld%U 
index 197ccd71a38cbbeac3039da8400d19f2c277eb98..53049f6ecf265ddd414c436c831c82c92c14734d 100644 (file)
@@ -1,7 +1,7 @@
 %BNickServ PROFILE%B stores information about you for others
 to read.
 
-Syntax: %BPROFILE READ%B %Unickname%U [%Unickname%U ...]%B
+Syntax: %BPROFILE READ%B <%Unick%U> [%Unick%U ...]
         %BPROFILE%B [%Unick%U] %BSET%B %Uitem%U %Udata%U
         %BPROFILE%B [%Unick%U] %BDEL%B %Uitem%U
         %BPROFILE%B [%Unick%U] %BWIPE%B
index b932407f74efc4444a793a86246df5aaba513092..69db90574810460ee7cb677a18035bbaa7222156 100644 (file)
@@ -5,7 +5,7 @@ NickServ will change the target's nick to a guestnick, and
 jupes the nick for one minute. To use it yourself, use 
 %BNS RELEASE%B, then change your nick, OR use %BNS SIDENTIFY%B
 
-Syntax: %BRECOVER%B %Unickname%U [%Upassword%U]
+Syntax: %BRECOVER%B %Unick%U [%Upassword%U]
 
 This command also identifies you to your nick if you are not
 already. If you are already identified, the password is
index aa4d42b6140cf80d6a85b10e578615d8d80e5146..f071da4b0fe2c7c8ce4e132f95cc1ff85427ef9c 100644 (file)
@@ -1,7 +1,7 @@
 %BNickServ REGISTER%B allows you to reserve a particular nickname for
 your own use and prove your identity using a password.
 
-Syntax: %BREGISTER <password> <e-mail>%B
+Syntax: %BREGISTER%B <%Upassword%U> <%Ue-mail%U>
 
 %BNOTICE:%B The email address is %BNOT%B optional and you are strongly
 discouraged from using a fake address, as this will make it impossible
index 05cd8f271d700b2ed6402ca70758b6605ec5d5e3..fa437f800d54ddad3cfada4b986e66e9d455d937 100644 (file)
@@ -2,4 +2,4 @@
 will hold a nickname that is used without authorization for one
 minute; this command releases it sooner.
 
-Syntax: %BRELEASE%B %Unickname%U [%Upassword%U]
+Syntax: %BRELEASE%B %Unick%U [%Upassword%U]
index 8b238856231b47f9fe6f73f726fc8643b63c3ac8..6b22421ddc9ad48686141f71de08f5d0bf3327ac 100644 (file)
@@ -13,10 +13,11 @@ with your nickname.  The following settings are available:
               without authorization from you.
   VACATION  Extend the time your nick will last before expiring.
   ROOT      Change the root nick for your nickgroup.
+  UMODE     Set user modes to be added or removed upon identifying
 
 Oper only flags:
-  HOLD         Prevent channel from expiring
-  FREEZE       Suspend access in this channel
+  HOLD         Prevent nickname from expiring
+  FREEZE       Suspend access to this nickname
   EMAILREG     If enabled, forces user to revalidate their email address.
                If disabled, force validates their email address.
 
index 39b5b3c11a841bc4f7c2c7e4ea59d150d8c660b3..c6d4d1b5da081d09eb0defe8d488307e8f77b725 100644 (file)
@@ -1,4 +1,4 @@
 NickServ SET AUTH enables selective Channel Rank acceptance.
 See HELP NickServ AUTH for more information.
 
-Syntax: %BSET [nick] AUTH %U{ON|OFF}%U%B
+Syntax: %BSET%B [%Unick%U] %BAUTH%B <%UON|OFF%U>
index 727d143008a88ecfbb717a2c2717b23945ba3fc5..05a7f0fe4e194e3b4114b150915af57de6cf03f4 100644 (file)
@@ -2,10 +2,10 @@
 the nick. This address will be displayed whenever someone
 requests information on the nick with the INFO command.
 
-Syntax: %BSET [nick] EMAIL %Uaddress%U%B
+Syntax: %BSET%B [%Unick%U] %BEMAIL%B <%Uaddress%U>
 
 The %BHIDEMAIL%B command will hide your email from INFO
 requests.
 
 If %BHIDEMAIL%B is given, your email will no longer be hidden
-and will be visible to all in INFO requests. 
\ No newline at end of file
+and will be visible to all in INFO requests. 
index cccb9239bee8f62732e275c4e7583516121d5b29..db5c4a5d154d323321378ef617512ee2e34e2cc5 100644 (file)
@@ -2,6 +2,6 @@
 displayed when joining a channel that you have sufficient
 access to and has GREET enabled.
 
-Syntax: SET GREET <NONE|message>
+Syntax: %BSET%B GREET%B <%UNONE|message%U>
 
 NONE will remove/unset your greet.
index e434ed3e742b6c43e303c16390f3aedb5fbf62e4..714bd996453adb08985f6d117f49b13800e1b95c 100644 (file)
@@ -1,6 +1,6 @@
 %BNickServ SET HIDEMAIL%B hides your email address from users.
 
-Syntax: %BSET [nick] HIDEMAIL%B %UON/OFF%U
+Syntax: %BSET%B [%Unick%U] %BHIDEMAIL%B <%UON|OFF%U>
 
 If %BHIDEMAIL%B is disabled, your email will no longer be
 hidden and will be visible in your NickServ INFO listing.
index 252a56a47efed9f85cded78e2cb9af1813d95704..7737322e412a865e983ebd2a44c9c05fec9d184c 100644 (file)
@@ -1,5 +1,5 @@
-%BNickServ SET HOLD%B Sets whether the given nick will
+%BNickServ SET HOLD%B sets whether the given nick will
 expire.  Setting this to ON prevents the nick from
 expiring.
 
-Syntax: NickServ SET [nick] HOLD <ON/OFF>
+Syntax: %BSET%B [%Unick%U] %BHOLD%B <%UON/OFF%U>
index b780120ab1f2d936b5122544a14e9c6c8cef1c84..f96bd7b2aeaec67ac6c9e775d50c15814e2f160b 100644 (file)
@@ -2,4 +2,4 @@
 channel status upon channels. You may still use the UP command
 to gain your status.
  
-Syntax: %BSET [nick] NEVEROP %U{ON|OFF}%U%B
+Syntax: %BSET%B [%Unick%U] %BNEVEROP%B <%UON|OFF%U>
index e4d75114e13887db0156f243b335c4b4c34ec878..990f809ea6e1be9c262c7e1054eecceb40653899 100644 (file)
@@ -1,4 +1,4 @@
 %BNickServ SET NOACC%B Prevents other people from adding
 you to channel access lists.
  
-Syntax: %BSET [nick] NOACC %U{ON|OFF}%U%B
+Syntax: %BSET%B [%Unick%U] %BNOACC%B <%UON|OFF%U>
index 146e15239d3c181c903a1ee737c0f98ab84d93e1..ad5460e20739444b2b105c6551a4ada2340df4b4 100644 (file)
@@ -2,4 +2,4 @@
 nick. This does not prevent you from sending memos, it will
 only block receieving memos to you from others.
 
-Syntax: %BSET [nick] NOMEMO %U{ON|OFF}%U%B
+Syntax: %BSET%B [%Unick%U] %BNOMEMO%B <%UON|OFF%U>
index 0ec8eb47f50ee2cb086e2313e22848c4e511e18d..46896a34147bccbdb8696128fd1f4db0b5fe6ff9 100644 (file)
@@ -1,4 +1,4 @@
 %BNickServ SET PASSWD%B Changes the password used to
 identify you as the nick's owner
 
-Syntax: %BSET [nick] PASSWD%B %Upassword%U
+Syntax: %BSET%B [%Unick%U] %BPASSWD%B %Upassword%U
index 79d6bb13ec8a5e51c6d9b928451760c957e2f598..4145f06e2d771dcbca0e3c94aadf4a94dddcc743 100644 (file)
@@ -1,7 +1,7 @@
 %BNickServ SET PROTECT%B allows you to control the extent to which
 your nick will be protected from unauthorized use.
 
-Syntax: %BSET [nick] PROTECT (OFF|ON|HIGH|KILL)%B
+Syntax: %BSET%B [%Unick%U] %BPROTECT%B <%UOFF|ON|HIGH|KILL%U>
 
 With PROTECT OFF, anyone may use your nick without authorization.
 
index cdc60b9e7e25fb1fe1ffebf39806da9e0a481c97..1ead793e87af426830e7fca6b75e4dd3c75363e5 100644 (file)
@@ -1,4 +1,4 @@
 %BNickServ SET UMODE%B sets the umodes that NickServ will set
 on you when you identify.
 
-Syntax: %BSET [nick] UMODE %U<+modes-modes|none>%U%B
+Syntax: %BSET%B [%Unick%U] %BUMODE%B <%U+modes-modes|none%U>
index 557def0538bd286e3d412fc715acbad4ba98079b..1ac35c31b47c963412c9edfe831e1a7122511c01 100644 (file)
@@ -5,4 +5,4 @@ be at least %Eint($services::conf{'vacationexpire'}/3)%E days old for this to be
 The flag is cleared on your next identify, and you will not be
 able to use it again until %Eint($services::conf{'vacationexpire'}/3)%E days have passed.
 
-Syntax: %BSET%B [%Unick%U] %BVACATION%B %Uon%U/%Uoff%U
+Syntax: %BSET%B [%Unick%U] %BVACATION%B <%UON|OFF%U>
index 98a6804a435df5e5a89c2e77e2fd085af916c040..84fe211cc0e4968c57807f4d73ab52102a676470 100644 (file)
@@ -4,7 +4,7 @@ identifying.
 It will automatically release an enforced nick, but it will
 not ghost or recover your nick if another user is using it.
 
-Syntax: SIDENTIFY <nick> <password>
+Syntax: %BSIDENTIFY%B %Unick%U <%Upassword%U>
 
 Examples:
     SIDENTIFY n00b
index 6de85cff1776d88b3fb6b7c170d3e756a46f4f0d..c63492334dc0b32e8eb89eee381b62fd3a909017 100644 (file)
@@ -2,9 +2,9 @@
 list.  Users on your silence list will not be able to send you private
 messages.
 
-Syntax: %BSILENCE ADD%B %Unick!ident@host%U [%U+expiry%U] [%Ucomment%U]
-        %BSILENCE ADD%B %Unick%U [%U+expiry%U] [%Ucomment%U]
-        %BSILENCE DEL%B %Unick!ident@host%U
+Syntax: %BSILENCE ADD%B <%Unick!ident@host%U> [%U+expiry%U] [%Ucomment%U]
+        %BSILENCE ADD%B <%Unick%U> [%U+expiry%U] [%Ucomment%U]
+        %BSILENCE DEL%B <%Unick!ident@host%U>
         %BSILENCE LIST%B
 
 The %BSILENCE ADD%B command adds a nick or hostmask to your silence list.
@@ -14,4 +14,10 @@ The %BSILENCE DEL%B command removes a hostmask from your silence list.
 
 The %BSILENCE LIST%B command displays your silence list.
 
+%BExamples:%B
+  /ns silence add erry +24h
+  /ns silence del 3
+  /ns silence add candyland101 +365d
+
 %BCAVEATS:%B You cannot have more than 32 silence entries.
+Use of an appropriate expiration is highly recommended.
index dfa4db6c18c46210492b0442c868522c0f88c25d..be1f1f49c2a86c45c1db5e95377768fa5358e479 100644 (file)
@@ -1,3 +1,3 @@
 %BNickServ UNLINK%B allows you to delete a linked nickname.
 
-Syntax: %BUNLINK%B %Unick%U %Upassword%U
+Syntax: %BUNLINK%B %Unick%U <%Upassword%U>
index cdf0d35a4286ef7bb7d6b7b40fac6d03d8c57055..2f5a1a148e59194250045698acaddf09246eb6af 100644 (file)
@@ -1,8 +1,8 @@
 %BNickServ WATCH%B allows you to view and modify your NickServ watch list.
 You will be notified when a user on your watch list connects to IRC.
 
-Syntax: %BWATCH ADD%B %Unick/mask%U
-        %BWATCH DEL%B %Unick/mask%U
+Syntax: %BWATCH ADD%B <%Unick/mask%U>
+        %BWATCH DEL%B <%Unick/mask%U>
         %BWATCH LIST%B
 
 The %BWATCH ADD%B command adds the specified nick or hostmask to your
index 7454c59bac7d9838afb0769b2f35ab4000bb3207..5404cf423d6d9359f041d3f09eb5e747973e0ecd 100644 (file)
@@ -26,3 +26,4 @@ Commands:
   CLONES      Lists and/or manipulates clones.
               Similar to LONERS.
   MASSKILL    Alias for CLONES KILL.
+  KILLNEW     List/kill/uinfo/kline newly connected users.
index dd8602c39300e85e7b87091ffc925a778b28355e..a2558e93856d9cbeff77cd8b4549f14feb6c58b1 100644 (file)
@@ -1,4 +1,4 @@
 %BOperServ CHANKILL%B adds a G:line for every user in a channel,
 IRCOps excepted.
 
-Syntax: %BCHANKILL%B %U#channel%U %Ureason%U
+Syntax: %BCHANKILL%B %U#channel%U <%Ureason%U>
index f17b22848dfeebd20f8f82ba7c6607c26eedb550..eba111b421cde05e5b8aec74d0e31e6004e40be2 100644 (file)
@@ -7,7 +7,7 @@ specific host, IP, or nickname and optionally:
 * %BKILL%Bs the users,
 * %BKLINE%Bs the users.
 
-Syntax: OS CLONES [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [msg/reason]
+Syntax: %BCLONES%B <%ULIST|UINFO|MSG|FJOIN|KILL|KLINE%U> [%Umsg/reason%U]
 
     LIST    -    Lists all users that match.
     UINFO   -    Retrieves UINFO for all users that match. %BWARNING%B May flood you off.
index 911237c085cae2e8d37e9e93c85eff68436d488b..74e9ede01285b8b744c7b508f4ad669d2299882d 100644 (file)
@@ -1,6 +1,6 @@
 %BOperServ KILL%B KILLs a user off the network.
 One possible use is to allow helpers to KILL.
 
-Syntax: %BKILL%B %Utarget%U %Ureason here%U
+Syntax: %BKILL%B %Utarget%U <%Ureason%U>
 
 Example: KILL erry not kool.
diff --git a/branches/mus-0.4.x-devel/help/operserv/killnew.txt b/branches/mus-0.4.x-devel/help/operserv/killnew.txt
new file mode 100644 (file)
index 0000000..ce85508
--- /dev/null
@@ -0,0 +1,23 @@
+%BOperServ KILLNEW%B gets the list of users that connected within a
+certain period of time.
+
+* retrieves %BUINFO%B,
+* sends a %BMSG%B to the users,
+* %BFJOIN%Bs the users,
+* %BKILL%Bs the users,
+* %BKLINE%Bs the users.
+
+Syntax: %BKILLNEW%B <%ULIST|UINFO|MSG|FJOIN|KILL|KLINE%U> [NOTID] +time [%Umsg/reason%U]
+
+    LIST    -    Lists all users that match.
+    UINFO   -    Retrieves UINFO for all users that match. %BWARNING%B May flood you off.
+    MSG     -    Sends a NOTICE from OperServ to the users. Message is not optional. (DUH)
+    FJOIN   -    Force-Join to a channel.
+    KILL    -    Kill the users. Reason is optional, but recommended.
+    KLINE   -    G:Line the users. Reason is optional, but recommended.
+
+Examples:
+
+    KILLNEW LIST NOTID +5m
+    KILLNEW UINFO +30s
+    KILLNEW KILL NOTID +30s
index 305693088ce9515390ba097b8c4dd3bfec70c5ed..865e1cb4db3d8d6eb3675a3038eff0cf0e887f09 100644 (file)
@@ -4,6 +4,6 @@ There are two lists. one for Users, and one for Opers (not
 finished yet). A news item may be permanent (does not expire) or
 may have a limited life (expires).
 
-Syntax: %BLOGONNEWS ADD%B %UU/O%U [%U+expiry%U] %Umessage%U
-        %BLOGONNEWS LIST%B %UU/O%U
-        %BLOGONNEWS DEL%B %UU/O%U %Unum%U
+Syntax: %BLOGONNEWS ADD%B <%UU|O%U> [%U+expiry%U] <%Umessage%U>
+        %BLOGONNEWS LIST%B <%UU/O%U>
+        %BLOGONNEWS DEL%B <%UU/O%U> <%Unum%U>
index 6c7f8f0319da0c6714322ca860a11a0264608b09..842755e7f47fab124eb79534f64ace05c5b5fc2a 100644 (file)
@@ -3,9 +3,9 @@ optionally retrieves %BUINFO%B, sends a %BMSG%B, %BFJOIN%B,
 %BKILL%B, or %BKLINE%B. if you specify %BNOID%B it will only act on
 users that have not identified to any nicks.
 
-Syntax: OS LONERS [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [NOTID] [msg/reason]
+Syntax: %BLONERS%B [%ULIST|UINFO|MSG|FJOIN|KILL|KLINE%U] [%UNOTID%U] [%Umsg/reason%U]
 
-If no command is specified, defauls to LIST.
+If no command is specified, defaults to LIST.
 
     LIST    -    Lists all users in zero channels
     UINFO   -    Retrieves UINFO for all users in zero channels. %BWARNING%B May flood you off.
index 40d49272ad0d10bb343081b95a45487ea20bba96..f596458eea922a734f7ba77657644148d9b2ab4f 100644 (file)
@@ -1,6 +1,6 @@
 %BOperServ QLINE%B prevents a nick or nickmask from being used,
 except by opers and services agents.
 
-Syntax: %BQLINE ADD%B [%U+expiry%U] %Umask%U %Ureason%U
-        %BQLINE DEL%B %Umask%U
+Syntax: %BQLINE ADD%B [%U+expiry%U] <%Umask%U> <%Ureason%U>
+        %BQLINE DEL%B <%Umask%U>
         %BQLINE LIST%B
index f8c1a2da5cba7a0889a000dc811d3a1eede1cecb..adfd680f4ed09a8c480509eebbb0d11f4faa61cf 100644 (file)
@@ -4,6 +4,6 @@ QUIT message.
 This command is limited to %BServices Roots%B, as it is
 %Uhighly%U abuseable.
 
-Syntax: %BSVSKILL%B %Utarget%U %Ureason here%U
+Syntax: %BSVSKILL%B <%Utarget%U> <%Ureason here%U>
 
 Example: SVSKILL Alucard Quit: I am the very model of a modern major general.
index 758b0af94b530672f30d2cbed7e7b832dad7f055..a0eef36674a8c9857cdc2e9babf733cf9183910b 100644 (file)
@@ -1,3 +1,3 @@
 %BOperServ SVSNICK%B forcibly changes a user's nick.
 
-Syntax: %BSVSNICK%B %Uoldnick%U %Unewnick%U
+Syntax: %BSVSNICK%B <%Uoldnick%U> <%Unewnick%U>
diff --git a/branches/mus-0.4.x-devel/help/spamserv.txt b/branches/mus-0.4.x-devel/help/spamserv.txt
new file mode 100644 (file)
index 0000000..e9b5c8a
--- /dev/null
@@ -0,0 +1,4 @@
+%BSpamServ%B allows you to watch for unwanted spam.
+
+Commands:
+  WATCH    Modify channels being watched
diff --git a/branches/mus-0.4.x-devel/help/spamserv/listconf.txt b/branches/mus-0.4.x-devel/help/spamserv/listconf.txt
new file mode 100644 (file)
index 0000000..b91ace1
--- /dev/null
@@ -0,0 +1,4 @@
+%BSpamServ LISTCONF%B lists the known settings of
+the current configuration.
+
+Syntax: %LISTCONF%B
diff --git a/branches/mus-0.4.x-devel/help/spamserv/rehash.txt b/branches/mus-0.4.x-devel/help/spamserv/rehash.txt
new file mode 100644 (file)
index 0000000..294daec
--- /dev/null
@@ -0,0 +1,5 @@
+%BSpamServ REHASH%B reloads the values of
+the configuration.  This does not save any
+recently set values with the %BSET%B command.
+
+Syntax: %BREHASH%B
diff --git a/branches/mus-0.4.x-devel/help/spamserv/save.txt b/branches/mus-0.4.x-devel/help/spamserv/save.txt
new file mode 100644 (file)
index 0000000..b2de278
--- /dev/null
@@ -0,0 +1,4 @@
+%BSpamServ SAVE%B saves the list of watched channels
+as well as the current configuration.
+
+Syntax: %BSAVE%B
diff --git a/branches/mus-0.4.x-devel/help/spamserv/set.txt b/branches/mus-0.4.x-devel/help/spamserv/set.txt
new file mode 100644 (file)
index 0000000..058895e
--- /dev/null
@@ -0,0 +1,8 @@
+%BSpamServ SET%B allows you to modify the
+configuration on the fly.
+
+Syntax: %BSET%B %Uoption%U <%Uvalue%U>
+
+Caveats: This command is limited toi previously
+known options in the configuration file. Use 
+%BSpamServ LISTCONF%B to list those options.
diff --git a/branches/mus-0.4.x-devel/help/spamserv/watch.txt b/branches/mus-0.4.x-devel/help/spamserv/watch.txt
new file mode 100644 (file)
index 0000000..c20ced2
--- /dev/null
@@ -0,0 +1,5 @@
+%BSpamServ WATCH%B modifies the list of channels being watched.
+
+  ADD   - Add a channel to be watched for spam.
+  DEL   - Remove a channel from being watched.
+  LIST  - List channels currently being watched.
diff --git a/branches/mus-0.4.x-devel/help/spamserv/watch/add.txt b/branches/mus-0.4.x-devel/help/spamserv/watch/add.txt
new file mode 100644 (file)
index 0000000..21a51c6
--- /dev/null
@@ -0,0 +1,4 @@
+%SpamServ WATCH ADD%B adds the specified channel
+to be watched by the SpamServ pseudoclients.
+
+Syntax: %BWATCH ADD%B %U#channel%U
diff --git a/branches/mus-0.4.x-devel/help/spamserv/watch/del.txt b/branches/mus-0.4.x-devel/help/spamserv/watch/del.txt
new file mode 100644 (file)
index 0000000..d75cda8
--- /dev/null
@@ -0,0 +1,5 @@
+%BSpamServ WATCH DEL%B removes a channel from the watch
+list, causing the SpamServ pseudoclient to part the
+channel.
+
+Syntax: %BWATCH DEL%B %U#channel%U
diff --git a/branches/mus-0.4.x-devel/help/spamserv/watch/list.txt b/branches/mus-0.4.x-devel/help/spamserv/watch/list.txt
new file mode 100644 (file)
index 0000000..2d6d3f9
--- /dev/null
@@ -0,0 +1,4 @@
+%BSpamServ WATCH LIST%B lists the currently watched
+channels.
+
+Syntax: %BWATCH LIST%B
index 71687184099dc460af31b149cdfa81f34e65741a..ef3a2bd1ef19b1dd2b13f1c857849907423962b6 100755 (executable)
@@ -1,7 +1,5 @@
 #!/bin/bash
 
-for X in `pgrep services.pl`; do
-       if [[ `vdir /proc/$X/cwd |sed 's/.*-> //'` == `pwd` ]]; then
-               kill $X
-       fi
+for X in `cat data/worker.pids`; do
+       kill $X
 done
index c7fb5ba87ebf08150c2f4af57989120495b180b0..2bbe126658a39e1b20903fbee84fed4343824b39 100644 (file)
@@ -120,7 +120,13 @@ sub calc($$$$) {
                        #shift @sargs if($sign == 2);
                        my $t = shift @sargs;
                        if($type == MERGE) {
-                               $status{$x}{lc $t} = $sign;
+                               my $key;
+                               if($x =~ /^[beIk]$/) {
+                                       $key = $t;
+                               } else {
+                                       $key = lc $t;
+                               }
+                               $status{$x}{$key} = $sign;
                        }
                        next;
                }
@@ -162,7 +168,13 @@ sub calc($$$$) {
                        #shift @dargs if($sign == 2);
                        my $t = shift @dargs;
                        if($type == MERGE) {
-                               $status{$x}{lc $t} = $sign;
+                               my $key;
+                               if($x =~ /^[beIk]$/) {
+                                       $key = $t;
+                               } else {
+                                       $key = lc $t;
+                               }
+                               $status{$x}{$key} = $sign;
                        }
                        next;
                }
index 0d558a18c25a565bdc3885ca59511fbb05e77be3..9381a77ec41aba34675c07d30cf1b0bf73a90a4a 100644 (file)
@@ -18,10 +18,15 @@ package connectserv;
 use strict;
 no strict 'refs';
 
-use SrSv::IRCd::State 'initial_synced';
+use SrSv::IRCd::State qw( initial_synced synced );
 use SrSv::IRCd::Event 'addhandler';
+use SrSv::RunLevel qw( $runlevel :levels );
 
-use SrSv::Conf2Consts qw(main);
+use SrSv::Conf::Parameters connectserv => [
+       [ joinpart => 0 ],
+];
+
+use SrSv::Conf2Consts qw( main connectserv );
 
 use SrSv::Log;
 
@@ -42,9 +47,9 @@ ircd::setmode($csnick, main_conf_diag, '+o', $csnick);
 addhandler('NICKCONN', undef, undef, 'connectserv::ev_nickconn', 1);
 sub ev_nickconn {
        my ($nick, $ident, $host, $server, $gecos) = @_[0,3,4,5,9];
-       
+
        $userlist{lc $nick} = [$ident, $host, $gecos, $server];
-       
+
        return unless initial_synced();
        message("\00304\002SIGNED ON\002 user: \002$nick\002 ($ident\@$host - $gecos\017\00304) at $server");
 }
@@ -65,7 +70,8 @@ addhandler('CHGIDENT', undef, undef, 'connectserv::ev_identchange', 1);
 sub ev_identchange {
        my (undef, $nick, $ident) = @_;
 
-       my ($oldident, $host, $gecos, $server) = @{$userlist{lc $nick}} if (defined($userlist{lc $nick}));
+       my ($oldident, $host, $gecos, $server);
+       ($oldident, $host, $gecos, $server) = @{$userlist{lc $nick}} if (defined($userlist{lc $nick}));
        $userlist{lc $nick} = [$ident, $host, $gecos, $server];
 
        message("\00310\002IDENT CHANGE\002 user: \002$nick\002 ($oldident\@$host) changed their virtual ident to \002$ident\002");
@@ -74,6 +80,7 @@ sub ev_identchange {
 addhandler('QUIT', undef, undef, 'connectserv::ev_quit', 1);
 sub ev_quit {
        my ($nick, $reason) = @_;
+       return unless synced() && $runlevel == ST_NORMAL;
        my ($ident, $host, $gecos, $server);
        if(defined($userlist{lc $nick})) {
                ($ident, $host, $gecos, $server) = @{$userlist{lc $nick}};
@@ -117,6 +124,34 @@ sub ev_umode {
        }
 }
 
+addhandler('SJOIN', undef, undef, 'connectserv::chan_join', 1) if connectserv_conf_joinpart;
+sub chan_join {
+       my ($server, $cn, $ts, $chmodes, $chmodeparms, $userarray, $banarray, $exceptarray) = @_;
+       return unless synced() && $runlevel == ST_NORMAL;
+       foreach my $user (@$userarray) {
+               my $nick = $user->{NICK};
+               message ("\00310CHANNEL JOIN: \002$nick\002 joined to \002$cn\002\003");
+       }
+}
+
+addhandler('PART', undef, undef, 'connectserv::chan_part', 1) if connectserv_conf_joinpart;
+sub chan_part {
+       my ($nick, $cn) = @_;
+       return unless synced() && $runlevel == ST_NORMAL;
+       message ("\00310CHANNEL PART: \002$nick\002 parted from \002$cn\002\003");
+}
+
+addhandler('JOIN', undef, undef, 'connectserv::chan_join0', 1) if connectserv_conf_joinpart;
+sub chan_join0 {
+       my ($nick, $cn) = @_;
+       return unless synced() && $runlevel == ST_NORMAL;
+       if($cn eq '0') {
+               message ("\00310CHANNEL PART: \002$nick\002 parted all channels\003");
+       } else {
+               message ("\00310CHANNEL JOIN: \002$nick\002 joined to \002$cn\002\003");
+       }
+}
+
 sub message(@) {
        ircd::privmsg($csnick, main_conf_diag, @_);
        write_log('diag', '<'.$csnick.'>', @_);
index 1488dd67c2ac5bbc1b8534c6e9800bdb9c6ea7b8..c2af21a8a64cd4accaf4c1b215b8515eacb29487 100644 (file)
@@ -28,7 +28,7 @@ sub ev_privmsg { ircd::privmsg($_[1], $_[0], $_[2]) }
 addhandler('NOTICE', undef, lc $esnick, 'echoserv::ev_notice');
 sub ev_notice { ircd::notice($_[1], $_[0], $_[2]) }
 
-agent_connect($esnick, 'services', 'services.SC.net', '+pqzBGHS', 'Echo Server');
+agent_connect($esnick, 'services', undef, '+pqzBGHS', 'Echo Server');
 agent_join($esnick, main_conf_diag);
 ircd::setmode($esnick, main_conf_diag, '+o', $esnick);
 
index 3500a46e34d40ac867df5989f3d1c987cbe0eb17..d300aa9f0445c3aa5ef89319d10f37668932cdc9 100644 (file)
@@ -133,9 +133,9 @@ sub stringify_location(@) {
                                #normalize stuff like "London, City of"
                                $regionName = join(' ', reverse(split(', ', $regionName)));
                        }
-                       $location .= (defined($city) && length($city)) ? ' ' : '(';
+                       $location .= (defined($city) && length($city)) ? ', ' : '(';
                        $location .= "$regionName)";
-               } else {
+               } elsif(defined($city) && length($city)) {
                        $location .= ')';
                }
                if(defined($metro)) {
index 9c9d41fcd33e1f3647f7ef50d5a0ed06624240e7..85ed5ee2b4b870a7518efc04452908e9eadd6e0a 100644 (file)
@@ -20,7 +20,6 @@ use SrSv::Timer qw(add_timer);
 use SrSv::Time;
 use SrSv::Agent;
 use SrSv::HostMask qw( parse_hostmask );
-#use SrSv::Conf qw(main sql);
 use SrSv::Conf2Consts qw(main sql);
 use SrSv::SimpleHash qw(readHash writeHash);
 
@@ -33,14 +32,12 @@ use SrSv::Help qw( sendhelp );
 use SrSv::MySQL '$dbh';
 use SrSv::MySQL::Glob;
 
-use SrSv::Shared qw(%conf %torip %unwhois);
+use SrSv::Shared qw(%conf $torip %unwhois);
 
-use SrSv::Process::InParent qw(list_conf loadconf saveconf);
+use SrSv::Process::InParent qw(list_conf loadconf saveconf update_tor_list);
 
 use SrSv::TOR;
 
-#use modules::ss2tkl; # package securitybot::ss2tkl, but has exports
-
 #this stuff needs to be put into files
 our $sbnick = "SecurityBot";
 our $ident = 'Security';
@@ -101,6 +98,7 @@ proc_init {
 };
 
 sub init {
+       return if main::COMPILE_ONLY();
        my $tmpdbh = DBI->connect(
                "DBI:mysql:".sql_conf_mysql_db,
                sql_conf_mysql_user,
@@ -138,7 +136,7 @@ sub globops($$) {
 }
 
 sub register {
-       agent_connect($sbnick, $ident, $vhost, $umodes, $gecos);
+       agent_connect($sbnick, $ident, undef, $umodes, $gecos);
        ircd::sqline($sbnick, 'Reserved for Services');
        
        agent_join($sbnick, main_conf_diag);
@@ -151,7 +149,7 @@ sub start_timers {
 }
 
 sub start_timers2 {
-       update_tor_list_timed(3540) if $conf{'EnableTor'};
+       update_tor_list_timed(3540) if $enabletor;
        #securitybot::ss2tkl::update_ss_timed(3300) if $conf{'EnableSS'};
 };
 
@@ -228,7 +226,7 @@ sub mk_banreason($$) {
 sub check_blacklists($$) {
        my ($rnick, $ip) = @_;
        
-       if(initial_synced and $enabletor && $torip{$ip}) {
+       if(initial_synced and $enabletor && $torip->{$ip}) {
                if (lc $enabletor eq lc 'vhost') {
                        ircd::chghost($sbnick, $rnick, misc::gen_uuid(1, 20).'.session.tor');
                } else {
@@ -242,7 +240,11 @@ sub check_blacklists($$) {
                return 0;
        }
 
-       if($conf{'BanCountry'} && module::is_loaded('country') && (my $country = check_country($ip))) {
+sub hasGeoCountry() {
+       return module::is_loaded('country') || module::is_loaded('geoip');
+}
+
+       if($conf{'BanCountry'} && hasGeoCountry() && (my $country = check_country($ip))) {
                ircd::zline($sbnick, $ip, $conf{'ProxyZlineTime'}, mk_banreason($conf{'CountryZlineReason'}, $country));
                return 0;
        }
@@ -256,23 +258,33 @@ sub update_tor_list_timed($) {
 
        add_timer('', $time, __PACKAGE__, 'securitybot::update_tor_list_timed');
        
-       update_tor_list();
+       update_tor_list() if $enabletor;
 }
 
 sub update_tor_list() {
+       return unless (defined($conf{'TorServer'}) && length($conf{'TorServer'}));
        diagmsg( " -- Loading Tor server list.");
        
        # path may be a local one if you run a tor-client.
        # most configs are /var/lib/tor/cached-directory
        my %newtorip;
-       foreach my $torIP (getTorRouters($conf{'TorServer'})) {
+       my @entries;
+       local $SIG{__DIE__} = undef;
+       eval {
+               @entries = getTorRouters($conf{'TorServer'});
+       };
+       if($@) {
+               ircd::debug("SecurityBot failed to load TOR data", $@);
+               return;
+       }
+       foreach my $torIP (@entries) {
                $newtorip{$torIP} = 1;
        }
 
        my $torcount = scalar(keys(%newtorip));
 
        if($torcount > 0) {
-               %torip = %newtorip;
+               $torip = \%newtorip;
                diagmsg( " -- Finished loading Tor server list - $torcount servers found.");
        } else {
                diagmsg( " -- Failed to load Tor server list, CHECK YOUR TorServer SETTING.");
index 1e9ac295ba60c5728af3594cbf23ea8b4fcfacda..aa6f603f38b18cc2eb14ad96267689271360ebb2 100644 (file)
 package services;
 use strict;
 
-use SrSv::Conf::Parameters services => [
-       [noexpire => undef],
-       [nickexpire => 21],
-       [vacationexpire => 90],
-       [nearexpire => 7],
-       [chanexpire => 21],
-       [validate_email => undef],
-       [validate_expire => 1],
-       [clone_limit => 3],
-       [chankilltime => 86400],
-
-       [default_protect => 'normal'],
-       [default_chanbot => undef],
-       [old_user_age => 300],
-
-       [log_overrides => 0],
-
-       [botserv => undef],
-       [nickserv => undef],
-       [chanserv => undef],
-       [memoserv => undef],
-       [adminserv => undef],
-       [operserv => undef],
-       [hostserv => undef],
-];
-
-use DBI;
+use SrSv::Conf::services;
+
 use SrSv::MySQL qw($dbh);
 use SrSv::Conf qw(main services sql);
 use SrSv::Conf2Consts qw(main services sql);
@@ -71,31 +46,31 @@ our @agents = (
        [$adminserv::asnick_default, '+pqzBS', 'Services\' Administration Agent'],
        [$hostserv::hsnick_default, '+pqzBS', 'vHost Agent']
 );
-if(services_conf_nickserv) {
+if(services_conf_nickserv && (lc(services_conf_nickserv) ne lc($nickserv::nsnick_default)) ) {
        push @agents, [services_conf_nickserv, '+opqzBHS', 'Nick Registration Agent'];
        $nickserv::nsnick = services_conf_nickserv;
 }
-if(services_conf_chanserv) {
+if(services_conf_chanserv && (lc(services_conf_chanserv) ne lc($chanserv::csnick_default)) ) {
        push @agents, [services_conf_chanserv, '+pqzBS', 'Channel Registration Agent'];
-       $chanserv::csnick = services_conf_nickserv;
+       $chanserv::csnick = services_conf_chanserv;
 }
-if(services_conf_operserv) {
+if(services_conf_operserv && (lc(services_conf_operserv) ne lc($operserv::osnick_default)) ) {
        push @agents, [services_conf_operserv, '+opqzBHS', 'Operator Services Agent'];
        $operserv::osnick = services_conf_operserv;
 }
-if(services_conf_memoserv) {
+if(services_conf_memoserv && (lc(services_conf_memoserv) ne lc($memoserv::msnick_default)) ) {
        push @agents, [services_conf_memoserv, '+pqzBS', 'Memo Exchange Agent'];
        $memoserv::msnick = services_conf_memoserv;
 }
-if(services_conf_botserv) {
+if(services_conf_botserv && (lc(services_conf_botserv) ne lc($botserv::bsnick_default)) ) {
        push @agents, [services_conf_botserv, '+pqzBS', 'Channel Bot Control Agent'];
        $botserv::bsnick = services_conf_botserv;
 }
-if(services_conf_adminserv) {
+if(services_conf_adminserv && (lc(services_conf_adminserv) ne lc($adminserv::asnick_default)) ) {
        push @agents, [services_conf_adminserv, '+pqzBS', 'Services\' Administration Agent'];
        $adminserv::asnick = services_conf_adminserv;
 }
-if(services_conf_hostserv) {
+if(services_conf_hostserv && (lc(services_conf_hostserv) ne lc($adminserv::asnick_default)) ) {
        push @agents, [services_conf_hostserv, '+pqzBS', 'vHost Agent'];
        $hostserv::hsnick = services_conf_hostserv;
 }
@@ -211,6 +186,7 @@ sub maint {
 }
 
 sub init {
+       return if main::COMPILE_ONLY();
        my $tmpdbh = DBI->connect("DBI:mysql:".sql_conf_mysql_db, sql_conf_mysql_user, sql_conf_mysql_pass, {  AutoCommit => 1, RaiseError => 1 });
 
        $tmpdbh->do("TRUNCATE TABLE chanuser");
index d8ff7cfd7eec6c97926af59bf2a0f52bb96963cd..9d8a0516f8bfecb54911e1f7ca1346be19f004dc 100644 (file)
@@ -39,6 +39,9 @@ use constant {
        S_ROOT => 4,
 };
 
+our (%flags, @levels, @defflags, $allflags);
+
+BEGIN {
 # BE CAREFUL CHANGING THESE
 my @flags = (
        'SERVOP',
@@ -51,24 +54,27 @@ my @flags = (
        'KILL',
        'HELP',
 );
-my $allflags = (2**@flags) - 1;
-my %flags;
-for(my $i=0; $i<@flags; $i++) {
-       $flags{$flags[$i]} = 2**$i;
-}
-
-our $asnick_default = 'AdminServ';
-our $asnick = $asnick_default;
 
+for(my $i = scalar(@flags) - 1; $i >= 0; $i--) {
+       $flags{$flags[$i]} = 1 << $i;
+}
+$allflags = (1 << scalar(@flags)) - 1;
 our @levels = ('Normal User', 'HelpOp', 'Operator', 'Administrator', 'Root');
-my @defflags = (
+# BE CAREFUL CHANGING THESE
+our @defflags = (
        0, # Unused
        $flags{HELP}, # HelpOp
        $flags{HELP}|$flags{FJOIN}|$flags{QLINE}|$flags{SUPER}|$flags{FREEZE}|$flags{KILL}, # Operator
-       $flags{HELP}|$flags{FJOIN}|$flags{QLINE}|$flags{SUPER}|$flags{FREEZE}|$flags{HOLD}|$flags{BOT}|$flags{SERVOP}|$flags{KILL}, # Admin
+       $flags{HELP}|$flags{FJOIN}|$flags{QLINE}|$flags{SUPER}|$flags{FREEZE}|$flags{KILL}|
+               $flags{HOLD}|$flags{BOT}|$flags{SERVOP}, # Admin
        $allflags # Root
 );
 
+}
+our $asnick_default = 'AdminServ';
+our $asnick = $asnick_default;
+
+
 our (
        $create_svsop, $delete_svsop, $rename_svsop,
 
index 8964f96919026de1d41aa7a5d5733bd9918d1d2c..6041cfe46b41e0420ec34350b3a52f2aad8a6b63 100644 (file)
@@ -125,7 +125,7 @@ sub bs_dispatch($$$) {
        my @args = split(/\s+/, $msg);
        my $cmd = shift @args;
 
-       my $user = { NICK => $src, AGENT => $bsnick };
+       my $user = { NICK => $src, AGENT => $dst };
 
        return if flood_check($user);
 
@@ -490,17 +490,17 @@ sub chan_dispatch($$$) {
        my $user = { NICK => $src, AGENT => agent($chan) };
 
        my %cmdhash = (
-               'voice'         =>      \&give_ops,
-               'devoice'       =>      \&give_ops,
-               'hop'           =>      \&give_ops,
-               'halfop'        =>      \&give_ops,
-               'dehop'         =>      \&give_ops,
-               'dehalfop'      =>      \&give_ops,
-               'op'            =>      \&give_ops,
-               'deop'          =>      \&give_ops,
-               'protect'       =>      \&give_ops,
+               'voice'         =>      \&give_ops,
+               'devoice'       =>      \&give_ops,
+               'hop'           =>      \&give_ops,
+               'halfop'        =>      \&give_ops,
+               'dehop'         =>      \&give_ops,
+               'dehalfop'      =>      \&give_ops,
+               'op'            =>      \&give_ops,
+               'deop'          =>      \&give_ops,
+               'protect'       =>      \&give_ops,
                'admin'         =>      \&give_ops,
-               'deprotect'     =>      \&give_ops,
+               'deprotect'     =>      \&give_ops,
                'deadmin'       =>      \&give_ops,
 
                'up'            =>      \&up,
@@ -546,6 +546,10 @@ sub chan_dispatch($$$) {
                'commands'      =>      \&help,
                'botcmds'       =>      \&help,
 
+               'abbrevs'       =>      \&help,
+               'abbreviations' =>      \&help,
+               'abbrev'        =>      \&help,
+
                'users'         =>      \&alist,
                'alist'         =>      \&alist,
 
@@ -566,18 +570,26 @@ sub chan_dispatch($$$) {
                'm'             =>      \&mode,
 
                'resync'        =>      \&resync,
+
+               'topic'         =>      \&topic,
+               't'             =>      \&topic,
+
+               'why'           =>      \&why,
+               'tempban' => \&tempban,
+               'tmpban' => \&tempban,
+               "tb" => \&tempban,
        );
 
        sub give_ops {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                chanserv::cs_setmodes($user, $cmd, $chan, @args);
        }
        sub up {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                chanserv::cs_updown($user, $cmd, $chan->{CHAN}, @args);
        }
        sub down {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                if(lc $cmd eq 'molest') {
                        chanserv::unset_modes($user, $chan);
                } else {
@@ -586,34 +598,44 @@ sub chan_dispatch($$$) {
        }
 
        sub invite {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                chanserv::cs_invite($user, $chan, @args) unless @args == 0;
        }
 
        sub kick {
-               my ($user, $cmd, $chan, @args) = @_;
-               my $target = shift @args;
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               my $target = shift @args or return;
                chanserv::cs_kick($user, $chan, $target, 0, join(' ', @args));
        }
+       sub tempban {
+               my ($user, $chan, $cmd, undef, @args) = @_;
+       
+        my $cn = $chan->{CHAN};
+        use Data::Dumper;
+
+        unshift @args, $cn;
+        print ("ARGS " . Dumper (@args));
+               chanserv::cs_tempban($user, join(' ', @args));
+       }
        sub kickban {
-               my ($user, $cmd, $chan, @args) = @_;
-               my $target = shift @args;
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               my $target = shift @args or return;
                chanserv::cs_kick($user, $chan, $target, 1, join(' ', @args));
        }
 
        sub kickmask {
-               my ($user, $cmd, $chan, @args) = @_;
-               my $target = shift @args;
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               my $target = shift @args or return;
                chanserv::cs_kickmask($user, $chan, $target, 0, join(' ', @args));
        }
        sub kickbanmask {
-               my ($user, $cmd, $chan, @args) = @_;
-               my $target = shift @args;
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               my $target = shift @args or return;
                chanserv::cs_kickmask($user, $chan, $target, 1, join(' ', @args));
        }
 
        sub calc {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                my $msg = join(' ', @args);
                for ($msg) {
                        s/,/./g;
@@ -630,7 +652,7 @@ sub chan_dispatch($$$) {
        }
 
        sub seen {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                
                if(@args >= 1) {
                        nickserv::ns_seen($user, @args);
@@ -640,17 +662,21 @@ sub chan_dispatch($$$) {
        }
 
        sub help {
-               my ($user, $cmd, $chan, @args) = @_;
-               sendhelp($user, 'chanbot');
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               if($cmd =~ /^abbrev(iation)?s?$/) {
+                       sendhelp($user, 'chanbot', 'abbreviations');
+               } else {
+                       sendhelp($user, 'chanbot');
+               }
        }
 
        sub alist {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                chanserv::cs_alist($user, $chan);
        }
 
        sub unban {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                if(@args == 0) {
                        chanserv::cs_unban($user, $chan, get_user_nick($user));
                }
@@ -660,7 +686,7 @@ sub chan_dispatch($$$) {
        }
 
        sub ban {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                $cmd =~ /^(q|n)?ban$/; my $type = $1;
                if(@args >= 1) {
                        chanserv::cs_ban($user, $chan, $type, @args);
@@ -668,14 +694,14 @@ sub chan_dispatch($$$) {
        }
 
        sub banlist {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                chanserv::cs_banlist($user, $chan);
        }
 
        sub dice {
        # FIXME: If dice is disabled, don't count towards flooding.
-               my ($user, $cmd, $chan, @args) = @_;
-               
+               my ($user, $chan, $cmd, undef, @args) = @_;
+
                if(chanserv::can_do($chan, 'DICE', $user)) {
                        ircd::privmsg(agent($chan), $chan->{CHAN},
                                get_dice($args[0]));
@@ -683,21 +709,38 @@ sub chan_dispatch($$$) {
        }
 
        sub mode {
-               my ($user, $cmd, $chan, @args) = @_;
+               my ($user, $chan, $cmd, undef, @args) = @_;
                if(@args >= 1) {
                        chanserv::cs_mode($user, $chan, shift @args, @args);
                }
        }
 
        sub resync {
-               my ($user, $cmd, $chan) = @_;
+               my ($user, $chan, $cmd) = @_;
                chanserv::cs_resync($user, $chan->{CHAN});
        }
 
+       sub topic {
+               my ($user, $chan, $cmd, $msg) = @_;
+               if (@args >= 1) {
+                       $msg =~ s/^!$cmd //;
+                       chanserv::cs_topic($user, $chan, $msg);
+               }
+       }
+
+       sub why {
+               my ($user, $chan, $cmd, undef, @args) = @_;
+               
+               if(@args >= 1) {
+                       chanserv::cs_why($user, $chan, @args);
+               } else {
+                       notice($user, 'Syntax: WHY <nick> [nick ...]');
+               }
+       }
        if(defined($cmdhash{$cmd})) {
                return if flood_check($user);
 
-               &{$cmdhash{$cmd}}($user, $cmd, $chan, @args);
+               &{$cmdhash{$cmd}}($user, $chan, $cmd, $msg, @args);
        }
 }
 
@@ -710,7 +753,8 @@ sub bot_say($$$) {
                        if cr_chk_flag($chan, CRF_VERBOSE());
                ircd::privmsg(agent($chan), $cn, $botmsg);
        } else {
-               notice($user, $err_deny);
+               # can_do will give the $err_deny for us.
+               #notice($user, $err_deny);
        }
 }
 
index ed90a8e0eb5ed1104a8d85f983d74bb549d74fbc..270ea85be8c432ead5a63f70b05affe601638da0 100644 (file)
 package chanserv;
 
 use strict;
-use DBI qw(:sql_types);
 
 use SrSv::Timer qw(add_timer);
-
+use Data::Dumper;
 use SrSv::Message qw(current_message);
 use SrSv::IRCd::State qw($ircline synced initial_synced %IRCd_capabilities);
 use SrSv::Message qw(message current_message);
 use SrSv::HostMask qw(normalize_hostmask make_hostmask parse_mask);
-
 #FIXME: This needs to be abstracted into a proper SrSv::IRCd module
 use SrSv::Unreal::Modes qw(@opmodes %opmodes $scm $ocm $acm sanitize_mlockable);
 use SrSv::IRCd::Validate qw( valid_nick validate_chmodes validate_ban );
@@ -33,7 +31,7 @@ use SrSv::Agent;
 use SrSv::Shared qw(%enforcers $chanuser_table);
 
 #use SrSv::Conf qw(services);
-use SrSv::Conf2Consts qw( services sql );
+use SrSv::Conf2Consts qw( services sql main );
 
 use SrSv::Time;
 use SrSv::Text::Format qw( columnar enum );
@@ -41,16 +39,21 @@ use SrSv::Errors;
 
 use SrSv::Log;
 
-use SrSv::User qw(get_user_nick get_user_id is_online :user_flags get_host get_vhost :flags :flood);
+use SrSv::User qw(
+       get_user_nick get_user_agent get_user_id
+       is_online :user_flags get_host get_vhost
+       :flags :flood
+       );
 use SrSv::User::Notice;
 use SrSv::Help qw( sendhelp );
 
 use SrSv::ChanReg::Flags;
 
 use SrSv::NickReg::Flags;
+use SrSv::NickReg::NickText;
 use SrSv::NickReg::User qw(is_identified get_nick_users get_nick_user_nicks);
 
-use SrSv::MySQL '$dbh';
+use SrSv::MySQL qw( $dbh :sql_types );
 use SrSv::MySQL::Glob;
 
 use SrSv::Util qw( makeSeqList );
@@ -93,11 +96,14 @@ our @override = (
                        AKICK => 1,
                        LEVELS => 1,
                        COPY => 1,
+                       WELCOME => 1,
                }
        ],
        ['SUPER',
                {
                        BAN => 1,
+                       UNBANSELF => 1,
+                       UNBAN => 1,
                        KICK => 1,
                        VOICE => 1,
                        HALFOP => 1,
@@ -107,7 +113,6 @@ our @override = (
                        SETTOPIC => 1,
                        INVITE => 1,
                        INVITESELF => 1,
-                       JOIN => 1,
                        CLEAR => 1,
                        AKICKENFORCE => 1,
                        UPDOWN => 1,
@@ -116,6 +121,7 @@ our @override = (
        ],
        ['HELP',
                {
+                       JOIN => 1,
                        ACCLIST => 1,
                        LEVELSLIST => 1,
                        AKICKLIST => 1,
@@ -141,7 +147,7 @@ our ($cur_lock, $cnt_lock);
 our (
        $get_joinpart_lock, $get_modelock_lock, $get_update_modes_lock,
        
-       $chanjoin, $chanpart, $chop, $chdeop, $get_op, $get_user_chans, $get_user_chans_recent,
+       $chanjoin, $chanpart, $chanpart2, $chop, $chdeop, $get_op, $get_user_chans, $get_user_chans_recent,
        $get_all_closed_chans, $get_user_count,
 
        $is_in_chan,
@@ -205,8 +211,9 @@ our (
        $set_bantype, $get_bantype,
 
        $drop_chantext, $drop_nicktext,
-
-       $get_recent_private_chans,
+       $get_host,
+       $get_host_inchan,
+       $get_expired_bans,
 );
 
 sub init() {
@@ -218,6 +225,7 @@ sub init() {
        $chanjoin = $dbh->prepare("REPLACE INTO chanuser (seq,nickid,chan,op,joined) VALUES (?, ?, ?, ?, 1)");
        $chanpart = $dbh->prepare("UPDATE chanuser SET joined=0, seq=?
                WHERE nickid=? AND chan=? AND (seq <= ? OR seq > ?)");
+       $chanpart2 = $dbh->prepare("UPDATE chanuser SET joined=0 WHERE nickid=? AND chan=?");
        #$chop = $dbh->prepare("UPDATE chanuser SET op=op+? WHERE nickid=? AND chan=?");
        $chop = $dbh->prepare("UPDATE chanuser SET op=IF(op & ?, op, op ^ ?) WHERE nickid=? AND chan=?");
        $chdeop = $dbh->prepare("UPDATE chanuser SET op=IF(op & ?, op ^ ?, op) WHERE nickid=? AND chan=?");
@@ -428,7 +436,7 @@ sub init() {
 
 
        $get_info = $dbh->prepare("SELECT chanreg.descrip, chanreg.regd, chanreg.last, chantext.data, 
-               chanreg.topicer, chanreg.modelock, foundernick.nick, successornick.nick, chanreg.bot, chanreg.bantype
+               chanreg.topicer, chanreg.modelock, foundernick.nick, successornick.nick, chanreg.bot, chanreg.bantype, chanreg.bantime
                FROM nickreg AS foundernick, chanreg
                LEFT JOIN nickreg AS successornick ON(successornick.id=chanreg.successorid)
                LEFT JOIN chantext ON (chanreg.chan=chantext.chan AND chantext.type=".CRT_TOPIC().")
@@ -436,13 +444,12 @@ sub init() {
 
        $register = $dbh->prepare("INSERT INTO chanreg
                SELECT ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), NULL, NULL,
-               NULL, id, NULL, NULL, NULL, ".DEFAULT_BANTYPE()." FROM nickreg WHERE nick=?");
+               NULL, id, NULL, NULL, NULL, ".DEFAULT_BANTYPE().",0 FROM nickreg WHERE nick=?");
        $register->{PrintError} = 0;
        $copy_chanreg = $dbh->prepare("INSERT INTO chanreg
-               (      chan, descrip, regd,             last,             modelock, founderid, successorid, bot, flags, bantype)
-               SELECT ?,    descrip, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), modelock, founderid, successorid, bot, flags, bantype
+               (      chan, descrip, regd,             last,             modelock, founderid, successorid, bot, flags, bantype,bantime)
+               SELECT ?,    descrip, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), modelock, founderid, successorid, bot, flags, bantype,bantime
                FROM chanreg WHERE chan=?");
-
        $drop_acc = $dbh->prepare("DELETE FROM chanacc WHERE chan=?");
        $drop_lvl = $dbh->prepare("DELETE FROM chanlvl WHERE chan=?");
        $drop_akick = $dbh->prepare("DELETE FROM akick WHERE chan=?");
@@ -464,15 +471,14 @@ sub init() {
        $drop_welcome = $dbh->prepare("DELETE FROM welcome WHERE chan=?");
        $count_welcome = $dbh->prepare("SELECT COUNT(*) FROM welcome WHERE chan=?");
        $consolidate_welcome = $dbh->prepare("UPDATE welcome SET id=id-1 WHERE chan=? AND id>?");
-
-       $add_ban = $dbh->prepare("REPLACE INTO chanban SET chan=?, mask=?, setter=?, type=?, time=UNIX_TIMESTAMP()");
+       $add_ban = $dbh->prepare("INSERT IGNORE INTO chanban SET chan=?, mask=?, setter=?, type=?, time=UNIX_TIMESTAMP()");
        $delete_bans = $dbh->prepare("DELETE FROM chanban WHERE chan=? AND ? LIKE mask AND type=?");
        # likely need a better name for this or for the above.
        $delete_ban = $dbh->prepare("DELETE FROM chanban WHERE chan=? AND mask=? AND type=?");
        $find_bans = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND ? LIKE mask AND type=?");
        $get_all_bans = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND type=?");
-       $get_ban_num = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? ORDER BY time, mask LIMIT 1 OFFSET ?");
-       $get_ban_num->bind_param(2, 0, SQL_INTEGER);
+       $get_ban_num = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND type=? ORDER BY time, mask LIMIT 1 OFFSET ?");
+       $get_ban_num->bind_param(3, 0, SQL_INTEGER);
        $list_bans = $dbh->prepare("SELECT mask, setter, time FROM chanban WHERE chan=? AND type=? ORDER BY time, mask");
        $wipe_bans = $dbh->prepare("DELETE FROM chanban WHERE chan=?");
 
@@ -486,18 +492,18 @@ sub init() {
                WHERE chan=? AND user.id=? AND type=? AND $chanban_mask");
 
        $add_auth = $dbh->prepare("REPLACE INTO nicktext
-               SELECT nickalias.nrid, (".nickserv::NTF_AUTH()."), 1, ?, ? FROM nickalias WHERE nickalias.alias=?");
+               SELECT nickalias.nrid, (".NTF_AUTH()."), 1, ?, ? FROM nickalias WHERE nickalias.alias=?");
        $list_auth_chan = $dbh->prepare("SELECT nickreg.nick, nicktext.data FROM nickreg, nicktext
-               WHERE nickreg.id=nicktext.nrid AND nicktext.type=(".nickserv::NTF_AUTH().") AND nicktext.chan=?");
+               WHERE nickreg.id=nicktext.nrid AND nicktext.type=(".NTF_AUTH().") AND nicktext.chan=?");
        $get_auth_nick = $dbh->prepare("SELECT nicktext.data FROM nickreg, nickalias, nicktext
-               WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".nickserv::NTF_AUTH().")
+               WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".NTF_AUTH().")
                AND nicktext.chan=? AND nickalias.alias=?");
        $get_auth_num = $dbh->prepare("SELECT nickreg.nick, nicktext.data FROM nickreg, nickalias, nicktext
-               WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".nickserv::NTF_AUTH().")
+               WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".NTF_AUTH().")
                AND nicktext.chan=? LIMIT 1 OFFSET ?");
        $get_auth_num->bind_param(2, 0, SQL_INTEGER);
        $find_auth = $dbh->prepare("SELECT 1 FROM nickalias, nicktext
-               WHERE nickalias.nrid=nicktext.nrid AND nicktext.type=(".nickserv::NTF_AUTH().")
+               WHERE nickalias.nrid=nicktext.nrid AND nicktext.type=(".NTF_AUTH().")
                AND nicktext.chan=? AND nickalias.alias=?");
 
        $set_bantype = $dbh->prepare("UPDATE chanreg SET bantype=? WHERE chan=?");
@@ -505,21 +511,115 @@ sub init() {
 
        $drop_chantext = $dbh->prepare("DELETE FROM chantext WHERE chan=?");
        $drop_nicktext = $dbh->prepare("DELETE nicktext.* FROM nicktext WHERE nicktext.chan=?");
+       $get_host = $dbh->prepare ("SELECT user.host from user where user.nick=?");
+       $get_host_inchan = $dbh->prepare ("SELECT clonedUsers.nick FROM user AS curUser
+               JOIN user AS clonedUsers ON (curUser.host=clonedUsers.host)
+               JOIN chanuser ON (chanuser.nickid=clonedUsers.id)
+               WHERE clonedUsers.id!=curUser.id AND curUser.id=? AND chanuser.chan=? AND chanuser.joined=1");
+       $get_expired_bans = $dbh->prepare("SELECT channel, banmask, expiry, timeset FROM tmpban
+               WHERE expiry < UNIX_TIMESTAMP()");
 
-       $get_recent_private_chans = $dbh->prepare("SELECT chanuser.chan FROM chanperm, chanlvl, chanuser, nickid, chanacc WHERE chanperm.name='Join' AND chanlvl.perm=chanperm.id AND chanlvl.level > 0 AND nickid.id=? AND chanacc.nrid=nickid.nrid AND chanuser.nickid=nickid.id AND chanuser.joined=0 AND chanuser.chan=chanacc.chan AND chanlvl.level <= chanacc.level");
 }
 
 use SrSv::MySQL::Stub {
        set_lastop => ['NULL', "UPDATE chanreg SET last=UNIX_TIMESTAMP() WHERE chan=?"],
        set_lastused => ['NULL', "UPDATE chanacc, nickid SET chanacc.last=UNIX_TIMESTAMP() WHERE 
                chanacc.chan=? AND nickid.id=? AND chanacc.nrid=nickid.nrid AND chanacc.level > 0"],
+       get_recent_private_chans => ['COLUMN', "SELECT DISTINCT chanuser.chan FROM chanuser
+               JOIN chanacc ON (chanuser.chan=chanacc.chan AND chanuser.joined=0)
+               JOIN chanlvl ON (chanlvl.level <= chanacc.level AND chanlvl.level > 0 AND chanuser.chan=chanlvl.chan)
+               JOIN chanperm ON (chanlvl.perm=chanperm.id)
+               JOIN nickid ON (chanuser.nickid=nickid.id AND chanacc.nrid=nickid.nrid)
+               WHERE chanperm.name='Join'
+               AND nickid.id=?"],
+       add_tempban => ['INSERT', "INSERT INTO tmpban values (?,?,UNIX_TIMESTAMP()+?,UNIX_TIMESTAMP())"],
+       del_tempban  => ['NULL', "DELETE FROM tmpban WHERE channel=? AND banmask = ?"],
+       __get_bantime => ['SCALAR', "SELECT bantime FROM chanreg WHERE chan=?"],
+       set_bantime => ['NULL', "UPDATE chanreg SET bantime=? WHERE chan=?"],
 };
 
+
+sub get_bantime($) {
+       my ($chan) = @_;
+       my $cn;
+       if(ref $chan) {
+               if(exists $chan->{BANTIME}) {
+                       return $chan->{BANTIME};
+               }
+               $cn = $chan->{CHAN};
+       } else {
+               $cn = $chan;
+       }
+       my $bantime = __get_bantime($cn);
+       if(ref $chan) {
+               $chan->{BANTIME} = $bantime;
+       }
+       return $bantime;
+}
+
 ### CHANSERV COMMANDS ###
 
+our %high_priority_cmds = (
+       kick => 1,
+       mode => 1,
+       kb => 1,
+       kickban => 1,
+       kickb => 1,
+       kban => 1,
+       down => 1,
+);
+sub check_expired_bans() {
+       add_timer('ChanServ Expire', 10, __PACKAGE__, 'chanserv::check_expired_bans');
+       $get_expired_bans->execute();
+
+       while (my ($cn, $ban) = $get_expired_bans->fetchrow_array()) {
+               my $chan = { CHAN => $cn };
+               ircd::setmode(agent($chan), $cn, '-b', $ban);
+       }
+       ircd::flushmodes();
+}
+sub tempban($@) {
+       my ($chan, @bans) = @_;
+
+       my $cn = $chan->{CHAN};
+       return unless scalar(@bans);
+
+       foreach my $ban (@bans) {
+               my ($mask, $expiry);
+               if(ref($ban)) {
+                       ($mask, $expiry) = @$ban;
+               } else {
+                       $mask = $ban;
+               }
+               if($expiry) {
+                       add_tempban($cn, $mask, $expiry);
+               }
+       }
+
+       ircd::ban_list(agent($chan), $cn, +1, 'b', map { ref($_) ? $_->[0] : $_ } @bans);
+}
+sub clones_exist ($$) {
+       my ($user, $chan) = @_;
+       my $cn = $chan->{CHAN};
+
+       unless(cr_chk_flag($chan, CRF_NOCLONES)) {
+               return;
+       }
+
+       my $nick = $user->{NICK};
+       $get_host_inchan->execute(get_user_id($user), $cn);
+       my ($joined) = $get_host_inchan->fetchrow_array;
+       $get_host_inchan->finish();
+
+       if ($joined) {
+               return $joined;
+       }
+
+       return 0;
+}
 sub dispatch($$$) {
        my ($src, $dst, $msg) = @_;
-       
+
        $msg =~ s/^\s+//;
        my @args = split(/\s+/, $msg);
        my $cmd = shift @args;
@@ -528,6 +628,14 @@ sub dispatch($$$) {
 
        return if flood_check($user);
 
+       if(!defined($high_priority_cmds{lc $cmd}) &&
+               !adminserv::is_svsop($user) &&
+               $SrSv::IRCd::State::queue_depth > main_conf_queue_highwater) 
+       {
+               notice($user, get_user_agent($user)." is too busy right now. Please try your command again later.");
+               return;
+       }
+
        if($cmd =~ /^register$/i) {
                if(@args >= 1) {
                        my @args = split(/\s+/, $msg, 4);
@@ -536,10 +644,15 @@ sub dispatch($$$) {
                        notice($user, 'Syntax: REGISTER <#channel> [password] [description]');
                }
        }
+       elsif ($cmd =~ /^t((e)?mp)?b(an)?$/i) {
+               my @args = split (/\s+/, $msg, 2);
+
+               cs_tempban ($user, $args[1]);
+       }
        elsif($cmd =~ /^(?:[uvhas]op|co?f(ounder)?)$/i) {
                my ($cn, $cmd2) = splice(@args, 0, 2);
                my $chan = { CHAN => $cn };
-               
+
                if($cmd2 =~ /^add$/i) {
                        if(@args == 1) {
                                cs_xop_add($user, $chan, $cmd, $args[0]);
@@ -724,7 +837,7 @@ sub dispatch($$$) {
                        notice($user, 'Syntax: COUNT <channel>');
                }
        }
-       elsif($cmd =~ /^kick$/i) {
+       elsif($cmd =~ /^k(?:ick)?$/i) {
                my @args = split(/\s+/, $msg, 4); shift @args;
                if(@args >= 2) {
                        cs_kick($user, { CHAN => $args[0] }, $args[1], 0, $args[2])
@@ -947,7 +1060,7 @@ sub dispatch($$$) {
                        cs_mlock($user, { CHAN => $chan }, @args)
                }
                else {
-                       notice($user, 'Syntax: MLOCK <channel> <ADD|DEL> <modes> [parms]');
+                       notice($user, 'Syntax: MLOCK <channel> <ADD|DEL|SET|RESET> <modes> [parms]');
                }
        }
        elsif($cmd =~ /^resync$/i) {
@@ -957,6 +1070,40 @@ sub dispatch($$$) {
                        cs_resync($user, @args);
                }
        }
+       elsif($cmd =~ /^JOIN$/i) {
+               if (@args == 0) {
+                       notice($user, 'Syntax: JOIN <chan1> [chan2 [chan3 [..]]]');
+               } else {
+                       cs_join($user, @args);
+               }
+       }
+       elsif($cmd =~ /^topic$/i) {
+               my $chan = shift @args;
+               if (@args == 0) {
+                       notice($user, 'Syntax: TOPIC <#channel> <message|NONE>');
+               } else {
+                       $msg =~ s/^topic #(?:\S+)? //i;
+                       cs_topic($user, { CHAN => $chan }, $msg);
+               }
+       }
+       elsif($cmd =~ /^topicappend$/i) {
+               my $chan = shift @args;
+               if (@args == 0) {
+                       notice($user, 'Syntax: TOPICAPPEND <#channel> <message>');
+               } else {
+                       $msg =~ s/^topicappend #(?:\S+)? //i;
+                       cs_topicappend($user, $chan, $msg);
+               }
+       }
+       elsif($cmd =~ /^topicprepend$/i) {
+               my $chan = shift @args;
+               if (@args == 0) {
+                       notice($user, 'Syntax: TOPICPREPEND <#channel> <message>');
+               } else {
+                       $msg =~ s/^topicprepend #(?:\S+)? //i;
+                       cs_topicprepend($user, $chan, $msg);
+               }
+       }
        else {
                notice($user, "Unrecognized command \002$cmd\002.", "For help, type: \002/msg chanserv help\002");
                wlog($csnick, LOG_DEBUG(), "$src tried to use $csnick $msg");
@@ -979,10 +1126,14 @@ sub cs_register($$;$$) {
                return;
        }
 
+       if(services_conf_chanreg_needs_oper && !adminserv::is_svsop($user)) {
+               notice($user, "You must be network staff to register a channel\n");
+               return;
+       }
        unless(get_op($user, $chan) & ($opmodes{o} | $opmodes{a} | $opmodes{q})) {
        # This would be preferred to be a 'opmode_mask' or something
        # However that might be misleading due to hop not being enough to register
-               notice($user, "You must have channel operator status to register \002$cn\002.");
+               notice($user, "You must have channel operator status to register \002$cn\002.");
                return;
        }
 
@@ -1001,7 +1152,7 @@ sub cs_register($$;$$) {
                notice($user, ' ', "\002NOTICE:\002 Channel passwords are not used, as a security precaution.")
                        if $pass;
                set_acc($root, $user, $chan, FOUNDER);
-               $set_modelock->execute('+rnt', $cn);
+               $set_modelock->execute(services_conf_default_channel_mlock, $cn);
                do_modelock($chan);
                services::ulog($csnick, LOG_INFO(), "registered $cn", $user, $chan);
                botserv::bs_assign($user, $chan, services_conf_default_chanbot) if services_conf_default_chanbot;
@@ -1221,6 +1372,7 @@ sub cs_xop_del($$$) {
        }
 
        my $root = get_root_nick($nick);
+       my $srcnick = can_do($chan, 'ACCLIST', $user);
 
        del_acc($root, $chan);
 
@@ -1230,7 +1382,6 @@ sub cs_xop_del($$$) {
        ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str")
                if cr_chk_flag($chan, CRF_VERBOSE);
        services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
-       my $srcnick = can_do($chan, 'ACCLIST', $user);
        memolog($chan, "\002$srcnick\002 $log_str");
 }
 
@@ -1576,7 +1727,7 @@ sub cs_akick_wipe($$$) {
        memolog($chan, "\002$adder\002 $log_str");
 }
 
-sub cs_akick_enforce($$$) {
+sub cs_akick_enforce($$) {
        my ($user, $chan) = @_;
        my $cn = $chan->{CHAN};
 
@@ -1620,7 +1771,7 @@ sub cs_info($@) {
                        next;
                }
 
-               my ($descrip, $regd, $last, $topic, $topicer, $modelock, $founder, $successor, $bot, $bantype) = @result;
+               my ($descrip, $regd, $last, $topic, $topicer, $modelock, $founder, $successor, $bot, $bantype,$bantime) = @result;
 
                $modelock = modes::sanitize($modelock) unless can_do($chan, 'GETKEY', $user, { NOREPLY => 1 });
 
@@ -1644,6 +1795,7 @@ sub cs_info($@) {
                push @opts, 'Verbose' if cr_chk_flag($chan, CRF_VERBOSE);
                push @opts, 'NeverOp' if cr_chk_flag($chan, CRF_NEVEROP);
                push @opts, 'Ban type '.$bantype if $bantype;
+               push @opts, 'Ban time '.$bantime . ' seconds'  if $bantype;
                my $opts = join(', ', @opts);
 
                my @data;
@@ -1683,10 +1835,12 @@ sub cs_set_pre($$$$) {
 
                'autovoice' => 1, 'avoice' => 1,
                'neverop' => 1, 'noop' => 1,
+               'noclones' => 1,
+               'bantime' => 1,
        );
        my %override_set = (
                'hold' => 'SERVOP', 'noexpire' => 'SERVOP', 'no-expire' => 'SERVOP',
-               'freeze' => 'FREEZE', 'botstay' => 'BOT', 
+               'freeze' => 'FREEZE', 'botstay' => 'BOT', 'log' => 'LOG',
        );
 
        chk_registered($user, $chan) or return 0;
@@ -1851,7 +2005,22 @@ sub cs_set($$$;$) {
 
                return;
        }
-       
+       if($set =~ /^bantime$/i) {
+               if ( ( my $p = substr($parm, 0, 1) ) != '+' ) {
+                       $parm = '+' . $parm;
+               }
+               my $time = $parm;
+               unless ($time == 0) {
+                       $time = parse_time ($parm);
+                       if(!$time) {
+                               notice ($user, "Invalid bantime. See /msg chanserv help set bantime for examples.");
+                               return;
+                       }
+               }
+               set_bantime($time, $cn);
+               notice($user, "Ban time for \002$cn\002 now set to \002$time\002 seconds.");
+               return;
+       }
        my $val;
        if($parm =~ /^(?:no|off|false|0)$/i) { $val = 0; }
        elsif($parm =~ /^(?:yes|on|true|1)$/i) { $val = 1; }
@@ -1859,7 +2028,20 @@ sub cs_set($$$;$) {
                notice($user, "Please say \002on\002 or \002off\002.");
                return;
        }
-       
+       if ($set =~ /^(?:noclones)$/i) {
+               cr_set_flag($chan, CRF_NOCLONES, $val);
+               if($val) {
+                       notice($user,
+                               "Noclones is now \002ON\002.",
+                               "Clones will be kicked out of \002$cn\002."
+                       );
+               } else {
+                       notice($user,
+                               "Noclones is now \002OFF\002.",
+                               "People are allowed to bring clones in \002$cn\002."
+                       );
+               }
+       }
        if($set =~ /^(?:opguard|secureops)$/i) {
                cr_set_flag($chan, CRF_OPGUARD, $val);
 
@@ -2023,16 +2205,17 @@ sub cs_why($$@) {
 
        chk_registered($user, $chan) or return;
 
-       my ($candoNick, $override) = can_do($chan, 'ACCLIST', undef, $user, undef);
+       my $cn = $chan->{CHAN};
+
+       my ($candoNick, $override) = can_do($chan, 'ACCLIST', $user, { OVERRIDE_MSG => "WHY $cn @tnicks" });
        return unless $candoNick;
 
-       my $cn = $chan->{CHAN};
        my @reply;
        foreach my $tnick (@tnicks) {
                my $tuser = { NICK => $tnick };
                unless(get_user_id($tuser)) {
-                       notice($user, "\002$tnick\002: No such user.");
-                       return;
+                       push @reply, "\002$tnick\002: No such user.";
+                       next;
                }
 
                my $has;
@@ -2053,10 +2236,6 @@ sub cs_why($$@) {
                unless($n) {
                        push @reply, "\002$tnick\002 has no access to \002$cn\002.";
                }
-               if(services_conf_log_overrides && $override) {
-                       my $src = get_user_nick($user);
-                       wlog($csnick, LOG_INFO(), "\002$src\002 used override CS WHY $cn $tnick");
-               }
        }
        notice($user, @reply);
 }
@@ -2124,7 +2303,7 @@ sub cs_setmodes($$$@) {
                        notice($user, "\002$target\002 is not in \002$cn\002.");
                        next;
                }
-               
+
                my $top = get_op($tuser, $chan);
                
                if($de) {
@@ -2132,7 +2311,7 @@ sub cs_setmodes($$$@) {
                                notice($user, "\002$target\002 has no $cmd in \002$cn\002.");
                                next;
                        }
-                       
+
                        if(!$override and get_best_acc($tuser, $chan) > $acc) {
                                unless($check_override) {
                                        $override = adminserv::can_do($user, 'SUPER');
@@ -2190,11 +2369,185 @@ sub cs_drop($$) {
        botserv::bot_part_if_needed(undef(), $chan, "Channel dropped.");
 }
 
+#my ($bansref, $unbansref, $expires) = parse_bans ($user, $chan, '', @targets, 1, $default_expiry);
+#my ($bansref, $unbansref, $expires) = parse_bans ($user, $chan, '', @targets, 1, $expiry);
+sub parse_bans($$$$;$$) {
+       my ($user, $chan, $type, $targetsref, $temp, $expiry) = (@_);
+       my (@bans, @unbans);
+       my ($nick, $override);
+
+       my @targets = @$targetsref;
+       my $cn = $chan->{CHAN};
+       my $src = get_user_nick($user);
+       my $srclevel = get_best_acc($user, $chan);
+
+       ($nick, $override) = can_do($chan, 'BAN', $user, { ACC => $srclevel }); 
+       return unless $nick;
+
+       my @errors = (
+               ["I'm sorry, $src, I'm afraid I can't do that."],
+               ["They are not in \002$cn\002."],
+               [$err_deny],
+               ["User not found"],
+       );
+
+       foreach my $target (@targets) {
+               my $tuser;
+
+               if ($target =~ /^\+/ && $temp) {
+                       $expiry = $target;
+                       next;
+               }
+               elsif(ref($target)) {
+                       $tuser = $target;
+               }
+               elsif($target =~ /\,/) {
+                       push @targets, split(',', $target);
+                       next;
+               }
+               elsif($target eq '') {
+                       # Should never happen
+                       # but it could, given the split above
+                       next;
+               }
+               elsif($target =~ /^-/) {
+                       $target =~ s/^-//;
+                       push @unbans, $target;
+                       next;
+               }
+               elsif($target =~ /[!@]+/) {
+                       $target = normalize_hostmask($target);
+                       if ($temp) {
+                               push @bans, [$target, $expiry];
+                       } else {
+                               push @bans, $target;
+                       }
+                       next;
+               }
+               elsif(valid_nick($target)) {
+                       $tuser = { NICK => $target };
+               }
+               elsif($target = validate_ban($target)) {
+                       if ($temp) {
+                               push @bans, [$target, $expiry];
+                       } else {
+                               push @bans, $target;
+                       }
+                       next;
+               } else {
+                       notice($user, "Not a valid ban target: $target");
+                       next;
+               }
+
+               my $targetlevel = get_best_acc($tuser, $chan);
+
+               if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
+                       push @{$errors[0]}, get_user_nick($tuser);
+                       next;
+               }
+
+               unless(get_user_id($tuser)) {
+                       push @{$errors[1]}, get_user_nick($tuser);
+                       next;
+               }
+
+               if( $srclevel <= $targetlevel and not ($override && check_override($user, 'BAN', "BAN $cn $target")) ) {
+                       push @{$errors[2]}, $target;
+                       next;
+               }
+
+               if ($temp) {
+                       push @bans, [make_banmask($chan, $tuser, $type), $expiry];
+               } else {
+                       push @bans, make_banmask($chan, $tuser, $type);
+               }
+       }
+
+       if (!is_registered($chan)) {
+               notice ($user,
+                       "$cn is not registered"
+               );
+               return;
+       }
+
+       foreach my $errlist (@errors) {
+               if(@$errlist > 1) {
+                       my $msg = shift @$errlist;
+
+                       foreach my $e (@$errlist) { $e = "\002$e\002" }
+
+                       notice($user,
+                               "Cannot ban ".
+                               enum("or", @$errlist).
+                               ": $msg"
+                       );
+               }
+       }
+
+       return (\@bans, \@unbans);
+}
+
+sub cs_tempban($$) {
+       my ($user, $argstring) = @_;
+       my ( $expiry, $cn, $chan );
+
+       my @args = split(/ /, $argstring);
+       my $numargs = scalar @args;
+
+       for (my $i = 0; $i < $numargs; $i++) {
+               if ($args[$i] =~ /\#/) {
+                       $cn = $args[$i];
+                       $chan = { CHAN => $cn };
+                       splice (@args, $i, 1);
+               }
+       }
+
+       if (!defined($cn) or !length($cn)) {
+               notice ($user, "No channel given. The channel name \002must\002 include the # character.");
+               return;
+       }
+
+       if ($args[-1] =~ /\+/) { #expire time is last arguement
+               $expiry = pop @args;
+               $expiry = parse_time($expiry);
+       } else { #expire time is somewhere else (if given), get default expiry for now. 
+               $expiry = get_bantime($chan);
+       }
+
+       my @targets;
+
+       foreach my $arg (@args) {
+               if ($arg =~ /\,/) {
+                       push @targets, split(/\,/, $arg);
+                       next;
+               } else {
+                       push @targets, $arg;
+               }
+       }
+
+       my $src = get_user_nick($user);
+
+       my ($bansref, $unbansref) = parse_bans ($user, $chan, '', \@targets, 1, $expiry);
+
+       if ((!$bansref || !scalar @$bansref) && (!$unbansref || !scalar @$unbansref)) {
+               return;
+       }
+
+       if(scalar @$bansref) {
+               tempban ($chan, @$bansref);
+
+               ircd::notice(agent($chan), $cn, "$src used TEMPBAN ".join(' ', @$bansref))
+                       if (lc $user->{AGENT} eq lc $csnick) and (cr_chk_flag($chan, CRF_VERBOSE) and scalar(@$bansref));
+       }
+       cs_unban($user, $chan, @$unbansref) if scalar(@$unbansref);
+}
+
 sub cs_kick($$$;$$) {
        my ($user, $chan, $target, $ban, $reason) = @_;
 
        my $cmd = ($ban ? 'KICKBAN' : 'KICK');
        my $perm = ($ban ? 'BAN' : 'KICK');
+
        if(ref($chan) ne 'HASH' || !defined($chan->{CHAN})) {
                notice($user, "Invalid $cmd command, no channel specified");
                return;
@@ -2218,7 +2571,7 @@ sub cs_kick($$$;$$) {
        );
        my @notinchan = ();
        my $peace = ({modes::splitmodes(get_modelock($chan))}->{Q}->[0] eq '+');
-       
+
        my @targets = split(/\,/, $target);
        foreach $target (@targets) {
                my $tuser = { NICK => $target };
@@ -2228,7 +2581,7 @@ sub cs_kick($$$;$$) {
                        push @{$errors[0]}, $target;
                        next;
                }
-               
+
                if(get_user_id($tuser)) {
                        unless(is_in_chan($tuser, $chan)) {
                                if ($ban) {
@@ -2249,20 +2602,21 @@ sub cs_kick($$$;$$) {
                        push @{$errors[2]}, $target;
                        next;
                }
-       
+
                if($ban) {
-                       kickban($chan, $tuser, undef, $reason);
+                       kickban($chan, $tuser, undef, $reason, 1);
                } else {
                        ircd::kick(agent($chan), $cn, $target, $reason) unless adminserv::is_service($user);
                }
        }
-       
+       ircd::flushmodes() if($ban);
+
        foreach my $errlist (@errors) {
                if(@$errlist > 1) {
                        my $msg = shift @$errlist;
-                       
+       
                        foreach my $e (@$errlist) { $e = "\002$e\002" }
-                       
+
                        notice($user,
                                "Cannot $cmd ".
                                enum("or", @$errlist).
@@ -2293,101 +2647,22 @@ sub cs_kickmask($$$;$$) {
 
 sub cs_ban($$$@) {
        my ($user, $chan, $type, @targets) = @_;
-       my $cn = $chan->{CHAN};
-       my $src = get_user_nick($user);
 
-       my $srclevel = get_best_acc($user, $chan);
-       my ($nick, $override) = can_do($chan, 'BAN', $user, { ACC => $srclevel });
-       return unless $nick;
+       my $src = get_user_nick ($user);
 
-       my @errors = (
-               ["I'm sorry, $src, I'm afraid I can't do that."],
-               ["User not found"],
-               [$err_deny]
-       );
-
-       my (@bans, @unbans);
-       foreach my $target (@targets) {
-               my $tuser;
-
-               if(ref($target)) {
-                       $tuser = $target;
-               } 
-               elsif($target =~ /\,/) {
-                       push @targets, split(',', $target);
-                       next;
-               }
-               elsif($target eq '') {
-                       # Should never happen
-                       # but it could, given the split above
-                       next;
-               }
-               elsif($target =~ /^-/) {
-                       $target =~ s/^\-//;
-                       push @unbans, $target;
-                       next;
-               }
-=cut
-               elsif($target =~ /[!@]+/) {
-                       ircd::debug("normalizing hostmask $target");
-                       #$target = normalize_hostmask($target);
-#=cut
-                       my ($nick, $ident, $host) = parse_mask($target);
-                       $nick = '*' unless length($nick);
-                       $ident = '*' unless length($ident);
-                       $host = '*' unless length($host);
-                       $target = "$nick\!$ident\@$host";
-#=cut
-                       ircd::debug("normalized hostmask: $target");
-
-                       push @bans, $target;
-                       next;
-               }
-=cut
-               elsif(valid_nick($target)) {
-                       $tuser = { NICK => $target };
-               }
-               elsif($target = validate_ban($target)) {
-                       push @bans, $target;
-                       next;
-               }
-               my $targetlevel = get_best_acc($tuser, $chan);
-
-               if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
-                       push @{$errors[0]}, get_user_nick($tuser);
-                       next;
-               }
-               
-               unless(get_user_id($tuser)) {
-                       push @{$errors[1]}, get_user_nick($tuser);
-                       next;
-               }
-               if( $srclevel <= $targetlevel and not ($override && check_override($user, 'BAN', "BAN $cn $target")) ) {
-                       push @{$errors[2]}, $target;
-                       next;
-               }
+       my $cn = $chan->{CHAN};
 
-               push @bans, make_banmask($chan, $tuser, $type);
-       }
+       my ($bansref, $unbansref) = parse_bans ($user, $chan, $type, \@targets);
 
-       foreach my $errlist (@errors) {
-               if(@$errlist > 1) {
-                       my $msg = shift @$errlist;
-                       
-                       foreach my $e (@$errlist) { $e = "\002$e\002" }
-                       
-                       notice($user,
-                               "Cannot ban ".
-                               enum("or", @$errlist).
-                               ": $msg"
-                       );
-               }
+       if ((!$bansref || !scalar @$bansref) && (!$unbansref || !scalar @$unbansref)) {
+               return;
        }
 
-       ircd::ban_list(agent($chan), $cn, +1, 'b', @bans) if (scalar(@bans));
-       ircd::notice(agent($chan), $cn, "$src used BAN ".join(' ', @bans))
-               if (lc $user->{AGENT} eq lc $csnick) and (cr_chk_flag($chan, CRF_VERBOSE) and scalar(@bans));
-       cs_unban($user, $chan, @unbans) if scalar(@unbans);
+       #ircd::ban_list(agent($chan), $cn, +1, 'b', @bans) if (scalar(@bans));
+       tempban($chan, map { [ $_, get_bantime($chan)] } @$bansref);
+       ircd::notice(agent($chan), $cn, "$src used BAN ".join(' ', @$bansref))
+               if (lc $user->{AGENT} eq lc $csnick) and (cr_chk_flag($chan, CRF_VERBOSE) and scalar(@$bansref));
+       cs_unban($user, $chan, @$unbansref) if scalar(@$unbansref);
 }
 
 sub cs_invite($$@) {
@@ -2405,52 +2680,55 @@ sub cs_invite($$@) {
        my @invited;
        foreach my $target (@targets) {
                my $tuser;
-               if(lc($src) eq lc($target)) {
+               my $tnick;
+               if(ref($target)) {
+                       $tuser = $target;
+                       $tnick = get_user_nick($tuser);
+               } elsif(lc($src) eq lc($target)) {
                        $tuser = $user;
-               }
-               elsif($target =~ /\,/) {
+                       $tnick = $src;
+               elsif($target =~ /\,/) {
                        push @targets, split(',', $target);
                        next;
-               }
-               elsif($target eq '') {
+               } elsif($target eq '') {
                        # Should never happen
                        # but it could, given the split above
                        next;
-               }
-               else {
+               } else {
                        $tuser = { NICK => $target };
+                       $tnick = $target;
                }
 
                my $candoOpts = { ACC => $srclevel, NOREPLY => 1, OVERRIDE_MSG => "INVITE $cn $target" };
-               if(lc($src) eq lc($target)) {
+               if(lc($src) eq lc($tnick)) {
                        unless(can_do($chan, 'InviteSelf', $user, $candoOpts)) {
-                               push @{$errors[2]}, $target;
+                               push @{$errors[2]}, $tnick;
                                next;
                        }
                }
                else {
                        unless(can_do($chan, 'INVITE', $user, $candoOpts)) {
-                               push @{$errors[2]}, $target;
+                               push @{$errors[2]}, $tnick;
                                next;
                        }
-                       
-                       unless(nickserv::is_online($target)) {
-                               push @{$errors[0]}, $target;
+
+                       unless(nickserv::is_online($tnick)) {
+                               push @{$errors[0]}, $tnick;
                                next;
                        }
-                       
+
                        # invite is annoying, so punish them mercilessly
                        return if flood_check($user, 2);
                }
-               
+
                if(is_in_chan($tuser, $chan)) {
-                       push @{$errors[1]}, $target;
+                       push @{$errors[1]}, $tnick;
                        next;
                }
-               
-               ircd::invite(agent($chan), $cn, $target); push @invited, $target;
-               ircd::notice(agent($chan), $target, "\002$src\002 has invited you to \002$cn\002.")
-                       unless(lc($src) eq lc($target));
+
+               ircd::invite(agent($chan), $cn, $tnick); push @invited, $tnick;
+               ircd::notice(agent($chan), $tnick, "\002$src\002 has invited you to \002$cn\002.")
+                       unless(lc($src) eq lc($tnick));
        }
 
        foreach my $errlist (@errors) {
@@ -2466,7 +2744,7 @@ sub cs_invite($$@) {
                        );
                }
        }
-       
+
        ircd::notice(agent($chan), $cn, "$src used INVITE ".join(' ', @invited))
                if (lc $user->{AGENT} eq lc $csnick)and cr_chk_flag($chan, CRF_VERBOSE) and scalar(@invited);
 }
@@ -2477,7 +2755,7 @@ sub cs_close($$$) {
        my $cn = $chan->{CHAN};
        my $oper;
 
-       unless($oper = adminserv::is_svsop($user, adminserv::S_ROOT())) {
+       unless($oper = adminserv::can_do($user, 'SERVOP')) {
                notice($user, $err_deny);
                return;
        }
@@ -2515,10 +2793,12 @@ sub cs_close($$$) {
        my $cmsg = "is closed [$src $time]: $reason";
 
        if ($type == CRF_CLOSE) {
+               cr_set_flag($chan, CRF_CLOSE, 1); #set flags
                clear_users($chan, "Channel $cmsg");
                ircd::settopic(agent($chan), $cn, $src, time(), "Channel $cmsg")
        }
        elsif ($type == CRF_DRONE) {
+               cr_set_flag($chan, CRF_DRONE, 1); #set flags
                chan_kill($chan, "$cn $cmsg");
        }
 
@@ -2558,7 +2838,7 @@ sub cs_clear_users($$;$) {
                notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
                return;
        }
-       
+
        clear_users($chan, "CLEAR USERS by \002$src\002".($reason?" reason: $reason":''));
 }
 
@@ -2696,12 +2976,12 @@ sub cs_welcome_del($$$) {
 }
 
 sub cs_alist($$;$) {
-        my ($user, $chan, $mask) = @_;
+       my ($user, $chan, $mask) = @_;
        my $cn = $chan->{CHAN};
 
        chk_registered($user, $chan) or return;
 
-        my $slevel = get_best_acc($user, $chan);
+       my $slevel = get_best_acc($user, $chan);
 
        can_do($chan, 'ACCLIST', $user, { ACC => $slevel }) or return;
 
@@ -2754,7 +3034,8 @@ sub cs_unban($$@) {
        my ($user, $chan, @parms) = @_;
        my $cn = $chan->{CHAN};
 
-       my $self = 1 if ( (scalar(@parms) == 1) and ( lc($parms[0]) eq lc(get_user_nick($user)) ) );
+       my $self;
+       $self = 1 if ( (scalar(@parms) == 1) and ( lc($parms[0]) eq lc(get_user_nick($user)) ) );
        if ($parms[0] eq '*') {
                cs_clear_bans($user, $chan);
                return;
@@ -2962,14 +3243,21 @@ sub cs_mode($$$@) {
                'v' => 'VOICE',
        );
        my $sign = '+'; my $cn = $chan->{CHAN};
-       my ($modes_out, @parms_out);
+       my ($modes_out, @parms_out, @bans);
        foreach my $mode (split(//, $modes_in)) {
                $sign = $mode if $mode =~ /[+-]/;
                if ($permhash{$mode}) {
                        my $parm = shift @parms_in;
                        cs_setmodes($user, ($sign eq '-' ? 'de' : '').$permhash{$mode}, $chan, $parm);
                }
-               elsif($mode =~ /[beIlLkjf]/) {
+               elsif ($mode eq 'b') {
+                       my $parm = shift @parms_in;
+                       if($sign eq '-') {
+                               $parm = '-'.$parm;
+                       }
+                       push @bans, $parm;
+               }
+               elsif($mode =~ /[eIlLkjf]/) {
                        $modes_out .= $mode;
                        push @parms_out, shift @parms_in;
                } else {
@@ -2977,6 +3265,9 @@ sub cs_mode($$$@) {
                }
        }
 
+       if(scalar(@bans)) {
+               cs_ban($user, $chan, undef, @bans);
+       }
        return if $modes_out =~ /^[+-]*$/;
        ircd::setmode(agent($chan), $chan->{CHAN}, $modes_out, join(' ', @parms_out));
        do_modelock($chan, $modes_out.' '.join(' ', @parms_out));
@@ -3027,7 +3318,7 @@ sub cs_copy($$@) {
                } elsif(!(get_op($user, $chan2) & ($opmodes{o} | $opmodes{a} | $opmodes{q}))) {
                        # This would be preferred to be a 'opmode_mask' or something
                        # However that might be misleading due to hop not being enough to register
-                       notice($user, "You must have channel operator status to register \002$cn2\002.");
+                       notice($user, "You must have channel operator status to register \002$cn2\002.");
                        return;
                } else {
                        cs_copy_chan_all($user, $chan1, $chan2);
@@ -3151,7 +3442,7 @@ sub cs_mlock($$$@) {
        # does this need its own privilege now?
        can_do($chan, 'SET', $user) or return;
        my $modes;
-       {
+       if(scalar(@args)) {
                my ($modes_in, @parms_in) = validate_chmodes(shift @args, @args);
                $modes = $modes_in.' '.join(' ', @parms_in);
                @args = undef;
@@ -3171,8 +3462,12 @@ sub cs_mlock($$$@) {
        elsif(lc $cmd eq 'set') {
                $modes = modes::merge($modes, "+r", 1);
                $set_modelock->execute($modes, $cn);
+       }
+       elsif(lc $cmd eq 'reset') {
+               $set_modelock->execute(services_conf_default_channel_mlock, $cn);
        } else {
                notice($user, "Unknown MLOCK command \"$cmd\"");
+               return;
        }
 
        notice($user, "Mode lock for \002$cn\002 has been set to: \002$modes\002");
@@ -3185,8 +3480,8 @@ sub cs_mlock($$$@) {
 }
 
 use SrSv::MySQL::Stub {
-       getChanUsers => ['COLUMN', "SELECT user.nick FROM chanuser, user
-               WHERE chanuser.chan=? AND user.id=chanuser.nickid AND chanuser.joined=1"]
+       getChanUsers => ['COLUMN', "SELECT user.nick FROM chanuser JOIN user ON (user.id=chanuser.nickid)
+               WHERE chanuser.chan=? AND chanuser.joined=1"]
 };
 
 sub cs_resync($@) {
@@ -3195,9 +3490,52 @@ sub cs_resync($@) {
                my $chan = { CHAN => $cn };
                next unless cs_clear_ops($user, $chan, 'Resync');
                cs_updown2($user, 'up', $chan, getChanUsers($cn));
+               if(can_do($chan, 'AKickEnforce', $user, { OVERRIDE_MSG => "AKICK $cn ENFORCE", NOREPLY => 1 })) {
+                       cs_akick_enforce($user, $chan);
+               }
        }
 }
 
+sub cs_join($@) {
+       my ($user, @cns) = @_;
+       my @reply;
+       my @out_cns;
+       foreach my $cn (@cns) {
+               if($cn =~ /,/) {
+                       push @cns, split(',', $cn);
+               }
+               elsif($cn eq '') {
+                       next;
+               }
+               my $chan = { CHAN => $cn };
+               my $cando_opts = { NOREPLY => 1 };
+               if(check_akick($user, $chan, 1)) {
+                       push @reply, "You are banned from $cn";
+                       next;
+               } elsif(!can_do($chan, 'JOIN', $user, $cando_opts)) {
+                       push @reply, "$cn is a private channel.";
+                       next;
+               }
+               if(is_in_chan($user, $chan)) {
+                       next;
+               }
+               if(can_do($chan, 'InviteSelf', $user, $cando_opts)) {
+                       cs_invite($user, $chan, $user);
+               }
+               push @out_cns, $cn;
+               
+       }
+       ircd::svsjoin(get_user_agent($user), get_user_nick($user), @out_cns) if scalar @out_cns;
+       notice($user, @reply) if scalar @reply;
+}
+
+sub cs_topic($$@) {
+       my ($user, $cn, @args) = @_;
+       my ($chan, $msg) = ($cn->{CHAN}, join(" ", @args));
+       can_do($cn, 'SETTOPIC', $user) or return undef;
+       ircd::settopic(agent($cn), $chan, get_user_nick($user), time, ($msg =~ /^none/i ? "" : $msg));
+}   
+
 ### MISCELLANEA ###
 
 # these are helpers and do NOT check if $cn1 or $cn2 is reg'd
@@ -3301,12 +3639,18 @@ sub make_banmask($$;$) {
        return $type."$nick!$ident\@$vhost";
 }
 
-sub kickban($$$$) {
-       my ($chan, $user, $mask, $reason) = @_;
+sub kickban($$$$;$$) {
+       my ($chan, $user, $mask, $reason, $noflush) = @_;
+
        my $cn = $chan->{CHAN};
-       my $nick = get_user_nick($user);
+       my $nick;
+       $nick = get_user_nick($user) if ($user);
+
+       if (!$user && !$mask) {
+               return;
+       }
 
-       return 0 if adminserv::is_service($user);
+       return 0 if $user && adminserv::is_service($user);
 
        my $agent = agent($chan);
 
@@ -3315,9 +3659,11 @@ sub kickban($$$$) {
        }
 
        enforcer_join($chan) if (get_user_count($chan) <= 1);
-       ircd::setmode($agent, $cn, '+b', $mask);
-       ircd::flushmodes();
-       ircd::kick($agent, $cn, $nick, $reason);
+       #ircd::setmode($agent, $cn, '+b', $mask);
+       tempban($chan, [$mask, get_bantime($chan)]);
+
+       ircd::flushmodes() unless $noflush;
+       ircd::kick($agent, $cn, $nick, $reason) if ($nick);
        return 1;
 }
 
@@ -3325,7 +3671,7 @@ sub kickban_multi($$$) {
        my ($chan, $users, $reason) = @_;
        my $cn = $chan->{CHAN};
        my $agent = agent($chan);
-       
+
        enforcer_join($chan);
        ircd::setmode($agent, $cn, '+b', '*!*@*');
        ircd::flushmodes();
@@ -3341,7 +3687,7 @@ sub clear_users($$)  {
        my $cn = $chan->{CHAN};
        my $agent = agent($chan);
        my $i;
-       
+
        enforcer_join($chan);
        ircd::setmode($agent, $cn, '+b', '*!*@*');
        ircd::flushmodes();
@@ -3365,7 +3711,7 @@ sub kickmask($$$$)  {
        $nick = '%' if ($nick eq '');
        $ident = '%' if ($ident eq '');
        $host = '%' if ($host eq '');
-       
+
        if ($ban) {
                my $banmask = $nick.'!'.$ident.'@'.$host;
                $banmask =~ tr/%_/*?/;
@@ -3395,7 +3741,7 @@ sub kickmask_noacc($$$$)  {
        $nick = '%' if ($nick eq '');
        $ident = '%' if ($ident eq '');
        $host = '%' if ($host eq '');
-       
+
        if ($ban) {
                my $banmask = $nick.'!'.$ident.'@'.$host;
                $banmask =~ tr/%_/*?/;
@@ -3443,7 +3789,7 @@ sub clear_bans($;$) {
        my $agent = agent($chan);
        $type = 0 unless defined $type;
        my $mode = ($type == 128 ? 'e' : 'b');
-       
+
        my @banlist = ();
        $get_all_bans->execute($cn, $type);
        while(my ($mask) = $get_all_bans->fetchrow_array) {
@@ -3467,7 +3813,7 @@ sub unban_user($@) {
                ircd::unban_nick(agent($chan), $cn, @nicklist);
                return scalar(@nicklist);
        }
-       
+
        foreach my $tuser (@userlist) {
                my $tuid;
                unless($tuid = get_user_id($tuser)) {
@@ -3529,41 +3875,85 @@ sub do_nick_akick($$;$) {
        my ($reason) = $get_nick_akick->fetchrow_array(); $get_nick_akick->finish();
 
        return 0 if adminserv::is_svsop($tuser, adminserv::S_HELP());
+       if(defined($reason) && $reason =~ /\|/) {
+               ($reason, undef) = split(/ ?\| ?/, $reason, 2);
+       }
        kickban($chan, $tuser, undef, "User has been banned from ".$cn.($reason?": $reason":''));
 }
 
-sub do_status($$) {
-       my ($user, $chan) = @_;
-
-       return 0 if cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE));
-       
-       my $uid = get_user_id($user);
-       my $nick = get_user_nick($user);
-       my $cn = $chan->{CHAN};
+sub check_akick($$;$) {
+       my ($user, $chan, $check_only) = @_;
 
-       my ($acc, $root) = get_best_acc($user, $chan, 2);
-       if ($acc == -1) {
-               do_nick_akick($user, $chan, $root);
+       if(adminserv::is_svsop($user, adminserv::S_HELP())) {
                return 0;
        }
-       unless(can_do($chan, 'JOIN', $user, { ACC => $acc })) {
-               kickban($chan, $user, undef, 'This is a private channel.');
-               return 0;
+       my ($acc, $root) = get_best_acc($user, $chan, 2);
+       if ($acc == -1) {
+               do_nick_akick($user, $chan, $root) unless $check_only;
+               return 1;
        }
-       
-       unless($acc or adminserv::is_svsop($user, adminserv::S_HELP()) ) {
+       my $cn = $chan->{CHAN};
+       my $uid = get_user_id($user);
+       unless($acc) {
                $get_akick->execute($uid, $cn);
                if(my @akick = $get_akick->fetchrow_array) {
-                       akickban($cn, @akick);
+                       akickban($cn, @akick) unless $check_only;
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+sub do_status($$;$) {
+       my ($user, $chan, $check_only) = @_;
+
+       return 0 if cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE));
+
+       my $nick = get_user_nick($user);
+
+       if(check_akick($user, $chan, $check_only)) {
+               return 0;
+       }
+       my ($acc, $root) = get_best_acc($user, $chan, 2);
+       my ($accnick, $override) = can_do($chan, 'JOIN', $user, { ACC => $acc, NOREPLY => 1 });
+       unless ($acc > 0 || $override) {
+               if (clones_exist ($user, $chan) && !adminserv::is_service($user)) {
+                       my $mask = make_banmask($chan, $user);
+                       my $cn = $chan->{CHAN};
+
+                       tempban($chan, [ $mask, 60 ]);
+                       ircd::kick(agent($chan), $cn, $nick, "No clones allowed in this channel.");
+
                        return 0;
                }
        }
        
-       set_modes($user, $chan, $acc, cr_chk_flag($chan, CRF_SPLITOPS, 0))
-               if is_registered($chan)
-               and not is_neverop_user($user)
-               and not cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE | CRF_NEVEROP));
-       
+       if(!$accnick && !$override) {
+               kickban($chan, $user, undef, 'This is a private channel.')
+                       unless $check_only;
+               return 0;
+       }
+
+       if( !$check_only && is_registered($chan) &&
+               !cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE)) )
+       {
+               my $neverop = (is_neverop_user($user) || cr_chk_flag($chan, CRF_NEVEROP, 1));
+               my $no_deop = cr_chk_flag($chan, CRF_SPLITOPS, 0);
+               my $op_anyway = 0;
+               if($neverop && cr_chk_flag($chan, CRF_AUTOVOICE, 1) && $acc > 2) {
+                       $acc = 2;
+                       $no_deop = 0;
+                       $op_anyway = 1;
+               }
+               set_modes($user, $chan, $acc,
+                       # $acc == 3 is +h
+                       # this probably needs to be configurable for ports
+                       # also Unreal may [optionally] set +q on join.
+                       $no_deop,
+                       !$neverop || $op_anyway,
+               );
+       }
+
        return 1;
 }
 
@@ -3604,6 +3994,10 @@ sub akickban(@) {
                $host =~ tr/\%\_/\*\?/;
        }
 
+       if(defined($reason) && $reason =~ /\|/) {
+               ($reason, undef) = split(/ ?\| ?/, $reason, 2);
+       }
+
        return kickban($chan, $target, "$bnick!$ident\@$host", "User has been banned from ".$cn.($reason?": $reason":''));
 }
 
@@ -3671,13 +4065,10 @@ sub enforcer_part($) {
 sub fix_private_join_before_id($) {
        my ($user) = @_;
 
-       my @cns;
-       
-       $get_recent_private_chans->execute(get_user_id($user));
-       while(my ($cn) = $get_recent_private_chans->fetchrow_array) {
+       my @cns = get_recent_private_chans(get_user_id($user));
+       foreach my $cn (@cns) {
                my $chan = { CHAN => $cn };
                unban_user($chan, $user);
-               push @cns, $cn;
        }
 
        ircd::svsjoin($csnick, get_user_nick($user), @cns) if @cns;
@@ -3688,9 +4079,9 @@ sub fix_private_join_before_id($) {
 sub get_user_count($) {
        my ($chan) = @_;
        my $cn = $chan->{CHAN};
-       
+
        $get_user_count->execute($cn);
-       
+
        return $get_user_count->fetchrow_array;
 }
 
@@ -3757,7 +4148,7 @@ sub get_modelock($) {
        } else {
                $cn = $chan;
        }
-                                                       
+
        $get_modelock->execute($cn);
        my ($ml) = $get_modelock->fetchrow_array;
        $get_modelock->finish();
@@ -3771,7 +4162,7 @@ sub do_modelock($;$) {
        my $seq = $ircline;
 
        $get_modelock_lock->execute; $get_modelock_lock->finish;
-       
+
        $get_chanmodes->execute($cn);
        my ($omodes) = $get_chanmodes->fetchrow_array;
        my $ml = get_modelock($chan);
@@ -3788,7 +4179,7 @@ sub do_modelock_fast($$$$) {
        my $nmodes = modes::add($omodes, $modes, 1);
        $ml = modes::diff($nmodes, $ml, 1);
        $set_chanmodes->execute(modes::add($nmodes, $ml, 1), $cn);
-       
+
        return $ml;
 }
 
@@ -3807,7 +4198,7 @@ sub is_level($) {
        my ($perm) = @_;
 
        $is_level->execute($perm);
-       
+
        return $is_level->fetchrow_array;
 }
 
@@ -3835,7 +4226,7 @@ sub is_in_chan($$) {
 sub is_registered($) {
        my ($chan) = @_;
        my $cn = $chan->{CHAN};
-       
+
        $is_registered->execute($cn);
        if($is_registered->fetchrow_array) {
                return 1;
@@ -3848,7 +4239,7 @@ sub get_user_chans($) {
        my ($user) = @_;
        my $uid = get_user_id($user);
        my @chans;
-       
+
        $get_user_chans->execute($uid, $ircline, $ircline+1000);
        while(my ($chan) = $get_user_chans->fetchrow_array) {
                push @chans, $chan;
@@ -3861,7 +4252,7 @@ sub get_user_chans_recent($) {
        my ($user) = @_;
        my $uid = get_user_id($user);
        my (@curchans, @oldchans);
-       
+
        $get_user_chans_recent->execute($uid);
        while(my ($cn, $joined, $op) = $get_user_chans_recent->fetchrow_array) {
                if ($joined) {
@@ -3939,7 +4330,8 @@ sub get_acc($$) {
 sub set_acc($$$$) {
        my ($nick, $user, $chan, $level) = @_;
        my $cn = $chan->{CHAN};
-       my $adder = get_best_acc($user, $chan, 1) if $user;
+       my $adder;
+       $adder = get_best_acc($user, $chan, 1) if $user;
 
        $set_acc1->execute($cn, $level, $nick);
        $set_acc2->execute($level, $adder, $cn, $nick);
@@ -4011,7 +4403,7 @@ sub set_modes_allchan($;$) {
 sub set_modes_allnick($$$) {
        my ($nick, $chan, $level) = @_;
        my $cn = $chan->{CHAN};
-       
+
        $get_using_nick_chans->execute($nick, $cn);
        while(my ($n) = $get_using_nick_chans->fetchrow_array) {
                my $user = { NICK => $n };
@@ -4025,19 +4417,22 @@ sub set_modes_allnick($$$) {
 }
 
 # If channel has OPGUARD, $doneg is true.
-sub set_modes($$$;$) {
-       my ($user, $chan, $acc, $doneg) = @_;
+sub set_modes($$$;$$) {
+       my ($user, $chan, $acc, $doneg, $dopos) = @_;
+       # can you say eww?
+       $dopos = 1 unless defined($dopos);
+       $doneg = 0 unless defined($doneg);
        my $cn = $chan->{CHAN};
 
-       
+
        if ($acc < 0) {
        # Do akick stuff here.
        }
-       
-       my $dst = $ops[$acc];
+
+       my $dst = ( $acc > 0 ? $ops[$acc] : 0 );
        my $cur = get_op($user, $chan);
        my ($pos, $neg);
-       
+
        if (cr_chk_flag($chan, CRF_FREEZE)) {
                set_mode_mask($user, $chan, $cur, undef);
                return;
@@ -4051,7 +4446,7 @@ sub set_modes($$$;$) {
        $neg = ($dst ^ $cur) & $cur if $doneg;
 
        if($pos or $neg) {
-               set_mode_mask($user, $chan, $neg, $pos);
+               set_mode_mask($user, $chan, ($doneg ? $neg : '-'), ($dopos ? $pos : '+'));
        }
 
        if($pos) {
@@ -4154,7 +4549,10 @@ sub can_do($$$;$) {
                # and checking if an override was used is becoming tricky.
                # We had a case in cs_kick where an oper should be able to override +Q/$peace
                # but cannot b/c they have regular access in that channel.
-               my (undef, $override) = check_override($user, $perm);
+               my $override;
+               if(defined($user)) {
+                       (undef, $override) = check_override($user, $perm);
+               }
                return (wantarray ? ($nick, $override) : $nick);
        } elsif ( $user and adminserv::is_svsop($user, adminserv::S_HELP()) ) { 
                #set_lastused($cn, get_user_id($user));
@@ -4222,7 +4620,7 @@ sub can_keep_op($$$$) {
 # LEVELS affect opguard, the loop will have to be unrolled.
 # --
 # Only call this if you've already checked opguard, as we do not check it here.
-# -- 
+# --
 # Remember, this isn't a permission check if someone is allowed to op someone [else],
 # rather this checks if the person being opped is allowed to keep/have it.
        my ($user, $chan, $tuser, $opmode) = @_;
@@ -4384,7 +4782,7 @@ sub memolog($$) {
 
 sub get_ban_num($$) {
        my ($chan, $num) = @_;
-       $get_ban_num->execute($chan->{CHAN}, $num-1);
+       $get_ban_num->execute($chan->{CHAN}, 0, $num-1);
        my ($mask) = $get_ban_num->fetchrow_array();
        $get_ban_num->finish();
        return sql2glob($mask);
@@ -4438,6 +4836,7 @@ sub user_join_multi($$) {
 
        foreach my $user (@$users) {
                $user->{__ID} = get_user_id($user);
+               
                unless (defined($user->{__ID})) {
                        # This does happen occasionally. it's a BUG.
                        # At least we have a diagnostic for it now.
@@ -4621,20 +5020,20 @@ sub chan_mode($$$$) {
        my $user = { NICK => $src };
        my $chan = { CHAN => $cn };
        my ($sign, $num);
-       
+
        # XXX This is not quite right, but maybe it's good enough.
        my $mysync = ($src =~ /\./ ? 0 : 1);
        
        if($modes !~ /^[beIvhoaq+-]+$/ and (!synced() or $mysync)) {
                do_modelock($chan, "$modes $args");
        }
-       
+
        my $opguard = (!current_message->{SYNC} and cr_chk_flag($chan, CRF_OPGUARD, 1));
        
        my @perms = ('VOICE', 'HALFOP', 'OP', 'PROTECT');
        my $unmodes = '-';
        my @unargs;
-       
+
        my @modes = split(//, $modes);
        my @args = split(/ /, $args);
 
@@ -4653,7 +5052,7 @@ sub chan_mode($$$$) {
                        $num = 2 if $mode eq 'o';
                        $num = 3 if $mode eq 'a';
                        $num = 4 if $mode eq 'q';
-               
+
                        if($opguard and $sign == 1 and
                                !can_keep_op($user, $chan, $auser, $mode)
                        ) {
@@ -4690,23 +5089,48 @@ sub chan_mode($$$$) {
 
 sub process_ban($$$$) {
        my ($cn, $arg, $src, $type, $sign) = @_;
-       
+
+       my $arg2 = $arg;
+
        $arg =~ tr/\*\?/\%\_/;
-       
+
        if ($sign > 0) {
                $add_ban->execute($cn, $arg, $src, $type);
        } else {
                $delete_ban->execute($cn, $arg, $type);
+               del_tempban($cn, $arg2);
        }
 }
-
+sub cs_topicappend {
+       my ($user, $cn, $topicappend) = @_;
+       $get_topic->execute($cn);
+       my ($ntopic, $nsetter, $ntime) = $get_topic->fetchrow_array;
+       my $newtopic;
+       if ($ntopic) {
+               $newtopic = $ntopic . " | " . $topicappend;
+       }
+       else { $newtopic = $topicappend; }
+       cs_topic ($user, { CHAN => $cn }, $newtopic);
+}
+sub cs_topicprepend {
+       my ($user, $cn, $topicprepend) = @_;
+       $get_topic->execute($cn);
+       my ($ntopic, $nsetter, $ntime) = $get_topic->fetchrow_array;
+       my $newtopic;
+       if ($ntopic) {
+               $newtopic = $topicprepend . " | " . $ntopic;
+       }
+       else { $newtopic = $topicprepend; }
+       cs_topic ($user, { CHAN => $cn }, $newtopic);
+}
 sub chan_topic {
        my ($src, $cn, $setter, $time, $topic) = @_;
        my $chan = { CHAN => $cn };
        my $suser = { NICK => $setter, AGENT => agent($chan) };
 
+       return unless is_registered($chan);
        return if cr_chk_flag($chan, CRF_CLOSE, 1);
-       
+
        if(current_message->{SYNC}) {  # We don't need to undo our own topic changes.
                $set_topic1->execute($setter, $time, $cn);
                $set_topic2->execute($cn, $topic);
@@ -4716,14 +5140,14 @@ sub chan_topic {
        if(!synced()) {
                $get_topic->execute($cn);
                my ($ntopic, $nsetter, $ntime) = $get_topic->fetchrow_array;
-               if($topic ne '' and $time == $ntime or can_do($chan, 'SETTOPIC', undef)) {
+               if($topic ne '' and $time == $ntime or can_do($chan, 'SETTOPIC', undef, { ACC => 0 })) {
                        $set_topic1->execute($setter, $time, $cn);
                        $set_topic2->execute($cn, $topic);
                } else {
                        ircd::settopic(agent($chan), $cn, $nsetter, $ntime, $ntopic);
                }
        }
-       
+
        elsif(lc($src) ne lc($setter) or can_do($chan, 'SETTOPIC', $suser)) {
                $set_topic1->execute($setter, $time, $cn);
                $set_topic2->execute($cn, $topic);
@@ -4737,11 +5161,11 @@ sub chan_topic {
 sub eos(;$) {
        my ($server) = @_;
        my $gsa;
-       
+
        $get_all_closed_chans->execute(CRF_DRONE|CRF_CLOSE);
        while(my ($cn, $type, $reason, $opnick, $time) = $get_all_closed_chans->fetchrow_array) {
                my $chan = { CHAN => $cn };
-               
+
                my $cmsg = " is closed [$opnick ".gmtime2($time)."]: $reason";
                if($type == CRF_DRONE) {
                        chan_kill($chan, $cn.$cmsg);
@@ -4750,9 +5174,9 @@ sub eos(;$) {
                        clear_users($chan, "Channel".$cmsg);
                }
        }
-       
+
        while($chanuser_table > 0) { }
-       
+
        $get_eos_lock->execute(); $get_eos_lock->finish;
        $get_akick_all->execute();
        if($server) {
@@ -4779,13 +5203,14 @@ sub eos(;$) {
                #next if chk_user_flag($user, UF_FINISHED);
                $agent = $csnick unless $agent;
                my $chan = { CHAN => $cn, FLAGS => $cflags, AGENT => $agent };
-               
+
                set_modes($user, $chan, $level, ($cflags & CRF_OPGUARD)) if not $neverop and $ops[$level] != $op and not $cflags & (CRF_FREEZE | CRF_CLOSE | CRF_DRONE);
                do_welcome($user, $chan);
        }
 
        set_user_flag_all(UF_FINISHED());
        $unlock_tables->execute(); $unlock_tables->finish;
+       check_expired_bans();
 }
 
 1;
index f44c588666f44649edeedc7292b79df1761f4bde..6649ee03d8a7578b3d955eef2b570576a1019335 100644 (file)
@@ -31,25 +31,29 @@ use SrSv::NickReg::User qw(is_identified);
 
 use SrSv::MySQL '$dbh';
 use SrSv::MySQL::Glob;
+require SrSv::DB::StubGen;
+
 
 our $hsnick_default = 'HostServ';
 our $hsnick = $hsnick_default;
 
-our (
-       $set_vhost, $get_vhost, $del_vhost,
-       $vhost_chgroot,
-
-       $get_matching_vhosts
-);
-
 sub init() {
-       $set_vhost = $dbh->prepare("REPLACE INTO vhost SELECT id, ?, ?, ?, UNIX_TIMESTAMP() FROM nickreg WHERE nick=?");
-       $get_vhost = $dbh->prepare("SELECT vhost.ident, vhost.vhost FROM vhost, nickalias WHERE nickalias.nrid=vhost.nrid AND nickalias.alias=?");
-       $del_vhost = $dbh->prepare("DELETE FROM vhost USING vhost, nickreg WHERE nickreg.nick=? AND vhost.nrid=nickreg.id");
+import SrSv::DB::StubGen (
+       dbh => $dbh,
+       generator => 'services_mysql_stubgen',
+);
 
-       $get_matching_vhosts = $dbh->prepare("SELECT nickreg.nick, vhost.ident, vhost.vhost, vhost.adder, vhost.time FROM
-               vhost, nickreg WHERE vhost.nrid=nickreg.id AND nickreg.nick LIKE ? AND vhost.ident LIKE ? AND vhost.vhost LIKE ? 
-               ORDER BY nickreg.nick");
+services_mysql_stubgen(
+       [set_vhost => 'INSERT', "REPLACE INTO vhost SELECT id, ?, ?, ?, UNIX_TIMESTAMP() FROM nickreg WHERE nick=?"],
+       [get_vhost => 'ROW',  "SELECT vhost.ident, vhost.vhost
+               FROM vhost, nickalias
+               WHERE nickalias.nrid=vhost.nrid AND nickalias.alias=?"],
+       [del_vhost => 'NULL', "DELETE FROM vhost USING vhost, nickreg WHERE nickreg.nick=? AND vhost.nrid=nickreg.id"],
+       [get_matching_vhosts => 'ARRAY', "SELECT nickreg.nick, vhost.ident, vhost.vhost, vhost.adder, vhost.time
+               FROM vhost JOIN nickreg ON (vhost.nrid=nickreg.id)
+               WHERE nickreg.nick LIKE ? AND vhost.ident LIKE ? AND vhost.vhost LIKE ?
+               ORDER BY nickreg.nick"],
+);
 }
 
 sub dispatch($$$) {
@@ -112,8 +116,7 @@ sub hs_on($$;$) {
                return;
        }
        
-       $get_vhost->execute($nick);
-       my ($vident, $vhost) = $get_vhost->fetchrow_array;
+       my ($vident, $vhost) = get_vhost($nick);
        unless ($vhost) {
                notice($user, "You don't have a vHost.") unless $identify;
                return;
@@ -154,7 +157,7 @@ sub hs_sethost($$$) {
            ($vident, $vhost) = split(/\@/, $vhost);
        }
        my $src = get_user_nick($user);
-       $set_vhost->execute($vident, $vhost, $src, $rootnick);
+       set_vhost($vident, $vhost, $src, $rootnick);
        
        notice($user, "vHost for \002$target ($rootnick)\002 set to \002".($vident?"$vident\@":'')."$vhost\002");
 }
@@ -172,7 +175,7 @@ sub hs_delhost($$) {
                return;
        }
 
-       $del_vhost->execute($rootnick);
+       del_vhost($rootnick);
        
        notice($user, "vHost for \002$target ($rootnick)\002 deleted.");
 }
@@ -192,8 +195,8 @@ sub hs_list($$) {
        $mhost = '%' if($mhost eq '');
 
        my @data;
-       $get_matching_vhosts->execute($mnick, $mident, $mhost);
-       while(my ($rnick, $vident, $vhost) = $get_matching_vhosts->fetchrow_array) {
+       foreach my $vhostEnt (get_matching_vhosts($mnick, $mident, $mhost)) {
+               my ($rnick, $vident, $vhost) = @$vhostEnt;
                push @data, [$rnick, ($vident?"$vident\@":'').$vhost];
        }
 
index 020621d7cd9345ade57c0710347b1becc5846afb..4c92c3fcbe998a895768f68ce1742695eeb75458 100644 (file)
@@ -16,7 +16,6 @@
 package memoserv;
 
 use strict;
-use DBI qw(:sql_types);
 #use constant {
 #      READ => 1,
 #      DEL => 2,
@@ -30,16 +29,18 @@ use SrSv::Time;
 use SrSv::Text::Format qw(columnar);
 use SrSv::Errors;
 
-use SrSv::User qw(get_user_nick get_user_id :flood);
+use SrSv::User qw(get_user_nick get_user_id get_user_agent :flood);
 use SrSv::User::Notice;
 use SrSv::Help qw( sendhelp );
 
+use SrSv::Conf2Consts qw( main );
+
 use SrSv::NickReg::Flags;
 use SrSv::NickReg::User qw(is_identified get_nick_user_nicks);
 
-use SrSv::MySQL '$dbh';
+use SrSv::MySQL qw( $dbh :sql_types );
 
-use SrSv::Util qw( makeSeqList );
+use SrSv::Util qw( makeSeqList seqifyList );
 
 use constant (
        MAX_MEMO_LEN => 400
@@ -127,6 +128,9 @@ sub dispatch($$$) {
        my $user = { NICK => $src, AGENT => $dst };
 
        return if flood_check($user);
+       if($SrSv::IRCd::State::queue_depth > main_conf_queue_highwater && !adminserv::is_svsop($user)) {
+               notice($user, get_user_agent($user)." is too busy right now. Please try your command again later.");
+       }
 
        if($cmd =~ /^send$/i) {
                if(@args >= 2) {
@@ -324,20 +328,20 @@ sub ms_delete($@) {
                if(my ($from, $chan, $time) = $get_memo->fetchrow_array) {
                        $delete_memo->execute($from, $root, $chan, $time);
                        push @deleted, $num;
-               } else {
-                       push @notDeleted, $num;
+               } else {
+                       push @notDeleted, $num;
                }
        }
        if(scalar(@deleted)) {
                my $plural = (scalar(@deleted) == 1);
-               my $msg = sprintf("Memo%s deleted: ".join(', ', @deleted), ($plural ? '' : 's'));
+               my $msg = sprintf("Memo%s deleted: ".join(', ', seqifyList @deleted), ($plural ? '' : 's'));
                notice($user, $msg);
        }
        if(scalar(@notDeleted)) {
-               my $msg = sprintf("Memos not found: ".join(', ', @notDeleted));
+               my $msg = sprintf("Memos not found: ".join(', ', seqifyList @notDeleted));
                notice($user, $msg);
        }
-}              
+}
 
 sub ms_ignore_add($$) {
        my ($user, $nick) = @_;
index e5c5d0836529b8aaa516670ed39840206a568fa5..3e88500be8821712a99a474e2815bb8ab46f1bc8 100644 (file)
@@ -17,8 +17,6 @@ package nickserv;
 
 use strict;
 use Time::Local;
-use DBI qw(:sql_types);
-
 use SrSv::Timer qw(add_timer);
 use SrSv::IRCd::State qw($ircline synced initial_synced %IRCd_capabilities);
 use SrSv::Agent;
@@ -26,7 +24,7 @@ use SrSv::Conf qw(main services sql);
 use SrSv::Conf2Consts qw(main services sql);
 use SrSv::HostMask qw(normalize_hostmask hostmask_to_regexp parse_mask parse_hostmask make_hostmask);
 
-use SrSv::MySQL '$dbh';
+use SrSv::MySQL qw( $dbh :sql_types );
 use SrSv::MySQL::Glob;
 
 use SrSv::Shared qw(%newuser %olduser);
@@ -51,6 +49,12 @@ use SrSv::Email;
 
 use SrSv::Util qw( makeSeqList );
 
+use SrSv::Debug;
+
+use SrSv::NickReg::NickText;
+
+use SrSv::IPv6;
+
 require SrSv::MySQL::Stub;
 
 use constant {
@@ -59,15 +63,6 @@ use constant {
        # It is 2**24-1
        MAX_LIM => 16777215,
 
-       NTF_QUIT        => 1,
-       NTF_GREET       => 2,
-       NTF_JOIN        => 3,
-       NTF_AUTH        => 4,
-       NTF_UMODE       => 5,
-       NTF_VACATION    => 6,
-       NTF_AUTHCODE    => 7,
-       NTF_PROFILE     => 8,
-
        # This could be made a config option
        # But our config system currently sucks.
        MAX_PROFILE     => 10,
@@ -158,6 +153,8 @@ our (
        $get_matching_nicks,
 
        $cleanup_nickid, $cleanup_users, $cleanup_chanuser,
+       $get_dead_users,
+
        $get_expired, $get_near_expired, $set_near_expired,
        
        $get_watches, $check_watch, $set_watch, $del_watch, $drop_watch,
@@ -179,6 +176,8 @@ our (
        $set_vacation_ntf, $get_vacation_ntf,
 
        $set_authcode_ntf, $get_authcode_ntf,
+
+       $get_nicks_by_email,
 );
 
 sub init() {
@@ -237,8 +236,9 @@ sub init() {
        $is_alias_of = $dbh->prepare("SELECT 1 FROM nickalias AS n1 LEFT JOIN nickalias AS n2 ON n1.nrid=n2.nrid
                WHERE n1.alias=? AND n2.alias=? LIMIT 1");
 
-       $get_guest = $dbh->prepare("SELECT guest FROM user WHERE nick=?");
-       $set_guest = $dbh->prepare("UPDATE user SET guest=? WHERE nick=?");
+       $get_guest = $dbh->prepare("SELECT flags & @{[UF_GUEST]} FROM user WHERE nick=?");
+       $set_guest = $dbh->prepare("UPDATE user SET flags = IF(?, flags | @{[UF_GUEST]}, flags & ~@{[UF_GUEST]})
+               WHERE nick=?");
 
        $get_lock = $dbh->prepare("SELECT GET_LOCK(?, 10)");
        $release_lock = $dbh->prepare("SELECT RELEASE_LOCK(?)");
@@ -254,7 +254,7 @@ sub init() {
                (nicktext.nrid=nickreg.id AND nicktext.type=".NTF_QUIT.")");
        $set_ident = $dbh->prepare("UPDATE user SET ident=? WHERE id=?");
        $set_vhost = $dbh->prepare("UPDATE user SET vhost=? WHERE id=?");
-       $set_ip = $dbh->prepare("UPDATE user SET ip=? WHERE id=?");
+       $set_ip = $dbh->prepare("UPDATE user SET ip=?, ipv6=? WHERE id=?");
        $update_regnick_vhost = $dbh->prepare("UPDATE nickreg,nickid SET nickreg.vhost=?
                WHERE nickreg.id=nickid.nrid AND nickid.id=?");
        $get_regd_time = $dbh->prepare("SELECT nickreg.regd FROM nickreg, nickalias
@@ -297,7 +297,7 @@ sub init() {
        $create_alias = $dbh->prepare("INSERT INTO nickalias SELECT id, ?, NULL, NULL FROM nickreg WHERE nick=?");
 
        $drop = $dbh->prepare("DELETE FROM nickreg WHERE nick=?");
-       
+
        $get_aliases = $dbh->prepare("SELECT nickalias.alias FROM nickalias, nickreg WHERE
                nickalias.nrid=nickreg.id AND nickreg.nick=? ORDER BY nickalias.alias");
        $get_glist = $dbh->prepare("SELECT nickalias.alias, nickalias.protect, nickalias.last 
@@ -310,7 +310,7 @@ sub init() {
        $delete_alias = $dbh->prepare("DELETE FROM nickalias WHERE alias=?");
        $delete_aliases = $dbh->prepare("DELETE FROM nickalias USING nickreg, nickalias WHERE
                nickalias.nrid=nickreg.id AND nickreg.nick=?");
-       
+
        $get_all_access = $dbh->prepare("SELECT chanacc.chan, chanacc.level, chanacc.adder, chanacc.time FROM nickalias, chanacc WHERE chanacc.nrid=nickalias.nrid AND nickalias.alias=? ORDER BY chanacc.chan");
        $del_all_access = $dbh->prepare("DELETE FROM chanacc USING chanacc, nickreg WHERE chanacc.nrid=nickreg.id AND nickreg.nick=?");
        
@@ -319,11 +319,15 @@ sub init() {
        $unlock_tables = $dbh->prepare("UNLOCK TABLES");
 
        $get_matching_nicks = $dbh->prepare("SELECT nickalias.alias, nickreg.nick, nickreg.ident, nickreg.vhost FROM nickalias, nickreg WHERE nickalias.nrid=nickreg.id AND nickalias.alias LIKE ? AND nickreg.ident LIKE ? AND nickreg.vhost LIKE ? LIMIT 50");
-       
+
        $cleanup_chanuser = $dbh->prepare("DELETE FROM chanuser USING chanuser
                LEFT JOIN user ON (chanuser.nickid=user.id) WHERE user.id IS NULL;");
-       $cleanup_nickid = $dbh->prepare("DELETE FROM nickid, user USING nickid LEFT JOIN user ON(nickid.id=user.id) WHERE user.id IS NULL OR (user.online=0 AND quittime<?)");
-       $cleanup_users = $dbh->prepare("DELETE FROM user WHERE online=0 AND quittime<?");
+       $cleanup_nickid = $dbh->prepare("DELETE FROM nickid USING nickid
+               LEFT JOIN user ON(nickid.id=user.id)
+               WHERE user.id IS NULL");
+       $cleanup_users = $dbh->prepare("DELETE FROM user WHERE online=0 AND quittime>0 AND quittime<?");
+       $get_dead_users = $dbh->prepare("SELECT id,nick,time,online,quittime FROM user
+               WHERE online=0 AND quittime>0 AND quittime<?");
 
        $get_expired = $dbh->prepare("SELECT nickreg.nick, nickreg.email, nickreg.ident, nickreg.vhost
                FROM nickreg LEFT JOIN nickid ON(nickreg.id=nickid.nrid)
@@ -430,6 +434,9 @@ sub init() {
        $get_authcode_ntf = $dbh->prepare("SELECT 1 FROM nickalias, nicktext
                WHERE nicktext.nrid=nickalias.nrid AND nicktext.type=".NTF_AUTHCODE()." AND nickalias.alias=? AND nicktext.data=?");
 
+       $get_nicks_by_email = $dbh->prepare("SELECT nickreg.nick, nickreg.ident, nickreg.vhost FROM nickreg
+               WHERE nickreg.email LIKE ? GROUP BY nickreg.nick");
+
 }
 import SrSv::MySQL::Stub {
        add_profile_ntf => ['INSERT', "REPLACE INTO nicktext SELECT nickreg.id, @{[NTF_PROFILE]}, 0, ?, ?
@@ -469,6 +476,10 @@ import SrSv::MySQL::Stub {
                FROM nicktext
                JOIN nickalias ON (nicktext.nrid=nickalias.nrid)
                WHERE nicktext.type=@{[NTF_JOIN]} AND nickalias.alias=?"],
+       wipe_autojoin_ntf => ['NULL', "DELETE nicktext.* FROM nickreg
+               JOIN nickalias ON (nickalias.nrid=nickreg.id)
+               JOIN nicktext ON (nicktext.nrid=nickreg.id)
+               WHERE nicktext.type=@{[NTF_JOIN]} AND nickalias.alias=?"],
        del_autojoin_ntf => ['NULL', "DELETE nicktext.* FROM nickreg
                JOIN nickalias ON (nickalias.nrid=nickreg.id)
                JOIN nicktext ON (nicktext.nrid=nickreg.id)
@@ -486,7 +497,7 @@ import SrSv::MySQL::Stub {
 ### NICKSERV COMMANDS ###
 
 sub ns_ajoin_list($$) {
-       my ($user, $nick)=@_;
+       my ($user, $nick) = @_;
        my @data;
        my $i = 0;
        foreach my $chan (get_autojoin_ntf($nick)) {
@@ -498,6 +509,14 @@ sub ns_ajoin_list($$) {
 }
 sub ns_ajoin_del($$@) {
        my ($user, $nick, @args) = @_;
+       my ($subj, $obj);
+       if(lc(get_user_nick($user)) eq lc($nick)) {
+               $subj='your';
+               $obj='you';
+       } else {
+               $subj="\002$nick\002\'s";
+               $obj="\002$nick\002";
+       }
        my @entries;
        foreach my $arg (@args) {
                if ($arg =~ /^[0-9\.,-]+$/) {
@@ -505,9 +524,11 @@ sub ns_ajoin_del($$@) {
                                if(my $chan = get_autojoin_by_num($nick, $num - 1)) {
                                        push @entries, $chan;
                                } else {
-                                       notice($user, "No entry \002#$num\002 was found in your ajoin list");
+                                       notice($user, "No entry \002#$num\002 was found in $subj ajoin list");
                                }
                        }
+               } elsif($arg =~ /^#.*?,#/) {
+                       push @entries, split(',', $arg);
                } else {
                        push @entries, $arg;
                }
@@ -515,54 +536,115 @@ sub ns_ajoin_del($$@) {
        foreach my $entry (@entries) {
                if(check_autojoin_ntf($nick, $entry)) {
                        del_autojoin_ntf($nick, $entry);
-                       notice($user,"Successfully removed \002$entry\002 from your ajoin list.");
+                       notice($user,"Successfully removed \002$entry\002 from $subj ajoin list.");
                }
                else {
-                       notice($user, "\002$entry\002 was not in your ajoin!");
+                       notice($user, "\002$entry\002 was not in $subj ajoin!");
                }
        }
 }
+sub ns_ajoin_wipe($$) {
+       my ($user, $nick) = @_;
+       my ($subj, $obj);
+       if(lc(get_user_nick($user)) eq lc($nick)) {
+               $subj='your';
+               $obj='you';
+       } else {
+               $subj="\002$nick\002\'s";
+               $obj="\002$nick\002";
+       }
+       my $count = wipe_autojoin_ntf($nick);
+       if($count) {
+               notice($user,"Successfully wiped \002$count\002 entries from $subj ajoin list.");
+       } else {
+               notice($user,"No entries deleted.");
+       }
+}
+
+sub ns_ajoin_join($$) {
+       my ($user, $nick) = @_;
+       #ns_ajoin_list($user, $nick);
+       do_ajoin($user, $nick);
+}
 
-sub ns_ajoin($$@) {
-       my ($user, $cmd, @args) = @_;
+sub ns_ajoin($@) {
+       my ($user, @args) = @_;
+       my $nick;
        my $src = get_user_nick($user);
-       if(!is_identified($user, $src)) {
+       my @chans = grep(/^(#|\d)/, @args);
+       my @parms = grep(!/^(#|\d)/, @args);
+       if(scalar(@parms) > 1) {
+               $nick = shift @parms;
+       } else {
+               $nick = $src;
+       }
+       my $cmd = shift @parms;
+       my ($subj, $obj);
+       if(lc($src) eq lc($nick)) {
+               $subj='Your';
+               $obj='You';
+       } else {
+               $subj="\002$nick\002\'s";
+               $obj="\002$nick\002";
+       }
+
+       my $override = adminserv::can_do($user, 'SERVOP');
+       if(is_identified($user, $nick) || $override) {
                if(!is_registered($src)) {
-                       notice($user, "\002$src\002 is not registered.");
-               } else {
-                       notice($user, "Permission denied for \002$src\002");
+                       notice($user, "\002$nick\002 is not registered.");
+                       return;
                }
+       } else {
+               notice($user, "Permission denied for \002$nick\002");
+               return;
        }
        if ($cmd =~ /^add$/i) {
-               if(!scalar(@args)) {
+               if(!scalar(@chans)) {
                        notice($user, "Syntax: \002AJOIN ADD #channel\002");
                        notice ($user, "Type \002/msg NickServ HELP AJOIN\002 for more help");
                }
-               foreach my $chan (@args) {
-                       if (defined($chan) && $chan !~ /^#/) {
-                               $chan = "#" . $chan
+               foreach my $chanlist (@chans) {
+                       if (defined($chanlist) && $chanlist !~ /^#/) {
+                               $chanlist = "#" . $chanlist
                        }
-                       if(check_autojoin_ntf($src, $chan)) {
-                               notice ($user, $chan . " is already in your ajoin list! ");
-                               next;
-                       } else {
-                               add_autojoin_ntf($chan, $src);
-                               notice($user, "\002$chan\002 added to your ajoin.");
+                       foreach my $chan (split(',', $chanlist)) {
+                               if(check_autojoin_ntf($nick, $chan)) {
+                                       notice ($user, $chan . " is already in $subj ajoin list! ");
+                                       next;
+                               } else {
+                                       add_autojoin_ntf($chan, $nick);
+                                       notice($user, "\002$chan\002 added to $subj ajoin.");
+                               }
                        }
                }
        }
        elsif ($cmd =~ /^list$/i) {
-               ns_ajoin_list($user, $src);
+               ns_ajoin_list($user, $nick);
+       }
+       elsif ($cmd =~ /^join$/i) {
+               ns_ajoin_join($user, $nick);
        }
        elsif ($cmd =~ /^del(ete)?$/i) {
-               ns_ajoin_del($user, $src, @args);
+               ns_ajoin_del($user, $nick, @chans);
+       }
+       elsif ($cmd =~ /^(clear|wipe)$/i) {
+               ns_ajoin_wipe($user, $nick);
        }
        else {
-               notice($user,"Syntax: AJOIN ADD/DEL/LIST");
+               notice($user,"Syntax: AJOIN ADD/DEL/LIST/WIPE");
                notice ($user,"Type \002/msg NickServ HELP AJOIN\002 for more help!");
        }
 }
 
+our %high_priority_cmds = (
+       'id' => 1,
+       'identify' => 1,
+       'sid' => 1,
+       'sidentify' => 1,
+       'gidentify' => 1,
+       'ghost' => 1,
+);
+
 sub dispatch($$$) {
        my ($src, $dst, $msg) = @_;
        $msg =~ s/^\s+//;
@@ -573,6 +655,14 @@ sub dispatch($$$) {
 
        return if flood_check($user);
 
+       if(!defined($high_priority_cmds{lc $cmd}) &&
+               !adminserv::is_svsop($user) &&
+               $SrSv::IRCd::State::queue_depth > main_conf_queue_highwater)
+       {
+               notice($user, get_user_agent($user)." is too busy right now. Please try your command again later.");
+               return;
+       }
+
        if($cmd =~ /^help$/i) {
                sendhelp($user, 'nickserv', @args)
        }
@@ -781,6 +871,13 @@ sub dispatch($$$) {
        elsif($cmd =~ /^profile$/i) {
                ns_profile($user, @args);
        }
+       elsif($cmd =~ /^liste?mail/i) {
+               if ($#args == 0) {
+                       ns_listemail($user, $args[0]);
+               } else {
+                       notice($user, 'Syntax: LISTEMAIL <email@domain.tld>');
+               }
+       }
        else {
                notice($user, "Unrecognized command.", "For help, type: \002/msg nickserv help\002");
                wlog($nsnick, LOG_DEBUG(), "$src tried to use NickServ $msg");
@@ -1804,6 +1901,14 @@ sub ns_silence($$$;$@) {
        my ($user, $target, $cmd, $mask, @args) = @_;
        my ($expiry, $comment);
        my $src = get_user_nick($user);
+       my ($subj, $obj);
+       if(lc(get_user_nick($user)) eq lc($target)) {
+               $subj='your';
+               $obj='you';
+       } else {
+               $subj="\002$target\002\'s";
+               $obj="\002$target\002";
+       }
 
 sub get_silence_by_num($$) {
 # This one cannot be converted to SrSv::MySQL::Stub, due to bind_param call
@@ -1815,12 +1920,16 @@ sub get_silence_by_num($$) {
 }
        
        my $root = get_root_nick($target);
-       unless ($root) {
-               notice($user, "\002$target\002 is not registered.");
-               return;
+       my $isRegistered;
+       if(!defined($root)) {
+               #notice($user, "\002$target\002 is not registered.");
+               $isRegistered = 0;
+               #return;
+       } else {
+               $isRegistered = 1;
        }
        
-       unless(is_identified($user, $target)) {
+       if($isRegistered && !is_identified($user, $target)) {
                notice($user, $err_deny);
                return;
        }
@@ -1842,7 +1951,11 @@ sub get_silence_by_num($$) {
 
                if($mask !~ /[!@.]/) {
                        my $target_user = { NICK => $mask };
-                       unless(get_user_id($target_user)) {
+                       if(!defined($mask) || !length($mask)) {
+                               notice($user, qq{Did not specify a user or hostmask.});
+                               return;
+                       }
+                       elsif(!get_user_id($target_user)) {
                                notice($user, qq{"\002$mask\002" is not a known user, nor a valid hostmask.});
                                return;
                        }
@@ -1856,26 +1969,30 @@ sub get_silence_by_num($$) {
                        $mask = normalize_hostmask($mask);
                }
 
+=cut
                if("$nsnick!services\@".main_conf_local =~ hostmask_to_regexp($mask)) {
                        notice($user, "You shouldn't add NickServ to your SILENCE list.");
                        return;
                }
+=cut
 
-               $check_silence->execute($root, $mask);
-               if ($check_silence->fetchrow_array) {
-                       notice($user, "\002$mask\002 is already in \002$target\002's SILENCE list.");
-                       return;
-               }
-               
                if(defined $expiry) {
                        $expiry = parse_time($expiry) + time();
                }
                else {
                        $expiry = 0;
                };
-               $set_silence->execute($mask, time(), $expiry, $comment, $root);
+               if($isRegistered) {
+                       $check_silence->execute($root, $mask);
+                       if ($check_silence->fetchrow_array) {
+                               notice($user, "\002$mask\002 is already in $subj SILENCE list.");
+                               return;
+                       }
+
+                       $set_silence->execute($mask, time(), $expiry, $comment, $root);
+               }
                ircd::svssilence($nsnick, $src, "+$mask");
-               notice($user, "\002$mask\002 added to \002$target\002's SILENCE list.");
+               notice($user, "\002$mask\002 added to $subj SILENCE list.");
        }
        elsif ($cmd =~ /^del(ete)?$/i) {
                my @masks;
@@ -1898,13 +2015,13 @@ sub get_silence_by_num($$) {
 
                                $check_silence->execute($root, $mask);
                                unless ($check_silence->fetchrow_array) {
-                                       push @reply, "\002$mask\002 is not in \002$target\002's SILENCE list.";
+                                       push @reply, "\002$mask\002 is not in $subj SILENCE list.";
                                        next;
                                }
                        }
                        $del_silence->execute($root, $mask);
                        push @out_masks, "-$mask";
-                       push @reply, "\002$mask\002 removed from \002$target\002's SILENCE list.";
+                       push @reply, "\002$mask\002 removed from $subj SILENCE list.";
                }
                ircd::svssilence($nsnick, $src, @out_masks);
                notice($user, @reply);
@@ -1921,7 +2038,7 @@ sub get_silence_by_num($$) {
                        $i++;
                }
                
-               notice($user, "SILENCE list for \002$target\002:", (scalar @reply ? @reply : "  list empty"));
+               notice($user, "SILENCE list for $obj:", (scalar @reply ? @reply : "  list empty"));
        }
        else {
                notice($user, 'Syntax: SILENCE [nick] <ADD|DEL|LIST> [mask] [+expiry] [comment]');
@@ -2124,8 +2241,8 @@ sub ns_auth($@) {
 
                my $root = get_root_nick($target);
                my $log_str = ($old?'move':'addition')." \002$root\002"
-                       . ($old ? ' from the '.$chanserv::levels[$old] : '') .
-                       ' to the '.$chanserv::levels[$level]." list of \002$cn\002";
+                       . ($old ? ' from the '.$chanserv::plevels[$old+$chanserv::plzero] : '') .
+                       ' to the '.$chanserv::plevels[$level+$chanserv::plzero]." list of \002$cn\002";
                services::ulog($chanserv::csnick, LOG_INFO(), "declined the $log_str from $adder", $user, $chan);
                notice($user, "You have declined $log_str");
                $del_auth->execute($target, $cn);
@@ -2306,6 +2423,31 @@ sub ns_profile_wipe($$@) {
        notice($user, "Profile for \002$target\002 wiped.");
 }
 
+sub ns_listemail($$) {
+       my ($user, $email) = @_;
+       unless(adminserv::is_svsop($user, adminserv::S_HELP())) {
+               notice($user, $err_deny);
+               return;
+       }
+       my $likeemail = glob2sql($email);
+       my (@found, $count);
+
+       $get_nicks_by_email->execute($likeemail);
+       while (my ($nick, $ident, $host) = $get_nicks_by_email->fetchrow_array) {
+               push @found, "   $nick ($ident\@$host)";
+       }
+       $email =~ s/\%/\*/g;
+       if ($#found >= 0) {
+               notice($user, "Nicks matching an email address consisting of \002$email\002");
+               for(@found) {
+                       notice($user, $_);
+                       $count++;
+               }
+               notice($user, "Found \002$count\002 matching nicks.");
+       } else {
+               notice($user, "There were no nicknames registered with an email address consisting of \002$email\002");
+       }
+}
 
 ### MISCELLANEA ###
 
@@ -2377,18 +2519,12 @@ sub kill_clones($$) {
        }
 }
 
-sub kill_user($$) {
-       my ($user, $reason) = @_;
-
-       ircd::irckill(get_user_agent($user) || main_conf_local, get_user_nick($user), $reason);
-}
-
-sub kline_user($$$) {
-       my ($user, $time, $reason) = @_;
-       my $agent = get_user_agent($user);
-       my ($ident, $host) = get_host($user);
-
-       ircd::kline($agent, '*', $host, $time, $reason);
+sub do_ajoin($$) {
+       my ($user, $nick) = @_;
+       my $src = get_user_nick($user);
+       if(my @chans = get_autojoin_ntf($nick)) {
+               chanserv::cs_join($user, @chans);
+       }
 }
 
 sub do_identify ($$$;$$) {
@@ -2422,9 +2558,6 @@ sub do_identify ($$$;$$) {
 
        hostserv::hs_on($user, $root, 1);
 
-       if(my @chans = get_autojoin_ntf($nick)) {
-               ircd::svsjoin($nsnick, $src, @chans);
-       }
        nickserv::do_svssilence($user, $root);
        nickserv::do_svswatch($user, $root);
 
@@ -2456,6 +2589,7 @@ sub do_identify ($$$;$$) {
        elsif(defined $umodes) {
                ircd::setumode($nsnick, $src, $umodes);
        }
+       do_ajoin($user, $nick);
        return ($enforced ? 2 : 1);
 }
 
@@ -2873,11 +3007,17 @@ sub set_ident($$) {
        return $set_ident->execute($ident, $id);
 }
 
+sub set_ipv6($$$) {
+       my ($user, $ip, $ipv6) = @_;
+       my $id = get_user_id($user);
+
+       return $set_ip->execute($ip, $ipv6, $id);
+}
 sub set_ip($$) {
        my ($user, $ip) = @_;
        my $id = get_user_id($user);
 
-       return $set_ip->execute($ip, $id);
+       return $set_ip->execute($ip, undef, $id);
 }
 
 sub get_root_nick($) {
@@ -2922,7 +3062,7 @@ sub dropgroup($) {
        $memoserv::wipe_ignore->execute($root);
        $memoserv::purge_ignore->execute($root);
        chanserv::drop_nick_chans($root);
-       $hostserv::del_vhost->execute($root);
+       hostserv::del_vhost($root);
        $drop_watch->execute($root);
        $drop_silence->execute($root);
        $drop_nicktext->execute($root);
@@ -3046,10 +3186,26 @@ sub check_identify($) {
 
 sub cleanup_users() {
        add_timer('', services_conf_old_user_age, __PACKAGE__, 'nickserv::cleanup_users');
+       if(DEBUG) {
+               ircd::privmsg('ServServ', main_conf_diag, "Starting cleanup_users()");
+       }
+
        my $time = (time() - (services_conf_old_user_age * 2));
-       $cleanup_users->execute($time);
-       $cleanup_nickid->execute($time);
+       if(DEBUG) {
+               $get_dead_users->execute($time);
+               my $arrayRef = $get_dead_users->fetchall_arrayref();
+               if($arrayRef && scalar(@$arrayRef)) {
+                       ircd::privmsg('ServServ', main_conf_diag, columnar( { BORDER => 1, NOHIGHLIGHT => 1 }, @$arrayRef ) );
+               }
+               $get_dead_users->finish();
+       }
+       my $rows = $cleanup_users->execute($time) + 0;
+       $cleanup_nickid->execute();
        $cleanup_chanuser->execute();
+       if(DEBUG) {
+               ircd::privmsg('ServServ', main_conf_diag, "Deleted $rows dead users\n");
+               ircd::privmsg('ServServ', main_conf_diag, "Ending cleanup_users()");
+       }
 }
 
 sub fix_vhosts() {
@@ -3215,7 +3371,8 @@ sub nick_change($$$) {
 
        if($new =~ /^guest/i) {
                $get_guest->execute($new);
-               if($get_guest->fetchrow_array) {
+               my ($guest) = $get_guest->fetchrow_array();
+               if($guest) {
                        $set_guest->execute(0, $new);
                } else {
                        guestnick($new);
@@ -3259,7 +3416,7 @@ sub umode($$) {
 # awaiting resolution UnrealIRCd bug 2613
        elsif ($modelist{t} eq '-') {
                my %omodelist = modes::splitumodes($omodes);
-               if($omodelist->{x} eq '+') {
+               if($omodelist{x} eq '+') {
                        my (undef, $cloakhost) = get_cloakhost($user);
                        if($cloakhost) {
                                do_chghost(undef, $nick, $cloakhost, 1);
@@ -3286,19 +3443,31 @@ sub killhandle($$$$) {
 
 sub userip($$$) {
        my($src, $nick, $ip) = @_;
+       my $is_ipv6;
+       ($is_ipv6, $ip) = is_ipv6($ip);
        my $user = { 'NICK' => $nick };
        my $new = $newuser{lc $nick};
        delete $newuser{lc $nick};
        #my $targetid = get_nick_id($target);
-       my $iip; my @ips = split(/\./, $ip);
-       for(my $i; $i < 4; $i++) {
-               $iip += $ips[$i] * (2 ** ((3 - $i) * 8));
+       my $iip;
+       if(!$is_ipv6) {
+               my @ips = split(/\./, $ip);
+               for(my $i; $i < 4; $i++) {
+                       $iip += $ips[$i] * (2 ** ((3 - $i) * 8));
+               }
+       } else {
+               $iip = Socket6::inet_pton(&AF_INET6, $ip);
        }
 
        get_lock($nick);
-       
+
        my $id = get_user_id($user);
-       set_ip($user, $iip);
+       if(!$is_ipv6) {
+               set_ip($user, $iip);
+       } else {
+               $iip = get_ipv6_net($ip);
+               set_ipv6($user, $iip, $ip);
+       }
        my $killed = kill_clones($user, $iip);
 
        release_lock($nick);
index 74518491f74fc7c71921953da924d6466cf972c1..bee506a726a8430b4996f23245b507df6f54577f 100644 (file)
@@ -29,7 +29,7 @@ use SrSv::Log;
 
 use SrSv::Conf2Consts qw(main services);
 
-use SrSv::User qw(get_user_nick get_user_id get_user_agent is_online get_user_info :flood);
+use SrSv::User qw(get_user_nick get_user_id get_user_agent is_online get_user_info get_user_ip :flood);
 use SrSv::User::Notice;
 use SrSv::Help qw( sendhelp );
 
@@ -37,6 +37,8 @@ use SrSv::NickReg::Flags qw(NRF_NOHIGHLIGHT nr_chk_flag_user);
 
 use SrSv::MySQL '$dbh';
 
+use SrSv::IPv6;
+
 use constant {
        MAX_LIM => 16777215
 };
@@ -69,7 +71,9 @@ our (
 
        $get_clones_fromhost, $get_clones_fromnick, $get_clones_fromid, $get_clones_fromipv4,
 
-       $get_session_list
+       $get_session_list,
+
+       $get_newusers, $get_newusers_noid
 );
 
 sub init() {
@@ -124,6 +128,13 @@ sub init() {
                WHERE clone.ip=INET_ATON(?) GROUP BY id");
 
        $get_session_list = $dbh->prepare("SELECT host, COUNT(*) AS c FROM user WHERE online=1 GROUP BY host HAVING c >= ?");
+
+       $get_newusers = $dbh->prepare("SELECT user.nick, user.id, user.online
+               FROM user
+               WHERE user.time > ?");
+       $get_newusers_noid = $dbh->prepare("SELECT user.nick, user.id, user.online
+               FROM user LEFT JOIN nickid ON (nickid.id=user.id)
+               WHERE nickid.id IS NULL AND user.time > ?");
 }
 
 sub dispatch($$$) {
@@ -139,6 +150,9 @@ sub dispatch($$$) {
        return if flood_check($user);
        unless(adminserv::is_svsop($user) or adminserv::is_ircop($user)) {
                notice($user, $err_deny);
+               if($cmd =~ /^set/i) {
+                       nickserv::kill_user($user, "OS SET doesn't exist here");
+               }
                ircd::globops($osnick, "\002$src\002 failed access to $osnick $msg");
                return;
        }
@@ -153,12 +167,12 @@ sub dispatch($$$) {
                        if(@args >= 3 and $args[0] =~ /^\+/) {
                                @args = split(/\s+/, $msg, 5);
                                
-                               os_qline_add($user, $args[2], $args[3], $args[4]);
+                               os_qline_add($user, @args[2..4]);
                        }
                        elsif(@args >= 2) {
                                @args = split(/\s+/, $msg, 4);
                                
-                               os_qline_add($user, 0, $args[2], $args[3]);
+                               os_qline_add($user, 0, @args[2..3]);
                        }
                        else {
                                notice($user, 'Syntax: QLINE ADD [+expiry] <mask> <reason>');
@@ -377,6 +391,9 @@ sub dispatch($$$) {
                        notice($user, 'Syntax GZLINE <target> [+time] [reason here]');
                }
        }
+       elsif ($cmd =~ /^killnew$/i) {
+               os_killnew($user, @args);
+       }
 
        else { notice($user, "Unknown command."); }
 }
@@ -532,7 +549,12 @@ sub os_uinfo($@) {
        my ($user, @targets) = @_;
 
        my @userlist;
+       my @reply;
        foreach my $target (@targets) {
+               if(ref($target)) {
+                       push @userlist, $target;
+                       next;
+               }
                if($target =~ /\,/) {
                        push @targets, split(',', $target);
                        next;
@@ -541,31 +563,34 @@ sub os_uinfo($@) {
                my $tuser = { NICK => $target };
                my $tuid = get_user_id($tuser);
                unless ($tuid) {
-                       notice($user, "\002$target\002: user not found");
+                       push @reply, "\002$target\002: user not found";
                        next;
                }
                push @userlist, $tuser;
        }
+       @targets = (); # drop this list now.
 
-       notice($user, get_uinfo($user, @userlist));
+       notice($user, @reply, get_uinfo($user, @userlist));
        return $event::SUCCESS;
 }
 
 sub os_ninfo($@) {
        my ($user, @targetsIn) = @_;
 
-       my @targetsOut;
+       my (@targetsOut, @reply);
        foreach my $target (@targetsIn) {
                if(not nickserv::is_registered($target)) {
-                       notice($user, "\002$target\002: is not registered.");
+                       push @reply, "\002$target\002: is not registered.";
                }
-               my @targets = nickserv::get_nick_user_nicks($target);
+               my @targets = SrSv::NickReg::User::get_nick_users_all($target);
                if(scalar(@targets) == 0) {
-                       notice($user, "\002$target\002: no user[s] online.");
+                       push @reply, "\002$target\002: no user[s] online.";
                        next;
                }
                push @targetsOut, @targets;
        }
+       @targetsIn = (); # drop this list now.
+       notice($user, @reply) if scalar(@reply);
        if(scalar(@targetsOut)) {
                return os_uinfo($user, @targetsOut);
        }
@@ -893,86 +918,6 @@ sub os_rehash($;$) {
        return $event::SUCCESS;
 }
 
-sub os_loners($@) {
-       my ($user, @args) = @_;
-       my $cmd = shift @args;
-       my $noid;
-       if ($cmd =~ /(not?id|noidentify)/) {
-               $noid = 1;
-               $cmd = shift @args;
-       }
-       if (defined($args[0]) and $args[0] =~ /(not?id|noidentify)/) {
-               $noid = 1;
-               shift @args;
-       }
-
-       if(!$cmd or $cmd =~ /^list$/i) {
-               my @reply;
-               foreach my $tuser (chanserv::get_users_nochans($noid)) {
-                       push @reply, get_user_nick($tuser);
-               }
-               notice($user, "Users in zero channels", @reply);
-       }
-       elsif($cmd =~ /^uinfo$/i) {
-               notice($user, get_uinfo($user, chanserv::get_users_nochans($noid)));
-       }
-       elsif($cmd =~ /^kill$/i) {
-               unless(adminserv::can_do($user, 'KILL')) {
-                       notice($user, $err_deny);
-                       return;
-               }
-               foreach my $tuser (chanserv::get_users_nochans($noid)) {
-                       $tuser->{AGENT} = $osnick;
-                       nickserv::kill_user($tuser,
-                               "Killed by \002".get_user_nick($user)."\002".
-                               (@args ? ": ".join(' ', @args) : '')
-                       );
-               }
-       }
-       elsif($cmd =~ /^kline$/i) {
-               unless(adminserv::is_svsop($user, adminserv::S_OPER())) {
-                       notice($user, $err_deny);
-                       return;
-               }
-               foreach my $tuser (chanserv::get_users_nochans($noid)) {
-                       $tuser->{AGENT} = $osnick;
-                       nickserv::kline_user($tuser, services_conf_chankilltime,
-                               "K:Lined by \002".get_user_nick($user)."\002".
-                               (@args ? ": ".join(' ', @args) : '')
-                       );
-               }
-       }
-       elsif($cmd =~ /^(msg|message|notice)$/i) {
-               notice($user, "Must have message to send") unless(@args);
-               foreach my $tuser (chanserv::get_users_nochans($noid)) {
-                       $tuser->{AGENT} = $osnick;
-                       notice($tuser, 
-                               "Automated message from \002".get_user_nick($user),
-                               join(' ', @args)
-                       );
-               }
-       }
-       elsif($cmd =~ /^fjoin$/i) {
-               unless(adminserv::can_do($user, 'FJOIN')) {
-                       notice($user, $err_deny);
-                       return;
-               }
-
-               if ($args[0] !~ /^#/) {
-                       notice($user, "\002".$args[0]."\002 is not a valid channel name");
-                       return;
-               }
-
-               foreach my $tuser (chanserv::get_users_nochans($noid)) {
-                       $tuser->{AGENT} = $osnick;
-                       ircd::svsjoin($osnick, get_user_nick($tuser), $args[0]);
-               }
-       }
-       else {
-               notice($user, "Unknown LONERS command: $cmd",
-                       'Syntax: OS LONERS [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [NOID] [msg/reason]');
-       }
-}
 
 sub os_svskill($$$) {
        my ($user, $targets, $reason) = @_;
@@ -1023,7 +968,8 @@ sub os_gline($$$@) {
        my $opernick;
        return unless ($opernick = adminserv::is_svsop($user, adminserv::S_OPER));
 
-       my $expiry = parse_time(shift @args) if $args[0] =~ /^\+/;
+       my $expiry;
+       $expiry = parse_time(shift @args) if $args[0] =~ /^\+/;
        my $reason = join(' ', @args);
        $reason =~ s/^\:// if $reason;
        my $remove;
@@ -1051,7 +997,10 @@ sub os_gline($$$@) {
                        (undef, $host) = nickserv::get_host($tuser);
                        $ident = '*';
                } else {
-                       $host = nickserv::get_ip($tuser);
+                       $host = get_user_ip($tuser);
+                       if ($host =~ /:/) {
+                               $host = get_ipv6_64($host);
+                       }
                }
        } else {
                notice($user, "Invalid G:line target \002$target\002");
@@ -1077,6 +1026,9 @@ sub os_gline($$$@) {
                        # THIS MAY BE A SOURCE OF BUGS.
 
                        # all is well, do nothing
+               } elsif($host =~ /:/) {
+                       #validating IPv6 addrs without using inet_pton and inet_ntop is a crapshoot
+                       # for now, we do nothing.
                } else {
                        notice($user, "Z:lines can only be placed on IPs or IP ranges");
                        return;
@@ -1091,62 +1043,113 @@ sub os_gline($$$@) {
        return $event::SUCCESS;
 }
 
+sub os_loners($@) {
+       my ($user, @args) = @_;
+       my $cmd = shift @args;
+       my $noid;
+       if ($cmd =~ /(not?id|noidentify)/) {
+               $noid = 1;
+               $cmd = shift @args;
+       }
+       if (defined($args[0]) and $args[0] =~ /(not?id|noidentify)/) {
+               $noid = 1;
+               shift @args;
+       }
+
+       return __os_massmod($user, uc 'clones', $cmd, \&chanserv::get_users_nochans, $noid, @args);
+}
 sub os_clones($@) {
        my ($user, @args) = @_;
        my $cmd = shift @args;
        my $target = shift @args;
 
-       if($cmd =~ /^list$/i) {
+       return __os_massmod($user, 'Clones', $cmd, \&get_clones, $target, @args);
+}
+
+sub os_killnew($@) {
+       my ($user, @args) = @_;
+       my $cmd = shift @args;
+
+       my ($noid, $time);
+       if ($cmd =~ /(not?id|noidentify)/) {
+               $noid = 1;
+               $cmd = shift @args;
+       }
+       if (defined($args[0]) and $args[0] =~ /(not?id|noidentify)/) {
+               $noid = 1;
+               shift @args;
+       }
+       if(defined($args[0] and $args[0] =~ /^\+/)) {
+               $time = parse_time(shift @args);
+       }
+
+       return __os_massmod($user, 'killnew', $cmd, \&get_newusers, [$noid, $time], @args);
+}
+
+sub __os_massmod($$$$@) {
+       my ($user, $cmd0, $cmd1, $func, $arg, @args) = @_;
+       my $msg = join(' ', @args);
+
+       if($cmd1 =~ /^list$/i) {
                my @data;
-               foreach my $tuser (get_clones($target)) {
+               my $noun;
+               foreach my $tuser (&$func($arg)) {
                        push @data, [get_user_nick($tuser), (is_online($tuser) ? "\002Online\002" : "\002Offline\002")];
                }
-               notice($user, columnar {TITLE => "Clones matching \002$target\002",
+               my $title;
+               if($cmd0 eq 'Clones') {
+                       $title = "$cmd0 matching \002$arg\002";
+               } elsif($cmd0 eq 'Loners') {
+                       $title = "$cmd0 ".($arg ? 'Not identified' : '');
+               } elsif($cmd0 eq 'Loners') {
+                       $title = "New users ".($arg ? 'Not identified' : '');
+               }
+               notice($user, columnar {TITLE => $title,
                        NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
        }
-       elsif($cmd =~ /^uinfo$/i) {
-               notice($user, get_uinfo($user, get_clones($target)));
+       elsif($cmd1 =~ /^uinfo$/i) {
+               notice($user, get_uinfo($user, &$func($arg)));
        }
-       elsif($cmd =~ /^kill$/i) {
+       elsif($cmd1 =~ /^kill$/i) {
                unless(adminserv::can_do($user, 'KILL')) {
                        notice($user, $err_deny);
                        return;
                }
-               foreach my $tuser (get_clones($target)) {
+               foreach my $tuser (&$func($arg)) {
                        next unless is_online($tuser);
                        $tuser->{AGENT} = $osnick;
                        nickserv::kill_user($tuser,
                                "Killed by \002".get_user_nick($user)."\002".
-                               (@args ? ": ".join(' ', @args) : '')
+                               ($msg ? ": $msg" : '')
                        );
                }
        }
-       elsif($cmd =~ /^kline$/i) {
+       elsif($cmd1 =~ /^kline$/i) {
                unless(adminserv::is_svsop($user, adminserv::S_OPER())) {
                        notice($user, $err_deny);
                        return;
                }
-               foreach my $tuser (get_clones($target)) {
+               foreach my $tuser (&$func($arg)) {
                        next unless is_online($tuser);
                        $tuser->{AGENT} = $osnick;
                        nickserv::kline_user($tuser, services_conf_chankilltime,
                                "K:Lined by \002".get_user_nick($user)."\002".
-                               (@args ? ": ".join(' ', @args) : '')
+                               ($msg ? ": $msg" : '')
                        );
                }
        }
-       elsif($cmd =~ /^(msg|message|notice)$/i) {
+       elsif($cmd1 =~ /^(msg|message|notice)$/i) {
                notice($user, "Must have message to send") unless(@args);
-               foreach my $tuser (get_clones($target)) {
+               foreach my $tuser (&$func($arg)) {
                        next unless is_online($tuser);
                        $tuser->{AGENT} = $osnick;
                        notice($tuser,
                                "Automated message from \002".get_user_nick($user),
-                               join(' ', @args)
+                               $msg
                        );
                }
        }
-       elsif($cmd =~ /^fjoin$/i) {
+       elsif($cmd1 =~ /^fjoin$/i) {
                unless(adminserv::can_do($user, 'FJOIN')) {
                        notice($user, $err_deny);
                        return;
@@ -1157,15 +1160,16 @@ sub os_clones($@) {
                        return;
                }
 
-               foreach my $tuser (get_clones($target)) {
+               foreach my $tuser (&$func($arg)) {
                        next unless is_online($tuser);
+                       my $cn = $msg; # not a message, most cases it is
                        $tuser->{AGENT} = $osnick;
-                       ircd::svsjoin($osnick, get_user_nick($tuser), $args[0]);
+                       ircd::svsjoin($osnick, get_user_nick($tuser), $cn);
                }
        }
        else {
-               notice($user, "Unknown CLONES command: $cmd",
-                       'Syntax: OS CLONES [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [msg/reason]');
+               notice($user, "Unknown $cmd0 command: $cmd1",
+                       "Syntax: OS $cmd0 [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [msg/reason]");
        }
 }
 
@@ -1231,14 +1235,18 @@ sub get_uinfo($@) {
        my ($user, @userlist) = @_;
        my @reply;
        foreach my $tuser (@userlist) {
-               my ($ident, $host, $vhost, $gecos, $server) = get_user_info($tuser);
+               my ($ident, $host, $vhost, $gecos, $server, $signontime, $quittime) = get_user_info($tuser);
                my $modes = nickserv::get_user_modes($tuser);
                my $target = get_user_nick($tuser);
 
                my ($curchans, $oldchans) = chanserv::get_user_chans_recent($tuser);
        
                my @data = (
-                       ["Status:", (nickserv::is_online($tuser) ? "Online" : "Offline")],
+                       ["Status:", (nickserv::is_online($tuser) ?
+                               "Online (".gmtime2($signontime).')' :
+                               "Offline (".gmtime2($quittime).')'
+                               )
+                       ],
                        ["ID Nicks:", join(', ', nickserv::get_id_nicks($tuser))],
                        ["Channels:", join(', ', @$curchans)],
                        ["Recently Parted:", join(', ', @$oldchans)],
@@ -1283,6 +1291,25 @@ sub get_clones($) {
        return @users;
 }
 
+sub get_newusers($) {
+       my ($noid, $time) = @{$_[0]};
+       ircd::debug("get_newusers: $time");
+       my @users;
+       my $sth; # statement handle. You'll see what I'll do with it next!
+       if($noid) {
+               $sth = $get_newusers_noid;
+       } else {
+               $sth = $get_newusers;
+       }
+
+       $sth->execute(CORE::time()-$time);
+       while(my ($nick, $id, $online) = $sth->fetchrow_array()) {
+               push @users, { NICK => $nick, ID => $id, ONLINE => $online };
+       }
+       $sth->finish();
+       return @users;
+}
+
 ## IRC EVENTS ##
 
 1;
diff --git a/branches/mus-0.4.x-devel/modules/spamserv.pm b/branches/mus-0.4.x-devel/modules/spamserv.pm
new file mode 100644 (file)
index 0000000..941eb67
--- /dev/null
@@ -0,0 +1,263 @@
+package spamserv;
+
+use strict;
+use Storable;
+
+use SrSv::MySQL '$dbh';
+use SrSv::Timer qw(add_timer);
+use SrSv::IRCd::Event qw( addhandler );
+use SrSv::Agent;
+use SrSv::Conf2Consts qw( main services );
+use SrSv::Shared qw($fakehost %conf $idlelength);
+use SrSv::User::Notice;
+use SrSv::Help qw( sendhelp );
+use SrSv::SimpleHash qw(readHash writeHash);
+
+my $ssnick = 'SpamServ';
+my %chanlist;
+
+use SrSv::Process::InParent qw(list_conf loadconf loadchans saveconf savechans);
+
+# should load both spamserv.conf and chans.conf (if available)
+loadconf();
+loadchans();
+
+addhandler('PRIVMSG', undef, undef, 'spamserv::ss_privmsg');
+addhandler('NOTICE', undef, undef, 'spamserv::ss_notice');
+
+agent_connect($ssnick, 'services', undef, '+pqzBGHS', 'Spam Serv');
+agent_join($ssnick, main_conf_diag);
+ircd::setmode($ssnick, main_conf_diag, '+o', $ssnick);
+
+add_timer('', 5, __PACKAGE__, 'spamserv::ss_newclient');
+
+sub ss_newclient {
+       unless (!module::is_loaded('services')) {
+               open ((my $SSNICKFILE), main::PREFIX()."/config/spamserv/nicklist.txt");
+               my ($nick, $ident, $hostmask) = ('','','');
+               my @hexset = ('A'..'F','0'..'9');
+               srand;
+               rand($.) < 1 and ($nick=$_) while <$SSNICKFILE>;
+               chomp $nick;
+               close $SSNICKFILE;
+               if (!nickserv::is_registered($nick) && !nickserv::is_online($nick)) {
+                       $ident = "htIRC-".lc(misc::gen_uuid(1,4));
+                       for (my $i = 1;$i <= 3;$i++) {
+                               for (my $x = 1;$x <= 8;$x++) {
+                                       $hostmask .= $hexset[rand @hexset];
+                               }
+                               $hostmask .= ".";
+                       }
+                       $hostmask .= "IP";
+                       $fakehost = $nick."!".$ident."@".$hostmask;
+
+                       agent_connect($nick, $ident, $hostmask,'+pqH', 'WWW user');
+                       agent_join($nick, main_conf_diag);
+                       ircd::setmode($ssnick, main_conf_diag, '+h', $nick);
+
+                       $idlelength = int(rand($conf{'idlemax'} - $conf{'idlemin'})) + $conf{'idlemin'};
+
+                       add_timer($fakehost, $idlelength, __PACKAGE__, 'spamserv::ss_respawn');
+
+                       join_chans();
+               }
+               else {
+                       add_timer('', 30, __PACKAGE__, 'spamserv::ss_newclient');
+               }
+       }
+}
+
+sub ss_privmsg {
+       my ($src, $dst, $msg) = @_;
+       if (lc $dst eq lc((split /!/,$fakehost)[0])) {
+               ircd::privmsg("SpamServ", main_conf_diag, "Received PRIVMSG: <$src> $msg");
+       }
+       elsif (lc $dst eq "spamserv") {
+               my $user = { NICK => $src, AGENT => $dst };
+               unless(adminserv::is_ircop($user)) {
+                       notice($user, "Permission denied");
+                       return;
+               }
+               my @args = split(/\s+/, $msg);
+               my $cmd = shift @args;
+
+               if ($cmd =~ /^help$/i) {
+                       sendhelp($user, 'spamserv', @args);
+               }
+
+               elsif ($cmd =~ /^rehash/i) {
+                       notice($user, "Loading configuration");
+                       loadconf();
+               }
+
+               if ($cmd =~ /^listconf$/i) {
+                       notice($user, "Configuration:", list_conf);
+               }
+
+               elsif ($cmd =~ /^save/i) {
+                       notice($user, "Saving configuration");
+                       saveconf();
+               }
+
+               elsif ($msg =~ /^set (\S+) (.*)/i) {
+                       if (!adminserv::is_svsop($user, adminserv::S_ROOT())) {
+                               notice($user, 'You do not have sufficient rank for this command');
+                               return;
+                       }
+                       if (update_conf($1, $2)) {
+                               notice($user, "Configuration: $1 = $2");
+                       } else {
+                               notice($user, "This appears to be an invalid option");
+                       }
+               }
+               elsif ($cmd =~ /^watch$/i) {
+                       ss_watch($user, shift @args, @args);
+               }
+       }
+}
+
+sub ss_notice {
+       my ($src, $dst, $msg) = @_;
+       if (lc $dst eq lc((split /!/,$fakehost)[0])) {
+               ircd::privmsg("SpamServ", main_conf_diag, "Received NOTICE: -$src- $msg");
+       }
+       elsif ($dst =~ /^(?:\+|%|@|&|~)?(#.*)/ and exists($chanlist{lc $1})) {
+               ircd::privmsg("SpamServ", main_conf_diag, "Received NOTICE: -$src:$dst- $msg");
+       }
+       
+}
+
+sub ss_chnotice {
+       my ($nick, $cn, $msgs) = @_;
+       $cn =~ s/^[+%@&~]+//;
+       return unless exists($chanlist{lc $cn});
+       foreach my $message (@$msgs) {
+               my $message = "-$nick:$cn- $message";
+       }
+       ircd::privmsg("SpamServ", main_conf_diag, @$msgs);
+}
+
+sub ss_respawn($) {
+        my ($fakehost) = @_;
+       if (defined($fakehost)) {
+               foreach my $cn (keys(%chanlist)) {
+                       agent_part((split /!/, $fakehost)[0], $cn, '');
+               }
+               agent_quit((split /!/, $fakehost)[0], '');
+               add_timer('', 120, __PACKAGE__, 'spamserv::ss_newclient');
+               undef $fakehost;
+       }
+}
+
+sub ss_watch($$@) {
+       my ($user, $cmd, @args) = @_;
+       if ($cmd =~ /^add$/i) {
+               if (@args == 1) {
+                       add_channel($user,$args[0]);
+               } else {
+                       notice($user, 'Syntax: WATCH ADD <#chan>');
+               }
+       }
+       if ($cmd =~ /^del(ete)?$/i) {
+               if (@args == 1) {
+                       del_channel($user,$args[0]);
+               } else {
+                       notice($user, 'Syntax: WATCH DEL <#chan>');
+               }
+       }
+       elsif ($cmd =~ /^list$/i) {
+               ss_list($user);
+       }
+}
+
+sub ss_list($) {
+       my ($user) = @_;
+       notice($user, 'Channels currently being watched');
+       foreach my $cn (keys(%chanlist)) {
+               notice($user, '  '.$cn);
+       }
+}
+
+sub add_channel($$) {
+       my ($user, $cn) = @_;
+       if (!exists($chanlist{lc $cn})) {
+               $chanlist{lc $cn} = 1;
+               agent_join((split /!/, $fakehost)[0], $cn) if defined $fakehost;
+               notice($user, "Channel \002$cn\002 will now be watched");
+               savechans();
+               return 1;
+       } else {
+               notice($user, "Channel \002$cn\002 is already being watched");
+               return 0;
+       }
+}
+
+sub del_channel($$) {
+       my ($user, $cn) = @_;
+       if (exists($chanlist{lc $cn})) {
+               delete($chanlist{lc $cn});
+               agent_part((split /!/, $fakehost)[0], $cn, '') if defined $fakehost;
+               notice($user, "Channel \002$cn\002 will not be watched");
+               savechans();
+               return 1;
+       } else {
+               notice($user, "Channel \002$cn\002 is not being watched");
+               return 0;
+       }
+}
+
+sub savechans() {
+       my @channels = keys(%chanlist);
+       Storable::nstore(\@channels, "config/spamserv/chans.conf");
+}
+
+sub saveconf() {
+       writeHash(\%conf, "config/spamserv/spamserv.conf");
+}
+
+sub list_conf() {
+       my @k = keys(%conf);
+       my @v = values(%conf);
+       my @reply;
+
+       for(my $i=0; $i<@k; $i++) {
+               push @reply, $k[$i]." = ".$v[$i];
+       }
+       return @reply;
+}
+
+sub loadconf() {
+       # doesn't seem to pick up any of the values
+       %conf = readHash("config/spamserv/spamserv.conf");
+}
+
+sub loadchans() {
+       return unless(-f "config/spamserv/chans.conf");
+       my @channels = @{Storable::retrieve("config/spamserv/chans.conf")};
+       foreach my $cn (@channels) {
+               $chanlist{lc $cn} = 1;
+       }
+}
+
+sub update_conf($$) {
+       my ($k,$v) = @_;
+       if (exists($conf{$k})) {
+               $conf{$k} = $v;
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+sub join_chans() {
+       foreach my $cn (keys(%chanlist)) {
+               agent_join((split /!/, $fakehost)[0], $cn);
+       }
+}
+
+sub init { }
+sub begin { }
+sub end { }
+sub unload { savechans(); saveconf(); }
+
+1;
index a356de03da6a555f5d5107785a01867843186093..2f03ac4c481218574cc851e35ce97b2bda27ed0f 100644 (file)
@@ -16,6 +16,8 @@
 package sql;
 use strict;
 
+use Time::HiRes qw( time );
+
 use SrSv::MySQL qw( $dbh );
 use SrSv::Text::Format qw( columnar );
 use SrSv::IRCd::Event qw( addhandler );
@@ -24,10 +26,18 @@ use SrSv::Conf2Consts qw( main );
 use SrSv::User qw( get_user_nick );
 use SrSv::User::Notice;
 
+# these are really a layer violation
+# but there's not much else way to requeue our events
+use SrSv::Process::Worker qw( multi );
+use SrSv::IRCd::Event qw( callfuncs );
+
+use SrSv::Process::InParent qw( ev_privmsg );
+
+our %users;
 
-my $sqlnick = 'SQLServ';
+our $sqlnick = 'SQLServ';
 
-agent_connect($sqlnick, 'services', 'services.SC.net', '+pqzBGHS', 'Database Query Agent');
+agent_connect($sqlnick, 'services', undef, '+pqzBGHS', 'Database Query Agent');
 agent_join($sqlnick, main_conf_diag);
 ircd::setmode($sqlnick, main_conf_diag, '+o', $sqlnick);
 
@@ -35,24 +45,168 @@ addhandler('PRIVMSG', undef, lc $sqlnick, 'sql::ev_privmsg');
 sub ev_privmsg {
        my ($src, $dst, $payload) = @_;
        my $user = { NICK => $src, AGENT => $sqlnick };
-       if(adminserv::is_svsop($user, adminserv::S_ROOT)) {
+       #FIXME: More fine grained permissions needed.
+       # SELECT is relatively safe. EXPLAIN is too.
+       unless(adminserv::is_svsop($user, adminserv::S_ROOT())) {
                notice($user, "Permission denied"); #FIXME: need $err_deny
+               return;
+       }
+       #irssi's splitlong uses ... for beginning and end of a split payload
+       $payload =~ s/(^\.\.\.|\.\.\.$)//g;
+       if($payload =~ /^help/) {
+               notice($user, "Sorry, no documentation yet.");
+       }
+       elsif($payload =~ /^(SELECT|SHOW CREATE|SHOW TABLES|UPDATE|INSERT|ALTER|EXPLAIN) ?(.*)$/i) {
+               my $cmd = $1;
+               my $statement = $2;
+               $users{$src}{STMT} = $statement;
+               $users{$src}{CMD} = uc $cmd;
+       } else {
+               $users{$src}{STMT} .= ' '.$payload;
+       }
+       if ($payload =~ /(\\G|;)$/) {
+               if(!multi) {
+                       ev_loopback($src, $dst, "$users{$src}{CMD} $users{$src}{STMT}");
+               } else {
+                       callfuncs('LOOPBACK', 0, 1, 0,
+                               [$src, $sqlnick, "$users{$src}{CMD} $users{$src}{STMT}"]);
+               }
+               delete($users{$src});
+       }
+}
+
+addhandler('LOOPBACK', undef, lc $sqlnick, 'sql::ev_loopback');
+sub ev_loopback {
+       my ($src, $dst, $payload) = @_;
+       my $user = { NICK => $src, AGENT => $sqlnick };
+       if($payload =~ /^SELECT (.*)$/i) {
+               my $statement = $1;
+               if ($statement =~ /(\\G|;)$/) {
+                       my $mode = ($1 eq ';' ? 1 : 2);
+                       SELECT($user, $statement, $mode);
+               }
+       } elsif($payload =~ /^SHOW (CREATE|TABLES) ?(.*)$/i) {
+               my $cmd = $1;
+               my $statement = $2;
+               if ($statement =~ /(\\G|;)$/) {
+                       my $mode = ($1 eq ';' ? 1 : 2);
+                       if(uc($cmd) eq 'CREATE') {
+                               SHOW_CREATE($user, $statement, $mode);
+                       }
+                       elsif(uc($cmd) eq 'TABLES') {
+                               SHOW_TABLES($user, $statement, $mode);
+                       }
+               }
+       } elsif($payload =~ /^UPDATE (.*)$/i) {
+               my $statement = $1;
+               if ($statement =~ /(\\G|;)$/) {
+                       UPDATE($user, $statement);
+               }
+       } elsif($payload =~ /^INSERT (.*)$/i) {
+               my $statement = $1;
+               if ($statement =~ /(\\G|;)$/) {
+                       INSERT($user, $statement);
+               }
+       } elsif($payload =~ /^ALTER (.*)$/i) {
+               my $statement = $1;
+               if ($statement =~ /(\\G|;)$/) {
+                       ALTER($user, $statement);
+               }
+       } elsif($payload =~ /^EXPLAIN (.*)$/i) {
+               my $statement = $1;
+               if ($statement =~ /(\\G|;)$/) {
+                       my $mode = ($1 eq ';' ? 1 : 2);
+                       EXPLAIN($user, $statement, $mode);
+               }
        }
-       if($payload =~ /^SELECT (.*)$/) {
-               my $arrayRef;
-               eval {
-                       $arrayRef = $dbh->selectall_arrayref("SELECT $1");
-               };
-               if($@) {
-                       #ircd::debug("AIEEEEE! $@");
-               } elsif(scalar(@$arrayRef)) {
-                       notice($user, columnar( { BORDER => 1, NOHIGHLIGHT => 1 }, @$arrayRef ) );
+}
+
+sub queryMode2($$) {
+       my ($inRef, $namesRef) = @_;
+       my @out;
+       for(my $i = 1; $i <= scalar(@$inRef); $i++) {
+               my @rowIn = @{$inRef->[$i-1]};
+               my @rowTmp;
+               push @out, "*************************** $i. row ***************************";
+               for(my $j = 0; $j < scalar(@rowIn); $j++) {
+                       push @rowTmp, [$namesRef->[$j].':', $rowIn[$j]];
+               }
+               push @out, columnar( { JUSTIFIED => 1, NOHIGHLIGHT => 1 }, @rowTmp );
+       }
+       return @out;
+}
+
+sub UPDATE {
+       my ($user, $statement) = @_;
+       notice($user, "Unsupported command");
+}
+sub ALTER {
+       my ($user, $statement) = @_;
+       notice($user, "Unsupported command");
+}
+sub EXPLAIN {
+       my ($user, $statement, $mode) = @_;
+       readonlyQuery($user, 'EXPLAIN', $statement, $mode);
+}
+sub INSERT {
+       my ($user, $statement) = @_;
+       notice($user, "Unsupported command");
+}
+
+sub SELECT {
+       my ($user, $statement, $mode) = @_;
+       readonlyQuery($user, 'SELECT', $statement, $mode);
+}
+
+sub readonlyQuery {
+       my ($user, $cmd, $statement, $mode) = @_;
+       my ($arrayRef, $namesRef);
+       $statement =~ s/(;|\\G)$//;
+       my ($startTime, $endTime, $error);
+       eval {
+               local $SIG{__WARN__} = sub { $error = \@_ };
+               my $sth = $dbh->prepare("$cmd $statement");
+               $startTime = time();
+               my $ret = $sth->execute();
+               if(defined($ret)) {
+                       $namesRef = $sth->FETCH('NAME');
+                       $arrayRef = $sth->fetchall_arrayref();
+                       $endTime = time();
+               }
+       };
+       if($@) {
+               #ircd::debug("AIEEEEE! $@");
+               notice($user, "AIEEEEE!", "$cmd $statement", $@, '--');
+       } elsif(!defined($arrayRef)) {
+               notice($user, 'Error:', @$error, '--');
+       } elsif(scalar(@$arrayRef)) {
+               my @out;
+               if($mode == 2) {
+                       @out = queryMode2($arrayRef, $namesRef);
                } else {
-                       notice($user, "Empty result.");
+                       @out = columnar( { BORDER => 1, NOHIGHLIGHT => 1 }, $namesRef, @$arrayRef );
                }
+               my $elapsed = $endTime-$startTime;
+               $elapsed = sprintf('%.2f sec%s', $elapsed, $elapsed == 1 ? '' : 's');
+               notice($user, @out, scalar(@$arrayRef).' rows in set ('.$elapsed.')');
+       } else {
+               my $elapsed = $endTime-$startTime;
+               $elapsed = sprintf('%.2f sec%s', $elapsed, $elapsed == 1 ? '' : 's');
+               notice($user, "Empty result. ($elapsed)");
        }
 }
 
+sub SHOW_CREATE {
+       my ($user, $statement, $mode) = @_;
+       readonlyQuery($user, 'SHOW CREATE', $statement, $mode);
+}
+
+sub SHOW_TABLES {
+       my ($user, $statement, $mode) = @_;
+       readonlyQuery($user, 'SHOW TABLES', $statement, $mode);
+}
+
+
 sub init { }
 sub begin { }
 sub end { }
index 546c551e01b77afc0ef9735fdd08ac4b321842f3..ba4a5e05c3d5a4fe92a01b64499121d2819ad87b 100755 (executable)
@@ -28,20 +28,21 @@ use constant { # Need them up here, before anybody derefs them.
        NETDUMP => 0,
 };
 
-use Cwd 'abs_path';
+use Cwd qw( abs_path getcwd );
 use File::Basename;
 
 BEGIN {
-       use Cwd qw( abs_path getcwd );
-       use File::Basename;
        my %constants = (
                CWD => getcwd(),
                PREFIX => dirname(abs_path($0)),
        );
        require constant; import constant(\%constants);
 }
+# FIXME: remove the chdir call!
 chdir PREFIX;
-use lib PREFIX;
+use lib PREFIX, "@{[PREFIX]}/CPAN";
+
+die("Please don't run services as root!\n") if $< eq 0;
 
 use Getopt::Long;
 BEGIN {
@@ -65,18 +66,7 @@ BEGIN {
        import constant { COMPILE_ONLY => $compile_only };
 }
 
-use SrSv::Conf::Parameters main => [
-       qw(local remote port numeric pass load email replyto),
-       [info => 'SurrealServices'],
-       [procs => 4],
-       [diag => '#Diagnostics'],
-       [netname => 'Network'],
-       [sig => 'Thank you for chatting with us.'],
-       [unsyncserver => undef],
-       [nomail => undef],
-       [logmail => undef],
-       [hashed_passwords => undef],
-];
+use SrSv::Conf::main;
 
 use SrSv::OnIRC (1);
 
@@ -88,27 +78,24 @@ use IO::Socket;
 use Carp;
 
 use SrSv::IRCd::Send; # <-- is package ircd
-#use libs::config;
 use libs::misc;
 use libs::event;
 use libs::modes;
 use libs::module;
 
-# This is only here for compile checking. NEVER enable it for anything else.
-#use modules::services;
-
 use SrSv::Process::Init ();
-use SrSv::Process::Worker qw(spawn);
+use SrSv::Process::Worker qw(spawn write_pidfiles);
 use SrSv::Message qw(add_callback);
 use SrSv::Timer qw(begin_timer);
 
 #*conf = \%main_conf; #FIXME
 
 STDOUT->autoflush(1);
+STDERR->autoflush(1);
 
 our $progname = 'SurrealServices';
 our $version = '0.4.3-pre';
-our $extraversion = 'configured for UnrealIRCd 3.2.7';
+our $extraversion = 'configured for UnrealIRCd 3.2.8.1';
 
 #FIXME: Figure out where $rsnick belongs and update all references
 our $rsnick; *rsnick = \$core::rsnick;
@@ -117,9 +104,21 @@ print "Starting $progname $version.\n";
 
 #config::loadconfig();
 
+{
+       use SrSv::DB::Schema;
+       my $schemaVer = check_schema();
+       my $newestSchema = find_newest_schema();
+       if($schemaVer != $newestSchema) {
+               print "Found schema version ($schemaVer). Expected ($newestSchema). Did you run db-setup.pl ?\n";
+               die unless COMPILE_ONLY;
+       }
+}
+
 module::load();
 exit() if COMPILE_ONLY;
+print "Connecting...";
 ircd::serv_connect();
+print " Connected.\n";
 
 unless(DEBUG) {
        exit if fork;
@@ -135,8 +134,10 @@ unless(DEBUG) {
 if(main_conf_procs) {
        for(1..main_conf_procs) { spawn(); }
 }
+write_pidfiles();
 
 SrSv::Process::Init::do_init();
+
 module::begin();
 
 begin_timer();
diff --git a/branches/mus-0.4.x-devel/sql/004003000.sql b/branches/mus-0.4.x-devel/sql/004003000.sql
new file mode 100644 (file)
index 0000000..41a706b
--- /dev/null
@@ -0,0 +1,28 @@
+#0.4.3
+alter table user
+  modify column id bigint unsigned not null auto_increment,
+  drop primary key,
+  add primary key using btree (id),
+  drop key nick,
+  add key nick using hash (nick),
+  drop key ip,
+  add key using btree (ip);
+
+# Duplicate key given PRIMARY already indexes this column first.
+ALTER TABLE `nickalias` DROP KEY `root`;
+
+# Duplicate keys given PRIMARY already indexes this column first.
+ALTER TABLE `akick` DROP INDEX `chan`;
+ALTER TABLE `silence` DROP KEY `nick`;
+ALTER TABLE `nickid` DROP INDEX `id`, ADD KEY `nrid` (`nrid`);
+ALTER TABLE `watch` DROP KEY `nick`;
+
+# merged into above 'alter table user'
+#ALTER TABLE `user` MODIFY `id` bigint unsigned NOT NULL auto_increment;
+DROP TABLE `srsv_schema`;
+CREATE table `srsv_schema` (
+       `ver` int unsigned NOT NULL,
+       `singleton` int unsigned default 0,
+       PRIMARY KEY (`singleton`)
+) ENGINE=MyISAM;
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003000);
diff --git a/branches/mus-0.4.x-devel/sql/004003001.sql b/branches/mus-0.4.x-devel/sql/004003001.sql
new file mode 100644 (file)
index 0000000..b2cdf82
--- /dev/null
@@ -0,0 +1,7 @@
+CREATE TABLE usertags (
+       `userid` bigint NOT NULL,
+       `tag` char(30) NOT NULL,
+       PRIMARY KEY USING HASH (`userid`, `tag`)
+) ENGINE=HEAP;
+
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003001);
diff --git a/branches/mus-0.4.x-devel/sql/004003002.sql b/branches/mus-0.4.x-devel/sql/004003002.sql
new file mode 100644 (file)
index 0000000..2d3a021
--- /dev/null
@@ -0,0 +1,4 @@
+ALTER TABLE `user` MODIFY `ip` bigint unsigned,
+       ADD COLUMN `ipv6` char(39) default NULL;
+
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003002);
diff --git a/branches/mus-0.4.x-devel/sql/004003003.sql b/branches/mus-0.4.x-devel/sql/004003003.sql
new file mode 100644 (file)
index 0000000..5aea4e1
--- /dev/null
@@ -0,0 +1,3 @@
+ALTER TABLE `user` DROP COLUMN guest;
+
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003003);
diff --git a/branches/mus-0.4.x-devel/sql/004003004.sql b/branches/mus-0.4.x-devel/sql/004003004.sql
new file mode 100644 (file)
index 0000000..96a4209
--- /dev/null
@@ -0,0 +1,12 @@
+ALTER TABLE  `chanreg` ADD  `bantime` BIGINT( 20 ) UNSIGNED NOT NULL;
+
+CREATE TABLE IF NOT EXISTS `tmpban` (
+  `channel` varchar(20) NOT NULL,
+  `banmask` varchar(20) NOT NULL,
+  `expiry` bigint(20) unsigned NOT NULL,
+  `timeset` bigint(20) unsigned NOT NULL,
+  KEY `banmask` (`banmask`),
+  KEY `timeset` (`timeset`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003004);
diff --git a/branches/mus-0.4.x-devel/sql/004003005.sql b/branches/mus-0.4.x-devel/sql/004003005.sql
new file mode 100644 (file)
index 0000000..8ef5a2e
--- /dev/null
@@ -0,0 +1,14 @@
+ALTER TABLE  `chanreg` DROP `bantime`;
+ALTER TABLE `chanreg` ADD  `bantime` int(11) UNSIGNED default 0;
+
+DROP TABLE IF EXISTS `tmpban`;
+CREATE TABLE IF NOT EXISTS `tmpban` (
+  `channel` varchar(32) NOT NULL,
+  `banmask` varchar(110) NOT NULL,
+  `expiry` bigint(20) unsigned NOT NULL,
+  `timeset` bigint(20) unsigned NOT NULL,
+  UNIQUE KEY `banmask` (`channel`, `banmask`),
+  KEY `expiry` (`expiry`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+REPLACE INTO `srsv_schema` (`ver`) VALUES (4003005);
index e39e5aadbff4d7b1478d6754fd054f9c006a80c1..8a34b1a4491c7a4f03deda89bb03e19bd52e1a45 100644 (file)
@@ -7,7 +7,7 @@ CREATE TABLE `akick` (
   `reason` text,
   `time` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`chan`,`nick`,`ident`,`host`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 
 CREATE TABLE `bot` (
@@ -17,7 +17,7 @@ CREATE TABLE `bot` (
   `gecos` char(50) NOT NULL default '',
   `flags` mediumint NOT NULL default '1',
   PRIMARY KEY  (`nick`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `chanacc` (
   `chan` char(32) NOT NULL default '',
@@ -27,7 +27,7 @@ CREATE TABLE `chanacc` (
   `time` int(10) unsigned NOT NULL default '0',
   `last` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`chan`,`nrid`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `chanclose` (
   `chan` char(30) NOT NULL default '',
@@ -36,14 +36,14 @@ CREATE TABLE `chanclose` (
   `time` int(11) unsigned NOT NULL default '0',
   `type` tinyint(3) unsigned NOT NULL default '0',
   PRIMARY KEY (`chan`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `chanlvl` (
   `chan` char(32) NOT NULL default '',
   `perm` smallint(5) unsigned NOT NULL default '0',
   `level` tinyint(4) NOT NULL default '0',
   PRIMARY KEY  (`chan`,`perm`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `chanperm` (
   `name` char(10) NOT NULL default '',
@@ -52,7 +52,7 @@ CREATE TABLE `chanperm` (
   `max` tinyint(3) unsigned NOT NULL default 0,
   PRIMARY KEY  (`name`),
   UNIQUE KEY `id` (`id`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `chanreg` (
   `chan` varchar(32) NOT NULL default '',
@@ -68,7 +68,7 @@ CREATE TABLE `chanreg` (
   `flags` mediumint(8) unsigned NOT NULL default '0',
   `bantype` tinyint(8) unsigned NOT NULL default '0',
   PRIMARY KEY  (`chan`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 
 CREATE TABLE `ircop` (
@@ -76,7 +76,7 @@ CREATE TABLE `ircop` (
   `level` tinyint(3) unsigned NOT NULL default '0',
   `pass` char(127) binary NOT NULL default '',
   PRIMARY KEY  (`nick`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `logonnews` (
   `setter` char(30) NOT NULL default '',
@@ -85,7 +85,7 @@ CREATE TABLE `logonnews` (
   `time` int(11) unsigned NOT NULL default '0',
   `expire` int(11) unsigned NOT NULL default '0',
   `msg` text NOT NULL
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `memo` (
   `src` varchar(30) NOT NULL default '',
@@ -96,14 +96,14 @@ CREATE TABLE `memo` (
   `msg` text NOT NULL,
   PRIMARY KEY  (`src`,`dstid`,`chan`,`time`),
   KEY `dst` (`dstid`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `ms_ignore` (
   `nrid` int(11) unsigned NOT NULL default '0',
   `ignoreid` int(11) unsigned NOT NULL default '0',
   `time` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`nrid`,`ignoreid`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `nickalias` (
   `nrid` int(11) unsigned NOT NULL default '0',
@@ -112,14 +112,14 @@ CREATE TABLE `nickalias` (
   `last` int(11) unsigned NOT NULL default 0,
   PRIMARY KEY  (`nrid`,`alias`),
   UNIQUE KEY `alias` (`alias`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `nickid` (
   `id` int(10) unsigned NOT NULL default '0',
   `nrid` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`,`nrid`),
   KEY `nrid` (`nrid`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 CREATE TABLE `nickreg` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
@@ -136,7 +136,7 @@ CREATE TABLE `nickreg` (
   `nearexp` tinyint(3) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`),
   UNIQUE KEY `nick` (`nick`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `sesexname` (
   `host` varchar(64) NOT NULL default '',
@@ -165,7 +165,7 @@ CREATE TABLE `qline` (
   PRIMARY KEY  (`mask`),
   KEY `time` (`time`),
   KEY `expire` (`expire`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `silence` (
   `nrid` int(11) unsigned NOT NULL default '0',
@@ -174,7 +174,7 @@ CREATE TABLE `silence` (
   `expiry` int(10) unsigned NOT NULL default '0',
   `comment` char(100) default NULL,
   PRIMARY KEY  (`nrid`,`mask`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 
 CREATE TABLE `svsop` (
@@ -182,7 +182,7 @@ CREATE TABLE `svsop` (
   `level` tinyint(3) unsigned NOT NULL default '0',
   `adder` char(30) NOT NULL default '',
   PRIMARY KEY  (`nrid`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `vhost` (
   `nrid` int(11) unsigned NOT NULL default '0',
@@ -191,14 +191,14 @@ CREATE TABLE `vhost` (
   `adder` char(30) NOT NULL default '',
   `time` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`nrid`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `watch` (
   `nrid` int(11) unsigned NOT NULL default '0',
   `mask` char(106) NOT NULL default '',
   `time` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`nrid`,`mask`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `welcome` (
   `chan` varchar(32) NOT NULL default '',
@@ -207,7 +207,7 @@ CREATE TABLE `welcome` (
   `time` int(10) NOT NULL default '0',
   `msg` text NOT NULL,
   PRIMARY KEY  (`chan`,`id`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 CREATE TABLE `nicktext` (
   `nrid` int(11) unsigned NOT NULL default 0,
@@ -216,7 +216,7 @@ CREATE TABLE `nicktext` (
   `chan` varchar(32) default NULL,
   `data` text default NULL,
   PRIMARY KEY (`nrid`, `type`, `id`, `chan`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 #################################################
 # Volatile tables
@@ -227,7 +227,7 @@ CREATE TABLE `chan` (
   `modes` char(63) binary NOT NULL default '',
   `seq` mediumint(8) unsigned NOT NULL default '0',
   PRIMARY KEY  (`chan`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 DROP TABLE IF EXISTS `chanban`;
 CREATE TABLE `chanban` (
@@ -237,7 +237,7 @@ CREATE TABLE `chanban` (
   `time` int(10) unsigned NOT NULL default '0',
   `type` tinyint(3) unsigned NOT NULL default '0',
   PRIMARY KEY  (`chan`,`mask`,`type`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 #DROP TABLE IF EXISTS `chantext`;
 CREATE TABLE `chantext` (
@@ -246,7 +246,7 @@ CREATE TABLE `chantext` (
   `key` varchar(32) default NULL,
   `data` text default NULL,
   PRIMARY KEY (`chan`, `type`, `key`)
-) TYPE=MyISAM;
+) ENGINE=MyISAM;
 
 DROP TABLE IF EXISTS `chanuser`;
 CREATE TABLE `chanuser` (
@@ -258,7 +258,7 @@ CREATE TABLE `chanuser` (
   PRIMARY KEY  (`nickid`,`chan`),
   KEY `chan` (`chan`),
   KEY `nickid` (`nickid`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 DROP TABLE IF EXISTS `nickchg`;
 CREATE TABLE `nickchg` (
@@ -266,7 +266,7 @@ CREATE TABLE `nickchg` (
   `nickid` int(11) unsigned NOT NULL default '0',
   `nick` char(30) NOT NULL default '',
   PRIMARY KEY  (`nick`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 DROP TABLE IF EXISTS `tklban`;
 CREATE TABLE `tklban` (
@@ -278,7 +278,7 @@ CREATE TABLE `tklban` (
   `time` int(11) unsigned NOT NULL default 0,
   `reason` char(255) NOT NULL default '',
   PRIMARY KEY (`type`, `ident`, `host`)
-) TYPE = HEAP;
+) ENGINE = HEAP;
 
 DROP TABLE IF EXISTS `spamfilter`;
 CREATE TABLE `spamfilter` (
@@ -291,7 +291,7 @@ CREATE TABLE `spamfilter` (
   `reason` char(255) NOT NULL default '',
   `mask` char(255) NOT NULL default '',
   PRIMARY KEY (`target`, `action`, `mask`)
-) TYPE = HEAP;
+) ENGINE = HEAP;
 
 # Keep this even though it is volatile; it still contains useful data
 CREATE TABLE `user` (
@@ -315,7 +315,7 @@ CREATE TABLE `user` (
   PRIMARY KEY  (`id`),
   UNIQUE KEY `nick` (`nick`),
   KEY `ip` (`ip`)
-) TYPE=HEAP;
+) ENGINE=HEAP;
 
 #################################################
 # Not used
@@ -329,7 +329,7 @@ DROP TABLE IF EXISTS `chanlog`;
 #    `time` unsigned int NOT NULL default 0,
 #    `email` varchar(100) NOT NULL default ''
 #    PRIMARY KEY (`chan`)
-#) TYPE = MyISAM;
+#) ENGINE = MyISAM;
 
 #################################################
 # Upgrades
@@ -338,23 +338,3 @@ DROP TABLE IF EXISTS `chanlog`;
 ALTER TABLE chanperm MODIFY `name` char(16) NOT NULL default '';
 UPDATE chanperm SET name='AkickEnforce' WHERE name LIKE 'AkickEn%';
 ALTER TABLE `ms_ignore` DROP KEY `nickid`, DROP COLUMN id;
-
-alter table user
-  modify column id int(11) unsigned not null auto_increment,
-  drop primary key,
-  add primary key using btree (id),
-  drop key nick,
-  add key nick using hash (nick),
-  drop key ip,
-  add key using btree (ip);
-
-# Duplicate key given PRIMARY already indexes this column first.
-ALTER TABLE `nickalias` DROP KEY `root`;
-
-# Duplicate keys given PRIMARY already indexes this column first.
-ALTER TABLE `akick` DROP INDEX `chan`;
-ALTER TABLE `silence` DROP KEY `nick`;
-ALTER TABLE `nickid` DROP INDEX `id`, ADD KEY `nrid` (`nrid`);
-ALTER TABLE `watch` DROP KEY `nick`;
-
-ALTER TABLE `user` MODIFY `id` bigint unsigned NOT NULL default 0;
diff --git a/branches/mus-0.4.x-devel/tests/inspConnect.pl b/branches/mus-0.4.x-devel/tests/inspConnect.pl
new file mode 100755 (executable)
index 0000000..78e123f
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use IO::Socket;
+use Time::HiRes qw(gettimeofday);
+my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
+               PeerPort => 7000,
+               Proto    => "tcp")
+               or die "Couldn't connect to localhost:7000 : $@\n";
+$socket->autoflush(1);
+&connected;
+my @serverlist;
+my %users;
+while ( <$socket> ) { 
+       print "-> $_";
+       parsemsg($_); 
+}
+
+sub connected {
+       # SERVER servername password hopcount id :description
+       print $socket "SERVER services.test.net polarbears 0 00A :Services \n";
+}
+sub parsemsg {
+       my $msg = $_;
+       $msg =~ s/[\r\n]//g;
+       if ($msg =~ /^SERVER (.*) (.*) (.*) (.*) :(.+)/) {
+               push @serverlist, $4;
+               ircsend(":00A BURST");
+               ircsend(":services.test.net VERSION :SurrealServices 00A");
+               ircsend(":00A UID 00AAAAAAB ".time." NickServ services.test.net services.test.net NickServ 0.0.0.0 ".time." +io :Nickname Services");
+               ircsend(":00AAAAAAB OPERTYPE Services");
+                ircsend(":00A UID 00AAAAAAC ".time." ChanServ services.test.net services.test.net ChanServ 0.0.0.0 ".time." +io :Channel Services");
+                ircsend(":00AAAAAAC OPERTYPE Services");
+                ircsend(":00A UID 00AAAAAAD ".time." MemoServ services.test.net services.test.net MemoServ 0.0.0.0 ".time." +io :Memo Services");
+                ircsend(":00AAAAAAD OPERTYPE Services");
+                ircsend(":00A UID 00AAAAAAE ".time." OperServ services.test.net services.test.net OperServ 0.0.0.0 ".time." +io :Oper Services");
+                ircsend(":00AAAAAAE OPERTYPE Services");
+               ircsend(":00A ENDBURST");
+               ircsend(":00A PING 00A $serverlist[0]");
+       }
+       if ($msg =~ /^:(.*) PING (.*) (.*)$/) {
+               if ($1 eq $serverlist[0]) {
+                       ircsend(":00A PONG 00A $serverlist[0]");
+               }
+       }
+       if ($msg =~ /^:(.*) FJOIN (.*) (.*) (.+) :?(.+)$/) {
+               parse_fjoin($1,$2,$3,$4,$5);
+       }
+       if ($msg =~ /^:(.*) IDLE (.*)$/) {
+               parse_idle($1,$2);
+       }
+       if ($msg =~ /^:(.*) UID (\S+) (\d+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (.+) :(.+)$/) {
+               parse_uid($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
+       }
+       if ($msg =~ /^:(.*) PRIVMSG (\S+) :(.+)$/) {
+               parse_privmsg($1,$2,$3);
+       }
+}
+sub ircsend {
+       my $msg = shift;
+       print "<- $msg\n";
+       $msg .= " \n";
+       print $socket $msg;
+}
+
+sub parse_fjoin {
+       #:431 FJOIN #test 1246571540 +nt :,431AAAAAC ,431AAAAAA
+       my ($src, $chan, $ts, $modes, $users) = @_;
+       if ($chan eq "#test") {
+               print "!!! aa - $modes\n";
+               ircsend(":00A FJOIN $chan $ts $modes :o,00AAAAAAB o,00AAAAAAC o,00AAAAAAD o,00AAAAAAE");
+       }
+}
+sub parse_idle {
+       my ($src, $target) = @_;
+       ircsend(":$target IDLE $users{$src}{'nick'} ".time." 0");
+}
+sub parse_uid {
+       #:431 UID 431AAAAAA 1246349244 MusashiX90 127.0.0.1 netadmin.omega.org.za nano 127.0.0.1 1246349249 +Wios +ACJKLNOQacdfgjklnoqtx :mwt
+       my ($src, $uid, $ts, $nick, $hostname, $cloak, $ident, $ip, $signon, $modes, $realname) = @_;
+       print "DEBUG: Added '$nick' to users\n";
+       $users{$uid}{'nick'} = $nick;
+}
+
+sub parse_privmsg {
+       my ($src, $target, $msg) = @_;
+       # PRIVMSG sent to MemoServ
+       if ($target eq "00AAAAAAD") {
+               ircsend(":$target NOTICE $src :Received your message");
+       }
+}
diff --git a/branches/mus-0.4.x-devel/tests/ipv6.pl b/branches/mus-0.4.x-devel/tests/ipv6.pl
new file mode 100755 (executable)
index 0000000..3da615e
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use MIME::Base64 qw( decode_base64 encode_base64 );
+use Socket;
+use Socket6;
+
+BEGIN {
+       use Cwd qw( abs_path getcwd );
+       use File::Basename qw( dirname );
+       use constant { PREFIX => abs_path(dirname(abs_path($0)).'/../') };
+}
+use lib PREFIX;
+
+use SrSv::Conf::main;
+use SrSv::IPv6;
+
+my $IPstring = 'AAAAAAAAAAAAAAAAAAAAAQ==';
+my $IPstring2 = 'CgECgw==';
+my $IPstring3 = 'IAEZOAJdvu8AAAAAAAEABA';
+
+#print length(decode_base64($IPstring)), "\n", length(decode_base64($IPstring2)), "\n";
+#exit;
+#print Socket6::inet_ntop(AF_INET6, decode_base64($IPstring)), "\n";
+#print Socket6::inet_ntop(AF_INET, decode_base64($IPstring2)), "\n";
+print Socket6::inet_ntop(AF_INET6, decode_base64($IPstring3)), "\n";
+print get_ipv6_net(Socket6::inet_ntop(AF_INET6, decode_base64($IPstring3))), "\n";
diff --git a/branches/mus-0.4.x-devel/tests/seqTest.pl b/branches/mus-0.4.x-devel/tests/seqTest.pl
new file mode 100755 (executable)
index 0000000..ae6d74b
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use strict;
+
+BEGIN {
+       use Cwd qw( abs_path getcwd );
+       use File::Basename qw( dirname );
+       use constant { PREFIX => abs_path(dirname(abs_path($0)).'/../') }
+}
+use lib PREFIX;
+
+use libs::misc;
+use SrSv::Util qw(say seqifyList makeSeqList);
+
+#say makeSeqList(92..99,1..3,5..9,);
+#say seqifyList(92..99,1..3,5..9,);
+say seqifyList(makeSeqList(92..99,1..3,5..9,10,11));
similarity index 91%
rename from branches/mus-0.4.x-devel/testHash.pl
rename to branches/mus-0.4.x-devel/tests/testHash.pl
index 025e0869818c8148e284fdcf522fdaf7958327c5..c746a6cc7614e5c0a2a30b7c006dad9f815ab3ac 100755 (executable)
@@ -7,7 +7,7 @@ use strict;
 BEGIN {
        use Cwd qw( abs_path getcwd );
        use File::Basename qw( dirname );
-       use constant { PREFIX => dirname(abs_path($0)), }
+       use constant { PREFIX => abs_path(dirname(abs_path($0)).'/../') }
 }
 use lib PREFIX;
 
diff --git a/branches/mus-0.4.x-devel/tests/testTime.pl b/branches/mus-0.4.x-devel/tests/testTime.pl
new file mode 100755 (executable)
index 0000000..762a1a4
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use strict;
+
+BEGIN {
+       use Cwd qw( abs_path getcwd );
+       use File::Basename qw( dirname );
+       use constant { PREFIX => abs_path(dirname(abs_path($0)).'/../') }
+}
+use lib PREFIX;
+
+use SrSv::Time;
+
+my ($weeks, $days, $hours, $minutes, $seconds) = split_time(103.2);
+
+print "$minutes $seconds\n";
index a810606b1defffe223de49bfa69e635e57d0d856..34c6e205741fda0d9430f9e1d3dbe3f62487c2d4 100755 (executable)
@@ -42,6 +42,8 @@ chomp ($gzip, $bzip2);
 # This is based on an average observed from chanlogs.
 # Thankfully bzcat and bzgrep tend to be agnostic.
 my $bzip_threshold = 1000; 
+# if less than 100 bytes, don't bother to gzip.
+my $gzip_threshold = 100; 
 
 opendir ((my $LOGDIR), $logdir.'/');
 
@@ -80,10 +82,24 @@ while (my $filename = readdir($LOGDIR)) {
                mkdir "$dir/$year/$month";
        }
        rename "$logdir/$filename", "$dir/$year/$month/$filename";
-       system(((stat("$dir/$year/$month/$filename")->[7] > $bzip_threshold) ? $bzip2 : $gzip),
-               '-9vv', "$dir/$year/$month/$filename");
+       compressFile("$dir/$year/$month/$filename");
        $i++;
 }
+
+sub compressFile($) {
+       my ($file) = @_;
+       my $fileStat = stat($file);
+       my $fileSize = $fileStat->[7];
+       my $compressor;
+       if($fileSize > $bzip_threshold) {
+               $compressor = $bzip2;
+       } elsif($fileSize < $gzip_threshold) {
+               return;
+       } else {
+               $compressor = $gzip;
+       }
+       system($compressor, '-9vv', $file);
+}
 closedir $LOGDIR;
 
 print "Processed $i logs\n";
index 71386138726db9a625a2c3f7bd586cca0312709e..314c2b44bf8e801abdbe2c4a77e1136d54c29284 100755 (executable)
@@ -42,15 +42,31 @@ use lib PREFIX;
 use Date::Parse;
 
 use SrSv::SimpleHash qw(readHash);
+use SrSv::Conf::sql;
 use SrSv::Conf2Consts 'sql';
 
-my $srcname = 'http://www.dronebl.org/buildzone.do';
+my $srcname = 'http://dronebl.org/buildzone.do';
 my $bindip = undef;
 my $unpackname = $srcname;
 my $diffname = $srcname.'.diff';
+my $agent = findAgent();
+
+sub findAgent {
+       my $agent;
+       my $ret = system('which curl');
+       if(($ret >> 8) == 0) {
+               # we prefer curl b/c it can handle gzip compression!
+               # we do IPv4 b/c either their IPv6 gateway or ours is SLOW
+               # UPDATE 2011/05: due to DDoS, IPv4 is swamped, IPv6 is only way!
+               $agent = 'curl --compressed --silent';
+       } else {
+               $agent = 'wget -q -O -';
+       }
+       return $agent;
+}
 
 my $OPMDATA;
-unless(open $OPMDATA, '-|', "wget -q -O - http://www.dronebl.org/buildzone.do") {
+unless(open $OPMDATA, '-|', "$agent $srcname") {
        print STDERR "FATAL: Processing failed.\n";
        exit -1;
 }
@@ -73,13 +89,13 @@ print "Creating new table...\n";
 
 $dbh->do("DROP TABLE IF EXISTS `newopm`");
 $dbh->do(
-"CREATE TABLE `newopm` (
+"CREATE TEMPORARY TABLE `newopm` (
        `ipnum` int(11) unsigned NOT NULL default 0,
        `ipaddr` char(15) NOT NULL default '0.0.0.0',
        `type` tinyint(3) NOT NULL default 0,
        PRIMARY KEY (`ipnum`),
        UNIQUE KEY `addrkey` (`ipaddr`)
-) TYPE=MyISAM;"
+) Engine=Memory;"
 );
 
 sub save2DB($@) {
@@ -91,21 +107,21 @@ sub processData() {
        print "Inserting data...     ";
 
        $dbh->do("ALTER TABLE `newopm` DISABLE KEYS");
-       $dbh->do("LOCK TABLES newopm WRITE");
+       $dbh->do("LOCK TABLES `newopm` WRITE");
        my $type;
        my $baseQuery = "REPLACE INTO `newopm` (ipnum, ipaddr, type) VALUES ";
        my @rows;
        my $count = 0;
        while(my $x = <$OPMDATA>) {
                chomp $x;
-               if($x =~ /^:(\d+):$/) {
+               if($x =~ /^:(\d{1,3}):/) {
                        $type = $1;
                } elsif($x =~ /^(\d+\.\d+\.\d+\.\d+)$/) {
                        next unless $type;
                        my $ipaddr = $1;
                        push @rows, '(INET_ATON('.$dbh->quote($ipaddr).'),'.$dbh->quote($ipaddr).','.$type.')';
                        $count++;
-                       if(scalar(@rows)) {
+                       if(scalar(@rows) > 1000) {
                                save2DB($baseQuery, @rows);
                                @rows = ();
                        }
@@ -125,12 +141,13 @@ close $OPMDATA;
 
 print "done.\nRemoving old table...\n";
 $dbh->do("DROP TABLE IF EXISTS `oldopm`");
-$dbh->do("OPTIMIZE TABLE `newopm`");
+$dbh->do("ALTER TABLE opm ENGINE=InnoDB");
+$dbh->do("START TRANSACTION");
 print "Renaming new table...\n";
-$dbh->{RaiseError} = $dbh->{PrintError} = 0; # the following commands can fail, but are harmless.
-$dbh->do("RENAME TABLE `opm` TO `oldopm`");
-$dbh->do("RENAME TABLE `newopm` TO `opm`");
-$dbh->do("DROP TABLE IF EXISTS `oldopm`");
+#$dbh->{RaiseError} = $dbh->{PrintError} = 0; # the following commands can fail, but are harmless.
+$dbh->do("TRUNCATE TABLE `opm`");
+$dbh->do("INSERT INTO opm SELECT * FROM newopm");
+$dbh->do("COMMIT");
 
 print "Blacklist table update complete.\n";
 
index e0dae2e8587b1eee28c9fbdd958ae023370f034c..1d8daa625f86be60d28e4299498450cde90105f6 100755 (executable)
@@ -38,6 +38,8 @@ use lib PREFIX;
 use Date::Parse;
 
 use SrSv::Conf 'sql';
+use SrSv::Conf::sql;
+use SrSv::Conf2Consts qw( sql );
 
 my $countrydb_url= 'http://ip.ludost.net/raw/country.db.gz';
 my $srcname = 'country.db.gz';
index 7cddd34bd459000f29b656140f78be4778a65c19..39d6f51d55a8ac36154ba22895733be8426d6e39 100755 (executable)
@@ -38,7 +38,8 @@ use lib PREFIX;
 
 use Date::Parse;
 
-use SrSv::Conf 'sql';
+use SrSv::Conf::sql;
+use SrSv::Conf2Consts qw( sql );
 
 use constant {
        countrydb_url =>  'http://www.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip',
index 4e3dc0c6c882bc48dd8d3e47fd9239862c7f436d..9c21d96f09a325054462932626f3943833e9cdfb 100755 (executable)
@@ -38,7 +38,8 @@ use lib PREFIX;
 
 use Date::Parse;
 
-use SrSv::Conf 'sql';
+use SrSv::Conf::sql;
+use SrSv::Conf2Consts qw( sql );
 
 use constant {
        countrydb_url => 'rsync://countries-ns.mdc.dk/zone/zz.countries.nerd.dk.rbldnsd',
index ea309a4d09116c7950abb2b0f79b40c132c580b4..37ca4f6d7a980d397ae36bd1ea30c746e09a2096 100755 (executable)
@@ -18,7 +18,7 @@ use DBI;
 
 # Add tables to this list to be skipped
 # SrSv wants to skip the country table
-our %skipList = ( 'country' => 1 );
+our %skipList = ( 'country' => 1, 'geoip' => 1, 'geolocation' => 1, 'georegion' => 1, 'opm' => 1 );
 
 use constant {
        DROP_TABLE => 1,
@@ -33,14 +33,14 @@ use constant {
        # WARNING: Doing this with hundred megabyte tables
        # will probably be slow, and possibly DoS your system
        # with an Out of Memory condition.
-       LARGE_TABLES => 0,
+       LARGE_TABLES => 1,
        # Most of the time, you don't want to preserve the contents
        # of a MEMORY or HEAP table, since they're just temporary
        # and would have been lost on a server restart anyway.
        # Then again, maybe you want to keep them. If so, set this to 0.
        # This does still save the schema.
-       SKIP_HEAP_DUMP => 1,
-       
+       SKIP_HEAP_DUMP => 0,
+
        # This should only be used for debugging purposes
        # as otherwise it throws junk into the output stream
        VERBOSE => 0,
@@ -62,6 +62,7 @@ use File::Basename;
 # or create a static hash table
 sub get_sql_conn {
 # These libs aren't needed for the generic case
+use SrSv::Conf::sql;
 use SrSv::Conf2Consts qw( sql );
 
        my %MySQL_config = (
@@ -161,7 +162,7 @@ sub get_data_large($$) {
                        if ($column_data->[++$i]->{TYPE_NAME} =~ /^(TEXT|BLOB)$/i and
                                length($element))
                        {
-                               $element = unpack ('H', $element);
+                               $element = '0x' . unpack ('H*', $element);
                        }
                        elsif ($column_data->[$i]->{TYPE_NAME} =~ /int$/i and
                                length($element))
@@ -201,7 +202,7 @@ sub do_dump() {
                        if ((SKIP_HEAP_DUMP) and 
                            (($schema =~ /(ENGINE|TYPE)=(HEAP|MEMORY)/)) or ($skipList{lc $table})
                        ) {
-                           next TABLE;
+                           next TABLE;
                        }
                }
 
diff --git a/branches/mus-0.4.x-devel/utils/geoip-slower.pl b/branches/mus-0.4.x-devel/utils/geoip-slower.pl
new file mode 100755 (executable)
index 0000000..45279ae
--- /dev/null
@@ -0,0 +1,506 @@
+#!/usr/bin/perl
+
+#      This file is part of SurrealServices.
+#
+#      SurrealServices 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.
+#
+#      SurrealServices 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 SurrealServices; if not, write to the Free Software
+#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  SurrealChat.net does not provide the Country/Allocation data,
+#  is in no way associated with maxmind.com,
+#  nor are we providing a license to download/use it.
+#  Be sure to direct availability/accuracy/licensing questions to maxmind.com
+
+use strict;
+#use warnings;
+use DBI;
+
+BEGIN {
+       use Cwd qw( abs_path getcwd );
+       use File::Basename;
+       my %constants = (
+               CWD => getcwd(),
+               PREFIX => abs_path(dirname(abs_path($0)).'/..'),
+       );
+       require constant; import constant(\%constants);
+}
+#chdir PREFIX;
+use lib PREFIX;
+
+use Date::Parse;
+use Text::ParseWords; # is a standard (in 5.8) module
+use Time::HiRes qw( time );
+
+use SrSv::Conf::sql;
+use SrSv::Conf2Consts qw( sql );
+use SrSv::Util qw( :say );
+use SrSv::Time qw( split_time );
+
+sub runSQL($@) {
+       my ($dbh, @strings) = @_;
+       foreach my $string (@strings) {
+               my $sql;
+               foreach my $x (split($/, $string)) { $sql .= $x unless $x =~ /^(#|--)/ or $x eq "\n"}
+#              $dbh->do("START TRANSACTION");
+               my $printError = $dbh->{PrintError};
+               $dbh->{PrintError} = 0;
+               foreach my $line (split(/;/s, $sql)) {
+                       next unless length($line);
+                       #print "$line\n";
+                       eval { $dbh->do($line); };
+                       if($@) {
+                               $line =~ s/\s{2,}/ /g;
+                               $line =~ s/\n//g;
+                               print "$line\n";
+                       }
+                       
+               }
+               $dbh->{PrintError} = $printError;
+#              $dbh->do("COMMIT");
+       }
+}
+
+BEGIN {
+       my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime();
+       $year += 1900;
+       $mon++; # gmtime returns months January=0
+       my $date = sprintf("%04d%02d01", $year, $mon);
+       require constant;
+       import constant {
+               #countrydb_url =>  'http://www.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip',
+               #FIXME: This needs a date generator!
+               countrydb_url => "http://www.maxmind.com/download/geoip/database/GeoLiteCity_CSV/GeoLiteCity_${date}.zip",
+               srcname => "GeoLiteCity_${date}.zip",
+       };
+}
+
+sub main() {
+       downloadData();
+       say "Connecting to database...";
+       my $dbh = dbConnect();
+       say "Creating new table...";
+       newTable($dbh);
+       say "Inserting data...     ";
+       loadData($dbh);
+       print "Converting geoip table...";
+       convert($dbh);
+       cleanup($dbh);
+       $dbh->disconnect();
+       say "GeoIP update complete.";
+}
+
+main();
+exit 0;
+
+sub downloadData() {
+       # This MAY be implementable with an open of a pipe
+       # pipe the output of wget through gzip -d
+       # and then into the load-loop.
+       # It's a bit heavy to run directly from inside services however.
+       # I'd recommend it be run as a crontab script separate from services.
+
+       #return;
+       my ($stat, $date, $size);
+       my $srcPath = PREFIX.'/data/'.srcname;
+       say $srcPath;
+       use File::stat;
+       if($stat = stat($srcPath)) {
+               print "Checking for updated country data...\n";
+               my $header = qx "wget --spider -S @{[countrydb_url]} 2>&1";
+               ($date) = ($header =~ /Last-Modified: (.*)/);
+               ($size) = ($header =~ /Content-Length: (.*)/);
+       }
+
+       if($stat and $stat->size == $size and $stat->mtime >= str2time($date)) {
+               say "Country data is up to date.";
+       } else {
+#              say $stat->size == $size;
+#              say $stat->mtime >= str2time($date);
+               say "Downloading country data...";
+#              return;
+
+               unlink $srcPath;
+               system('wget '.countrydb_url." -O $srcPath");
+               unless(-e $srcPath) {
+                       sayERR "FATAL: Download failed.";
+                       exit;
+               }
+       }
+
+       mkdir PREFIX.'/data/GeoIP/';
+       say "Decompressing...";
+       unlink(glob(PREFIX.'/data/GeoIP/Geo*.csv'));
+       system("unzip -j $srcPath -d ".PREFIX.'/data/GeoIP/');
+       unless(-f PREFIX.'/data/GeoIP/GeoLiteCity-Blocks.csv') {
+               sayERR "FATAL: Decompression failed.";
+               exit -1;
+       }
+}
+
+sub dbConnect() {
+       my $dbh;
+       eval { 
+               $dbh = DBI->connect("DBI:mysql:".sql_conf_mysql_db, sql_conf_mysql_user, sql_conf_mysql_pass,
+                       {  AutoCommit => 1, RaiseError => 1 })
+       };
+
+       if($@) {
+               print STDERR "FATAL: Can't connect to database:\n$@\n";
+               print STDERR "You must edit config/sql.conf and create a corresponding\nMySQL user and database!\n\n";
+               exit -1;
+       }
+       return $dbh;
+}
+
+sub newTable($) {
+       my ($dbh) = @_;
+       $dbh->{RaiseError} = 1;
+       $dbh->{PrintError} = 1;
+
+       runSQL($dbh, 
+               "DROP TABLE IF EXISTS new_geoip",
+               "CREATE TABLE `new_geoip` (
+                 `low` int unsigned NOT NULL,
+                 `high` int unsigned NOT NULL,
+                 `location` mediumint(8) unsigned NOT NULL,
+                 PRIMARY KEY (`low`, `high`)
+               ) Engine=MyISAM",
+               
+               "DROP TABLE IF EXISTS new_geolocation",
+       #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
+               "CREATE TABLE `new_geolocation` (
+                 `id` mediumint(8) unsigned NOT NULL,
+                 `country` char(2) NOT NULL default '-',
+                 `region` char(2) NOT NULL default '-',
+                 `city` varchar(255) NOT NULL default '-',
+                 `postalcode` varchar(6) NOT NULL default '-',
+                 `latitude` float NOT NULL default 0.0,
+                 `longitude` float NOT NULL default 0.0,
+                 `metrocode` int unsigned NOT NULL default 0,
+                 `areacode` int unsigned NOT NULL default 0,
+                 PRIMARY KEY (`id`),
+                 KEY `countrykey` (`country`)
+                 ) Engine=MyISAM;",
+               
+                 "DROP TABLE IF EXISTS `new_metrocode`",
+                 "CREATE TABLE `new_metrocode` (
+                   `id` smallint NOT NULL default 0,
+                   `metro` varchar(128) NOT NULL default '',
+                   PRIMARY KEY (`id`)
+                 ) Engine=MyISAM;",
+
+               "DROP TABLE IF EXISTS `new_geocountry`",
+       #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
+               "CREATE TABLE `new_geocountry` (
+                 `code` char(2) NOT NULL default '',
+                 `country` varchar(255) default '',
+                 PRIMARY KEY (`code`)
+                 ) Engine=MyISAM;",
+
+               "DROP TABLE IF EXISTS `new_georegion`",
+       #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
+               "CREATE TABLE `new_georegion` (
+                 `country` char(2) NOT NULL default '',
+                 `region` char(2) NOT NULL default '',
+                 `name` varchar(255) default '',
+                 PRIMARY KEY (`country`, `region`)
+                 ) Engine=MyISAM;",
+
+       );
+}
+
+sub timeDiff($$) {
+       my ($time1, $time2) = @_;
+       my ($weeks, $days, $hours, $minutes, $seconds) = split_time($time2 - $time1);
+       return sprintf("%02d:%02.2f", $minutes, $seconds);
+}
+
+sub loadData($) {
+       my ($dbh) = @_;
+       $| = 1;
+=cut
+       my $unpackPath = PREFIX.'/data/'.unpackname;
+       my ($lines) = qx{wc -l $unpackPath};
+       my $div = int($lines/100);
+=cut
+       my ($i, @entries);
+       my $fh;
+       my $table;
+
+       my $time1 = time();
+       print "Loading geoip data...";
+####### geoip #######
+       open ($fh, '<', PREFIX.'/data/GeoIP/GeoLiteCity-Blocks.csv');
+       $table = 'geoip';
+       #my $add_entry = $dbh->prepare("INSERT INTO `new_geoip` (low, high, location) VALUES (?,?,?)");
+       runSQL($dbh,
+               "LOCK TABLES `new_geoip` WRITE, `new_geolocation` WRITE,
+                       `new_metrocode` WRITE, `new_georegion` WRITE, `new_geocountry` WRITE",
+               "ALTER TABLE `new_$table` DISABLE KEYS",
+       );
+
+       my $columns = '(low, high, location)';
+       <$fh>; <$fh>; # pop first 2 lines off.
+       my $i = 0;
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = split(',', $x);
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+                       print $i," \n";
+               }
+
+               $i++;
+       }
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
+       @entries = ();
+       close $fh;
+####### END geoip #######
+       say " Done.";
+       my $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+       $time1 = time();
+       print "Loading location data...";
+####### locations #######
+       $table = 'geolocation';
+       $columns = "(`id`, `country`, `region`, `city`, `postalcode`, `latitude`, `longitude`, `metrocode`, `areacode`)";
+       open ($fh, '<', PREFIX.'/data/GeoIP/GeoLiteCity-Location.csv');
+
+       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
+
+       <$fh>; <$fh>; # pop first 2 lines off.
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 9;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+               }
+
+               $i++;
+       }
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       @entries = ();
+       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
+       close $fh;
+####### END locations #######
+       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+
+       $time1 = time();
+       print "Loading metrocode data...";
+####### metrocodes #######
+       open ($fh, '<', PREFIX.'/data/GeoIP/metrocodes.txt');
+       $table = 'metrocode';
+       $columns = "(`id`, `metro`)";
+
+       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
+
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = map( { $dbh->quote($_) } split(' ', $x, 2) );
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 2;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+               }
+
+               $i++;
+       }
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       @entries = ();
+       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
+       close $fh;
+####### END metrocodes #######
+       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+       $time1 = time();
+       print "Loading region data...";
+####### regions #######
+       $table = 'georegion';
+       $columns = "(`country`, `region`, `name`)";
+
+       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
+       open ($fh, '<', PREFIX.'/data/fips10_4');
+       <$fh>; # pop first line off.
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+               }
+
+               $i++;
+       }
+       close $fh;
+
+       open ($fh, '<', PREFIX.'/data/iso3166_2');
+       <$fh>; # pop first line off.
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+               }
+
+               $i++;
+       }
+       close $fh;
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       @entries = ();
+       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
+####### END regions #######
+       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+       $time1 = time();
+       print "Loading country data...";
+####### iso3166 Country Names #######
+       open ($fh, '<', PREFIX.'/data/iso3166');
+       $table = 'geocountry';
+       $columns = "(`code`, `country`)";
+
+       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
+
+       while(my $x = <$fh>) {
+               chomp $x;
+=cut
+               if($i == 0 or !($i % $div)) {
+                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
+               }
+=cut   
+               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
+               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 2;
+               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       @entries = ();
+               }
+
+               $i++;
+       }
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       @entries = ();
+       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
+       close $fh;
+####### END iso3166 Country Names #######
+       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+
+       $dbh->do("UNLOCK TABLES");
+}
+
+sub convert($) {
+       my ($dbh) = @_;
+
+       my $time1 = time();
+       runSQL($dbh, 
+               "DROP TABLE IF EXISTS `tmp_geoip`",
+               "RENAME TABLE `new_geoip` TO `tmp_geoip`",
+               "CREATE TABLE `new_geoip` (
+                 `low` int unsigned NOT NULL,
+                 `high` int unsigned NOT NULL,
+                 `location` mediumint(8) unsigned NOT NULL,
+                 `ip_poly` polygon not null,
+                 PRIMARY KEY (`low`, `high`),
+                 SPATIAL INDEX (`ip_poly`)
+               ) Engine=MyISAM",
+               "ALTER TABLE `new_geoip` DISABLE KEYS",
+               "INSERT INTO new_geoip (low,high,location,ip_poly)
+                       SELECT low, high, location,
+                       GEOMFROMWKB(POLYGON(LINESTRING( POINT(low, -1), POINT(high, -1),
+                       POINT(high, 1), POINT(low, 1), POINT(low, -1)))) FROM tmp_geoip;",
+               "ALTER TABLE `new_geoip` ENABLE KEYS",
+               "DROP TABLE IF EXISTS `tmp_geoip`",
+       );
+       my $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
+}
+
+sub cleanup($) {
+       my ($dbh) = @_;
+
+#      print "\b\b\b\bdone.\nRemoving old table...\n";
+       $dbh->do("DROP TABLE IF EXISTS `oldcountry`");
+       say "Renaming new tables...";
+       $dbh->{RaiseError} = 0;
+       $dbh->{PrintError} = 0;
+       $dbh->do("OPTIMIZE TABLE `new_geoip`");
+       $dbh->do("ANALYZE TABLE `new_geoip`");
+       # Doing the renames cannot be done atomically
+       # as sometimes `country` doesn't exist yet.
+       $dbh->do("START TRANSACTION");
+       $dbh->do("RENAME TABLE `geoip` TO `old_geoip`");
+       $dbh->do("RENAME TABLE `new_geoip` TO `geoip`");
+
+       $dbh->do("RENAME TABLE `geolocation` TO `old_geolocation`");
+       $dbh->do("RENAME TABLE `new_geolocation` TO `geolocation`");
+
+       $dbh->do("RENAME TABLE `metrocode` TO `old_metrocode`");
+       $dbh->do("RENAME TABLE `new_metrocode` TO `metrocode`");
+
+       $dbh->do("RENAME TABLE `georegion` TO `old_georegion`");
+       $dbh->do("RENAME TABLE `new_georegion` TO `georegion`");
+
+       $dbh->do("RENAME TABLE `geocountry` TO `old_geocountry`");
+       $dbh->do("RENAME TABLE `new_geocountry` TO `geocountry`");
+
+       $dbh->do("DROP TABLE `old_geoip`");
+       $dbh->do("DROP TABLE `old_geolocation`");
+       $dbh->do("DROP TABLE `old_metrocode`");
+       $dbh->do("DROP TABLE `old_georegion`");
+       $dbh->do("DROP TABLE `old_geocountry`");
+       $dbh->do("COMMIT");
+       #unlink PREFIX.'/data/'.unpackname;
+}
index a80f170bbd04e12cb9329fa9cb5b0720373dfb16..2cc0e38bb0ced89b2653c4659458e4b8836bf01f 100755 (executable)
@@ -39,9 +39,12 @@ use lib PREFIX;
 
 use Date::Parse;
 use Text::ParseWords; # is a standard (in 5.8) module
+use Time::HiRes qw( time );
 
+use SrSv::Conf::sql;
 use SrSv::Conf2Consts qw( sql );
 use SrSv::Util qw( :say );
+use SrSv::Time qw( split_time );
 
 sub runSQL($@) {
        my ($dbh, @strings) = @_;
@@ -89,7 +92,7 @@ sub main() {
        newTable($dbh);
        say "Inserting data...     ";
        loadData($dbh);
-       say "Converting geoip table...     ";
+       print "Converting geoip table...";
        convert($dbh);
        cleanup($dbh);
        $dbh->disconnect();
@@ -165,18 +168,17 @@ sub newTable($) {
        $dbh->{PrintError} = 1;
 
        runSQL($dbh, 
-               "DROP TABLE IF EXISTS new_geoip",
-               "CREATE TABLE `new_geoip` (
-                 `low` int unsigned NOT NULL default 0,
-                 `high` int unsigned NOT NULL default 0,
-                 `location` int NOT NULL default '0',
+               "CREATE TEMPORARY TABLE `tmp_geoip` (
+                 `low` int unsigned NOT NULL,
+                 `high` int unsigned NOT NULL,
+                 `location` mediumint(8) NOT NULL,
                  PRIMARY KEY (`low`, `high`)
-               ) TYPE=MyISAM",
+               ) Engine=MyISAM",
                
                "DROP TABLE IF EXISTS new_geolocation",
        #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
                "CREATE TABLE `new_geolocation` (
-                 `id` int unsigned NOT NULL default 0,
+                 `id` mediumint(8) unsigned NOT NULL,
                  `country` char(2) NOT NULL default '-',
                  `region` char(2) NOT NULL default '-',
                  `city` varchar(255) NOT NULL default '-',
@@ -187,14 +189,14 @@ sub newTable($) {
                  `areacode` int unsigned NOT NULL default 0,
                  PRIMARY KEY (`id`),
                  KEY `countrykey` (`country`)
-                 ) TYPE=MyISAM;",
-                 
-                 "DROP TABLE IF EXISTS `new_metrocode`",
+                 ) Engine=MyISAM;",
+               
+                 "DROP TABLE IF EXISTS `new_metrocode`",
                  "CREATE TABLE `new_metrocode` (
                    `id` smallint NOT NULL default 0,
                    `metro` varchar(128) NOT NULL default '',
                    PRIMARY KEY (`id`)
-                 ) TYPE=MyISAM;",
+                 ) Engine=MyISAM;",
 
                "DROP TABLE IF EXISTS `new_geocountry`",
        #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
@@ -202,7 +204,7 @@ sub newTable($) {
                  `code` char(2) NOT NULL default '',
                  `country` varchar(255) default '',
                  PRIMARY KEY (`code`)
-                 ) TYPE=MyISAM;",
+                 ) Engine=MyISAM;",
 
                "DROP TABLE IF EXISTS `new_georegion`",
        #"locId,country,region,city,postalCode,latitude,longitude,metroCode,areaCode";
@@ -211,11 +213,17 @@ sub newTable($) {
                  `region` char(2) NOT NULL default '',
                  `name` varchar(255) default '',
                  PRIMARY KEY (`country`, `region`)
-                 ) TYPE=MyISAM;",
+                 ) Engine=MyISAM;",
 
        );
 }
 
+sub timeDiff($$) {
+       my ($time1, $time2) = @_;
+       my ($weeks, $days, $hours, $minutes, $seconds) = split_time($time2 - $time1);
+       return sprintf("%02d:%02d.%02d", $minutes, int($seconds), 100*($seconds-int($seconds)));
+}
+
 sub loadData($) {
        my ($dbh) = @_;
        $| = 1;
@@ -228,80 +236,37 @@ sub loadData($) {
        my $fh;
        my $table;
 
+       my $time1 = time();
        print "Loading geoip data...";
 ####### geoip #######
-       open ($fh, '<', PREFIX.'/data/GeoIP/GeoLiteCity-Blocks.csv');
        $table = 'geoip';
-       #my $add_entry = $dbh->prepare("INSERT INTO `new_geoip` (low, high, location) VALUES (?,?,?)");
-       runSQL($dbh,
-#              "LOCK TABLES `new_geoip` WRITE, `new_geolocation` WRITE,
-#                      `new_metrocode` WRITE, `new_georegion` WRITE, `new_geocountry` WRITE",
-               "ALTER TABLE `new_$table` DISABLE KEYS",
-       );
-
-       my $columns = '(low, high, location)';
-       <$fh>; <$fh>; # pop first 2 lines off.
-       while(my $x = <$fh>) {
-               chomp $x;
-=cut
-               if($i == 0 or !($i % $div)) {
-                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
-               }
-=cut   
-               my @args = split(',', $x);
-               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
-               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
-                       @entries = ();
-               }
-
-               $i++;
-       }
-       $dbh->do(("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
-       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
-       @entries = ();
-       close $fh;
+       $dbh->do("LOAD DATA LOCAL INFILE
+               '@{[PREFIX]}/data/GeoIP/GeoLiteCity-Blocks.csv'
+               INTO TABLE tmp_${table}
+               FIELDS TERMINATED BY ',' ENCLOSED BY '\"' IGNORE 2 LINES");
 ####### END geoip #######
-       say " Done.";
+       my $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
 
+       $time1 = time();
        print "Loading location data...";
 ####### locations #######
        $table = 'geolocation';
-       $columns = "(`id`, `country`, `region`, `city`, `postalcode`, `latitude`, `longitude`, `metrocode`, `areacode`)";
-       open ($fh, '<', PREFIX.'/data/GeoIP/GeoLiteCity-Location.csv');
-
-       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
-
-       <$fh>; <$fh>; # pop first 2 lines off.
-       while(my $x = <$fh>) {
-               chomp $x;
-=cut
-               if($i == 0 or !($i % $div)) {
-                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
-               }
-=cut   
-               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
-               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 9;
-               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
-                       @entries = ();
-               }
-
-               $i++;
-       }
-       $dbh->do(("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
-       @entries = ();
-       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
-       close $fh;
+       $dbh->do("LOAD DATA LOCAL INFILE
+               '@{[PREFIX]}/data/GeoIP/GeoLiteCity-Location.csv'
+               INTO TABLE new_${table}
+               FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' IGNORE 2 LINES");
 ####### END locations #######
-       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
 
 
+       $time1 = time();
        print "Loading metrocode data...";
 ####### metrocodes #######
        open ($fh, '<', PREFIX.'/data/GeoIP/metrocodes.txt');
        $table = 'metrocode';
-       $columns = "(`id`, `metro`)";
+       my $columns = "(`id`, `metro`)";
 
        $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
 
@@ -315,127 +280,79 @@ sub loadData($) {
                my @args = map( { $dbh->quote($_) } split(' ', $x, 2) );
                push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 2;
                if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
+                       $dbh->do("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries));
                        @entries = ();
                }
 
                $i++;
        }
-       $dbh->do(("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
+       $dbh->do(("INSERT INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
        @entries = ();
        $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
        close $fh;
 ####### END metrocodes #######
-       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
 
+       $time1 = time();
        print "Loading region data...";
 ####### regions #######
        $table = 'georegion';
        $columns = "(`country`, `region`, `name`)";
 
-       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
-       open ($fh, '<', PREFIX.'/data/fips10_4');
-       <$fh>; # pop first line off.
-       while(my $x = <$fh>) {
-               chomp $x;
-=cut
-               if($i == 0 or !($i % $div)) {
-                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
-               }
-=cut   
-               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
-               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
-               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
-                       @entries = ();
-               }
-
-               $i++;
-       }
-       close $fh;
+       $dbh->do("LOAD DATA LOCAL INFILE
+               '@{[PREFIX]}/data/fips10_4'
+               INTO TABLE new_${table}
+               FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' IGNORE 1 LINES");
 
-       open ($fh, '<', PREFIX.'/data/iso3166_2');
-       <$fh>; # pop first line off.
-       while(my $x = <$fh>) {
-               chomp $x;
-=cut
-               if($i == 0 or !($i % $div)) {
-                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
-               }
-=cut   
-               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
-               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 3;
-               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
-                       @entries = ();
-               }
+       $dbh->do("LOAD DATA LOCAL INFILE
+               '@{[PREFIX]}/data/iso3166_2'
+               INTO TABLE new_${table}
+               FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' IGNORE 1 LINES");
 
-               $i++;
-       }
-       close $fh;
-       $dbh->do(("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
-       @entries = ();
-       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
 ####### END regions #######
-       say " Done.";
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
 
+       $time1 = time();
        print "Loading country data...";
 ####### iso3166 Country Names #######
-       open ($fh, '<', PREFIX.'/data/iso3166');
        $table = 'geocountry';
-       $columns = "(`code`, `country`)";
-
-       $dbh->do("ALTER TABLE `new_$table` DISABLE KEYS");
-
-       while(my $x = <$fh>) {
-               chomp $x;
-=cut
-               if($i == 0 or !($i % $div)) {
-                       printf("\b\b\b\b%3d%", ($i/$lines)*100);
-               }
-=cut   
-               my @args = map( { $dbh->quote($_) } parse_line(",\\s*", 0, $x) );
-               push @entries, '(' . join(',', @args) . ')' if scalar(@args) == 2;
-               if(scalar(@entries) >= 100) { #1000 only gives another 10% boost for 10x as much memory
-                       $dbh->do("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries));
-                       @entries = ();
-               }
-
-               $i++;
-       }
-       $dbh->do(("INSERT DELAYED INTO `new_$table` $columns VALUES ".join(',', @entries))) if scalar(@entries);
-       @entries = ();
-       $dbh->do("ALTER TABLE `new_$table` ENABLE KEYS");
-       close $fh;
+       $dbh->do("LOAD DATA LOCAL INFILE
+               '@{[PREFIX]}/data/iso3166'
+               INTO TABLE new_${table}
+               FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' IGNORE 1 LINES");
 ####### END iso3166 Country Names #######
-       say " Done.";
-
+       $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
 
-#      $dbh->do("UNLOCK TABLES");
+       $dbh->do("UNLOCK TABLES");
 }
 
 sub convert($) {
        my ($dbh) = @_;
 
+       my $time1 = time();
        runSQL($dbh, 
-               "DROP TABLE IF EXISTS `tmp_geoip`",
-               "RENAME TABLE `new_geoip` TO `tmp_geoip`",
                "CREATE TABLE `new_geoip` (
-                 `low` int unsigned NOT NULL default 0,
-                 `high` int unsigned NOT NULL default 0,
-                 `location` int NOT NULL default '0',
-                 `ip_poly` polygon not null,
+                 `low` int unsigned NOT NULL,
+                 `high` int unsigned NOT NULL,
+                 `location` mediumint(8) NOT NULL,
+                 `ip_poly` polygon NOT NULL,
                  PRIMARY KEY (`low`, `high`),
                  SPATIAL INDEX (`ip_poly`)
-               ) TYPE=MyISAM",
+               ) Engine=MyISAM",
                "ALTER TABLE `new_geoip` DISABLE KEYS",
-               "INSERT DELAYED INTO new_geoip (low,high,location,ip_poly)
+               "INSERT INTO new_geoip (low,high,location,ip_poly)
                        SELECT low, high, location,
                        GEOMFROMWKB(POLYGON(LINESTRING( POINT(low, -1), POINT(high, -1),
                        POINT(high, 1), POINT(low, 1), POINT(low, -1)))) FROM tmp_geoip;",
                "ALTER TABLE `new_geoip` ENABLE KEYS",
                "DROP TABLE IF EXISTS `tmp_geoip`",
        );
+       my $time2 = time();
+       print " Done. "; say timeDiff($time1, $time2);
+
 }
 
 sub cleanup($) {