Merge remote-tracking branch 'IOMultiplexer.git/master' into development
authorpk910 <philipp@zoelle1.de>
Mon, 17 Dec 2012 17:34:01 +0000 (18:34 +0100)
committerpk910 <philipp@zoelle1.de>
Mon, 17 Dec 2012 17:34:01 +0000 (18:34 +0100)
261 files changed:
.gitignore [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
QServer/NeonServ_QServer.class.php [new file with mode: 0644]
VERSION [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
configure.ac [new file with mode: 0644]
database.defaults.sql [new file with mode: 0644]
database.sql [new file with mode: 0644]
database.upgrade.sql [new file with mode: 0644]
database.upgrade_v4_v5.sql [new file with mode: 0644]
language.php [new file with mode: 0644]
neonserv.example.conf [new file with mode: 0644]
scripts/example.php.txt [new file with mode: 0644]
scripts/examples/calc.php [new file with mode: 0644]
scripts/examples/dnslookup.php [new file with mode: 0644]
src/BanNode.c [new file with mode: 0644]
src/BanNode.h [new file with mode: 0644]
src/ChanNode.c [new file with mode: 0644]
src/ChanNode.h [new file with mode: 0644]
src/ChanUser.c [new file with mode: 0644]
src/ChanUser.h [new file with mode: 0644]
src/ClientSocket.c [new file with mode: 0644]
src/ClientSocket.h [new file with mode: 0644]
src/ConfigParser.c [new file with mode: 0644]
src/ConfigParser.h [new file with mode: 0644]
src/DBHelper.c [new file with mode: 0644]
src/DBHelper.h [new file with mode: 0644]
src/EventLogger.c [new file with mode: 0644]
src/EventLogger.h [new file with mode: 0644]
src/HandleInfoHandler.c [new file with mode: 0644]
src/HandleInfoHandler.h [new file with mode: 0644]
src/IOEngine.h
src/IOHandler.h
src/IPNode.c [new file with mode: 0644]
src/IPNode.h [new file with mode: 0644]
src/IRCEvents.c [new file with mode: 0644]
src/IRCEvents.h [new file with mode: 0644]
src/IRCParser.c [new file with mode: 0644]
src/IRCParser.h [new file with mode: 0644]
src/IRCQueue.c [new file with mode: 0644]
src/IRCQueue.h [new file with mode: 0644]
src/ModeNode.c [new file with mode: 0644]
src/ModeNode.h [new file with mode: 0644]
src/ModuleFunctions.c [new file with mode: 0644]
src/ModuleFunctions.h [new file with mode: 0644]
src/QServer.c [new file with mode: 0644]
src/QServer.h [new file with mode: 0644]
src/UserNode.c [new file with mode: 0644]
src/UserNode.h [new file with mode: 0644]
src/WHOHandler.c [new file with mode: 0644]
src/WHOHandler.h [new file with mode: 0644]
src/bots.c [new file with mode: 0644]
src/bots.h [new file with mode: 0644]
src/lang.c [new file with mode: 0644]
src/lang.h [new file with mode: 0644]
src/log.c [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/main.h [new file with mode: 0644]
src/memoryDebug.c [new file with mode: 0644]
src/memoryDebug.h [new file with mode: 0644]
src/memoryInfo.h [new file with mode: 0644]
src/modcmd.c [new file with mode: 0644]
src/modcmd.h [new file with mode: 0644]
src/module_commands.c [new file with mode: 0644]
src/module_commands.h [new file with mode: 0644]
src/modules.c [new file with mode: 0644]
src/modules.h [new file with mode: 0644]
src/modules/DummyServ.mod/bot_DummyServ.c [new file with mode: 0644]
src/modules/DummyServ.mod/bot_DummyServ.h [new file with mode: 0644]
src/modules/DummyServ.mod/module.c [new file with mode: 0644]
src/modules/NeonBackup.mod/bot_NeonBackup.c [new file with mode: 0644]
src/modules/NeonBackup.mod/bot_NeonBackup.h [new file with mode: 0644]
src/modules/NeonBackup.mod/cmd_neonbackup.c [new file with mode: 0644]
src/modules/NeonBackup.mod/cmd_neonbackup.h [new file with mode: 0644]
src/modules/NeonBackup.mod/cmd_neonbackup_recover.c [new file with mode: 0644]
src/modules/NeonBackup.mod/module.c [new file with mode: 0644]
src/modules/NeonFun.mod/bot_NeonFun.c [new file with mode: 0644]
src/modules/NeonFun.mod/bot_NeonFun.h [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun.h [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_4stone.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_4view.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_4wins.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_bjenough.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_bjtake.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_blackjack.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_uno.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_unoplay.c [new file with mode: 0644]
src/modules/NeonFun.mod/cmd_neonfun_unotake.c [new file with mode: 0644]
src/modules/NeonFun.mod/game_4wins.c [new file with mode: 0644]
src/modules/NeonFun.mod/game_4wins.h [new file with mode: 0644]
src/modules/NeonFun.mod/game_blackjack.c [new file with mode: 0644]
src/modules/NeonFun.mod/game_blackjack.h [new file with mode: 0644]
src/modules/NeonFun.mod/game_uno.c [new file with mode: 0644]
src/modules/NeonFun.mod/game_uno.h [new file with mode: 0644]
src/modules/NeonFun.mod/module.c [new file with mode: 0644]
src/modules/NeonHelp.mod/NeonHelpHistory.php [new file with mode: 0644]
src/modules/NeonHelp.mod/bot_NeonHelp.c [new file with mode: 0644]
src/modules/NeonHelp.mod/bot_NeonHelp.h [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp.c [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp.h [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp_delete.c [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp_history.c [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp_next.c [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp_requests.c [new file with mode: 0644]
src/modules/NeonHelp.mod/cmd_neonhelp_stats.c [new file with mode: 0644]
src/modules/NeonHelp.mod/module.c [new file with mode: 0644]
src/modules/NeonKick.mod/bot_NeonKick.c [new file with mode: 0644]
src/modules/NeonKick.mod/bot_NeonKick.h [new file with mode: 0644]
src/modules/NeonKick.mod/event_neonkick_join.c [new file with mode: 0644]
src/modules/NeonKick.mod/module.c [new file with mode: 0644]
src/modules/NeonServ.mod/bot_NeonServ.c [new file with mode: 0644]
src/modules/NeonServ.mod/bot_NeonServ.h [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv.h [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_access.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_addban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_addrank.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_addtimeban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_adduser.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_assignrank.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_ban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_bans.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_chanservsync.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_clvl.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_csuspend.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_cunsuspend.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_dehalfop.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_dehalfopall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_delban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_delme.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_delrank.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_deluser.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_deop.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_deopall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_devoice.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_devoiceall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_down.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_downall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_events.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_giveowner.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_halfop.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_halfopall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_help.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_info.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_invite.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_inviteme.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_invitemeall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_kick.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_kickban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_listrank.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_mdeluser.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_mode.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_myaccess.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_nicklist.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_noregister.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_op.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_opall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_oplog.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_peek.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_recover.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_rename.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_resync.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_search.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_set.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_setrank.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_suspend.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_topic.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_trace.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_trim.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_unban.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_unbanall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_unbanme.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_unsuspend.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_unvisited.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_up.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_upall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_users.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_uset.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_voice.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_voiceall.c [new file with mode: 0644]
src/modules/NeonServ.mod/cmd_neonserv_wipeinfo.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_ctcp.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_invite.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_join.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_kick.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_mode.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_notice.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_part.c [new file with mode: 0644]
src/modules/NeonServ.mod/event_neonserv_topic.c [new file with mode: 0644]
src/modules/NeonServ.mod/module.c [new file with mode: 0644]
src/modules/NeonSpam.mod/bot_NeonSpam.c [new file with mode: 0644]
src/modules/NeonSpam.mod/bot_NeonSpam.h [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam.c [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam.h [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam_addbad.c [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam_badwords.c [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam_delbad.c [new file with mode: 0644]
src/modules/NeonSpam.mod/cmd_neonspam_set.c [new file with mode: 0644]
src/modules/NeonSpam.mod/event_neonspam_chanmsg.c [new file with mode: 0644]
src/modules/NeonSpam.mod/event_neonspam_join.c [new file with mode: 0644]
src/modules/NeonSpam.mod/module.c [new file with mode: 0644]
src/modules/botid.h [new file with mode: 0644]
src/modules/funcmd.mod/cmd_funcmds.c [new file with mode: 0644]
src/modules/funcmd.mod/cmd_funcmds.h [new file with mode: 0644]
src/modules/funcmd.mod/cmd_funcmds_bomb.c [new file with mode: 0644]
src/modules/funcmd.mod/module.c [new file with mode: 0644]
src/modules/global.mod/cmd_global.c [new file with mode: 0644]
src/modules/global.mod/cmd_global.h [new file with mode: 0644]
src/modules/global.mod/cmd_global_addbot.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_bind.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_bots.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_command.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_commands.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_delbot.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_die.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_emote.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_extscript.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_global.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_god.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_meminfo.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_modcmd.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_motd.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_move.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_netinfo.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_notice.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_raw.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_reconnect.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_register.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_reload.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_reloadlang.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_restart.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_say.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_setaccess.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_setbot.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_staff.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_unbind.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_unregister.c [new file with mode: 0644]
src/modules/global.mod/cmd_global_version.c [new file with mode: 0644]
src/modules/global.mod/module.c [new file with mode: 0644]
src/modules/module.h [new file with mode: 0644]
src/modules/stats.mod/module.c [new file with mode: 0644]
src/mutexDebug.c [new file with mode: 0644]
src/mutexDebug.h [new file with mode: 0644]
src/mysqlConn.c [new file with mode: 0644]
src/mysqlConn.h [new file with mode: 0644]
src/overall.h [new file with mode: 0644]
src/signal.c [new file with mode: 0644]
src/signal.h [new file with mode: 0644]
src/statistics.c [new file with mode: 0644]
src/statistics.h [new file with mode: 0644]
src/timeq.c [new file with mode: 0644]
src/timeq.h [new file with mode: 0644]
src/tools.c [new file with mode: 0644]
src/tools.h [new file with mode: 0644]
src/version.h [new file with mode: 0644]
src/version.sh [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..34025fe
--- /dev/null
@@ -0,0 +1,33 @@
+mysqlConfig.h
+neonserv.ini
+neonserv.conf
+src/version.c
+src/version.php
+src/year.php
+src/.deps
+src/.dirstamp
+src/lib/.deps
+src/lib/.dirstamp
+src/lib/*.o
+src/*.o
+bin/
+scripts/*.php
+autom4te.cache
+aclocal.m4
+config.h.in~
+config.h.in
+config.h
+config.status
+config.log
+configure
+depcomp
+install-sh
+Makefile.in
+Makefile
+missing
+stamp-h1
+neonserv
+neonserv*.exe
+libmysql.dll
+pthreadGC2.dll
+motd.txt
\ No newline at end of file
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..c19ce60
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,25 @@
+               NeonServ dev
+
+Project Admin:
+pk910      <neonserv@pk910.de>
+
+Coders:
+pk910      <neonserv@pk910.de>
+
+Code Testers:
+TeaTow
+Zer0n
+Phil
+
+Documentation:
+- none -
+
+Translation:
+Buschman
+Zer0n
+and some others?
+
+You can find us on irc://irc.onlinegamesnet.net/#NeonServ
+If you found a bug you're welcome to report it on 
+http://bugtrack.pk910.de/git_view.php?p=NeonServV5.git
+or contact us on IRC.
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..4775b94
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,68 @@
+             NeonServ Installation Instructions
+
+Introduction:
+----------
+If you encounter any problems compiling/running NeonServ, please make 
+sure you have followed the directions in this file correctly and that 
+all of the requirements listed below are met.
+
+If the problem persists, report it to one (or all) of the coders
+listed in the AUTHORS file. Please try to include ALL relevant
+information about the error/bug, including anything out of the
+ordinary reported from make and the appropriate entries from the log
+files.
+
+Quick Install:
+----------
+$ ./autogen.sh
+$ ./configure
+$ make
+$ ${EDITOR} neonserv.conf
+    NOTE: You may want to copy neonserv.example.conf to neonserv.conf and
+    edit that.
+$ php language.php    (optional) 
+$ ./neonserv
+
+Compiling:
+----------
+  1) Enter the root directory of the NeonServ tree.  If installation is 
+     done from outside of it, it may cause problems during compile, or 
+     during runtime.
+
+  2) If you've cloned a development snapshot of NeonServ from git.pk910.de
+     you need to generate the configure and Makefile files. For that you
+     simply need to run the autogen.sh script.
+     (chmood it to be executeable if needed)
+
+  3) Run the configure script (sh configure), it will verify that your
+     system will have the resources needed for NeonServ to compile. If 
+     you would like to change the path where NeonServ will be installed 
+     to, execute configure with the --prefix=/path option.  The default 
+     path is ~/neonserv/.
+
+
+  4) You may optionally edit config.h in case the configure script made a
+     mistake.
+
+  5) Execute the "make" command to begin compiling.  If you encounter any
+     uncorrectable errors/warnings, please scroll up to the introduction
+     section and follow the instructions.
+
+  6) You may now either type "make install" to install it to your
+     installation path, or work from your build directory, either is fine.
+
+  7) Copy neonserv.example.conf to neonserv.conf and edit the mysql information.
+
+  8) Start NeonServ by executing the "neonserv" binary.
+     When you start NeonServ for the first time, it will create the neccesary
+     tables in your database and ask you for the initial bot settings
+     (Bot Administrator, first IRC Connection)
+
+  9) To get the actual language pack (including help strings) run the included 
+     language.php  (and rehash the bot's language cache with `reloadlang <tag>`)
+
+ 10) You can now register a channel via the bots query ;)
+
+End of file, INSTALL.
+
+-pk910 (neonserv@pk910.de)
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..679e1f6
--- /dev/null
@@ -0,0 +1,243 @@
+AUTOMAKE_OPTIONS = foreign
+AM_CFLAGS = $(MYSQL_CFLAGS)
+
+BUILT_SOURCES = version.c
+version.c: checkversion
+checkversion:
+       cd src && ./version.sh && cd ..
+
+noinst_PROGRAMS = neonserv
+noinst_LTLIBRARIES = libDummyServ.la libfuncmds.la libglobalcmd.la libNeonHelp.la libNeonBackup.la libNeonServ.la libNeonSpam.la libstats.la libNeonFun.la libNeonKick.la
+
+libDummyServ_la_SOURCES = src/modules/DummyServ.mod/bot_DummyServ.c \
+      src/modules/DummyServ.mod/module.c
+libDummyServ_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libDummyServ_la_LIBADD = $(MYSQL_LIBS)
+
+libfuncmds_la_SOURCES = src/modules/funcmd.mod/cmd_funcmds.c \
+      src/modules/funcmd.mod/module.c
+libfuncmds_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libfuncmds_la_LIBADD = $(MYSQL_LIBS)
+
+libglobalcmd_la_SOURCES = src/modules/global.mod/cmd_global.c \
+      src/modules/global.mod/cmd_global_bind.c \
+      src/modules/global.mod/cmd_global_command.c \
+      src/modules/global.mod/cmd_global_commands.c \
+      src/modules/global.mod/cmd_global_emote.c \
+      src/modules/global.mod/cmd_global_god.c \
+      src/modules/global.mod/cmd_global_netinfo.c \
+      src/modules/global.mod/cmd_global_notice.c \
+      src/modules/global.mod/cmd_global_raw.c \
+      src/modules/global.mod/cmd_global_register.c \
+      src/modules/global.mod/cmd_global_reloadlang.c \
+      src/modules/global.mod/cmd_global_say.c \
+      src/modules/global.mod/cmd_global_setaccess.c \
+      src/modules/global.mod/cmd_global_unbind.c \
+      src/modules/global.mod/cmd_global_unregister.c \
+      src/modules/global.mod/cmd_global_version.c \
+      src/modules/global.mod/cmd_global_staff.c \
+      src/modules/global.mod/cmd_global_motd.c \
+      src/modules/global.mod/cmd_global_bots.c \
+      src/modules/global.mod/cmd_global_reload.c \
+      src/modules/global.mod/cmd_global_restart.c \
+      src/modules/global.mod/cmd_global_die.c \
+      src/modules/global.mod/cmd_global_setbot.c \
+      src/modules/global.mod/cmd_global_addbot.c \
+      src/modules/global.mod/cmd_global_delbot.c \
+      src/modules/global.mod/cmd_global_reconnect.c \
+      src/modules/global.mod/cmd_global_modcmd.c \
+      src/modules/global.mod/cmd_global_meminfo.c \
+      src/modules/global.mod/cmd_global_extscript.c \
+      src/modules/global.mod/cmd_global_move.c \
+      src/modules/global.mod/cmd_global_global.c \
+      src/modules/global.mod/module.c
+libglobalcmd_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libglobalcmd_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonHelp_la_SOURCES = src/modules/NeonHelp.mod/bot_NeonHelp.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp_next.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp_delete.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp_requests.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp_stats.c \
+      src/modules/NeonHelp.mod/cmd_neonhelp_history.c \
+      src/modules/NeonHelp.mod/module.c
+libNeonHelp_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonHelp_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonBackup_la_SOURCES = src/modules/NeonBackup.mod/bot_NeonBackup.c \
+      src/modules/NeonBackup.mod/cmd_neonbackup.c \
+      src/modules/NeonBackup.mod/cmd_neonbackup_recover.c \
+      src/modules/NeonBackup.mod/module.c
+libNeonBackup_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonBackup_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonServ_la_SOURCES = src/modules/NeonServ.mod/bot_NeonServ.c \
+      src/modules/NeonServ.mod/cmd_neonserv.c \
+      src/modules/NeonServ.mod/cmd_neonserv_access.c \
+      src/modules/NeonServ.mod/cmd_neonserv_addban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_addtimeban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_adduser.c \
+      src/modules/NeonServ.mod/cmd_neonserv_ban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_bans.c \
+      src/modules/NeonServ.mod/cmd_neonserv_chanservsync.c \
+      src/modules/NeonServ.mod/cmd_neonserv_clvl.c \
+      src/modules/NeonServ.mod/cmd_neonserv_csuspend.c \
+      src/modules/NeonServ.mod/cmd_neonserv_cunsuspend.c \
+      src/modules/NeonServ.mod/cmd_neonserv_delban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_delme.c \
+      src/modules/NeonServ.mod/cmd_neonserv_deluser.c \
+      src/modules/NeonServ.mod/cmd_neonserv_deop.c \
+      src/modules/NeonServ.mod/cmd_neonserv_deopall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_devoice.c \
+      src/modules/NeonServ.mod/cmd_neonserv_devoiceall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_down.c \
+      src/modules/NeonServ.mod/cmd_neonserv_downall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_events.c \
+      src/modules/NeonServ.mod/cmd_neonserv_giveowner.c \
+      src/modules/NeonServ.mod/cmd_neonserv_help.c \
+      src/modules/NeonServ.mod/cmd_neonserv_invite.c \
+      src/modules/NeonServ.mod/cmd_neonserv_inviteme.c \
+      src/modules/NeonServ.mod/cmd_neonserv_invitemeall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_kick.c \
+      src/modules/NeonServ.mod/cmd_neonserv_kickban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_mdeluser.c \
+      src/modules/NeonServ.mod/cmd_neonserv_mode.c \
+      src/modules/NeonServ.mod/cmd_neonserv_myaccess.c \
+      src/modules/NeonServ.mod/cmd_neonserv_op.c \
+      src/modules/NeonServ.mod/cmd_neonserv_opall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_oplog.c \
+      src/modules/NeonServ.mod/cmd_neonserv_peek.c \
+      src/modules/NeonServ.mod/cmd_neonserv_recover.c \
+      src/modules/NeonServ.mod/cmd_neonserv_resync.c \
+      src/modules/NeonServ.mod/cmd_neonserv_search.c \
+      src/modules/NeonServ.mod/cmd_neonserv_set.c \
+      src/modules/NeonServ.mod/cmd_neonserv_suspend.c \
+      src/modules/NeonServ.mod/cmd_neonserv_topic.c \
+      src/modules/NeonServ.mod/cmd_neonserv_trace.c \
+      src/modules/NeonServ.mod/cmd_neonserv_trim.c \
+      src/modules/NeonServ.mod/cmd_neonserv_unban.c \
+      src/modules/NeonServ.mod/cmd_neonserv_unbanall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_unbanme.c \
+      src/modules/NeonServ.mod/cmd_neonserv_unsuspend.c \
+      src/modules/NeonServ.mod/cmd_neonserv_up.c \
+      src/modules/NeonServ.mod/cmd_neonserv_upall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_users.c \
+      src/modules/NeonServ.mod/cmd_neonserv_uset.c \
+      src/modules/NeonServ.mod/cmd_neonserv_voice.c \
+      src/modules/NeonServ.mod/cmd_neonserv_voiceall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_wipeinfo.c \
+      src/modules/NeonServ.mod/cmd_neonserv_addrank.c \
+      src/modules/NeonServ.mod/cmd_neonserv_assignrank.c \
+      src/modules/NeonServ.mod/cmd_neonserv_delrank.c \
+      src/modules/NeonServ.mod/cmd_neonserv_listrank.c \
+      src/modules/NeonServ.mod/cmd_neonserv_setrank.c \
+      src/modules/NeonServ.mod/cmd_neonserv_info.c \
+      src/modules/NeonServ.mod/cmd_neonserv_rename.c \
+      src/modules/NeonServ.mod/cmd_neonserv_unvisited.c \
+      src/modules/NeonServ.mod/cmd_neonserv_noregister.c \
+      src/modules/NeonServ.mod/cmd_neonserv_nicklist.c \
+      src/modules/NeonServ.mod/cmd_neonserv_halfop.c \
+      src/modules/NeonServ.mod/cmd_neonserv_dehalfop.c \
+      src/modules/NeonServ.mod/cmd_neonserv_halfopall.c \
+      src/modules/NeonServ.mod/cmd_neonserv_dehalfopall.c \
+      src/modules/NeonServ.mod/module.c
+libNeonServ_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonServ_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonSpam_la_SOURCES = src/modules/NeonSpam.mod/bot_NeonSpam.c \
+      src/modules/NeonSpam.mod/cmd_neonspam.c \
+      src/modules/NeonSpam.mod/cmd_neonspam_set.c \
+      src/modules/NeonSpam.mod/cmd_neonspam_addbad.c \
+      src/modules/NeonSpam.mod/cmd_neonspam_delbad.c \
+      src/modules/NeonSpam.mod/cmd_neonspam_badwords.c \
+      src/modules/NeonSpam.mod/module.c
+libNeonSpam_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonSpam_la_LIBADD = $(MYSQL_LIBS)
+
+libstats_la_SOURCES = src/modules/stats.mod/module.c
+libstats_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libstats_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonFun_la_SOURCES = src/modules/NeonFun.mod/bot_NeonFun.c \
+      src/modules/NeonFun.mod/cmd_neonfun.c \
+      src/modules/NeonFun.mod/cmd_neonfun_uno.c \
+      src/modules/NeonFun.mod/cmd_neonfun_unoplay.c \
+      src/modules/NeonFun.mod/cmd_neonfun_unotake.c \
+      src/modules/NeonFun.mod/game_uno.c \
+      src/modules/NeonFun.mod/cmd_neonfun_4wins.c \
+      src/modules/NeonFun.mod/cmd_neonfun_4stone.c \
+      src/modules/NeonFun.mod/cmd_neonfun_4view.c \
+      src/modules/NeonFun.mod/game_4wins.c \
+      src/modules/NeonFun.mod/cmd_neonfun_blackjack.c \
+      src/modules/NeonFun.mod/cmd_neonfun_bjtake.c \
+      src/modules/NeonFun.mod/cmd_neonfun_bjenough.c \
+      src/modules/NeonFun.mod/game_blackjack.c \
+      src/modules/NeonFun.mod/module.c
+libNeonFun_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonFun_la_LIBADD = $(MYSQL_LIBS)
+
+libNeonKick_la_SOURCES = src/modules/NeonKick.mod/bot_NeonKick.c \
+      src/modules/NeonKick.mod/module.c
+libNeonKick_la_LDFLAGS = -module -rpath /nowhere -avoid-version -no-undefined
+libNeonKick_la_LIBADD = $(MYSQL_LIBS)
+
+neonserv_SOURCES = src/version.c \
+      src/IOEngine_epoll.c \
+      src/IOEngine_kevent.c \
+      src/IOEngine_select.c \
+      src/IOEngine_win32.c \
+      src/IOHandler.c \
+      src/IOHandler_SSL.c \
+      src/EventLogger.c \
+      src/IRCEvents.c \
+      src/main.c \
+      src/signal.c \
+      src/ChanNode.c \
+      src/IRCParser.c \
+      src/ClientSocket.c \
+      src/UserNode.c \
+      src/ChanUser.c \
+      src/ModeNode.c \
+      src/BanNode.c \
+      src/IPNode.c \
+      src/WHOHandler.c \
+      src/modcmd.c \
+      src/mysqlConn.c \
+      src/lang.c \
+      src/HandleInfoHandler.c \
+      src/tools.c \
+      src/timeq.c \
+      src/DBHelper.c \
+      src/IRCQueue.c \
+      src/bots.c \
+      src/ConfigParser.c \
+      src/QServer.c \
+      src/modules.c \
+      src/module_commands.c \
+      src/ModuleFunctions.c \
+      src/statistics.c \
+      src/log.c \
+      src/memoryDebug.c \
+      src/mutexDebug.c
+
+neonserv_LDADD = $(MYSQL_LIBS) $(SYSTEM_LIBS)
+
+install-exec-local:
+       $(INSTALL) -d -m 755 $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/AUTHORS $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/COPYING $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/INSTALL $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/VERSION $(prefix)
+       $(INSTALL) -m 744 ./neonserv $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/database.sql $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/database.upgrade.sql $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/database.defaults.sql $(prefix)
+       $(INSTALL) -m 600 $(srcdir)/neonserv.example.conf $(prefix)
+       $(INSTALL) -m 744 $(srcdir)/language.php $(prefix)
+       $(INSTALL) -m 644 $(srcdir)/.libs/*.so $(prefix)
+       @echo
+       @echo NeonServ-$(VERSION) has been installed to $(prefix)
+       @echo Edit the neonserv.example.conf and save it as neonserv.conf before you start!
+       @echo You should use an own database for this Bot - table names are NOT prefixed!
+       @echo
diff --git a/QServer/NeonServ_QServer.class.php b/QServer/NeonServ_QServer.class.php
new file mode 100644 (file)
index 0000000..e3fb4b0
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/* NeonServ_QServer.class.php - NeonServ v5
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+class NeonServ_QServer {
+    private $admin, $socket, $numeric;
+    
+    function NeonServ_QServer() {
+        $this->admin['pass']='*'; // QServer Passwort
+        $this->admin['host']='127.0.0.1';
+        $this->admin['port']=7499;
+    }
+    
+    public function disconnect() {
+        fclose($this->socket);
+        $this->socket=false;
+    }
+    
+    public function connect() {
+        if($this->socket) {
+            $this->disconnect();
+        }
+        $this->socket=@fsockopen($this->admin['host'], $this->admin['port'], $errno, $errstr, 3);
+        if ($this->socket) {
+            stream_set_timeout($this->socket, 2);
+            fputs($this->socket, "A ".$this->admin['pass']."\n");
+            $data = $this->parseLine(@fgets($this->socket));
+            if($data[0] == "A")
+                return true;
+            $this->disconnect(); //not logged in...
+            return false;
+        }
+    }
+    
+    public function connected() {
+        if($this->socket) return true;
+        return false;
+    }
+    
+    private function parseLine($line) {
+        $line = str_replace(array("\r", "\n"), array("", ""), $line);
+        $highExplode = explode(" :", $line, 2);
+        $tokens = explode(" ", $highExplode[0]);
+        if(count($highExplode) > 1)
+            $tokens[] = $highExplode[1];
+        return $tokens;
+    }
+    
+    /* Get IRC User Information
+    * returns array: nick, ident, host, auth, realname
+    */
+    public function getUser($nick) {
+        fputs($this->socket, "U ".$nick."\n");
+        $data = $this->parseLine(@fgets($this->socket));
+        if($data[0] != "U") return NULL;
+        if($data[1] == "0") return NULL; //User not found
+        $user = array();
+        $user['nick'] = $data[2];
+        $user['ident'] = $data[3];
+        $user['host'] = $data[4];
+        $user['auth'] = ($data[5] == "0" ? NULL : $data[5]);
+        $user['realname'] = $data[6];
+        return $user;
+    }
+    
+    /* Get IRC Channel Information
+    * returns array: name, usercount, modes, topic
+    */
+    public function getChannel($name) {
+        fputs($this->socket, "C ".$name."\n");
+        $data = $this->parseLine(@fgets($this->socket));
+        if($data[0] != "C") return NULL;
+        if($data[1] == "0") return NULL; //Channel not found
+        $chan = array();
+        $chan['name'] = $data[2];
+        $chan['usercount'] = $data[3];
+        $chan['modes'] = implode(" ", array_slice($data, 4, (count($data) - 5)));
+        $chan['topic'] = $data[count($data)-1];
+        return $chan;
+    }
+    
+    /* Get All IRC Channels
+    * returns array with subarrays: name, usercount, modes, topic
+    */
+    public function getChannels() {
+        fputs($this->socket, "AC\n");
+        $channels = array();
+        while(true) {
+            $data = $this->parseLine(@fgets($this->socket));
+            if($data[0] == "E") return NULL;
+            if($data[0] == "ACE") break;
+            if($data[0] == "AC") {
+                $chan = array();
+                $chan['name'] = $data[1];
+                $chan['usercount'] = $data[2];
+                $chan['modes'] = implode(" ", array_slice($data, 3, (count($data) - 4)));
+                $chan['topic'] = $data[count($data)-1];
+                $channels[] = $chan;
+            }
+        }
+        return $channels;
+    }
+    
+    /* Get IRC Channel Users
+    * returns array with subarrays: nick, auth, flags
+    */
+    public function getChannelUsers($name, $invisibles = false) {
+        fputs($this->socket, "ACU ".$name." ".($invisibles ? "1" : "0")."\n");
+        $chanusers = array();
+        while(true) {
+            $data = $this->parseLine(@fgets($this->socket));
+            if($data[0] == "E") return NULL;
+            if($data[0] == "ACUE") break;
+            if($data[0] == "ACU") {
+                $chanuser = array();
+                $chanuser['nick'] = $data[1];
+                $chanuser['auth'] = ($data[2] == "0" ? NULL : $data[2]);
+                $chanuser['flags'] = $data[3];
+                $chanusers[] = $chanuser;
+            }
+        }
+        return $chanusers;
+    }
+    
+    /* send IRC RAW
+    * returns true / false
+    */
+    public function sendRAW($class, $raw, $classIsNick = false) {
+        fputs($this->socket, "R ".($classIsNick ? "1" : "0")." ".$class." :".$raw."\n");
+        $data = $this->parseLine(@fgets($this->socket));
+        if($data[0] == "R" && $data[1] == "1") return true;
+        return false;
+    }
+}
+
+?>
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..ada9b65
--- /dev/null
+++ b/VERSION
@@ -0,0 +1,22 @@
+VERSION LOG
+2012-09-06   5.6
+    merged IOMultiplexer project into NeonServ (faster than the old select calls)
+    new version numbering
+
+2012-08-12   5.5
+    new core to increase stability (NeonServ is now 1 year old :))
+
+2012-02-16   5.4
+    modular buildup
+
+2011-12-22   5.3
+    dynamic bot management
+
+2011-10-26   5.2
+    added NeonSpam (anti spam service)
+
+2011-10-05   5.1
+    first production version...
+
+2011-08-09   5.0 (dev)
+    developing period...
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..01c075c
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+echo "Generating configure files... may take a while."
+
+autoreconf --install --force && \
+  echo "Preparing was successful if there was no error messages above." && \
+  echo "Now type:" && \
+  echo "  ./configure && make"  && \
+  echo "Run './configure --help' for more information"
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..0a6c9d9
--- /dev/null
@@ -0,0 +1,95 @@
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.63])
+AC_INIT([NeonServ], [5.6], [bugs@pk910.de], [neonserv], [http://neonserv.krypton-bouncer.de])
+AC_PREFIX_DEFAULT([~/neonserv])
+AC_CANONICAL_TARGET
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+AM_SILENT_RULES([yes])
+AC_CONFIG_HEADERS([config.h])
+
+LT_INIT([disable-static])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_AWK
+
+# Checks for libraries.
+# Get MySQL library and include locations
+AC_ARG_WITH([mysql],
+  [AS_HELP_STRING([--with-mysql=DIR],
+    [location of the MySQL headers, defaults to /usr/include/mysql])],
+  [MYSQL_CFLAGS="-I$withval"],
+  [MYSQL_CFLAGS='-I/usr/include/mysql'])
+AC_SUBST([MYSQL_CFLAGS])
+
+AC_ARG_WITH([mysql-lib],
+  [AS_HELP_STRING([--with-mysql-lib=DIR], [location of the MySQL libraries])],
+  [MYSQL_LIBS="-L$withval -lmysqlclient"],
+  [MYSQL_LIBS='-lmysqlclient'])
+AC_SUBST([MYSQL_LIBS])
+
+AC_CHECK_LIB([ws2_32], [_head_libws2_32_a], [LIBS="$LIBS -lws2_32"])
+AC_CHECK_LIB([dl], [dlopen], [LIBS="$LIBS -ldl"])
+AC_CHECK_LIB([pthread], [pthread_create], [LIBS="$LIBS -lpthread"])
+AC_CHECK_LIB([ssl], [SSL_read], [LIBS="$LIBS -lssl"])
+AC_CHECK_LIB([crypto], [X509_new], [LIBS="$LIBS -lcrypto"])
+
+do_have_ssl="no";
+AC_CHECK_LIB(ssl, SSL_read, [
+  AC_CHECK_LIB(crypto, X509_new, [
+    AC_CHECK_HEADERS(openssl/ssl.h openssl/err.h openssl/rand.h, [
+      AC_DEFINE([HAVE_SSL], 1, [Define if you are using SSL])
+    ])
+  ])
+])
+
+do_have_threads="no";
+AC_CHECK_LIB(pthread, pthread_create, [
+  AC_CHECK_HEADERS(pthread.h, [
+    do_have_threads="yes"
+  ])
+])
+
+AC_ARG_ENABLE([threads],
+  [AS_HELP_STRING([--enable-threads], [use threads if possible])],
+  [
+  if test x"$do_have_threads" = xyes; then
+    LIBS="$LIBS -lpthread"
+    AC_DEFINE([HAVE_THREADS], 1, [Define if you have Threads])
+  fi
+  ],
+  [])
+
+AC_ARG_ENABLE([memory-debug],
+  [AS_HELP_STRING([--enable-memory-debug], [run memory debugger])],
+  [
+    AC_DEFINE([ENABLE_MEMORY_DEBUG], 1, [Define if you enable memoryDebug.c])
+  ],
+  [])
+
+AC_ARG_ENABLE([mutex-debug],
+  [AS_HELP_STRING([--enable-mutex-debug], [run mutex debugger])],
+  [
+    AC_DEFINE([ENABLE_MUTEX_DEBUG], 1, [Define if you enable mutexDebug.c])
+  ],
+  [])
+
+AC_ARG_ENABLE([debug],
+  [AS_HELP_STRING([--enable-debug], [debug mode (compile using -O0 -Wall -Wshadow -Werror)])],
+  [CFLAGS='-g -O0 -Wall -Wshadow -Werror'],
+  [CFLAGS='-g -O2'])
+
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+
+# Checks for header files.
+AC_CHECK_HEADERS([stdio.h stdlib.h string.h ctype.h windows.h winsock2.h malloc.h features.h sys/types.h sys/socket.h netinet/in.h netinet/tcp.h arpa/inet.h netdb.h sys/wait.h errno.h unistd.h getopt.h stdarg.h sys/time.h time.h signal.h sys/epoll.h sys/event.h ws2tcpip.h mysql.h mysql/errmsg.h errmsg.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+
+# Checks for library functions.
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([gethostbyname memset select socket strchr strdup strstr])
+
+AC_CONFIG_FILES(Makefile)
+AC_OUTPUT
\ No newline at end of file
diff --git a/database.defaults.sql b/database.defaults.sql
new file mode 100644 (file)
index 0000000..25e3336
--- /dev/null
@@ -0,0 +1,275 @@
+--
+-- Daten für Tabelle `bot_binds`
+--
+--
+-- Daten für Tabelle `bot_binds`
+--
+
+INSERT INTO `bot_binds` (NULL, `botclass`, `botid`, `command`, `function`, `parameters`, `chan_access`, `global_access`, `flags`) VALUES
+(NULL, 1, 0, 'bind', 'bind', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unbind', 'unbind', '', NULL, NULL, 0),
+(NULL, 1, 0, 'adduser', 'adduser', '', NULL, NULL, 0),
+(NULL, 1, 0, 'addowner', 'adduser', '%1 500', NULL, NULL, 0),
+(NULL, 1, 0, 'addcoowner', 'adduser', '%1 400', NULL, NULL, 0),
+(NULL, 1, 0, 'addco', 'adduser', '%1 400', NULL, NULL, 0),
+(NULL, 1, 0, 'addmaster', 'adduser', '%1 300', NULL, NULL, 0),
+(NULL, 1, 0, 'addop', 'adduser', '%1 200', NULL, NULL, 0),
+(NULL, 1, 0, 'addpeon', 'adduser', '%1 100', NULL, NULL, 0),
+(NULL, 1, 0, 'addvoice', 'adduser', '%1 100', NULL, NULL, 0),
+(NULL, 1, 0, 'deluser', 'deluser', '', NULL, NULL, 0),
+(NULL, 1, 0, 'clvl', 'clvl', '', NULL, NULL, 0),
+(NULL, 1, 0, 'access', 'access', '', NULL, NULL, 0),
+(NULL, 1, 0, 'a', 'access', '', NULL, NULL, 0),
+(NULL, 1, 0, 'users', 'users', '', NULL, NULL, 0),
+(NULL, 1, 0, 'vlist', 'users', '%1|* 100 199', NULL, NULL, 0),
+(NULL, 1, 0, 'olist', 'users', '%1|* 200 299', NULL, NULL, 0),
+(NULL, 1, 0, 'plist', 'users', '%1|* 100 199', NULL, NULL, 0),
+(NULL, 1, 0, 'mlist', 'users', '%1|* 300 399', NULL, NULL, 0),
+(NULL, 1, 0, 'clist', 'users', '%1|* 400 499', NULL, NULL, 0),
+(NULL, 1, 0, 'suspend', 'suspend', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unsuspend', 'unsuspend', '', NULL, NULL, 0),
+(NULL, 1, 0, 'delme', 'delme', '', NULL, NULL, 0),
+(NULL, 1, 0, 'deleteme', 'delme', '', NULL, NULL, 0),
+(NULL, 1, 0, 'myaccess', 'myaccess', '', NULL, NULL, 0),
+(NULL, 1, 0, 'mya', 'myaccess', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ma', 'myaccess', '', NULL, NULL, 0),
+(NULL, 1, 0, 'up', 'up', '', NULL, NULL, 0),
+(NULL, 1, 0, 'down', 'down', '', NULL, NULL, 0),
+(NULL, 1, 0, 'upall', 'upall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'uall', 'upall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'downall', 'downall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'dall', 'downall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'mdeluser', 'mdeluser', '', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelop', 'mdeluser', '200 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelvoice', 'mdeluser', '100 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelpeon', 'mdeluser', '100 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelmaster', 'mdeluser', '300 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelcoowner', 'mdeluser', '400 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'mdelco', 'mdeluser', '400 %1', NULL, NULL, 0),
+(NULL, 1, 0, 'trim', 'trim', '', NULL, NULL, 0),
+(NULL, 1, 0, 'giveowner', 'giveowner', '', NULL, NULL, 0),
+(NULL, 1, 0, 'giveownership', 'giveowner', '', NULL, NULL, 0),
+(NULL, 1, 0, 'op', 'op', '', NULL, NULL, 0),
+(NULL, 1, 0, 'deop', 'deop', '', NULL, NULL, 0),
+(NULL, 1, 0, 'voice', 'voice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'devoice', 'devoice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'v', 'voice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'opall', 'opall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'deopall', 'deopall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'voiceall', 'voiceall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'vall', 'voiceall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'devoiceall', 'devoiceall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'set', 'set', '', NULL, NULL, 0),
+(NULL, 1, 0, 'kick', 'kick', '', NULL, NULL, 0),
+(NULL, 1, 0, 'k', 'kick', '', NULL, NULL, 0),
+(NULL, 1, 0, 'kickban', 'kickban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'kb', 'kickban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ban', 'ban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'b', 'ban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'wipeinfo', 'wipeinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'deluinfo', 'wipeinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'addban', 'addban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ab', 'addban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bans', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'shitlist', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'showbans', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'showban', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'delban', 'delban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'netinfo', 'netinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'topic', 'topic', '', NULL, NULL, 0),
+(NULL, 1, 0, 'chanservsync', 'chanservsync', '', NULL, NULL, 0),
+(NULL, 1, 0, 'resync', 'resync', '', NULL, NULL, 0),
+(NULL, 1, 0, 'addtimeban', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'addtimedban', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'timeban', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'timedban', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'tb', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'atb', 'addtimeban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'mode', 'mode', '', NULL, NULL, 0),
+(NULL, 1, 0, 'version', 'version', '', NULL, NULL, 0),
+(NULL, 1, 0, 'peek', 'peek', '', NULL, NULL, 0),
+(NULL, 1, 0, 'uset', 'uset', '', NULL, NULL, 0),
+(NULL, 1, 0, 'aset', 'uset', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unban', 'unban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ub', 'unban', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unbanall', 'unbanall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'uba', 'unbanall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unbanme', 'unbanme', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ubme', 'unbanme', '', NULL, NULL, 0),
+(NULL, 1, 0, 'invite', 'invite', '', NULL, NULL, 0),
+(NULL, 1, 0, 'inviteme', 'inviteme', '', NULL, NULL, 65536),
+(NULL, 1, 0, 'help', 'help', '', NULL, NULL, 0),
+(NULL, 1, 0, 'cmds', 'commands', '', NULL, NULL, 0),
+(NULL, 1, 0, 'trace', 'trace', '', NULL, NULL, 0),
+(NULL, 1, 0, 'register', 'register', '', NULL, NULL, 0),
+(NULL, 1, 0, 'reg', 'register', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unregister', 'unregister', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unreg', 'unregister', '', NULL, NULL, 0),
+(NULL, 1, 0, 'recover', 'recover', '', NULL, NULL, 0),
+(NULL, 1, 0, 'say', 'say', '', NULL, NULL, 0),
+(NULL, 1, 0, 'emote', 'emote', '', NULL, NULL, 0),
+(NULL, 1, 0, 'notice', 'notice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'raw', 'raw', '', NULL, NULL, 0),
+(NULL, 1, 0, 'god', 'god', '', NULL, NULL, 0),
+(NULL, 1, 0, 'godmode', 'god', '', NULL, NULL, 0),
+(NULL, 1, 0, 'reloadlang', 'reloadlang', '', NULL, NULL, 0),
+(NULL, 1, 0, 'csuspend', 'csuspend', '', NULL, NULL, 0),
+(NULL, 1, 0, 'cunsuspend', 'cunsuspend', '', NULL, NULL, 0),
+(NULL, 1, 0, 'move', 'move', '', NULL, NULL, 0),
+(NULL, 1, 0, 'events', 'events', '', NULL, NULL, 0),
+(NULL, 1, 0, 'oplog', 'oplog', '', NULL, NULL, 0),
+(NULL, 1, 0, 'search', 'search', '', NULL, NULL, 0),
+(NULL, 1, 0, 'command', 'command', '', NULL, NULL, 0),
+(NULL, 1, 0, 'va', 'voiceall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'setaccess', 'setaccess', '', NULL, NULL, 0),
+(NULL, 1, 0, 'blist', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'addrank', 'addrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'delrank', 'delrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'listrank', 'listrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'rank', 'assignrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'assignrank', 'assignrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'setrank', 'setrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ranks', 'listrank', '', NULL, NULL, 0),
+(NULL, 1, 0, 'setinfo', 'uset', 'info %1-', NULL, NULL, 0),
+(NULL, 1, 0, 'info', 'info', '', NULL, NULL, 0),
+(NULL, 1, 0, 'rename', 'rename', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unvisited', 'unvisited', '', NULL, NULL, 0),
+(NULL, 1, 0, 'add', 'adduser', '', NULL, NULL, 0),
+(NULL, 1, 0, 'sync', 'resync', '', NULL, NULL, 0),
+(NULL, 1, 0, 'dv', 'devoice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ping', 'funcmd.ping', '', NULL, NULL, 0),
+(NULL, 1, 0, 'pong', 'funcmd.pong', '', NULL, NULL, 0),
+(NULL, 1, 0, 'dice', 'funcmd.dice', '', NULL, NULL, 0),
+(NULL, 1, 0, 'd', 'funcmd.dice', '', NULL, NULL, 0),
+(NULL, 1, 0, '8ball', 'funcmd.8ball', '', NULL, NULL, 0),
+(NULL, 1, 0, '8', 'funcmd.8ball', '', NULL, NULL, 0),
+(NULL, 1, 0, 'staff', 'staff', '', NULL, NULL, 0),
+(NULL, 1, 0, 'noregister', 'noregister', '', NULL, NULL, 0),
+(NULL, 1, 0, 'commands', 'commands', '', NULL, NULL, 0),
+(NULL, 1, 0, 'oplogs', 'oplog', '', NULL, NULL, 0),
+(NULL, 1, 0, 'cmd', 'command', '', NULL, NULL, 0),
+(NULL, 1, 0, 'team', 'staff', '', NULL, NULL, 0),
+(NULL, 1, 0, 'syncusers', 'chanservsync', '', NULL, NULL, 0),
+(NULL, 1, 0, 'cookie', 'FunCMD.cookie', '', NULL, NULL, 0),
+(NULL, 1, 0, 'banlist', 'bans', '', NULL, NULL, 0),
+(NULL, 1, 0, 'del', 'deluser', '', NULL, NULL, 0),
+(NULL, 1, 0, 'nicklist', 'nicklist', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bots', 'bots', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot list', 'bots', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot add', 'addbot', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot del', 'delbot', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot reconnect', 'reconnect', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot', 'linker', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bot set', 'setbot', '', NULL, NULL, 0),
+(NULL, 1, 0, 'dva', 'devoiceall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'restart', 'restart', '', NULL, NULL, 0),
+(NULL, 1, 0, 'showcommands', 'commands', '', NULL, NULL, 0),
+(NULL, 1, 0, 'moo', 'extscript', 'toys apt-get moo %1+', NULL, NULL, 0),
+(NULL, 1, 0, 'botinfo', 'netinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'reload', 'reload', '', NULL, NULL, 0),
+(NULL, 1, 0, 'moo', 'extscript', 'toys apt-get moo %1+', NULL, NULL, 0),
+(NULL, 1, 0, 'botinfo', 'netinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'meminfo', 'meminfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'die', 'die', '', NULL, NULL, 0),
+(NULL, 1, 0, 'loadmod', 'loadmod', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unloadmod', 'unloadmod', '', NULL, NULL, 0),
+(NULL, 1, 0, 'reloadmod', 'reloadmod', '', NULL, NULL, 0),
+(NULL, 1, 0, 'modules', 'modules', '', NULL, NULL, 0),
+(NULL, 1, 0, 'changelevel', 'clvl', '', NULL, NULL, 0),
+(NULL, 1, 0, 'changelvl', 'clvl', '', NULL, NULL, 0),
+(NULL, 1, 0, '4wins', 'NeonFun.4wins', '', NULL, NULL, 0),
+(NULL, 1, 0, '4stone', 'NeonFun.4stone', '', NULL, NULL, 0),
+(NULL, 1, 0, '4view', 'NeonFun.4view', '', NULL, NULL, 0),
+(NULL, 1, 0, 'uno', 'NeonFun.uno', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unoplay', 'NeonFun.unoplay', '', NULL, NULL, 0),
+(NULL, 1, 0, 'unotake', 'NeonFun.unotake', '', NULL, NULL, 0),
+(NULL, 1, 0, 'netstats', 'netinfo', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bomb', 'funcmd.bomb', '', NULL, NULL, 0),
+(NULL, 1, 0, 'inviteme all', 'invitemeall', '', NULL, NULL, 0),
+(NULL, 1, 0, 'ship', 'funcmd.ship', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bj', 'NeonFun.blackjack', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bjtake', 'NeonFun.bjtake', '', NULL, NULL, 0),
+(NULL, 1, 0, 'bjenough', 'NeonFun.bjenough', '', NULL, NULL, 0),
+
+(NULL, 2, 0, 'bind', 'bind', '', NULL, NULL, 0),
+(NULL, 2, 0, 'unbind', 'unbind', '', NULL, NULL, 0),
+(NULL, 2, 0, 'register', 'register', '', NULL, NULL, 0),
+(NULL, 2, 0, 'unregister', 'unregister', '', NULL, NULL, 0),
+(NULL, 2, 0, 'version', 'version', '', NULL, NULL, 0),
+(NULL, 2, 0, 'netinfo', 'netinfo', '', NULL, NULL, 0),
+(NULL, 2, 0, 'set', 'set', '', NULL, NULL, 0),
+(NULL, 2, 0, 'csuspend', 'csuspend', '', NULL, NULL, 0),
+(NULL, 2, 0, 'cunsuspend', 'cunsuspend', '', NULL, NULL, 0),
+(NULL, 2, 0, 'say', 'say', '', NULL, NULL, 0),
+(NULL, 2, 0, 'emote', 'emote', '', NULL, NULL, 0),
+(NULL, 2, 0, 'notice', 'notice', '', NULL, NULL, 0),
+(NULL, 2, 0, 'raw', 'raw', '', NULL, NULL, 0),
+(NULL, 2, 0, 'god', 'god', '', NULL, NULL, 0),
+(NULL, 2, 0, 'commands', 'commands', '', NULL, NULL, 0),
+(NULL, 2, 0, 'bans', 'NeonServ.bans', '', NULL, NULL, 0),
+(NULL, 2, 0, 'banlist', 'NeonServ.bans', '', NULL, NULL, 0),
+(NULL, 2, 0, 'delban', 'NeonServ.delban', '', NULL, NULL, 0),
+(NULL, 2, 0, 'addban', 'NeonServ.addban', '', NULL, NULL, 0),
+(NULL, 2, 0, 'search', 'NeonServ.search', '', NULL, NULL, 0),
+(NULL, 2, 0, 'unreg', 'unregister', '', NULL, NULL, 0),
+(NULL, 2, 0, 'reg', 'register', '', NULL, NULL, 0),
+(NULL, 2, 0, 'move', 'move', '', NULL, NULL, 0),
+(NULL, 2, 0, 'badwords', 'badwords', '', NULL, NULL, 0),
+(NULL, 2, 0, 'listbad', 'badwords', '', NULL, NULL, 0),
+(NULL, 2, 0, 'addbad', 'addbad', '', NULL, NULL, 0),
+(NULL, 2, 0, 'delbad', 'delbad', '', NULL, NULL, 0),
+
+(NULL, 3, 0, 'bind', 'bind', '', NULL, NULL, 0),
+(NULL, 3, 0, 'unbind', 'unbind', '', NULL, NULL, 0),
+(NULL, 3, 0, 'say', 'say', '', NULL, NULL, 0),
+(NULL, 3, 0, 'notice', 'notice', '', NULL, NULL, 0),
+(NULL, 3, 0, 'emote', 'emote', '', NULL, NULL, 0),
+
+(NULL, 4, 0, 'raw', 'raw', '', NULL, NULL, 0),
+(NULL, 4, 0, 'next', 'next', '', NULL, NULL, 0),
+(NULL, 4, 0, 'requests', 'requests', '', NULL, NULL, 0),
+(NULL, 4, 0, 'delete', 'delete', '', NULL, NULL, 0),
+(NULL, 4, 0, 'del', 'delete', '', NULL, NULL, 0),
+(NULL, 4, 0, 'reqs', 'requests', '', NULL, NULL, 0),
+(NULL, 4, 0, 'commands', 'commands', '', NULL, NULL, 0),
+(NULL, 4, 0, 'version', 'version', '', NULL, NULL, 0),
+(NULL, 4, 0, 'netinfo', 'netinfo', '', NULL, NULL, 0),
+(NULL, 4, 0, 'stats', 'stats', '', NULL, NULL, 0),
+(NULL, 4, 0, 'history', 'history', '', NULL, NULL, 0),
+(NULL, 4, 0, 'register', 'register', '', NULL, NULL, 0),
+(NULL, 4, 0, 'unregister', 'unregister', '', NULL, NULL, 0),
+
+(NULL, 6, 0, 'backup', 'linker', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup bind', 'bind', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup unbind', 'unbind', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup modcmd', 'modcmd', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup register', 'register', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup unregister', 'unregister', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup commands', 'commands', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup command', 'command', '', NULL, NULL, 0),
+(NULL, 6, 0, 'ping', 'funcmd.ping', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup search', 'neonserv.search', '', NULL, NULL, 0),
+(NULL, 6, 0, 'backup raw', 'raw', '', NULL, NULL, 0),
+
+(NULL, 7, 0, 'kickserv register', 'register', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv unregister', 'unregister', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv raw', 'raw', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv bind', 'bind', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv unbind', 'unbind', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv modcmd', 'modcmd', '', NULL, NULL, 0),
+(NULL, 7, 0, 'kickserv', 'linker', '', NULL, NULL, 0),
+
+(NULL, 1, 0, 'dns', 'extscript', 'toys php scripts/examples/dnslookup.php $1 $2 %1+', NULL, NULL, 0), 
+(NULL, 1, 0, 'calc', 'funcmd.extscript', 'toys php scripts/examples/calc.php $1- %1+', NULL, NULL, 0);
+
+
+--
+-- Daten für Tabelle `channels`
+--
+
+INSERT INTO `channels` (`channel_id`, `channel_name`, `channel_key`, `channel_maxusers`, `channel_lastvisit`, `channel_lastgiveowner`, `channel_pubcmd`, `channel_nodelete`, `channel_nogaccess`, `channel_canadd`, `channel_candel`, `channel_canclvl`, `channel_cankick`, `channel_canban`, `channel_staticban`, `channel_protect`, `channel_canop`, `channel_canhalfop`, `channel_canvoice`, `channel_getop`, `channel_gethalfop`, `channel_getvoice`, `channel_greeting`, `channel_usergreeting`, `channel_userinfo`, `channel_dynlimit`, `channel_getinvite`, `channel_topicmask`, `channel_exttopic`, `channel_exttopic_topic`, `channel_defaulttopic`, `channel_wipeinfo`, `channel_modes`, `channel_enfmodes`, `channel_enftopic`, `channel_topicsnarf`, `channel_changetopic`, `channel_setters`, `channel_canresync`, `channel_cansuspend`, `channel_notice`, `channel_noticereaction`, `channel_ctcp`, `channel_ctcpreaction`, `channel_registered`, `channel_registrator`, `channel_toys`, `channel_scanner`, `channel_spam_limit`, `channel_spam_reaction`, `channel_spam_reaction_duration`, `channel_spam_except`, `channel_flood_limit`, `channel_flood_time`, `channel_flood_reaction`, `channel_flood_reaction_duration`, `channel_flood_except`, `channel_join_limit`, `channel_join_time`, `channel_join_reaction`, `channel_join_reaction_duration`, `channel_join_except`, `channel_botnet_bantime`, `channel_botnet_except`, `channel_caps_percent`, `channel_caps_reaction`, `channel_caps_reaction_duration`, `channel_caps_except`, `channel_digit_percent`, `channel_digit_reaction`, `channel_digit_reaction_duration`, `channel_digit_except`, `channel_badword_reaction`, `channel_badword_reaction_duration`, `channel_badword_except`) VALUES
+(NULL, 'defaults', '', 0, 0, 0, 0, 0, 0, 300, 300, 300, 200, 200, 250, 0, 200, 200, 200, 200, 150, 100, '', '', 1, 0, 1, '', 0, '', '', 300, '+', 400, 400, 501, 200, 400, 200, 300, 0, 0, 0, 0, 0, 0, 0, '', 4, 0, 120, 400, 5, 3, 0, 120, 400, 4, 20, 2, 300, 400, 1800, 1, 60, 0, 60, 200, 60, 0, 60, 200, 0, 60, 400);
+
+--
+-- please run language.php for help and language entries.
+--
diff --git a/database.sql b/database.sql
new file mode 100644 (file)
index 0000000..d378118
--- /dev/null
@@ -0,0 +1,443 @@
+-- NeonServ Database v5.1
+
+--
+-- Tabellenstruktur für Tabelle `bans`
+--
+
+CREATE TABLE IF NOT EXISTS `bans` (
+  `ban_id` int(11) NOT NULL AUTO_INCREMENT,
+  `ban_channel` int(11) NOT NULL,
+  `ban_mask` varchar(250) NOT NULL,
+  `ban_triggered` int(15) NOT NULL,
+  `ban_timeout` int(15) NOT NULL,
+  `ban_owner` int(11) NOT NULL,
+  `ban_reason` varchar(512) NOT NULL,
+  PRIMARY KEY (`ban_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `bots`
+--
+
+CREATE TABLE IF NOT EXISTS `bots` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `active` tinyint(1) NOT NULL,
+  `nick` varchar(255) NOT NULL,
+  `server` varchar(255) NOT NULL,
+  `port` int(5) NOT NULL,
+  `pass` varchar(255) NOT NULL,
+  `ssl` tinyint(1) NOT NULL,
+  `bind` varchar(255) DEFAULT NULL,
+  `ident` varchar(12) NOT NULL,
+  `realname` varchar(255) NOT NULL,
+  `automodes` varchar(20) NOT NULL,
+  `oper_user` varchar(50) DEFAULT NULL,
+  `oper_pass` varchar(50) DEFAULT NULL,
+  `botclass` int(10) NOT NULL,
+  `textbot` tinyint(1) NOT NULL,
+  `queue` tinyint(1) NOT NULL,
+  `secret` tinyint(1) NOT NULL,
+  `defaulttrigger` varchar(10) NOT NULL,
+  `max_channels` int(5) NOT NULL,
+  `register_priority` int(2) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `nick` (`nick`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `bot_binds`
+--
+
+CREATE TABLE IF NOT EXISTS `bot_binds` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `botclass` int(11) NOT NULL,
+  `botid` int(11) NOT NULL,
+  `command` varchar(60) NOT NULL,
+  `function` varchar(60) NOT NULL,
+  `parameters` varchar(100) NOT NULL,
+  `chan_access` varchar(256) DEFAULT NULL,
+  `global_access` int(3) DEFAULT NULL,
+  `flags` int(10) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `botid` (`botid`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `bot_channels`
+--
+
+CREATE TABLE IF NOT EXISTS `bot_channels` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `botid` int(11) NOT NULL,
+  `chanid` int(11) NOT NULL,
+  `trigger` varchar(50) NULL DEFAULT '+',
+  `suspended` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `channels`
+--
+
+CREATE TABLE IF NOT EXISTS `channels` (
+  `channel_id` int(11) NOT NULL AUTO_INCREMENT,
+  `channel_name` varchar(250) NOT NULL,
+  `channel_key` varchar(50) NOT NULL,
+  `channel_maxusers` int(5) NOT NULL,
+  `channel_lastvisit` int(11) NOT NULL,
+  `channel_lastgiveowner` int(15) NOT NULL,
+  `channel_pubcmd` smallint(3) DEFAULT NULL,
+  `channel_nodelete` tinyint(1) NOT NULL,
+  `channel_nogaccess` tinyint(1) DEFAULT NULL,
+  `channel_canadd` smallint(3) DEFAULT NULL,
+  `channel_candel` smallint(3) DEFAULT NULL,
+  `channel_canclvl` smallint(3) DEFAULT NULL,
+  `channel_cankick` smallint(3) DEFAULT NULL,
+  `channel_canban` smallint(3) DEFAULT NULL,
+  `channel_staticban` smallint(3) DEFAULT NULL,
+  `channel_protect` tinyint(1) DEFAULT NULL,
+  `channel_canop` smallint(3) DEFAULT NULL,
+  `channel_canhalfop` smallint(3) DEFAULT NULL,
+  `channel_canvoice` smallint(3) DEFAULT NULL,
+  `channel_getop` smallint(3) DEFAULT NULL,
+  `channel_gethalfop` smallint(3) DEFAULT NULL,
+  `channel_getvoice` smallint(3) DEFAULT NULL,
+  `channel_greeting` varchar(512) NOT NULL,
+  `channel_usergreeting` varchar(512) NOT NULL,
+  `channel_userinfo` smallint(3) DEFAULT NULL,
+  `channel_dynlimit` smallint(5) DEFAULT NULL,
+  `channel_getinvite` smallint(3) DEFAULT NULL,
+  `channel_topicmask` varchar(512) NOT NULL,
+  `channel_exttopic` tinyint(1) NOT NULL,
+  `channel_exttopic_topic` varchar(512) NOT NULL,
+  `channel_defaulttopic` varchar(512) NOT NULL,
+  `channel_wipeinfo` smallint(3) DEFAULT NULL,
+  `channel_modes` varchar(500) DEFAULT NULL,
+  `channel_enfmodes` smallint(3) DEFAULT NULL,
+  `channel_enftopic` smallint(3) DEFAULT NULL,
+  `channel_topicsnarf` smallint(3) DEFAULT NULL,
+  `channel_changetopic` smallint(3) DEFAULT NULL,
+  `channel_setters` smallint(3) DEFAULT NULL,
+  `channel_canresync` smallint(3) DEFAULT NULL,
+  `channel_cansuspend` smallint(3) DEFAULT NULL,
+  `channel_notice` smallint(3) DEFAULT NULL,
+  `channel_noticereaction` tinyint(1) DEFAULT NULL,
+  `channel_ctcp` smallint(3) DEFAULT NULL,
+  `channel_ctcpreaction` tinyint(1) DEFAULT NULL,
+  `channel_registered` int(11) NOT NULL,
+  `channel_registrator` int(11) NOT NULL,
+  `channel_toys` tinyint(1) NOT NULL,
+  `channel_scanner` varchar(50) DEFAULT NULL,
+  `channel_spam_limit` smallint(3) DEFAULT NULL,
+  `channel_spam_reaction` tinyint(1) DEFAULT NULL,
+  `channel_spam_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_spam_except` smallint(3) DEFAULT NULL,
+  `channel_flood_limit` smallint(3) DEFAULT NULL,
+  `channel_flood_time` smallint(3) DEFAULT NULL,
+  `channel_flood_reaction` tinyint(1) DEFAULT NULL,
+  `channel_flood_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_flood_except` smallint(3) DEFAULT NULL,
+  `channel_join_limit` smallint(3) DEFAULT NULL,
+  `channel_join_time` smallint(3) DEFAULT NULL,
+  `channel_join_reaction` tinyint(1) DEFAULT NULL,
+  `channel_join_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_join_except` smallint(3) DEFAULT NULL,
+  `channel_botnet_bantime` mediumint(7) DEFAULT NULL,
+  `channel_botnet_except` smallint(3) DEFAULT NULL,
+  `channel_caps_percent` tinyint(3) DEFAULT NULL,
+  `channel_caps_reaction` tinyint(1) DEFAULT NULL,
+  `channel_caps_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_caps_except` smallint(3) DEFAULT NULL,
+  `channel_digit_percent` tinyint(3) DEFAULT NULL,
+  `channel_digit_reaction` tinyint(1) DEFAULT NULL,
+  `channel_digit_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_digit_except` smallint(3) DEFAULT NULL,
+  `channel_badword_reaction` tinyint(1) DEFAULT NULL,
+  `channel_badword_reaction_duration` mediumint(7) DEFAULT NULL,
+  `channel_badword_except` smallint(3) DEFAULT NULL,
+  PRIMARY KEY (`channel_id`),
+  UNIQUE KEY `channel_name` (`channel_name`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `chanusers`
+--
+
+CREATE TABLE IF NOT EXISTS `chanusers` (
+  `chanuser_id` int(11) NOT NULL AUTO_INCREMENT,
+  `chanuser_cid` int(11) NOT NULL,
+  `chanuser_uid` int(11) NOT NULL,
+  `chanuser_access` int(3) NOT NULL,
+  `chanuser_flags` int(11) NOT NULL,
+  `chanuser_seen` int(11) NOT NULL,
+  `chanuser_infoline` varchar(512) NOT NULL,
+  PRIMARY KEY (`chanuser_id`),
+  KEY `chanuser_cid` (`chanuser_cid`),
+  KEY `chanuser_uid` (`chanuser_uid`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `events`
+--
+
+CREATE TABLE IF NOT EXISTS `events` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `cid` int(11) NOT NULL,
+  `nick` varchar(50) NOT NULL,
+  `auth` varchar(50) NOT NULL,
+  `time` int(11) NOT NULL,
+  `command` varchar(512) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `cid` (`cid`),
+  KEY `time` (`time`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `funcmd`
+--
+
+CREATE TABLE IF NOT EXISTS `funcmd` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `lang` int(11) NOT NULL,
+  `cmd` varchar(50) NOT NULL,
+  `text` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT='neonserv v3';
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `fundata`
+--
+
+CREATE TABLE IF NOT EXISTS `fundata` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `cid` int(11) NOT NULL,
+  `user` varchar(50) NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `value` text NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `cid` (`cid`),
+  KEY `user` (`user`),
+  KEY `name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='neonserv v3' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `godlog`
+--
+
+CREATE TABLE IF NOT EXISTS `godlog` (
+  `godlog_id` int(11) NOT NULL AUTO_INCREMENT,
+  `godlog_uid` int(11) NOT NULL,
+  `godlog_cid` int(15) NOT NULL,
+  `godlog_time` int(15) NOT NULL,
+  `godlog_cmd` varchar(512) NOT NULL,
+  PRIMARY KEY (`godlog_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `help`
+--
+
+CREATE TABLE IF NOT EXISTS `help` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `lang` varchar(6) NOT NULL,
+  `ident` varchar(64) NOT NULL,
+  `text` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `language`
+--
+
+CREATE TABLE IF NOT EXISTS `language` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `lang` varchar(5) NOT NULL,
+  `ident` varchar(64) NOT NULL,
+  `text` varchar(256) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `noinvite`
+--
+
+CREATE TABLE IF NOT EXISTS `noinvite` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `cid` int(11) NOT NULL,
+  `uid` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `owner_history`
+--
+
+
+CREATE TABLE IF NOT EXISTS `owner_history` (
+  `owner_history_id` int(11) NOT NULL AUTO_INCREMENT,
+  `owner_history_cid` int(11) NOT NULL,
+  `owner_history_to_uid` int(11) NOT NULL,
+  `owner_history_from_uid` int(11) NOT NULL,
+  `owner_history_time` int(11) NOT NULL,
+  PRIMARY KEY (`owner_history_id`),
+  KEY `owner_history_cid` (`owner_history_cid`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `users`
+--
+
+CREATE TABLE IF NOT EXISTS `users` (
+  `user_id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_user` varchar(250) NOT NULL,
+  `user_access` int(4) NOT NULL,
+  `user_rank` int(11) NOT NULL,
+  `user_god` tinyint(1) NOT NULL,
+  `user_lang` varchar(6) NOT NULL,
+  `user_reply_privmsg` tinyint(1) NOT NULL,
+  `user_block_invites` tinyint(1) NOT NULL,
+  `user_registered` INT(20) NOT NULL,
+  `user_lastcheck` INT(20) NOT NULL,
+  PRIMARY KEY (`user_id`),
+  UNIQUE KEY `user_user` (`user_user`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `support_ranks`
+--
+
+CREATE TABLE `support_ranks` (
+`rank_id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`rank_name` VARCHAR( 256 ) NOT NULL ,
+`rank_access` INT( 4 ) NOT NULL ,
+`rank_info` VARCHAR( 512 ) NOT NULL ,
+`rank_order` SMALLINT( 4 ) NOT NULL
+) ENGINE = MYISAM ;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `version`
+--
+
+CREATE TABLE IF NOT EXISTS `version` (
+  `database_version` int(5) NOT NULL,
+  PRIMARY KEY (`database_version`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `donotregister`
+--
+
+CREATE TABLE `donotregister` (
+`dnr_id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`dnr_target` VARCHAR( 256 ) NOT NULL ,
+`dnr_user` INT( 11 ) NOT NULL ,
+`dnr_timeout` INT( 20 ) NOT NULL ,
+`dnr_reason` TEXT NOT NULL ,
+UNIQUE (
+`dnr_target`
+)
+) ENGINE = MYISAM ;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `helpserv_requests`
+--
+
+CREATE TABLE IF NOT EXISTS `helpserv_requests` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `botid` INT(11) NOT NULL,
+  `host` varchar(200) NOT NULL,
+  `hand` varchar(50) NOT NULL,
+  `nick` varchar(50) NOT NULL,
+  `status` int(1) NOT NULL,
+  `supporter` int(11) NOT NULL,
+  `time` int(20) NOT NULL,
+  `delay` int(20) NOT NULL,
+  `text` text NOT NULL,
+  `log` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `helpserv_settings`
+--
+
+CREATE TABLE IF NOT EXISTS `helpserv_settings` (
+  `helpserv_botid` int(11) NOT NULL,
+  `helpserv_support` varchar(256) NOT NULL,
+  `helpserv_public` varchar(256) DEFAULT NULL,
+  `helpserv_intern` varchar(256) DEFAULT NULL,
+  `helpserv_intern_announce` TINYINT(1) NOT NULL,
+  PRIMARY KEY (`helpserv_botid`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `settings`
+--
+
+CREATE TABLE IF NOT EXISTS `settings` (
+  `name` varchar(100) NOT NULL,
+  `value` text NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `spamserv_badwords`
+--
+
+CREATE TABLE IF NOT EXISTS `spamserv_badwords` (
+  `badword_id` int(11) NOT NULL AUTO_INCREMENT,
+  `badword_cid` int(11) NOT NULL,
+  `badword_match` varchar(128) NOT NULL,
+  `badword_uid` int(11) NOT NULL,
+  `badword_use_default` tinyint(1) NOT NULL,
+  `badword_exceptlevel` smallint(3) NOT NULL,
+  `badword_scan_ops` tinyint(1) NOT NULL,
+  `badword_scan_voice` tinyint(1) NOT NULL,
+  `badword_use_default_reaction` tinyint(1) NOT NULL,
+  `badword_reaction` tinyint(1) NOT NULL,
+  `badword_reaction_time` int(20) NOT NULL,
+  PRIMARY KEY (`badword_id`),
+  KEY `badword_cid` (`badword_cid`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
diff --git a/database.upgrade.sql b/database.upgrade.sql
new file mode 100644 (file)
index 0000000..bb26d92
--- /dev/null
@@ -0,0 +1,188 @@
+-- Database upgrades (running all SQL commands below the matching "-- version: x" line)
+
+-- version: 1
+
+CREATE TABLE `support_ranks` (
+`rank_id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`rank_name` VARCHAR( 256 ) NOT NULL ,
+`rank_access` INT( 4 ) NOT NULL ,
+`rank_info` VARCHAR( 512 ) NOT NULL ,
+`rank_order` SMALLINT( 4 ) NOT NULL
+) ENGINE = MYISAM ;
+
+ALTER TABLE `users` ADD `user_rank` INT( 11 ) NOT NULL AFTER `user_access`;
+
+-- version: 2
+
+ALTER TABLE `owner_history` CHANGE `owner_history_uid` `owner_history_to_uid` INT( 11 ) NOT NULL;
+ALTER TABLE `owner_history` ADD `owner_history_from_uid` INT( 11 ) NOT NULL AFTER `owner_history_to_uid`;
+
+-- version: 3
+
+ALTER TABLE `channels`
+  DROP `channel_scanstate`,
+  DROP `channel_scanexcept`,
+  DROP `channel_maxrepeat`,
+  DROP `channel_repeatreaction`,
+  DROP `channel_maxflood`,
+  DROP `channel_floodtime`,
+  DROP `channel_floodreaction`,
+  DROP `channel_maxjoin`,
+  DROP `channel_jointime`,
+  DROP `channel_joinreaction`;
+
+ALTER TABLE `channels`  
+  ADD `channel_scanner` VARCHAR(50) NULL,  
+  ADD `channel_spam_limit` SMALLINT(3) NULL,  
+  ADD `channel_spam_reaction` TINYINT(1) NULL,  
+  ADD `channel_spam_reaction_duration` MEDIUMINT(7) NULL,  
+  ADD `channel_spam_except` SMALLINT(3) NULL,  
+  ADD `channel_flood_limit` SMALLINT(3) NULL,  
+  ADD `channel_flood_time` SMALLINT(3) NULL,  
+  ADD `channel_flood_reaction` TINYINT(1) NULL,  
+  ADD `channel_flood_reaction_duration` MEDIUMINT(7) NULL,  
+  ADD `channel_flood_except` SMALLINT(3) NULL,  
+  ADD `channel_join_limit` SMALLINT(3) NULL,  
+  ADD `channel_join_time` SMALLINT(3) NULL,  
+  ADD `channel_join_reaction` TINYINT(1) NULL,  
+  ADD `channel_join_reaction_duration` MEDIUMINT(7) NULL,  
+  ADD `channel_join_except` SMALLINT(3) NULL,  
+  ADD `channel_botnet_bantime` MEDIUMINT(7) NULL,  
+  ADD `channel_botnet_except` SMALLINT(3) NULL,  
+  ADD `channel_caps_percent` TINYINT(3) NULL,  
+  ADD `channel_caps_reaction` TINYINT(1) NULL,  
+  ADD `channel_caps_reaction_duration` MEDIUMINT(7) NULL,  
+  ADD `channel_caps_except` SMALLINT(3) NULL,  
+  ADD `channel_digit_percent` TINYINT(3) NULL,  
+  ADD `channel_digit_reaction` TINYINT(1) NULL,  
+  ADD `channel_digit_reaction_duration` MEDIUMINT(7) NULL,  
+  ADD `channel_digit_except` SMALLINT(3) NULL;
+  
+-- version: 4
+
+ALTER TABLE `bots` ADD `queue` TINYINT( 1 ) NOT NULL AFTER `textbot` 
+
+-- version: 5
+
+CREATE TABLE `donotregister` (
+`dnr_id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`dnr_target` VARCHAR( 256 ) NOT NULL ,
+`dnr_user` INT( 11 ) NOT NULL ,
+`dnr_timeout` INT( 20 ) NOT NULL ,
+`dnr_reason` TEXT NOT NULL ,
+UNIQUE (
+`dnr_target`
+)
+) ENGINE = MYISAM ;
+
+-- version: 6
+
+ALTER TABLE `fundata` CHANGE `uid` `user` VARCHAR( 50 ) NOT NULL;
+ALTER TABLE `fundata` ADD INDEX ( `cid` );
+ALTER TABLE `fundata` ADD INDEX ( `user` );
+ALTER TABLE `fundata` ADD INDEX ( `name` );
+
+-- version: 7
+
+ALTER TABLE `bot_binds` ADD `botid` INT( 11 ) NOT NULL AFTER `botclass`;
+ALTER TABLE `bot_binds` ADD INDEX ( `botclass` );
+
+-- version: 8
+
+ALTER TABLE `bot_channels` CHANGE `trigger` `trigger` VARCHAR( 50 ) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT '+';
+
+-- version: 9
+
+ALTER TABLE `bot_binds` CHANGE `flags` `flags` INT( 10 ) NOT NULL ;
+
+-- version: 10
+
+CREATE TABLE IF NOT EXISTS `helpserv_requests` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `host` varchar(200) NOT NULL,
+  `hand` varchar(50) NOT NULL,
+  `nick` varchar(50) NOT NULL,
+  `status` int(1) NOT NULL,
+  `supporter` int(11) NOT NULL,
+  `time` int(20) NOT NULL,
+  `text` text NOT NULL,
+  `log` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+CREATE TABLE IF NOT EXISTS `helpserv_settings` (
+  `helpserv_botid` int(11) NOT NULL,
+  `helpserv_support` varchar(256) NOT NULL,
+  `helpserv_public` varchar(256) DEFAULT NULL,
+  `helpserv_intern` varchar(256) DEFAULT NULL,
+  PRIMARY KEY (`helpserv_botid`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- version: 11
+
+ALTER TABLE `helpserv_requests` ADD `botid` INT( 11 ) NOT NULL AFTER `id`;
+
+-- version: 12
+
+ALTER TABLE `helpserv_settings` ADD `helpserv_intern_announce` TINYINT( 1 ) NOT NULL;
+
+-- version: 13
+
+ALTER TABLE `channels` ADD `channel_canhalfop` SMALLINT( 3 ) NULL AFTER `channel_canop`;
+ALTER TABLE `channels` ADD `channel_gethalfop` SMALLINT( 3 ) NULL AFTER `channel_getop`;
+UPDATE `channels` SET `channel_canhalfop` = '150',
+`channel_gethalfop` = '150' WHERE `channel_name` = 'defaults';
+
+-- version: 14
+
+ALTER TABLE `users` ADD `user_registered` INT( 20 ) NOT NULL ,
+ADD `user_lastcheck` INT( 20 ) NOT NULL;
+
+-- version: 15
+
+CREATE TABLE IF NOT EXISTS `settings` (
+  `name` varchar(100) NOT NULL,
+  `value` text NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- version: 16
+
+CREATE TABLE IF NOT EXISTS `spamserv_badwords` (
+  `badword_id` int(11) NOT NULL AUTO_INCREMENT,
+  `badword_cid` int(11) NOT NULL,
+  `badword_match` varchar(128) NOT NULL,
+  `badword_uid` int(11) NOT NULL,
+  `badword_use_default` tinyint(1) NOT NULL,
+  `badword_exceptlevel` smallint(3) NOT NULL,
+  `badword_scan_ops` tinyint(1) NOT NULL,
+  `badword_scan_voice` tinyint(1) NOT NULL,
+  `badword_use_default_reaction` tinyint(1) NOT NULL,
+  `badword_reaction` tinyint(1) NOT NULL,
+  `badword_reaction_time` int(20) NOT NULL,
+  PRIMARY KEY (`badword_id`),
+  KEY `badword_cid` (`badword_cid`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+ALTER TABLE `channels` ADD `channel_badword_reaction` TINYINT( 1 ) NULL ,
+ADD `channel_badword_reaction_duration` MEDIUMINT( 7 ) NULL AFTER `channel_badword_reaction` ,
+ADD `channel_badword_except` SMALLINT( 3 ) NULL AFTER `channel_badword_reaction_duration`;
+
+UPDATE `channels` SET `channel_badword_reaction` = '0',
+`channel_badword_reaction_duration` = '60',
+`channel_badword_except` = '400' WHERE `channel_name` = 'defaults';
+
+-- version: 17
+
+ALTER TABLE `bots` ADD `oper_user` VARCHAR( 50 ) NULL AFTER `automodes` ,
+ADD `oper_pass` VARCHAR( 50 ) NULL AFTER `oper_user`;
+
+-- version: 18
+
+ALTER TABLE `users` ADD `user_block_invites` TINYINT NOT NULL AFTER `user_reply_privmsg`;
+
+-- version: 19
+
+ALTER TABLE `bots` ADD `secret` TINYINT( 1 ) NOT NULL AFTER `queue`;
+
+-- version: 20
diff --git a/database.upgrade_v4_v5.sql b/database.upgrade_v4_v5.sql
new file mode 100644 (file)
index 0000000..3aee182
--- /dev/null
@@ -0,0 +1,76 @@
+-- Database of NeonServ V4 modifications for NeonServ V5
+
+ALTER TABLE `bots` CHANGE `botclass` `botclass` INT( 10 ) NOT NULL;
+
+ALTER TABLE `users` CHANGE `user_lang` `user_lang` VARCHAR( 6 ) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;
+ALTER TABLE `users` ADD `user_reply_privmsg` TINYINT( 1 ) NOT NULL ;
+
+CREATE TABLE IF NOT EXISTS `godlog` (
+  `godlog_id` int(11) NOT NULL AUTO_INCREMENT,
+  `godlog_uid` int(11) NOT NULL,
+  `godlog_cid` int(15) NOT NULL,
+  `godlog_time` int(15) NOT NULL,
+  `godlog_cmd` varchar(512) NOT NULL,
+  PRIMARY KEY (`godlog_id`)
+) ENGINE=MyISAM;
+
+ALTER TABLE `channels` ADD `channel_lastgiveowner` INT( 11 ) NOT NULL AFTER `channel_lastvisit`;
+
+CREATE TABLE IF NOT EXISTS `owner_history` (
+`owner_history_id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`owner_history_cid` INT( 11 ) NOT NULL ,
+`owner_history_uid` INT( 11 ) NOT NULL ,
+`owner_history_time` INT( 11 ) NOT NULL ,
+INDEX ( `owner_history_cid` )
+) ENGINE = MYISAM ;
+
+ALTER TABLE `chanusers` ADD INDEX ( `chanuser_cid` ) ;
+ALTER TABLE `chanusers` ADD INDEX ( `chanuser_uid` ) ;
+
+ALTER TABLE `bot_binds` ADD `chan_access` VARCHAR( 256 ) NULL DEFAULT NULL AFTER `parameters`;
+
+ALTER TABLE `bot_binds` CHANGE `global_access` `global_access` INT( 3 ) NULL;
+
+ALTER TABLE `bans` CHANGE `ban_owner` `ban_owner` INT( 11 ) NOT NULL;
+
+ALTER TABLE `channels` ADD `channel_exttopic` TINYINT( 1 ) NOT NULL AFTER `channel_topicmask` ,
+ADD `channel_exttopic_topic` VARCHAR( 512 ) NOT NULL AFTER `channel_exttopic`;
+
+ALTER TABLE `bots` ADD `max_channels` INT( 5 ) NOT NULL ;
+
+ALTER TABLE `bot_binds` CHANGE `botid` `botclass` INT( 11 ) NOT NULL;
+ALTER TABLE `bots` DROP `whoisbot` ;
+ALTER TABLE `bots` DROP `bindFrom` ;
+ALTER TABLE `bots` ADD `register_priority` INT( 2 ) NOT NULL;
+
+CREATE TABLE IF NOT EXISTS `help` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `lang` varchar(6) NOT NULL,
+  `ident` varchar(64) NOT NULL,
+  `text` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+CREATE TABLE IF NOT EXISTS `language` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `lang` varchar(5) NOT NULL,
+  `ident` varchar(64) NOT NULL,
+  `text` varchar(256) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
+
+ALTER TABLE `bot_channels` ADD `suspended` TINYINT( 1 ) NOT NULL;
+
+ALTER TABLE `users` ADD UNIQUE (`user_user`);
+
+ALTER TABLE `noinvite` ADD INDEX ( `cid`, `uid` );
+
+CREATE TABLE IF NOT EXISTS `version` (
+  `database_version` int(5) NOT NULL,
+  PRIMARY KEY (`database_version`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+INSERT INTO `version` (`database_version`) VALUES (1);
+
+INSERT INTO `channels` (`channel_name`, `channel_key`, `channel_maxusers`, `channel_lastvisit`, `channel_lastgiveowner`, `channel_pubcmd`, `channel_nodelete`, `channel_nogaccess`, `channel_canadd`, `channel_candel`, `channel_canclvl`, `channel_cankick`, `channel_canban`, `channel_staticban`, `channel_protect`, `channel_canop`, `channel_canvoice`, `channel_getop`, `channel_getvoice`, `channel_greeting`, `channel_usergreeting`, `channel_userinfo`, `channel_scanstate`, `channel_scanexcept`, `channel_maxrepeat`, `channel_repeatreaction`, `channel_maxflood`, `channel_floodtime`, `channel_floodreaction`, `channel_maxjoin`, `channel_jointime`, `channel_joinreaction`, `channel_dynlimit`, `channel_getinvite`, `channel_topicmask`, `channel_exttopic`, `channel_exttopic_topic`, `channel_defaulttopic`, `channel_wipeinfo`, `channel_modes`, `channel_enfmodes`, `channel_enftopic`, `channel_topicsnarf`, `channel_changetopic`, `channel_setters`, `channel_canresync`, `channel_cansuspend`, `channel_notice`, `channel_noticereaction`, `channel_ctcp`, `channel_ctcpreaction`, `channel_registered`, `channel_registrator`, `channel_toys`) VALUES
+('defaults', '', 0, 0, 0, 0, 0, 0, 300, 300, 300, 200, 200, 250, 0, 200, 200, 200, 100, '', '', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, '', 0, '', '', 300, '+', 400, 400, 501, 200, 400, 200, 300, 0, 0, 0, 0, 0, 0, 0);
diff --git a/language.php b/language.php
new file mode 100644 (file)
index 0000000..570b0d6
--- /dev/null
@@ -0,0 +1,152 @@
+#!/usr/bin/php
+<?php
+/* Language updater
+ * by pk910
+ * 2.11.11
+ */
+
+
+if(strtolower(PHP_OS) == "winnt") {
+ define(WIN32,true);
+} else {
+ define(WIN32,false);
+}
+
+$uplink = array();
+$uplink['host'] = 'dev.pk910.de';
+$uplink['user'] = 'neonserv_lang';
+$uplink['pass'] = 'LLcrzq28RN2GBqRP'; /* just ignore it :P  this user has only select access to the required 2 tables & 3 columns in them */
+$uplink['base'] = 'neonserv_dev';
+
+$local = array();
+
+function shell_color($fg,$bg,$attr=1) {
+    if(WIN32) return false;
+    echo sprintf("%c[%d;%d;%dm", 0x1B, $attr, $fg + 30, $bg + 40);
+}
+
+define("NORMAL",-30);
+define("BLACK",0);
+define("RED",1);
+define("GREEN",2);
+define("YELLOW",3);
+define("BLUE",4);
+define("PURPLE",5);
+define("CYAN",6);
+define("WHITE",7);
+
+$fp = fopen("php://stdin", "r");
+if(!$fp) {
+    die("ERROR: can not open stdin for reading.\n");
+}
+
+shell_color(BLUE,BLACK);
+echo"NeonServ v5 - Languagepack Updater\n";
+shell_color(CYAN,BLACK);
+echo"This tool will download the actual language pack from\n";
+echo"dev.pk910.de and copy it into your database.\n";
+echo"\n";
+shell_color(YELLOW,BLACK);
+echo "DATABASE INFORMATION\n";
+echo "Please enter your database login data.\n";
+shell_color(GREEN,BLACK);
+echo "Hostname [localhost]: ";
+shell_color(NORMAL,BLACK);
+$line = fgets($fp, 4096);
+$line = str_replace(array("\n", "\r"),array("", ""),$line);
+if($line == "") $line = "localhost";
+$local['host'] = $line;
+shell_color(GREEN,BLACK);
+echo "Username [neonserv]: ";
+shell_color(NORMAL,BLACK);
+$line = fgets($fp, 4096);
+$line = str_replace(array("\n", "\r"),array("", ""),$line);
+if($line == "") $line = "neonserv";
+$local['user'] = $line;
+shell_color(GREEN,BLACK);
+echo "Password []: ";
+shell_color(NORMAL,BLACK);
+$line = fgets($fp, 4096);
+$line = str_replace(array("\n", "\r"),array("", ""),$line);
+$local['pass'] = $line;
+shell_color(GREEN,BLACK);
+echo "Database [neonserv]: ";
+shell_color(NORMAL,BLACK);
+$line = fgets($fp, 4096);
+$line = str_replace(array("\n", "\r"),array("", ""),$line);
+if($line == "") $line = "neonserv";
+$local['base'] = $line;
+echo"\n\n";
+shell_color(YELLOW,BLACK);
+echo "Checking local MySQL... ";
+$local['conn'] = @mysql_connect($local['host'], $local['user'], $local['pass']);
+@mysql_select_db($local['base'], $local['conn']) OR $local['conn'] = NULL;
+if($local['conn']) {
+    shell_color(GREEN,BLACK);
+    echo"ok\n";
+} else {
+    shell_color(RED,BLACK);
+    echo"fail\n";
+    shell_color(NORMAL,NORMAL);
+    die();
+}
+shell_color(YELLOW,BLACK);
+echo "Checking remote MySQL... ";
+$uplink['conn'] = @mysql_connect($uplink['host'], $uplink['user'], $uplink['pass']);
+@mysql_select_db($uplink['base'], $uplink['conn']) OR $uplink['conn'] = NULL;
+if($uplink['conn']) {
+    shell_color(GREEN,BLACK);
+    echo"ok\n";
+} else {
+    shell_color(RED,BLACK);
+    echo"fail\n";
+    shell_color(NORMAL,NORMAL);
+    die();
+}
+shell_color(YELLOW,BLACK);
+echo "Copying `language` table... ";
+shell_color(RED,BLACK);
+$copied = 0;
+$updated = 0;
+$result = mysql_query("SELECT `lang`, `ident`, `text` FROM `language`", $uplink['conn']);
+while($row = mysql_fetch_array($result)) {
+    $result2 = mysql_query("SELECT `id`, `text` FROM `language` WHERE `lang` = '".mysql_real_escape_string($row['lang'])."' AND `ident` = '".mysql_real_escape_string($row['ident'])."'", $local['conn']);
+    if(mysql_num_rows($result2)) {
+        $row2 = mysql_fetch_array($result2);
+        if($row2['text'] != $row['text']) {
+            $updated++;
+            mysql_query("UPDATE `language` SET `text` = '".mysql_real_escape_string($row['text'])."' WHERE `id` = '".$row2['id']."'", $local['conn']);
+        }
+    } else {
+        $copied++;
+        mysql_query("INSERT INTO `language` (`lang`, `ident`, `text`) VALUES ('".mysql_real_escape_string($row['lang'])."', '".mysql_real_escape_string($row['ident'])."', '".mysql_real_escape_string($row['text'])."');", $local['conn']);
+    }
+}
+shell_color(GREEN,BLACK);
+echo"copied ".$copied." & updated ".$updated." entrys.\n";
+shell_color(YELLOW,BLACK);
+echo "Copying `help` table... ";
+shell_color(RED,BLACK);
+$copied = 0;
+$updated = 0;
+$result = mysql_query("SELECT `lang`, `ident`, `text` FROM `help`", $uplink['conn']);
+while($row = mysql_fetch_array($result)) {
+    $result2 = mysql_query("SELECT `id`, `text` FROM `help` WHERE `lang` = '".mysql_real_escape_string($row['lang'])."' AND `ident` = '".mysql_real_escape_string($row['ident'])."'", $local['conn']);
+    if(mysql_num_rows($result2)) {
+        $row2 = mysql_fetch_array($result2);
+        if($row2['text'] != $row['text']) {
+            $updated++;
+            mysql_query("UPDATE `help` SET `text` = '".mysql_real_escape_string($row['text'])."' WHERE `id` = '".$row2['id']."'", $local['conn']);
+        }
+    } else {
+        $copied++;
+        mysql_query("INSERT INTO `help` (`lang`, `ident`, `text`) VALUES ('".mysql_real_escape_string($row['lang'])."', '".mysql_real_escape_string($row['ident'])."', '".mysql_real_escape_string($row['text'])."');", $local['conn']);
+    }
+}
+shell_color(GREEN,BLACK);
+echo"copied ".$copied." & updated ".$updated." entrys.\n";
+echo"\n";
+echo"finished.";
+shell_color(NORMAL,NORMAL);
+echo"\n\n";
+?>
\ No newline at end of file
diff --git a/neonserv.example.conf b/neonserv.example.conf
new file mode 100644 (file)
index 0000000..b10eca7
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+  NeonServ example configuration
+*/
+
+"MySQL" {
+    "host" = "127.0.0.1";
+    "port" = 3306;
+    "user" = "neonserv";
+    "pass" = "password";
+    "base" = "neonserv";
+};
+"statistics" {
+    "enable" = 0;
+    "frequency" = 5; //minutes
+    "include_lusers" = 1; //general network statistics
+    "execute" = "php scripts/statistic.php";
+    /* parameters:
+     - visible users
+     - visible chanusers
+     - visible channels
+     - privmsg per minute
+     - commands per minute
+     - network users
+     - network channels
+    */
+};
+"General" {
+    "worker_threads" = 5; //threads
+    "alertchan" = ""; //internal channel for important notifications
+    "have_halfop" = 0;
+    //unregister NeonSpam channels together with NeonServ?
+    "sync_neonspam_unreg" = 1;
+    "CheckAuths" { //check all AuthNames in the database for existence (AuthServ querys)
+        "enabled" = 1;
+        "start_time" = 3; //24h format - 3 means 3am
+        "duration" = 180; //minutes - query till start_time + duration o'clock ;)
+        "interval" = 2; //seconds - query every x seconds
+        "min_unckecked" = 172800; //check auth only if unchecked for x seconds
+        //180 miutes, every 2 seconds: 5400 auth checks
+        "alertchan" = "#krypton.intern"; // notifications about deleted users
+    };
+    "UserBots" {
+        "OtherServ" {
+            "enabled" = 0; //enable, if you want to use ;)
+            "nick" = "OtherServ,OtherServ2"; //optional check (mask)
+            "sourcebot" = "NeonServ"; //send messages through a bot of this botclass
+            "opless_part" = "PRIVMSG %s :part %s"; //args: botnick, channel
+            "opless_join" = "PRIVMSG %s :join %s"; //args: botnick, channel
+        };
+    };
+};
+"QServer" { //QServer is an unused 
+    "enabled" = 0; //leave this disabled if you don't know what you're doing!
+    "port" = 7499;
+    "pass" = "blaa";
+};
+"logs" {
+    /*
+    module:section = target_type:target
+    possible sections:
+        "info","debug","raw","mysql","override","warning","error","fatal"
+    possible targets:
+        file:log.log
+        irc:#channel
+        std:out
+        std:err
+    */
+    "*:info,warning,error,fatal" = "file:neonserv.log";
+    // "*:override,error,fatal" = "irc:#neonserv";
+    "*:info" = "std:out";
+};
+"modules" {
+    "libglobalcmd" {
+        "enabled" = 1;
+        "protected" = 1; //may not be unloaded
+    };
+    "libDummyServ" {
+        "enabled" = 1;
+        "protected" = 0;
+    };
+    "libfuncmds" {
+        "enabled" = 1;
+        "protected" = 0;
+    };
+    "libNeonHelp" {
+        "enabled" = 1;
+        "protected" = 0;
+        //require users to be authenticated when opening a request
+        "need_auth" = 0;
+        //custom error message - if commented out "You need to be authenticated with AuthServ to use this command." will be used
+        //"need_auth_message" = "Sorry, we require you to be authenticated against AuthServ to use our Services. Please log in or register an account on http://xyz.de/register.php";
+    };
+    "libNeonServ" {
+        "enabled" = 1;
+        "protected" = 0;
+        // How often to look for channels that have expired?
+        "chan_expire_freq" = "3d";
+        // How long is a channel unvisited (by masters or above) before it can be expired?
+        "chan_expire_delay" = "30d";
+        // Automatically register a Backup bot with NeonServ
+        "auto_backup_register" = 0;
+        // Automatically unregister all Backup when NeonServ gets removed
+        "auto_backup_unregister" = 1;
+        // BackupServ channel setting
+        "channel_backup_setting" = 1;
+    };
+    "libNeonBackup" {
+        "enabled" = 1;
+        "protected" = 0;
+    };
+    "libNeonSpam" {
+        "enabled" = 1;
+        "protected" = 0;
+    };
+    "libstats" {
+        /* statistics module
+          this module doesn't extend the bot functions at all! 
+          It will simply send a small UDP packet to neonserv.krypton-bouncer.de
+          this packet is used to keep the statistics on the website up to date.
+          
+          If you don't want your bot to be listed on this site just disable this plugin
+          (entries will be removed after 6h on the website)
+        */
+        "enabled" = 1;
+        "protected" = 0;
+        "hide_networkname" = 0; //hide network name
+        "hide_botnick" = 0; //hide bot nick, ident, host
+        // "use_bot" = "BotNick"; //use this bot for the statistics
+        "hide_chancount" = 0; //hide joined channel count
+        "hide_usercount" = 0; //hide number of users the bot can see
+    };
+};
+
diff --git a/scripts/example.php.txt b/scripts/example.php.txt
new file mode 100644 (file)
index 0000000..090b3f4
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+//example script
+//bind it with:
+//  +bind example extscript php scripts/example.php.txt $n $1 $2 $3 %1+
+
+echo "Hello ".$argv[1].", this is an external PHP Script :)\n";
+echo "Your parameters:\n";
+for($i = 2; $i < count($argv); $i++) {
+    echo"  ".$argv[$i];
+}
+?>
\ No newline at end of file
diff --git a/scripts/examples/calc.php b/scripts/examples/calc.php
new file mode 100644 (file)
index 0000000..b907bc5
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/* calc.php - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+$text = implode(" ", array_slice($argv, 1));
+
+$descriptor = array(0 => array("pipe", "r"),1 => array("pipe", "w"),2 => array("pipe", "w"));
+$proc = proc_open('bc -l -q', $descriptor, $pipes);
+
+if(!is_resource($proc)) {
+    echo"internal calc error - please contact an administrator.\n";
+    die();
+}
+
+fwrite($pipes[0], $text."\nquit\n");
+
+$start = time();
+
+$read=array($pipes[1]);
+$write=NULL;
+$except=NULL;
+
+while(1) {
+    $data = proc_get_status($proc);
+    if(!$data['running']) {
+        $out="";
+        while(!feof($pipes[1])) { 
+            $out .= fgets($pipes[1], 128); 
+        }
+        if($out == "") {
+            $out="\0034";
+            while(!feof($pipes[2])) { 
+                $out .= fgets($pipes[2], 128); 
+            }
+            $out.="";
+        }
+        $out=str_replace("\n","",$out);
+        $out=str_replace("\r","",$out);
+        $out=str_replace("\\","",$out);
+        
+        if(strlen($text) > 20) {
+            if(strlen($out) > 450) {
+                echo "output too long (".strlen($out).")\n";
+            } else {
+                echo "".$text."\n";
+                echo $out."\n";
+            }
+        } else {
+            $fout="".$text." = ".$out;
+            if(strlen($fout) > 450) {
+                echo "output too long (".strlen($out).")";
+            } else {
+                echo $fout."\n";
+            }
+        }
+        
+        fclose($pipes[0]);
+        fclose($pipes[1]);
+        fclose($pipes[2]);
+        proc_close($proc);
+        
+        die();
+    } else {
+        
+        if($start+3 > time()) {
+            usleep(200000); //200ms
+        } else {
+            //TIMEOUT
+            fclose($pipes[0]);
+            fclose($pipes[1]);
+            fclose($pipes[2]);
+            //can't simply use proc_terminate: https://bugs.php.net/bug.php?id=39992
+            $ppid = $data['pid'];
+            //use ps to get all the children of this process, and kill them
+            $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid $ppid`);
+            foreach($pids as $pid) {
+                if(is_numeric($pid)) {
+                    posix_kill($pid, 9); //SIGKILL signal
+                }
+            }
+            proc_close($proc); 
+            echo "calculator timeout. (maximum of 3 seconds exceeded)\n";
+            die();
+        }
+    }
+}
+
+?>
diff --git a/scripts/examples/dnslookup.php b/scripts/examples/dnslookup.php
new file mode 100644 (file)
index 0000000..84ad4bc
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+/* dnslookup.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+function time2str($time, $anz = 9) {
+    $str="";
+    if(!$anz) $anz=9;
+    if($time>=(60*60*24*365) && $anz > 0) {
+        $anz--;
+        $years=floor($time/(60*60*24*365));
+        $str.=$years."Y";
+        $time=$time-((60*60*24*365)*$years);
+    }
+    if($time>=(60*60*24*30) && $anz > 0) {
+        $anz--;
+        $months=floor($time/(60*60*24*30));
+        if($str != "") $str.=" ";
+        $str.=$months."M";
+        $time=$time-((60*60*24*30)*$months);
+    }
+    if($time>=(60*60*24) && $anz > 0) {
+        $anz--;
+        $days=floor($time/(60*60*24));
+        if($str != "") $str.=" ";
+        $str.=$days."d";
+        $time=$time-((60*60*24)*$days);
+    }
+    if($time>=(60*60) && $anz > 0) {
+        $anz--;
+        $stunden=floor($time/(60*60));
+        if($str != "") $str.=" ";
+        $str.=$stunden."h";
+        $time=$time-((60*60)*$stunden);
+    }
+    if($time>=(60) && $anz > 0) {
+        $anz--;
+        $min=floor($time/(60));
+        if($str != "") $str.=" ";
+        $str.=$min."m";
+        $time=$time-((60)*$min);
+    }
+    if(($time>1 || $str == "") && $anz > 0){
+        $anz--;
+        if($str != "") $str.=" ";
+        $str.=$time."s";
+    }
+    return $str;
+}
+
+
+$host = $argv[1];
+$show_record = strtoupper($argv[2]);
+
+$pattern_ipv6 = '/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))(|\/[0-9]{1,3})$/';
+$pattern_ipv4 = '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(|\/[0-9]{1,2})$/';
+
+$record_order = array("SOA", "NS", "A", "AAAA", "MX");
+
+function show_subrecords($host) {
+    $dns = dns_get_record($host, DNS_ALL);
+    if(count($dns)) {
+        usort($dns, "sort_records");
+        foreach($dns as $record) {
+            switch($record['type']) {
+                case "A":
+                    echo"   \002A     \002 ".$record['ip'].($record['ttl'] < 86400 ? "  (ttl: ".time2str($record['ttl'],2).")" : "")."\n";
+                    break;
+                case "AAAA":
+                    echo"   \002AAAA  \002 ".$record['ipv6'].($record['ttl'] < 86400 ? "  (ttl: ".time2str($record['ttl'],2).")" : "")."\n";
+                    break;
+                case "CNAME":
+                    echo"   \002CNAME \002 ".$record['target']."\n";
+                    break;
+            }
+        }
+    }
+}
+
+function sort_records($a, $b) {
+    global $record_order;
+    $index_a = array_search($a['type'], $record_order);
+    if($index_a === FALSE) $index_a = count($record_order);
+    $index_b = array_search($b['type'], $record_order);
+    if($index_b === FALSE) $index_b = count($record_order);
+    $order = $index_a - $index_b;
+    if($order == 0) {
+        switch($record['type']) {
+        case "A":
+            $suborder = "ip";
+            break;
+        case "AAAA":
+            $suborder = "ipv6";
+            break;
+        case "TXT":
+            $suborder = "txt";
+            break;
+        default:
+            $suborder = "target";
+            break;
+        }
+        $order = strcmp($a[$suborder], $b[$suborder]);
+    }
+    return $order;
+}
+
+if(strlen($host) && preg_match("#^((([a-z0-9-.]*)\.|)([a-z]{2,5})|).?$#i", $host)) {
+    $dns = dns_get_record($host, DNS_ALL);
+    echo"DNS Records for \002".$host."\002:";
+    if($show_record != "" && $show_record != "*" && $show_record != "ANY")
+        echo" (".$show_record.")";
+    echo"\n";
+    if(count($dns)) {
+        usort($dns, "sort_records");
+        foreach($dns as $record) {
+            if($show_record != "" && $show_record != "*" && $show_record != "ANY" && $show_record != $record['type'])
+                continue;
+            switch($record['type']) {
+                case "A":
+                    echo" \002A     \002 ".$record['ip'].($record['ttl'] < 86400 ? "  (ttl: ".time2str($record['ttl'],2).")" : "")."\n";
+                    break;
+                case "AAAA":
+                    echo" \002AAAA  \002 ".$record['ipv6'].($record['ttl'] < 86400 ? "  (ttl: ".time2str($record['ttl'],2).")" : "")."\n";
+                    break;
+                case "MX":
+                    echo" \002MX    \002 ".$record['target']." (priority: ".$record['pri'].")\n";
+                    if($show_record == "MX") {
+                        show_subrecords($record['target']);
+                    }
+                    break;
+                case "NS":
+                    echo" \002NS    \002 ".$record['target']."\n";
+                    if($show_record == "NS") {
+                        show_subrecords($record['target']);
+                    }
+                    break;
+                case "CNAME":
+                    echo" \002CNAME \002 ".$record['target']."\n";
+                    break;
+                case "TXT":
+                    echo" \002TXT   \002 ".$record['txt']."\n";
+                    break;
+                case "SOA":
+                    if($show_record != "SOA") {
+                        echo" \002SOA   \002 (see: dns ".$host." SOA)\n";
+                        break;
+                    }
+                    echo" \002SOA   \002 (Start of Authority):\n";
+                    echo"    name:     ".$record['mname']."\n";
+                    echo"    serial:   ".$record['serial']."\n";
+                    echo"    refresh:  ".$record['refresh']." (".time2str($record['refresh'], 2).")\n";
+                    echo"    retry:    ".$record['retry']." (".time2str($record['retry'], 2).")\n";
+                    echo"    expire:   ".$record['expire']." (".time2str($record['expire'], 2).")\n";
+                    echo"    TTL:      ".$record['minimum-ttl']." (".time2str($record['minimum-ttl'], 2).")\n";
+                    break;
+            }
+        }
+    } else
+        echo"No records found.\n";
+} else if(preg_match($pattern_ipv4, $host) || preg_match($pattern_ipv6, $host)) {
+    $hostname = gethostbyaddr($host);
+    echo"Reverse Lookup for \002".$host."\002:\n";
+    echo " \002PTR  \002 ".$hostname."\n";
+} else {
+    echo"Invalid Hostname or IP-Address.\n";
+}
+?>
diff --git a/src/BanNode.c b/src/BanNode.c
new file mode 100644 (file)
index 0000000..df3e38e
--- /dev/null
@@ -0,0 +1,93 @@
+/* BanNode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "BanNode.h"
+#include "ChanNode.h"
+#include "tools.h"
+
+struct BanNode* addChannelBan(struct ChanNode *chan, char *mask) {
+    struct BanNode *ban = malloc(sizeof(*ban));
+    ban->chan = chan;
+    ban->mask = strdup(mask);
+    SYNCHRONIZE(cache_sync);
+    ban->next = chan->bans;
+    chan->bans = ban;
+    DESYNCHRONIZE(cache_sync);
+    return ban;
+}
+
+struct BanNode* getMatchingChannelBan(struct ChanNode *chan, char *mask) {
+    SYNCHRONIZE(cache_sync);
+    struct BanNode *cban;
+    for(cban = chan->bans; cban; cban = cban->next) {
+        if(!match(cban->mask, mask)) {
+            DESYNCHRONIZE(cache_sync);
+            return cban;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+void removeChannelBanMask(struct ChanNode *chan, char *mask) {
+    SYNCHRONIZE(cache_sync);
+    struct BanNode *cban, *last = NULL;
+    for(cban = chan->bans; cban; cban = cban->next) {
+        if(!strcmp(cban->mask, mask)) {
+            if(last)
+                last->next = cban->next;
+            else
+                chan->bans = cban->next;
+            free(cban->mask);
+            free(cban);
+            break;
+        } else 
+            last = cban;
+    }
+    DESYNCHRONIZE(cache_sync);
+}
+
+void removeChannelBan(struct BanNode *ban) {
+    SYNCHRONIZE(cache_sync);
+    struct BanNode *cban, *last = NULL;
+    struct ChanNode *chan = ban->chan;
+    for(cban = chan->bans; cban; cban = cban->next) {
+        if(cban == ban) {
+            if(last)
+                last->next = ban->next;
+            else
+                chan->bans = ban->next;
+            free(ban->mask);
+            free(ban);
+            break;
+        } else 
+            last = cban;
+    }
+    DESYNCHRONIZE(cache_sync);
+}
+
+void removeChannelBans(struct ChanNode *chan) {
+    SYNCHRONIZE(cache_sync);
+    struct BanNode *ban, *next;
+    for(ban = chan->bans; ban; ban = next) {
+        next = ban->next;
+        free(ban->mask);
+        free(ban);
+    }
+    chan->bans = NULL;
+    DESYNCHRONIZE(cache_sync);
+}
diff --git a/src/BanNode.h b/src/BanNode.h
new file mode 100644 (file)
index 0000000..8fa96fd
--- /dev/null
@@ -0,0 +1,38 @@
+/* BanNode.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _BanNode_h
+#define _BanNode_h
+#include "main.h"
+
+struct ChanNode;
+
+struct BanNode {
+    char *mask;
+    struct ChanNode *chan;
+    
+    struct BanNode *next;
+};
+
+#ifndef DND_FUNCTIONS
+struct BanNode* addChannelBan(struct ChanNode *chan, char *mask);
+/* MODULAR ACCESSIBLE */ struct BanNode* getMatchingChannelBan(struct ChanNode *chan, char *mask);
+void removeChannelBanMask(struct ChanNode *chan, char *mask);
+void removeChannelBan(struct BanNode *ban);
+void removeChannelBans(struct ChanNode *chan);
+#endif
+
+#endif
\ No newline at end of file
diff --git a/src/ChanNode.c b/src/ChanNode.c
new file mode 100644 (file)
index 0000000..44a335f
--- /dev/null
@@ -0,0 +1,260 @@
+/* ChanNode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "UserNode.h"
+#include "BanNode.h"
+#include "modcmd.h"
+#include "ModeNode.h"
+#include "IRCEvents.h"
+#include "tools.h"
+#include "log.h"
+
+static struct ChanNode **chanList;
+
+void init_ChanNode() {
+    /*
+     len pos chars 
+     26  0   a-z
+     10  26  0-9
+     10  36  {|}~[\]^_`
+     1   46  *everything else*
+     ---------------------------
+     = 47
+    */
+    #define CHANNEL_LIST_SIZE 47
+    chanList = calloc(CHANNEL_LIST_SIZE, sizeof(*chanList));
+}
+
+void free_ChanNode() {
+    SYNCHRONIZE(cache_sync);
+    //kamikaze free all channels and chanusers
+    int i;
+    struct ChanNode *chan, *next;
+    struct ChanUser *chanuser, *next_chanuser;
+    for(i = 0; i < CHANNEL_LIST_SIZE; i++) {
+        for(chan = chanList[i]; chan; chan = next) {
+            next = chan->next;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = next_chanuser) {
+                next_chanuser = getChannelUsers(chan, chanuser);
+                freeChanUser(chanuser);
+            }
+            freeChanNode(chan);
+        }
+    }
+    free(chanList);
+    DESYNCHRONIZE(cache_sync);
+}
+
+int is_valid_chan(const char *name) {
+    unsigned int ii;
+    if (*name !='#')
+        return 0;
+    for (ii=1; name[ii]; ++ii) {
+        if ((name[ii] > 0) && (name[ii] <= 32))
+            return 0;
+        if (name[ii] == ',')
+            return 0;
+        if (name[ii] == '\xa0')
+            return 0;
+    }
+    return 1;
+}
+
+static int get_chanlist_entry(int name) {
+    if((name > 0 && name <= 32) || name == ',' || name == '\xa0') return -1; //invalid name
+    if(tolower(name) >= 97 && tolower(name) <= 122) {
+        return (tolower(name) - 97);
+    }
+    if(tolower(name) >= 48 && tolower(name) <= 57) {
+        return (tolower(name) - 48 + 26);
+    }
+    /* {|}~[\]^_` */
+    if(name == '{') return 36;
+    if(name == '|') return 37;
+    if(name == '}') return 38;
+    if(name == '~') return 39;
+    if(name == '[') return 40;
+    if(name == '\\') return 41;
+    if(name == ']') return 42;
+    if(name == '^') return 43;
+    if(name == '_') return 44;
+    if(name == '`') return 45;
+    return 46;
+}
+
+struct ChanNode* getAllChans(struct ChanNode *last) {
+    if(last == NULL || last->next == NULL) {
+        int cindex;
+        if(last == NULL)
+            cindex = 0;
+        else
+            cindex = get_chanlist_entry(last->name[1]) + 1;
+        while(chanList[cindex] == NULL && cindex < CHANNEL_LIST_SIZE)
+            cindex++;
+        if(cindex >= CHANNEL_LIST_SIZE) return NULL;
+        return chanList[cindex];
+    } else {
+        return last->next;
+    }
+}
+
+struct ChanNode* getChanByName(const char *name) { //case insensitive
+    int chanListIndex = get_chanlist_entry(name[1]);
+    if(chanListIndex == -1 || chanList[chanListIndex] == NULL)
+        return NULL;
+    struct ChanNode *chan;
+    for(chan = chanList[chanListIndex]; chan; chan = chan->next) {
+        if(!stricmp(name, chan->name))
+            return chan;
+    }
+    return NULL;
+}
+
+struct ChanNode* addChannel(const char *name) {
+    int chanListIndex = get_chanlist_entry(name[1]);
+    if(chanListIndex == -1 || !is_valid_chan(name))
+        return NULL;
+    struct ChanNode *chan = malloc(sizeof(*chan));
+    if (!chan)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    strcpy(chan->name, name);
+    chan->user = NULL;
+    chan->bans = NULL;
+    chan->spam_settings = NULL;
+    chan->usercount = 0;
+    chan->botcount = 0;
+    chan->topic[0] = 0;
+    chan->flags = 0;
+    /* mode lists */
+    chan->modes = createModeNode(chan);
+    chan->trigger = NULL;
+    
+    SYNCHRONIZE(cache_sync);
+    chan->next = chanList[chanListIndex];
+    chanList[chanListIndex] = chan;
+    DESYNCHRONIZE(cache_sync);
+    return chan;
+}
+
+int getChannelCount() {
+    int i, count = 0;
+    struct ChanNode *chan;
+    for(i = 0; i < CHANNEL_LIST_SIZE; i++) {
+        for(chan = chanList[i]; chan; chan = chan->next) {
+            count++;
+        }
+    }
+    return count;
+}
+
+int getChanUserCount() {
+    int i, count = 0;
+    struct ChanNode *chan;
+    for(i = 0; i < CHANNEL_LIST_SIZE; i++) {
+        for(chan = chanList[i]; chan; chan = chan->next) {
+            count += chan->usercount;
+        }
+    }
+    return count;
+}
+
+int getChanBanCount() {
+    int i, count = 0;
+    struct ChanNode *chan;
+    struct BanNode *ban;
+    for(i = 0; i < CHANNEL_LIST_SIZE; i++) {
+        for(chan = chanList[i]; chan; chan = chan->next) {
+            for(ban = chan->bans; ban; ban = ban->next)
+                count ++;
+        }
+    }
+    return count;
+}
+
+void delChannel(struct ChanNode* chan, int freeChan) {
+    int chanListIndex = get_chanlist_entry(chan->name[1]);
+    if(chanListIndex == -1) return;
+    SYNCHRONIZE(cache_sync);
+    struct ChanNode *cchan, *last_chan = NULL;
+    for(cchan = chanList[chanListIndex]; cchan; cchan = cchan->next) {
+        if(cchan == chan) {
+            if(last_chan)
+                last_chan->next = chan->next;
+            else
+                chanList[chanListIndex] = chan->next;
+            break;
+        } else
+            last_chan = cchan;
+    }
+    if(chan->user) {
+        //free all chanusers
+        struct ChanUser *chanuser, *next;
+        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = next) {
+            next = getChannelUsers(chan, chanuser);
+            removeChanUserFromLists(chanuser, 0, 1, 1);
+        }
+    }
+    if(freeChan)
+        freeChanNode(chan);
+    else
+        chan->next = NULL;
+    DESYNCHRONIZE(cache_sync);
+}
+
+void freeChanNode(struct ChanNode* chan) {
+    event_freechan(chan);
+    if(chan->trigger) {
+        struct trigger_cache *trigger, *next_trigger;
+        for(trigger = chan->trigger; trigger; trigger = next_trigger) {
+            next_trigger = trigger->next;
+            free(trigger->trigger);
+            free(trigger);
+        }
+    }
+    freeModeNode(chan->modes);
+    if(chan->bans)
+        removeChannelBans(chan);
+    free(chan);
+}
+
+int checkChannelVisibility(struct ChanNode* chan) {
+    struct ChanUser *chanuser, *next;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(chanuser->user->flags & USERFLAG_ISBOT)
+            return 1;
+    }
+    //free the channel...
+    SYNCHRONIZE(cache_sync);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = next) {
+        next = getChannelUsers(chan, chanuser);
+        //remove the channel from the user's channel-list
+        removeChanUserFromLists(chanuser, 0, 1, 0);
+        if(!chanuser->user->channel) {
+            //free the user (no more channels)
+            delUser(chanuser->user, 1);
+        }
+        freeChanUser(chanuser);
+    }
+    chan->user = NULL;
+    delChannel(chan, 1);
+    DESYNCHRONIZE(cache_sync);
+    return 0;
+}
diff --git a/src/ChanNode.h b/src/ChanNode.h
new file mode 100644 (file)
index 0000000..fe3a4de
--- /dev/null
@@ -0,0 +1,70 @@
+/* ChanNode.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _ChanNode_h
+#define _ChanNode_h
+#include "main.h"
+
+struct ChanUser;
+struct trigger_cache;
+struct ModeNode;
+struct NeonSpamSettings;
+struct timeq_entry;
+
+#define CHANFLAG_RECEIVED_USERLIST  0x01
+#define CHANFLAG_REQUESTED_CHANINFO 0x02
+#define CHANFLAG_CHAN_REGISTERED    0x04
+#define CHANFLAG_HAVE_INVISIBLES    0x08
+#define CHANFLAG_REJOINING          0x10
+
+#define CHANFLAG_SCRIPTFLAG1        0x80 /* used by neonserv_cmd_search */
+
+struct ChanNode {
+    char name[CHANNELLEN+1];
+    char topic[TOPICLEN+1];
+    struct ChanUser *user;
+    unsigned int usercount;
+    unsigned int botcount;
+    unsigned char flags;
+    struct ModeNode *modes;
+    struct BanNode *bans;
+    
+    struct trigger_cache *trigger;
+    int channel_id;
+    
+    struct NeonSpamSettings *spam_settings;
+       
+    void *rejoin_bots;
+    struct timeq_entry *rejoin_timeout;
+    
+    struct ChanNode *next;
+};
+
+#ifndef DND_FUNCTIONS
+void init_ChanNode();
+void free_ChanNode();
+/* MODULAR ACCESSIBLE */ int is_valid_chan(const char *name);
+/* MODULAR ACCESSIBLE */ struct ChanNode* getAllChans(struct ChanNode *last);
+/* MODULAR ACCESSIBLE */ struct ChanNode* getChanByName(const char *name);
+struct ChanNode* addChannel(const char *chan);
+/* MODULAR ACCESSIBLE */ int getChannelCount();
+/* MODULAR ACCESSIBLE */ int getChanUserCount();
+/* MODULAR ACCESSIBLE */ int getChanBanCount();
+void delChannel(struct ChanNode* chan, int freeChan);
+void freeChanNode(struct ChanNode* chan);
+int checkChannelVisibility(struct ChanNode* chan);
+#endif
+#endif
\ No newline at end of file
diff --git a/src/ChanUser.c b/src/ChanUser.c
new file mode 100644 (file)
index 0000000..e18ce4d
--- /dev/null
@@ -0,0 +1,208 @@
+/* ChanUser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "ChanUser.h"
+#include "ChanNode.h"
+#include "ModeNode.h"
+#include "UserNode.h"
+#include "log.h"
+
+struct ChanUser* addChanUser(struct ChanNode *chan, struct UserNode *user) {
+    struct ChanUser *chanuser = malloc(sizeof(*chanuser));
+    if (!chanuser)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    chanuser->flags = 0;
+    chanuser->user = user;
+    chanuser->chan = chan;
+    chanuser->visCount = 0;
+    
+    chanuser->changeTime = 0;
+    chanuser->spamnode = NULL;
+
+    SYNCHRONIZE(cache_sync);
+
+    chanuser->next_user = chan->user;
+    chan->user = chanuser;
+    chan->usercount++;
+
+    chanuser->next_chan = user->channel;
+    user->channel = chanuser;
+
+    DESYNCHRONIZE(cache_sync);
+
+    return chanuser;
+}
+
+struct ChanUser* addInvisibleChanUser(struct ChanNode *chan, struct UserNode *user) {
+    struct ChanUser *chanuser = malloc(sizeof(*chanuser));
+    if (!chanuser)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    chanuser->flags = CHANUSERFLAG_INVISIBLE;
+    chanuser->user = user;
+    chanuser->chan = chan;
+    chanuser->visCount = 0;
+    
+    chanuser->changeTime = 0;
+    chanuser->spamnode = NULL;
+
+    SYNCHRONIZE(cache_sync);
+    chanuser->next_user = chan->user;
+    chan->user = chanuser;
+    DESYNCHRONIZE(cache_sync);
+    chan->usercount++;
+
+    return chanuser;
+}
+
+int isUserOnChan(struct UserNode *user, struct ChanNode *chan) {
+    struct ChanUser *chanuser;
+    if(isModeSet(chan->modes, 'd') || isModeSet(chan->modes, 'D')) {
+        for(chanuser = chan->user; chanuser; chanuser = chanuser->next_user) {
+            if(chanuser->user == user)
+                return 1;
+        }
+    } else {
+        for(chanuser = user->channel; chanuser; chanuser = chanuser->next_chan) {
+            if(chanuser->chan == chan)
+                return 1;
+        }
+    }
+    return 0;
+}
+
+struct ChanUser* getChanUser(struct UserNode *user, struct ChanNode *chan) {
+    struct ChanUser *chanuser;
+    if(isModeSet(chan->modes, 'd') || isModeSet(chan->modes, 'D')) {
+        for(chanuser = chan->user; chanuser; chanuser = chanuser->next_user) {
+            if(chanuser->user == user)
+                return chanuser;
+        }
+    } else {
+        for(chanuser = user->channel; chanuser; chanuser = chanuser->next_chan) {
+            if(chanuser->chan == chan)
+                return chanuser;
+        }
+    }
+    return NULL;
+}
+
+struct ChanUser* getChannelUsers(struct ChanNode *chan, struct ChanUser *last) {
+    if(last == NULL)
+        return chan->user;
+    else
+        return last->next_user;
+}
+
+struct ChanUser* getUserChannels(struct UserNode *user, struct ChanUser *last) {
+    if(last == NULL)
+        return user->channel;
+    else
+        return last->next_chan;
+}
+
+void delChanUser(struct ChanUser *chanuser, int do_freeChanUser) {
+    SYNCHRONIZE(cache_sync);
+    struct ChanUser *cchanuser, *last;
+    //remove it from the user's channel-list
+    last = NULL;
+    for(cchanuser = chanuser->user->channel; cchanuser; cchanuser = cchanuser->next_chan) {
+        if(cchanuser == chanuser) {
+            if(last) 
+                last->next_chan = chanuser->next_chan;
+            else
+                chanuser->user->channel = chanuser->next_chan;
+            break;
+        } else
+            last = cchanuser;
+    }
+
+    //remove it from the channel's user-list
+    last = NULL;
+    for(cchanuser = chanuser->chan->user; cchanuser; cchanuser = cchanuser->next_user) {
+        if(cchanuser == chanuser) {
+            chanuser->chan->usercount--;
+            if(last) 
+                last->next_user = chanuser->next_user;
+            else
+                chanuser->chan->user = chanuser->next_user;
+            break;
+        } else
+            last = cchanuser;
+    }
+    
+    if(do_freeChanUser) {
+        freeChanUser(chanuser);
+    } else {
+        chanuser->next_chan = NULL;
+        chanuser->next_user = NULL;
+    }
+    DESYNCHRONIZE(cache_sync);
+}
+
+void removeChanUserFromLists(struct ChanUser *chanuser, int remove_from_userlist, int remove_from_channellist, int do_freeChanUser) {
+    SYNCHRONIZE(cache_sync);
+    struct ChanUser *cchanuser, *last;
+    if(remove_from_userlist) {
+        //remove it from the channel's user-list
+        last = NULL;
+        for(cchanuser = chanuser->chan->user; cchanuser; cchanuser = cchanuser->next_user) {
+            if(cchanuser == chanuser) {
+                chanuser->chan->usercount--;
+                if(last) 
+                    last->next_user = chanuser->next_user;
+                else
+                    chanuser->chan->user = chanuser->next_user;
+                break;
+            } else
+                last = cchanuser;
+        }
+        chanuser->next_user = NULL;
+    }
+    if(remove_from_channellist) {
+        //remove it from the user's channel-list
+        last = NULL;
+        for(cchanuser = chanuser->user->channel; cchanuser; cchanuser = cchanuser->next_chan) {
+            if(cchanuser == chanuser) {
+                if(last) 
+                    last->next_chan = chanuser->next_chan;
+                else
+                    chanuser->user->channel = chanuser->next_chan;
+                break;
+            } else
+                last = cchanuser;
+        }
+        chanuser->next_chan = NULL;
+    }
+    
+    if(do_freeChanUser) {
+        freeChanUser(chanuser);
+    }
+    DESYNCHRONIZE(cache_sync);
+}
+
+void freeChanUser(struct ChanUser *chanuser) {
+    if(chanuser->spamnode)
+        free(chanuser->spamnode);
+    free(chanuser);
+}
+
diff --git a/src/ChanUser.h b/src/ChanUser.h
new file mode 100644 (file)
index 0000000..62d8361
--- /dev/null
@@ -0,0 +1,61 @@
+/* ChanUser.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _ChanUser_h
+#define _ChanUser_h
+#include "main.h"
+
+#define CHANUSERFLAG_OPPED     0x01
+#define CHANUSERFLAG_VOICED    0x02
+#define CHANUSERFLAG_INVISIBLE 0x04
+#define CHANUSERFLAG_HALFOPPED 0x08
+#define CHANUSERFLAG_PARTING   0x10
+
+#define CHANUSERFLAG_OPPED_OR_VOICED (CHANUSERFLAG_OPPED | CHANUSERFLAG_HALFOPPED | CHANUSERFLAG_VOICED)
+
+struct ChanNode;
+struct UserNode;
+struct NeonSpamNode;
+
+struct ChanUser {
+    unsigned char flags;
+    struct ChanNode *chan;
+    struct UserNode *user;
+    
+    int visCount : 8; //visible counter - if this is 0, the ChanUser gets removed
+    int old_visCount : 8;
+    
+    int chageEvents;
+    time_t changeTime;
+    
+    struct NeonSpamNode *spamnode;
+    
+    struct ChanUser *next_user;
+    struct ChanUser *next_chan;
+};
+
+#ifndef DND_FUNCTIONS
+struct ChanUser* addChanUser(struct ChanNode *chan, struct UserNode *user);
+struct ChanUser* addInvisibleChanUser(struct ChanNode *chan, struct UserNode *user);
+/* MODULAR ACCESSIBLE */ int isUserOnChan(struct UserNode *user, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ struct ChanUser* getChanUser(struct UserNode *user, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ struct ChanUser* getChannelUsers(struct ChanNode *chan, struct ChanUser *last);
+/* MODULAR ACCESSIBLE */ struct ChanUser* getUserChannels(struct UserNode *user, struct ChanUser *last);
+void delChanUser(struct ChanUser *chanuser, int freeChanUser);
+void removeChanUserFromLists(struct ChanUser *chanuser, int remove_from_userlist, int remove_from_channellist, int freeChanUser);
+void freeChanUser(struct ChanUser *chanuser);
+#endif
+#endif
diff --git a/src/ClientSocket.c b/src/ClientSocket.c
new file mode 100644 (file)
index 0000000..a624a30
--- /dev/null
@@ -0,0 +1,313 @@
+/* ClientSocket.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "main.h"
+#include "ClientSocket.h"
+#include "IRCParser.h"
+#include "UserNode.h"
+#include "IRCQueue.h"
+#include "WHOHandler.h"
+#include "HandleInfoHandler.h"
+#include "ConfigParser.h"
+#include "version.h"
+#include "IOHandler.h"
+#include "IRCEvents.h"
+#include "log.h"
+
+struct socket_list {
+    struct ClientSocket *data;
+    unsigned count;
+};
+
+#ifdef HAVE_THREADS
+static pthread_mutex_t synchronized;
+static pthread_mutex_t synchronized_recv;
+
+struct ParseOrder {
+    unsigned int tid;
+    struct ParseOrder *next;
+};
+struct ParseOrder *parse_order = NULL;
+#endif
+
+//the magic list :P
+static struct socket_list *sockets = NULL;
+
+static IOHANDLER_CALLBACK(socket_callback);
+
+void init_sockets() {
+    THREAD_MUTEX_INIT(synchronized);
+    THREAD_MUTEX_INIT(synchronized_recv);
+    
+    sockets = malloc(sizeof(*sockets));
+    if (!sockets)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    sockets->data = NULL;
+    sockets->count = 0;
+}
+
+struct ClientSocket* create_socket(char *host, int port, char *bindto, char *pass, char *nick, char *ident, char *realname) {
+    if(!sockets)
+        init_sockets();
+    struct ClientSocket *client = malloc(sizeof(*client));
+    if (!client) {
+        return NULL;
+    }
+    client->iofd = NULL;
+    client->host = strdup(host);
+    client->port = port;
+    client->bind = (bindto ? strdup(bindto) : NULL);
+    client->pass = (pass == NULL ? NULL : strdup(pass));
+    client->nick = strdup(nick);
+    client->ident = strdup(ident);
+    client->realname = strdup(realname);
+    client->user = NULL;
+    client->network_name = NULL;
+    client->flags = 0;
+    client->traffic_in = 0;
+    client->traffic_out = 0;
+    client->connection_time = 0;
+       client->botid = 0;
+    client->clientid = 0;
+    client->queue = NULL;
+    client->whoqueue_first = NULL;
+    client->whoqueue_last = NULL;
+    client->handleinfo_first = NULL;
+    client->handleinfo_last = NULL;
+    SYNCHRONIZE(synchronized);
+    client->next = sockets->data;
+    sockets->data = client;
+    DESYNCHRONIZE(synchronized);
+    return client;
+}
+
+void connect_socket(struct ClientSocket *client) {
+    client->iofd = iohandler_connect(client->host, client->port, ((client->flags & SOCKET_FLAG_SSL) ? 1 : 0), client->bind, socket_callback);
+    client->iofd->data = client;
+    client->flags |= SOCKET_FLAG_RECONNECT;
+}
+
+static int _close_socket(struct ClientSocket *client) {
+    if(client == NULL) return 0;
+    if((client->flags & SOCKET_FLAG_CONNECTED)) {
+        iohandler_printf(client->iofd, "QUIT :[NeonServ %s.%d] disconnect requested.\n", NEONSERV_VERSION, patchlevel);
+        
+        bot_disconnect(client);
+        
+        iohandler_close(client->iofd);
+        client->iofd = NULL;
+    }
+    client->flags &= ~(SOCKET_FLAG_READY | SOCKET_FLAG_CONNECTED);
+    if(client->queue)
+        queue_destroy(client);
+    if(client->whoqueue_first)
+        clear_whoqueue(client);
+    if(client->handleinfo_first)
+        clear_handleinfoqueue(client);
+    return 1;
+}
+
+int close_socket(struct ClientSocket *client) { //external call (don't reconnect)
+    if(client == NULL) return 0;
+    client->flags &= ~SOCKET_FLAG_RECONNECT;
+    return _close_socket(client);
+}
+
+int destroy_socket(struct ClientSocket *client) {
+    if(client == NULL) return 0;
+    close_socket(client);
+    SYNCHRONIZE(synchronized);
+    struct ClientSocket *sock, *last_sock = NULL;
+    for (sock = sockets->data; sock; sock = sock->next) {
+        if(sock == client) {
+            if(last_sock)
+                last_sock->next = sock->next;
+            else
+                sockets->data = sock->next;
+            sockets->count--;
+            break;
+        } else
+            last_sock = sock;
+    }
+    event_freeclient(client);
+    free(client->host);
+    if(client->bind)
+        free(client->bind);
+    if(client->pass)
+        free(client->pass);
+    if(client->network_name)
+        free(client->network_name);
+    if(client->iofd) //reconnect timer?
+        iohandler_close(client->iofd);
+    free(client);
+    DESYNCHRONIZE(synchronized);
+    return 1;
+}
+
+int write_socket_force(struct ClientSocket *client, char* msg, int len) {
+    if(!(client && (client->flags & SOCKET_FLAG_CONNECTED))) return 0;
+    SYNCHRONIZE(synchronized);
+    #ifdef HAVE_THREADS
+    printf_log("main", LOG_IRCRAW, "[%d send %d] %s", getCurrentThreadID(), len, msg);
+    #else
+    printf_log("main", LOG_IRCRAW, "[send %d] %s", len, msg);
+    #endif
+       iohandler_send(client->iofd, msg, len);
+    client->traffic_out += len;
+    DESYNCHRONIZE(synchronized);
+    return 1;
+}
+
+int write_socket(struct ClientSocket *client, char* msg, int len) {
+    if(!(client && (client->flags & SOCKET_FLAG_CONNECTED))) return 0;
+    if(client->flags & SOCKET_FLAG_USE_QUEUE)
+        return queue_add(client, msg, len);
+    else
+        return write_socket_force(client, msg, len);
+}
+
+#if HAVE_THREADS
+static void clientsocket_start_of_recv(unsigned int tid) {
+    SYNCHRONIZE(whohandler_sync);
+    struct ParseOrder *entry, *last;
+    for(last = parse_order; last; last = last->next) {
+        if(last->next == NULL)
+            break;
+    }
+    entry = malloc(sizeof(*entry));
+    entry->tid = tid;
+    entry->next = NULL;
+    if(last)
+        last->next = entry;
+    else
+        parse_order = entry;
+    DESYNCHRONIZE(whohandler_sync);
+}
+
+static void clientsocket_end_of_recv(unsigned int tid) {
+    SYNCHRONIZE(whohandler_sync);
+    struct ParseOrder *entry, *last = NULL;
+    for(entry = parse_order; entry; entry = entry->next) {
+        if(entry->tid == tid) {
+            if(last)
+                last->next = entry->next;
+            else
+                parse_order = entry->next;
+            free(entry);
+            break;
+        } else
+            last = entry;
+    }
+    DESYNCHRONIZE(whohandler_sync);
+}
+
+int clientsocket_parseorder_top(unsigned int tid) {
+    if(parse_order && parse_order->tid == tid)
+        return 1;
+    else
+        return 0;
+}
+#endif
+
+static IOHANDLER_CALLBACK(socket_callback) {
+    struct ClientSocket *client = event->iofd->data;
+    #ifdef HAVE_THREADS
+    unsigned int tid;
+    #endif
+    if(process_state.running == 0)
+        return; //just ignore the event (shutdown sequence)
+    switch(event->type) {
+    case IOEVENT_CONNECTED:
+        client->flags |= SOCKET_FLAG_CONNECTED;
+        if(client->pass && strcmp(client->pass, ""))
+            putsock(client, "PASS :%s", client->pass);
+        putsock(client, "USER %s 0 0 :%s", client->ident, client->realname);
+        putsock(client, "NICK %s", client->nick);
+        break;
+    case IOEVENT_NOTCONNECTED:
+    case IOEVENT_CLOSED:
+        _close_socket(client);
+        if(client->flags & SOCKET_FLAG_RECONNECT) {
+            struct timeval timeout;
+            gettimeofday(&timeout, NULL);
+            timeout.tv_sec += SOCKET_RECONNECT_TIME;
+            client->iofd = iohandler_timer(timeout, socket_callback);
+            client->iofd->data = client;
+        }
+        break;
+    case IOEVENT_TIMEOUT: //reconnect timer
+        connect_socket(client);
+        break;
+    case IOEVENT_RECV:
+        #ifdef HAVE_THREADS
+        tid = (unsigned int) pthread_self_tid();
+        clientsocket_start_of_recv(tid);
+        #endif
+        client->traffic_in += strlen(event->data.recv_str);
+        parse_line(client, event->data.recv_str);
+        #ifdef HAVE_THREADS
+        clientsocket_end_of_recv(tid);
+        #endif
+        break;
+    default:
+        break;
+    }
+}
+
+void putsock(struct ClientSocket *client, const char *text, ...) {
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!(client && (client->flags & SOCKET_FLAG_CONNECTED))) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    write_socket(client, sendBuf, pos+1);
+}
+
+struct ClientSocket* getBots(int flags, struct ClientSocket* last_bot) {
+    struct ClientSocket *sock = (last_bot ? last_bot->next : sockets->data);
+    if(sock == NULL) return NULL;
+    for (; sock; sock = sock->next) {
+        if(!flags || (sock->flags & flags) == flags)
+            return sock;
+    }
+    return NULL;
+}
+
+void free_sockets(int close_only) {
+    if(!sockets) return;
+    struct ClientSocket *client, *next;
+    for (client = sockets->data; client; client = next) {
+        next = client->next;
+        if(close_only) {
+            if((client->flags & SOCKET_FLAG_CONNECTED))
+                iohandler_printf(client->iofd, "QUIT :[NeonServ %s.%d] shutdown requested.\n", NEONSERV_VERSION, patchlevel);
+        } else
+            destroy_socket(client);
+    }
+    if(!close_only) {
+        free(sockets);
+        sockets = NULL;
+    }
+}
diff --git a/src/ClientSocket.h b/src/ClientSocket.h
new file mode 100644 (file)
index 0000000..9e73fd1
--- /dev/null
@@ -0,0 +1,97 @@
+/* ClientSocket.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _ClientSocket_h
+#define _ClientSocket_h
+
+#include "main.h"
+
+#define SOCKET_FLAG_CONNECTED      0x02
+#define SOCKET_FLAG_READY          0x04
+#define SOCKET_FLAG_PREFERRED      0x08 /* prefered bot to send datas to the IRC World (NOTICE's WHO's etc pp) */
+#define SOCKET_FLAG_USE_QUEUE      0x10
+#define SOCKET_FLAG_RECONNECT      0x20
+#define SOCKET_FLAG_SSL            0x40
+
+#define SOCKET_FLAG_FAST_JUMP      0x200
+#define SOCKET_FLAG_SILENT         0x400
+#define SOCKET_FLAG_CHANGENICK     0x800
+#define SOCKET_FLAG_REQUEST_INVITE 0x1000
+#define SOCKET_FLAG_REQUEST_OP     0x2000
+#define SOCKET_FLAG_SECRET_BOT     0x4000
+
+#define SOCKET_HAVE_BOTCLASSVALUE1 0x10000000
+#define SOCKET_HAVE_BOTCLASSVALUE2 0x20000000
+#define SOCKET_HAVE_BOTCLASSVALUE3 0x40000000
+
+#define BUF_SIZ 512
+
+struct UserNode;
+struct trigger_cache;
+struct IODescriptor;
+
+struct ClientSocket {
+    struct IODescriptor *iofd;
+    unsigned int flags;
+    char *host;
+    int port;
+    char *bind;
+    char *pass;
+    char *nick;
+    char *ident;
+    char *realname;
+    struct UserNode *user;
+    char *network_name;
+    unsigned long traffic_in;
+    unsigned long traffic_out;
+    time_t connection_time;
+    
+    struct BotQueue *queue;
+    
+    struct WHOQueueEntry *whoqueue_first;
+    struct WHOQueueEntry *whoqueue_last;
+    
+    struct HandleInfoQueueEntry *handleinfo_first;
+    struct HandleInfoQueueEntry *handleinfo_last;
+       
+       int botid : 16;
+    int clientid : 16;
+    
+    void *botclassvalue1;
+    void *botclassvalue2;
+    void *botclassvalue3;
+    
+    void *changenick_channels; //parted channels due raw 437
+    
+    struct ClientSocket *next;
+};
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ struct ClientSocket* create_socket(char *host, int port, char *bindto, char *pass, char *nick, char *ident, char *realname);
+/* MODULAR ACCESSIBLE */ void connect_socket(struct ClientSocket *client);
+/* MODULAR ACCESSIBLE */ int close_socket(struct ClientSocket *client);
+/* MODULAR ACCESSIBLE */ int destroy_socket(struct ClientSocket *client);
+int write_socket_force(struct ClientSocket *client, char* msg, int len);
+/* MODULAR ACCESSIBLE */ int write_socket(struct ClientSocket *client, char* msg, int len);
+#ifdef HAVE_THREADS
+int clientsocket_parseorder_top(unsigned int tid);
+#endif
+/* MODULAR ACCESSIBLE */ void putsock(struct ClientSocket *client, const char *text, ...) PRINTF_LIKE(2, 3);
+/* MODULAR ACCESSIBLE */ struct ClientSocket* getBots(int flags, struct ClientSocket* last_bot);
+void init_sockets();
+void free_sockets(int close_only);
+#endif
+#endif
diff --git a/src/ConfigParser.c b/src/ConfigParser.c
new file mode 100644 (file)
index 0000000..050d6b4
--- /dev/null
@@ -0,0 +1,377 @@
+/* ConfigParser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "ConfigParser.h"
+#include "tools.h"
+
+#define ENTRYTYPE_BLOCK   1
+#define ENTRYTYPE_STRING  2
+#define ENTRYTYPE_INTEGER 3
+
+struct ConfigEntry {
+    char *name;
+    int type;
+    void *value;
+    struct ConfigEntry *next;
+};
+
+static struct ConfigEntry *root_entry = NULL;
+
+static char *parse_config_recursive(struct ConfigEntry *centry, char *buffer, int root_element);
+static void free_entry_rekursiv(struct ConfigEntry *centry, int is_root_entry);
+
+int loadConfig(const char *filename) {
+    FILE *f;
+    f = fopen(filename, "rb");
+    if(!f) return 0;
+    
+    // obtain file size:
+    long lSize;
+    fseek (f, 0, SEEK_END);
+    lSize = ftell(f);
+    rewind(f);
+    
+    // allocate memory to contain the whole file:
+    char *buffer = (char*) malloc (sizeof(char)*lSize + 1);
+    if (buffer == NULL) {
+        fclose(f);
+        return 0;
+    }
+    
+    // copy the file into the buffer:
+    size_t result = fread (buffer, 1, lSize, f);
+    if (result != lSize) {
+        fclose(f);
+        return 0;
+    }
+    buffer[lSize] = '\0';
+    
+    fclose(f);
+    
+    // now parse the config file...
+    if(root_entry) {
+        free_loaded_config();
+    }
+    root_entry = malloc(sizeof(*root_entry));
+    if (!root_entry) return 0;
+    parse_config_recursive(root_entry, buffer, 1);
+    
+    // free the buffer...
+    free(buffer);
+    return 1;
+}
+
+#define PARSER_FLAG_ESCAPED    0x01
+#define PARSER_FLAG_BLOCK      0x02
+#define PARSER_FLAG_STRING     0x04
+#define PARSER_FLAG_EXPECT_END 0x08
+#define PARSER_FLAG_NEXTCHAR   0x10
+#define PARSER_FLAG_INTEGER    0x20
+#define PARSER_FLAG_EOBN       0x40 /* End of Block name */
+#define PARSER_FLAG_COMMAND    0x80
+static char *parse_config_recursive(struct ConfigEntry *centry, char *buffer, int root_element) {
+    int flags = 0;
+    int type = (root_element ? ENTRYTYPE_BLOCK : 0);
+    char cbuf[1024];
+    int cbufpos = 0;
+    struct ConfigEntry *sub_entrys = NULL;
+    while(*buffer) {
+        if(flags & PARSER_FLAG_NEXTCHAR) {
+            buffer++;
+            if(*buffer == '\0')
+                break;
+        }
+        flags |= PARSER_FLAG_NEXTCHAR;
+        if(flags & PARSER_FLAG_EOBN) {
+            flags &= ~(PARSER_FLAG_EOBN | PARSER_FLAG_NEXTCHAR);
+            struct ConfigEntry *new_entry = malloc(sizeof(*new_entry));
+            if (!new_entry) return buffer;
+            new_entry->name = strdup(cbuf);
+            buffer = parse_config_recursive(new_entry, buffer, 0);
+            if(sub_entrys)
+                new_entry->next = sub_entrys;
+            else
+                new_entry->next = NULL;
+            sub_entrys = new_entry;
+            centry->value = sub_entrys;
+        }
+        if(flags & PARSER_FLAG_ESCAPED) {
+            cbuf[cbufpos++] = *buffer;
+            flags &= ~PARSER_FLAG_ESCAPED;
+            continue;
+        }
+        if(flags & PARSER_FLAG_EXPECT_END) {
+            if(*buffer == ';') {
+                centry->type = type;
+                return (buffer+1);
+            }
+            continue;
+        }
+        if(flags & PARSER_FLAG_STRING) {
+            if(*buffer == '"') {
+                cbuf[cbufpos] = '\0';
+                flags &= ~PARSER_FLAG_STRING;
+                if(type == ENTRYTYPE_STRING) {
+                    flags |= PARSER_FLAG_EXPECT_END;
+                    centry->value = strdup(cbuf);
+                } else if(type == ENTRYTYPE_BLOCK) {
+                    flags |= PARSER_FLAG_EOBN;
+                }
+            } else if(*buffer == '\\')
+                flags |= PARSER_FLAG_ESCAPED;
+            else
+                cbuf[cbufpos++] = *buffer;
+            continue;
+        }
+        if(flags & PARSER_FLAG_INTEGER) {
+            if(!isdigit(*buffer)) {
+                cbuf[cbufpos] = '\0';
+                flags &= ~PARSER_FLAG_INTEGER;
+                if(type == ENTRYTYPE_INTEGER) {
+                    flags |= PARSER_FLAG_EXPECT_END;
+                    int *intbuf = malloc(sizeof(int));
+                    *intbuf = atoi(cbuf);
+                    centry->value = intbuf;
+                }
+                if(*buffer == ';') {
+                    centry->type = type;
+                    return (buffer+1);
+                }
+            } else
+                cbuf[cbufpos++] = *buffer;
+            continue;
+        }
+        if(flags & PARSER_FLAG_COMMAND) {
+            int found_command = 0;
+            char *tmp_buffer;
+            switch(*buffer) {
+                case '/':
+                    tmp_buffer = buffer;
+                    buffer = strchr(buffer, '\r');
+                    if(!buffer)
+                        buffer = strchr(tmp_buffer, '\n');
+                    if(!buffer)
+                        buffer = tmp_buffer + strlen(tmp_buffer)-1;
+                    found_command = 1;
+                    break;
+                case '*':
+                    //simple search for the next */
+                    buffer = strstr(buffer, "*/")+1;
+                    found_command = 1;
+            }
+            flags &= ~PARSER_FLAG_COMMAND;
+            if(found_command)
+                continue;
+        }
+        switch(*buffer) {
+            case '\\':
+                flags |= PARSER_FLAG_ESCAPED;
+                break;
+            case '/':
+                if(!(flags & PARSER_FLAG_STRING)) {
+                    flags |= PARSER_FLAG_COMMAND;
+                }
+                break;
+            case '{':
+                flags |= PARSER_FLAG_BLOCK;
+                type = ENTRYTYPE_BLOCK;
+                break;
+            case '}':
+                if(flags & PARSER_FLAG_BLOCK)
+                    flags &= ~PARSER_FLAG_BLOCK;
+                flags |= PARSER_FLAG_EXPECT_END;
+                break;
+            case '"':
+                flags |= PARSER_FLAG_STRING;
+                if(!type)
+                    type = ENTRYTYPE_STRING;
+                cbufpos = 0;
+                break;
+            case ';':
+                centry->type = type;
+                return (buffer+1);
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                if(!type)
+                    type = ENTRYTYPE_INTEGER;
+                flags |= PARSER_FLAG_INTEGER;
+                cbufpos = 0;
+                cbuf[cbufpos++] = *buffer;
+                break;
+            default:
+                break;
+        }
+    }
+    centry->type = type;
+    return buffer; //end of the buffer
+}
+
+int get_int_field(char *field_path) {
+    struct ConfigEntry *centry = root_entry;
+    char *a, *b = field_path;
+    struct ConfigEntry *subentry;
+    while((a = strstr(b, ".")) && centry) {
+        if(centry->type == ENTRYTYPE_BLOCK) {
+            int found = 0;
+            for(subentry = centry->value; subentry; subentry = subentry->next) {
+                if(!stricmplen(subentry->name, b, a-b)) {
+                    centry = subentry;
+                    found = 1;
+                    break;
+                }
+            }
+            if(!found)
+                return 0;
+        } else
+            return 0;
+        b = a+1;
+    }
+    if(centry->type == ENTRYTYPE_BLOCK) {
+        int found = 0;
+        for(subentry = centry->value; subentry; subentry = subentry->next) {
+            if(!stricmp(subentry->name, b)) {
+                centry = subentry;
+                found = 1;
+                break;
+            }
+        }
+        if(!found)
+            return 0;
+    } else
+        return 0;
+    if(centry->type == ENTRYTYPE_INTEGER)
+        return ((int *)centry->value)[0];
+    else
+        return 0;
+}
+
+char *get_string_field(char *field_path) {
+    struct ConfigEntry *centry = root_entry;
+    char *a, *b = field_path;
+    struct ConfigEntry *subentry;
+    while((a = strstr(b, ".")) && centry) {
+        if(centry->type == ENTRYTYPE_BLOCK) {
+            int found = 0;
+            for(subentry = centry->value; subentry; subentry = subentry->next) {
+                if(!stricmplen(subentry->name, b, a-b)) {
+                    centry = subentry;
+                    found = 1;
+                    break;
+                }
+            }
+            if(!found)
+                return NULL;
+        } else
+            return NULL;
+        b = a+1;
+    }
+    if(centry->type == ENTRYTYPE_BLOCK) {
+        int found = 0;
+        for(subentry = centry->value; subentry; subentry = subentry->next) {
+            if(!stricmp(subentry->name, b)) {
+                centry = subentry;
+                found = 1;
+                break;
+            }
+        }
+        if(!found)
+            return NULL;
+    } else
+        return NULL;
+    if(centry->type == ENTRYTYPE_STRING)
+        return centry->value;
+    else
+        return NULL;
+}
+
+char **get_all_fieldnames(char *block_path) {
+    struct ConfigEntry *centry = root_entry;
+    char *a, *b = block_path;
+    struct ConfigEntry *subentry;
+    while((a = strstr(b, ".")) && centry) {
+        if(centry->type == ENTRYTYPE_BLOCK) {
+            int found = 0;
+            for(subentry = centry->value; subentry; subentry = subentry->next) {
+                if(!stricmplen(subentry->name, b, a-b)) {
+                    centry = subentry;
+                    found = 1;
+                    break;
+                }
+            }
+            if(!found)
+                return NULL;
+        } else
+            return NULL;
+        b = a+1;
+    }
+    if(centry->type == ENTRYTYPE_BLOCK) {
+        int found = 0;
+        for(subentry = centry->value; subentry; subentry = subentry->next) {
+            if(!stricmp(subentry->name, b)) {
+                centry = subentry;
+                found = 1;
+                break;
+            }
+        }
+        if(!found)
+            return NULL;
+    } else
+        return NULL;
+    if(centry->type != ENTRYTYPE_BLOCK) return NULL;
+    int count = 0;
+    for(subentry = centry->value; subentry; subentry = subentry->next) {
+        count++;
+    }
+    char **fieldnames = calloc(count+1, sizeof(char *));
+    count = 0;
+    for(subentry = centry->value; subentry; subentry = subentry->next) {
+        fieldnames[count++] = subentry->name;
+    }
+    return fieldnames;
+}
+
+void free_loaded_config() {
+    if(root_entry) {
+        free_entry_rekursiv(root_entry, 1);
+        root_entry = NULL;
+    }
+}
+
+static void free_entry_rekursiv(struct ConfigEntry *centry, int is_root_entry) {
+    if(centry->type == ENTRYTYPE_BLOCK) {
+        struct ConfigEntry *subentry, *nextentry;
+        for(subentry = centry->value; subentry; subentry = nextentry) {
+            nextentry = subentry->next;
+            free_entry_rekursiv(subentry, 0);
+        }
+    } else if(centry->type == ENTRYTYPE_STRING) {
+        free(centry->value);
+    } else if(centry->type == ENTRYTYPE_INTEGER) {
+        free(centry->value);
+    }
+    if(!is_root_entry)
+        free(centry->name);
+    free(centry);
+}
diff --git a/src/ConfigParser.h b/src/ConfigParser.h
new file mode 100644 (file)
index 0000000..efdd237
--- /dev/null
@@ -0,0 +1,29 @@
+/* ConfigParser.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#ifndef _ConfigParser_h
+#define _ConfigParser_h
+#include "main.h"
+
+#ifndef DND_FUNCTIONS
+int loadConfig(const char *filename);
+/* MODULAR ACCESSIBLE */ int get_int_field(char *field_path);
+/* MODULAR ACCESSIBLE */ char *get_string_field(char *field_path);
+char **get_all_fieldnames(char *block_path);
+void free_loaded_config();
+#endif
+#endif
diff --git a/src/DBHelper.c b/src/DBHelper.c
new file mode 100644 (file)
index 0000000..6e2f843
--- /dev/null
@@ -0,0 +1,365 @@
+/* DBHelper.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "DBHelper.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "mysqlConn.h"
+#include "lang.h"
+#include "tools.h"
+#include "IRCEvents.h"
+#include "HandleInfoHandler.h"
+#include "ClientSocket.h"
+#include "bots.h"
+#include "ConfigParser.h"
+#include "log.h"
+
+void _loadUserSettings(struct UserNode *user) {
+    SYNCHRONIZE(cache_sync);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_lang`, `user_reply_privmsg`, `user_god`, `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        user->language = get_language_by_tag(row[0]);
+        if(user->language == NULL) user->language = get_default_language();
+        if(strcmp(row[1], "0"))
+            user->flags |= USERFLAG_REPLY_PRIVMSG;
+        if(strcmp(row[2], "0"))
+            user->flags |= USERFLAG_GOD_MODE;
+        user->user_id = atoi(row[3]);
+        user->flags |= USERFLAG_HAS_USERID;
+    } else
+        user->language = get_default_language();
+    user->flags |= USERFLAG_LOADED_SETTINGS;
+    DESYNCHRONIZE(cache_sync);
+}
+
+int isGodMode(struct UserNode *user) {
+    loadUserSettings(user);
+    return (user->flags & USERFLAG_GOD_MODE);
+}
+
+int getChannelAccess(struct UserNode *user, struct ChanNode *chan) {
+    if(!(user->flags & USERFLAG_ISAUTHED)) return 0;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int caccess = 0;
+    int userid;
+    if(user->flags & USERFLAG_HAS_USERID)
+        userid = user->user_id;
+    else {
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            userid = atoi(row[0]);
+            user->user_id = userid;
+            user->flags |= USERFLAG_HAS_USERID;
+        } else
+            return 0;
+    }
+    printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_uid` = '%d' AND `chanuser_cid` = '%d'", userid, chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        int cflags = atoi(row[1]);
+        if(!(cflags & DB_CHANUSER_SUSPENDED) && atoi(row[0]) > caccess)
+            caccess = atoi(row[0]);
+    }
+    return caccess;
+}
+
+char *getChanDefault(char *channel_setting) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_name` = 'defaults'", channel_setting);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return "";
+    return row[0];
+}
+
+int checkChannelAccess(struct UserNode *user, struct ChanNode *chan, char *channel_setting, int allow_501) {
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return 0;
+    if((user->flags & USERFLAG_ISIRCOP)) return 1;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", channel_setting, chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return 0;
+    int require_access = atoi((row[0] ? row[0] : getChanDefault(channel_setting)));
+    if(require_access == 0) return 1;
+    if(!(user->flags & USERFLAG_ISAUTHED)) return 0;
+    int caccess = 0;
+    int userid;
+    if(user->flags & USERFLAG_HAS_USERID)
+        userid = user->user_id;
+    else {
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            userid = atoi(row[0]);
+            user->user_id = userid;
+            user->flags |= USERFLAG_HAS_USERID;
+        } else
+            userid = -1;
+    }
+    if(userid > -1) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_uid` = '%d' AND `chanuser_cid` = '%d'", userid, chan->channel_id);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row[0]);
+        }
+    }
+    if(caccess >= require_access) return 1;
+    if(caccess == 500 && require_access == 501 && allow_501) return 1;
+    return 0;
+}
+
+void _loadChannelSettings(struct ChanNode *chan) {
+    SYNCHRONIZE(cache_sync);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(chan->name));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chan->flags |= CHANFLAG_CHAN_REGISTERED;
+        chan->channel_id = atoi(row[0]);
+    }
+    chan->flags |= CHANFLAG_REQUESTED_CHANINFO;
+    DESYNCHRONIZE(cache_sync);
+}
+
+//TODO: fix performance: we should cache the user access
+int isUserProtected(struct ChanNode *chan, struct UserNode *victim, struct UserNode *issuer) {
+    /* Don't protect if someone is attacking himself, or if the aggressor is an IRC Operator. */
+    if(victim == issuer || (issuer->flags & USERFLAG_ISIRCOP)) return 0;
+    
+    /* Don't protect if no one is to be protected. */
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char protection;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return 0;
+    printf_mysql_query("SELECT `channel_protect` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) return 0;
+    if(row[0]) {
+        protection = (char) atoi(row[0]);
+    } else {
+         printf_mysql_query("SELECT `channel_protect` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        protection = (char) atoi(row[0]);
+    }
+    if(protection == 3) return 0;
+    
+    /* Don't protect if the victim isn't added to the channel, unless we are to protect non-users also. */
+    int victim_access = getChannelAccess(victim, chan);
+    if (!victim_access && protection != 0) return 0;
+    
+    /* Protect if the aggressor isn't a user because at this point, the aggressor can only be less than or equal to the victim. */
+    int issuer_access = getChannelAccess(issuer, chan);
+    if (!issuer_access) return 1;
+    
+    /* If the aggressor was a user, then the victim can't be helped. */
+    if(!victim_access) return 0;
+    
+    switch(protection) {
+        case 0:
+        case 1:
+            if(victim_access >= issuer_access) return 1;
+            break;
+        case 2:
+            if(victim_access > issuer_access) return 1;
+            break;
+    }
+    return 0;
+}
+
+char *getBanAffectingMask(struct ChanNode *chan, char *mask) {
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `ban_mask` FROM `bans` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!match(row[0], mask))
+            return row[0];
+    }
+    return NULL;
+}
+
+int renameAccount(char *oldauth, char *newauth) {
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(oldauth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        int userid = atoi(row[0]);
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(newauth));
+        res = mysql_use();
+        if((row = mysql_fetch_row(res)) != NULL) {
+            //merge
+            int newuid = atoi(row[0]);
+            printf_mysql_query("SELECT `chanuser_id`, `chanuser_access`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_uid` = '%d'", newuid);
+            res = mysql_use();
+            while((row = mysql_fetch_row(res)) != NULL) {
+                printf_mysql_query("SELECT `chanuser_id`, `chanuser_access`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_uid` = '%d'", userid);
+                res2 = mysql_use();
+                if((row2 = mysql_fetch_row(res2)) != NULL) {
+                    if(atoi(row[1]) > atoi(row2[1])) {
+                        printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '%s' WHERE `chanuser_id` = '%s'", row[1], row2[0]);
+                    }
+                    printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_id` = '%s'", row[0]);
+                } else
+                    printf_mysql_query("UPDATE `chanusers` SET `chanuser_uid` = '%d' WHERE `chanuser_id` = '%s'", userid, row[0]);
+            }
+            printf_mysql_query("UPDATE `channels` SET `channel_registrator` = '%d' WHERE `channel_registrator` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `bans` SET `ban_owner` = '%d' WHERE `ban_owner` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `events` SET `auth` = '%s' WHERE `auth` = '%s'", escape_string(newauth), escape_string(oldauth));
+            printf_mysql_query("UPDATE `godlog` SET `godlog_uid` = '%d' WHERE `godlog_uid` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `owner_history` SET `owner_history_to_uid` = '%d' WHERE `owner_history_to_uid` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `owner_history` SET `owner_history_from_uid` = '%d' WHERE `owner_history_from_uid` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `owner_history` SET `owner_history_from_uid` = '%d' WHERE `owner_history_from_uid` = '%d'", userid, newuid);
+            printf_mysql_query("UPDATE `noinvite` SET `uid` = '%d' WHERE `uid` = '%d'", userid, newuid);
+            printf_mysql_query("DELETE FROM `users` WHERE `user_id` = '%d'", newuid);
+        }
+        //simply rename the account
+        printf_mysql_query("UPDATE `users` SET `user_user` = '%s' WHERE `user_id` = '%d'", escape_string(newauth), userid);
+        char *alertchan = get_string_field("General.CheckAuths.alertchan");
+        if(alertchan) {
+            struct ChanNode *alertchan_chan = getChanByName(alertchan);
+            struct ClientSocket *alertclient;
+            if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
+                putsock(alertclient, "PRIVMSG %s :Renamed User %s to %s", alertchan_chan->name, oldauth, newauth);
+            }
+        }
+        return 1;
+    }
+    return 0;
+}
+
+static AUTHLOOKUP_CALLBACK(event_user_registered_auth_lookup);
+
+struct event_user_registered_cache {
+    struct UserNode *user;
+    char *oldauth;
+};
+
+static void event_user_registered(struct UserNode *user, char *new_mask) {
+    //check if there is a fakehost on both sides...
+    //extract host from new_mask
+    char *new_host = strchr(new_mask, '@');
+    if(new_host)
+        new_host++;
+    else
+        return;
+    if(!isFakeHost(user->host) || !isFakeHost(new_host)) 
+        return;
+    //extract user names
+    char oldauth[AUTHLEN], newauth[AUTHLEN];
+    char *p;
+    if((p = strstr(user->host, "."))) {
+        *p = '\0';
+        strcpy(oldauth, user->host);
+        *p = '.';
+    }
+    if((p = strstr(new_host, "."))) {
+        *p = '\0';
+        strcpy(newauth, new_host);
+        *p = '.';
+    }
+    if(!stricmp(oldauth, newauth))
+        return;
+    //check if we know this user; then check the new auth
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(oldauth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        struct event_user_registered_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->user = user;
+        cache->oldauth = strdup(oldauth);
+        lookup_authname(newauth, 0, event_user_registered_auth_lookup, cache);
+    }
+    return;
+}
+
+static AUTHLOOKUP_CALLBACK(event_user_registered_auth_lookup) {
+    struct event_user_registered_cache *cache = data;
+    if(exists) {
+        renameAccount(cache->oldauth, auth);
+        strcpy(cache->user->auth, auth);
+        cache->user->flags |= USERFLAG_ISAUTHED;
+    }
+    free(cache->oldauth);
+    free(cache);
+}
+
+void deleteUser(int userid) {
+    //simply delete the user
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT a.`chanuser_access`, a.`chanuser_cid`, (SELECT COUNT(*) FROM `chanusers` AS b WHERE b.`chanuser_cid` = a.`chanuser_cid` AND b.`chanuser_access` = 500) FROM `chanusers` AS a WHERE a.`chanuser_uid` = '%d'", userid);
+    res = mysql_use();
+    while((row = mysql_fetch_row(res))) {
+        if(!strcmp(row[0], "500") && !strcmp(row[2], "1")) {
+            //unregister channel
+            printf_mysql_query("SELECT `botid`, `channel_name` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `chanid` = '%s' AND `suspended` = '0'", row[1]);
+            res2 = mysql_use();
+            while((row2 = mysql_fetch_row(res2))) {
+                struct ClientSocket *bot;
+                int clientid = atoi(row2[0]);
+                for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                    if(bot->clientid == clientid)
+                        putsock(bot, "PART %s :Channel unregistered.", row2[1]);
+                }
+            }
+            printf_mysql_query("DELETE FROM `bot_channels` WHERE `chanid` = '%s'", row[1]);
+        }
+    }
+    printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_uid` = '%d'", userid);
+    printf_mysql_query("UPDATE `bans` SET `ban_owner` = 0 WHERE `ban_owner` = '%d'", userid);
+    printf_mysql_query("UPDATE `donotregister` SET `dnr_user` = 0 WHERE `dnr_user` = '%d'", userid);
+    printf_mysql_query("UPDATE `bans` SET `ban_owner` = 0 WHERE `ban_owner` = '%d'", userid);
+    printf_mysql_query("UPDATE `godlog` SET `godlog_uid` = 0 WHERE `godlog_uid` = '%d'", userid);
+    printf_mysql_query("DELETE FROM `noinvite` WHERE `uid` = '%d'", userid);
+    printf_mysql_query("UPDATE `owner_history` SET `owner_history_to_uid` = 0 WHERE `owner_history_to_uid` = '%d'", userid);
+    printf_mysql_query("UPDATE `owner_history` SET `owner_history_from_uid` = 0 WHERE `owner_history_from_uid` = '%d'", userid);
+    printf_mysql_query("UPDATE `channels` SET `channel_registrator` = 0 WHERE `channel_registrator` = '%d'", userid);
+    printf_mysql_query("DELETE FROM `users` WHERE `user_id` = '%d'", userid);
+    struct UserNode *user;
+    for(user = getAllUsers(NULL); user; user = getAllUsers(user)) {
+        if(user->flags & USERFLAG_HAS_USERID)
+            user->flags &= ~USERFLAG_HAS_USERID;
+    }
+}
+
+void init_DBHelper() {
+    bind_registered(event_user_registered, 0);
+}
+
diff --git a/src/DBHelper.h b/src/DBHelper.h
new file mode 100644 (file)
index 0000000..b90ed11
--- /dev/null
@@ -0,0 +1,49 @@
+/* DBHelper.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _DBHelper_h
+#define _DBHelper_h
+
+#include "main.h"
+struct UserNode;
+struct ChanNode;
+
+#define DB_CHANUSER_SUSPENDED 0x01
+#define DB_CHANUSER_AUTOINVITE 0x02
+#define DB_CHANUSER_NOAUTOOP 0x04
+
+#define loadUserSettings(USER) if((USER->flags & USERFLAG_ISAUTHED) && !(USER->flags & USERFLAG_LOADED_SETTINGS)) _loadUserSettings(USER)
+#define loadChannelSettings(CHAN) if(!(CHAN->flags & CHANFLAG_REQUESTED_CHANINFO)) _loadChannelSettings(CHAN)
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ void _loadUserSettings(struct UserNode* user);
+/* MODULAR ACCESSIBLE */ int isGodMode(struct UserNode *user);
+/* MODULAR ACCESSIBLE */ char *getChanDefault(char *channel_setting);
+/* MODULAR ACCESSIBLE */ int getChannelAccess(struct UserNode *user, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ int checkChannelAccess(struct UserNode *user, struct ChanNode *chan, char *channel_setting, int allow_501);
+/* MODULAR ACCESSIBLE */ void _loadChannelSettings(struct ChanNode *chan);
+
+/* MODULAR ACCESSIBLE */ int isUserProtected(struct ChanNode *chan, struct UserNode *victim, struct UserNode *issuer);
+
+/* MODULAR ACCESSIBLE */ char *getBanAffectingMask(struct ChanNode *chan, char *mask); //returns bans that match a given mask   eg. *!*@ab*  if you pass  *!*@abcdefg.*
+
+/* MODULAR ACCESSIBLE */ int renameAccount(char *oldauth, char *newauth);
+
+/* MODULAR ACCESSIBLE */ void deleteUser(int userid);
+
+void init_DBHelper();
+#endif
+#endif
diff --git a/src/EventLogger.c b/src/EventLogger.c
new file mode 100644 (file)
index 0000000..e131478
--- /dev/null
@@ -0,0 +1,106 @@
+/* EventLogger.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "EventLogger.h"
+#include "modcmd.h"
+#include "mysqlConn.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "DBHelper.h"
+#include "log.h"
+
+static struct Event *first_event = NULL, *last_event = NULL;
+
+struct Event *createEvent(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct cmd_binding *command, char **args, int argc, int flags) {
+    struct Event *event = malloc(sizeof(*event));
+    if (!event)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    event->client = client;
+    event->user = user;
+    event->chan = chan;
+    event->event_time = time(0);
+    event->command = command;
+    char arguments[MAXLEN];
+    int argpos = 0;
+    int i;
+    for(i = 0; i < argc; i++)
+        argpos += sprintf(arguments + argpos, "%s ", args[i]);
+    arguments[(argpos ? argpos-1 : 0)] = '\0';
+    event->arguments = strdup(arguments);
+    event->flags = flags;
+    event->next = NULL;
+    if(last_event) {
+        last_event->next = event;
+        last_event = event;
+    } else {
+        last_event = event;
+        first_event = event;
+    }
+    return event;
+}
+
+void logEvent(struct Event *event) {
+    char fullcmd[MAXLEN];
+    sprintf(fullcmd, "%s %s", event->command->func->name, event->arguments);
+    if((event->flags & CMDFLAG_LOG) && event->chan) {
+        char *auth = ((event->user->flags & USERFLAG_ISAUTHED) ? event->user->auth : "*");
+        loadChannelSettings(event->chan);
+        if((event->chan->flags & CHANFLAG_CHAN_REGISTERED))
+            printf_mysql_query("INSERT INTO `events` (`cid`, `nick`, `auth`, `time`, `command`) VALUES ('%d', '%s', '%s', UNIX_TIMESTAMP(), '%s')", event->chan->channel_id, escape_string(event->user->nick), auth, escape_string(fullcmd));
+    }
+    if((event->flags & CMDFLAG_OPLOG)) {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        int userid;
+        char *auth = ((event->user->flags & USERFLAG_ISAUTHED) ? event->user->auth : "*");
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) == NULL) 
+            userid = 0;
+        else
+            userid = atoi(row[0]);
+        if(event->chan) {
+            loadChannelSettings(event->chan);
+            if((event->chan->flags & CHANFLAG_CHAN_REGISTERED))
+                printf_mysql_query("INSERT INTO `godlog` (`godlog_cid`, `godlog_uid`, `godlog_time`, `godlog_cmd`) VALUES ('%d', '%d', UNIX_TIMESTAMP(), '%s')", event->chan->channel_id, userid, escape_string(fullcmd));
+        }
+        printf_log("main", LOG_OVERRIDE, "[%s:%s (%s)] %s", (event->chan ? event->chan->name : "*"), event->user->nick, auth, fullcmd);
+    }
+}
+
+static void destroyEvent(struct Event *event) {
+    if(event == first_event)
+        first_event = event->next;
+    if(event == last_event) {
+        struct Event *last;
+        for(last = first_event; last; last = last->next)
+            if(last->next == NULL) break;
+        last_event = last;
+    }
+    free(event->arguments);
+    free(event);
+}
+
+void destroyEvents() {
+    time_t now = time(0);
+    while(first_event && now - first_event->event_time >= 60) {
+        destroyEvent(first_event);
+    }
+}
diff --git a/src/EventLogger.h b/src/EventLogger.h
new file mode 100644 (file)
index 0000000..63fb4e1
--- /dev/null
@@ -0,0 +1,43 @@
+/* EventLogger.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _EventLogger_h
+#define _EventLogger_h
+
+#include "main.h"
+struct ClientSocket;
+struct UserNode;
+struct ChanNode;
+struct cmd_binding;
+
+struct Event {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    time_t event_time;
+    struct cmd_binding *command;
+    char *arguments;
+    unsigned int flags; /* defined in modcmd.h */
+    
+    struct Event *next;
+};
+
+#ifndef DND_FUNCTIONS
+struct Event *createEvent(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct cmd_binding *command, char **args, int argc, int flags);
+/* MODULAR ACCESSIBLE */ void logEvent(struct Event *event);
+void destroyEvents();
+#endif
+#endif
diff --git a/src/HandleInfoHandler.c b/src/HandleInfoHandler.c
new file mode 100644 (file)
index 0000000..8a8a46c
--- /dev/null
@@ -0,0 +1,247 @@
+/* HandleInfoHandler.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "HandleInfoHandler.h"
+#include "ClientSocket.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "IRCEvents.h"
+#include "tools.h"
+#include "modules.h"
+#include "log.h"
+
+#define AUTHSERV_NICK "AuthServ"
+
+#define MAXCALLBACKS 3
+
+struct HandleInfoQueueEntry {
+    char *auth;
+    void *callback[MAXCALLBACKS];
+    int module_id[MAXCALLBACKS];
+    void *data[MAXCALLBACKS];
+    
+    struct HandleInfoQueueEntry *next;
+};
+
+static struct HandleInfoQueueEntry* addHandleInfoQueueEntry(struct ClientSocket *client) {
+    struct HandleInfoQueueEntry *entry = malloc(sizeof(*entry));
+    if (!entry)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    SYNCHRONIZE(cache_sync);
+    entry->next = NULL;
+    if(client->handleinfo_last)
+        client->handleinfo_last->next = entry;
+    else
+        client->handleinfo_first = entry;
+    client->handleinfo_last = entry;
+    DESYNCHRONIZE(cache_sync);
+    return entry;
+}
+
+static struct HandleInfoQueueEntry* getNextHandleInfoQueueEntry(struct ClientSocket *client, int freeEntry) {
+    if(!client->handleinfo_first) return NULL;
+    SYNCHRONIZE(cache_sync);
+    struct HandleInfoQueueEntry *entry = client->handleinfo_first;
+    if(freeEntry) {
+        client->handleinfo_first = entry->next;
+        if(entry == client->handleinfo_last) {
+            client->handleinfo_last = NULL;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return entry;
+}
+
+void clear_handleinfoqueue(struct ClientSocket *client) {
+    if(!client->handleinfo_first) return;
+    SYNCHRONIZE(cache_sync);
+    struct HandleInfoQueueEntry *entry, *next;
+    for(entry = client->handleinfo_first; entry; entry = next) {
+        next = entry->next;
+        free(entry);
+    }
+    client->handleinfo_last = NULL;
+    client->handleinfo_first = NULL;
+    DESYNCHRONIZE(cache_sync);
+}
+
+void lookup_authname(char *auth, int module_id, authlookup_callback_t callback, void *data) {
+    struct ClientSocket *bot;
+    struct HandleInfoQueueEntry* entry;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        for(entry = bot->handleinfo_first; entry; entry = entry->next) {
+            if(!stricmp(entry->auth, auth)) {
+                int i = 0;
+                for(i = 1; i < MAXCALLBACKS; i++) {
+                    if(!entry->callback[i]) {
+                        entry->callback[i] = callback;
+                        entry->module_id[i] = module_id;
+                        entry->data[i] = data;
+                        return;
+                    }
+                }
+            }
+        }
+        if(bot->flags & SOCKET_FLAG_PREFERRED)
+            break;
+    }
+    if(bot == NULL) return;
+    entry = addHandleInfoQueueEntry(bot);
+    int i;
+    entry->auth = strdup(auth);
+    entry->callback[0] = callback;
+    entry->module_id[0] = module_id;
+    for(i = 1; i < MAXCALLBACKS; i++)
+        entry->callback[i] = NULL;
+    entry->data[0] = data;
+    for(i = 1; i < MAXCALLBACKS; i++)
+        entry->data[i] = NULL;
+    putsock(bot, "PRIVMSG " AUTHSERV_NICK " :ACCOUNTINFO *%s", auth);
+}
+
+static void recv_notice(struct UserNode *user, struct UserNode *target, char *message) {
+    if(stricmp(user->nick, AUTHSERV_NICK)) return;
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->user == target) break;
+    }
+    if(!bot) return;
+    char *auth = NULL;
+    int do_match = 0, exists = 0;
+    char *tmp;
+    time_t registered = time(0);
+    struct tm *timeinfo;
+    //messages to parse:
+    //  Account * has not been registered.
+    //  Account information for Skynet:
+    if(!match("Account * has not been registered.", message)) {
+        do_match = 1;
+        tmp = strstr(message, "\002");
+        auth = tmp+1;
+        tmp = strstr(auth, "\002");
+        *tmp = '\0';
+    } else if(!match("Account information for *", message)) {
+        do_match = 2;
+        exists = 1;
+        tmp = strstr(message, "\002");
+        auth = tmp+1;
+        tmp = strstr(auth, "\002");
+        *tmp = '\0';
+    } else if(!match("  Registered on: *", message)) {
+        do_match = 1;
+        exists = 1;
+        tmp = strstr(message, ": ");
+        tmp += 2;
+        timeinfo = localtime(&registered);
+        timeinfo->tm_year = 0;
+        //parse time
+        //Sat Nov 19 14:52:57 2011
+        tmp = strchr(tmp, ' ');
+        if(!tmp) goto errparse;
+        tmp++;
+        char *tmp2 = strchr(tmp, ' ');
+        if(!tmp2) goto errparse;
+        *tmp2 = '\0';
+        if(!stricmp(tmp, "Jan"))
+            timeinfo->tm_mon = 0;
+        else if(!stricmp(tmp, "Feb"))
+            timeinfo->tm_mon = 1;
+        else if(!stricmp(tmp, "Mar"))
+            timeinfo->tm_mon = 2;
+        else if(!stricmp(tmp, "Apr"))
+            timeinfo->tm_mon = 3;
+        else if(!stricmp(tmp, "May"))
+            timeinfo->tm_mon = 4;
+        else if(!stricmp(tmp, "Jun"))
+            timeinfo->tm_mon = 5;
+        else if(!stricmp(tmp, "Jul"))
+            timeinfo->tm_mon = 6;
+        else if(!stricmp(tmp, "Aug"))
+            timeinfo->tm_mon = 7;
+        else if(!stricmp(tmp, "Sep"))
+            timeinfo->tm_mon = 8;
+        else if(!stricmp(tmp, "Oct"))
+            timeinfo->tm_mon = 9;
+        else if(!stricmp(tmp, "Nov"))
+            timeinfo->tm_mon = 10;
+        else if(!stricmp(tmp, "Dec"))
+            timeinfo->tm_mon = 11;
+        tmp = tmp2 + 1;
+        tmp2 = strchr(tmp, ' ');
+        if(!tmp2) goto errparse;
+        *tmp2 = '\0';
+        timeinfo->tm_mday = atoi(tmp);
+        tmp = tmp2 + 1;
+        if(*tmp == ' ') tmp++;
+        tmp2 = strchr(tmp, ':');
+        if(!tmp2) goto errparse;
+        *tmp2 = '\0';
+        timeinfo->tm_hour = atoi(tmp);
+        tmp = tmp2 + 1;
+        tmp2 = strchr(tmp, ':');
+        if(!tmp2) goto errparse;
+        *tmp2 = '\0';
+        timeinfo->tm_min = atoi(tmp);
+        tmp = tmp2 + 1;
+        tmp2 = strchr(tmp, ' ');
+        if(!tmp2) goto errparse;
+        *tmp2 = '\0';
+        timeinfo->tm_sec = atoi(tmp);
+        tmp = tmp2 + 1;
+        timeinfo->tm_year = atoi(tmp) - 1900;
+        registered = mktime(timeinfo);
+    }
+    errparse:
+    
+    if(do_match) {
+        #ifdef HAVE_THREADS
+        unsigned int tid = (unsigned int) pthread_self_tid();
+        while(!clientsocket_parseorder_top(tid)) {
+            usleep(1000); //1ms
+        }
+        #endif
+        struct HandleInfoQueueEntry* entry = getNextHandleInfoQueueEntry(bot, ((do_match != 2) ? 1 : 0));
+        if(entry) {
+            if(do_match == 2) {
+                free(entry->auth);
+                entry->auth = strdup(auth);
+                return;
+            }
+            authlookup_callback_t *callback;
+            int i;
+            for(i = 0; i < MAXCALLBACKS; i++) {
+                callback = entry->callback[i];
+                if(!callback) break;
+                if(!entry->module_id[i] || module_loaded(entry->module_id[i]))
+                    callback(entry->auth, exists, registered, entry->data[i]);
+            }
+            free(entry->auth);
+            free(entry);
+        }
+    }
+}
+
+void init_handleinfohandler() {
+    bind_privnotice(recv_notice, 0);
+}
+
+void free_handleinfohandler() {
+    
+}
diff --git a/src/HandleInfoHandler.h b/src/HandleInfoHandler.h
new file mode 100644 (file)
index 0000000..a796a6d
--- /dev/null
@@ -0,0 +1,34 @@
+/* HandleInfoHandler.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _HandleInfoHandler_h
+#define _HandleInfoHandler_h
+
+#include "main.h"
+
+struct ClientSocket;
+struct UserNode;
+
+#define AUTHLOOKUP_CALLBACK(NAME) void NAME(UNUSED_ARG(char *auth), UNUSED_ARG(int exists), UNUSED_ARG(time_t registered), UNUSED_ARG(void *data))
+typedef AUTHLOOKUP_CALLBACK(authlookup_callback_t);
+
+#ifndef DND_FUNCTIONS
+void clear_handleinfoqueue(struct ClientSocket *client);
+/* MODULAR ACCESSIBLE */ void lookup_authname(char *auth, int module_id, authlookup_callback_t callback, void *data);
+void init_handleinfohandler();
+void free_handleinfohandler();
+#endif
+#endif
index 5f68240617f1304da64ddbf9a1f6a9e1f84be53e..411173a82c443283ec77f3f9ac460777be73fc3b 100644 (file)
     pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE_VAL);\
     pthread_mutex_init(&var, &mutex_attr); \
 }
+#ifdef ENABLE_MUTEX_DEBUG
+#include "mutexDebug.h"
+#define IOSYNCHRONIZE(var) xmutex(1, &var, __FILE__, __LINE__); pthread_mutex_lock(&var)
+#define IODESYNCHRONIZE(var) xmutex(0, &var, __FILE__, __LINE__); pthread_mutex_unlock(&var)
+#else
 #define IOSYNCHRONIZE(var) pthread_mutex_lock(&var)
 #define IODESYNCHRONIZE(var) pthread_mutex_unlock(&var)
+#endif
 #else
 #define IOTHREAD_MUTEX_INIT(var)
 #define IOSYNCHRONIZE(var)
index eb32cdc71ab345ca8961e4e411b72acfa8064cef..82781d61e0be415f1ad662b0154b8b461316b820 100644 (file)
@@ -16,6 +16,7 @@
  */
 #ifndef _IOHandler_h
 #define _IOHandler_h
+#include "../config.h" /* configure script autogenerated */
 #include <stddef.h>
 #include <sys/time.h> /* struct timeval */
 
diff --git a/src/IPNode.c b/src/IPNode.c
new file mode 100644 (file)
index 0000000..bed8ac7
--- /dev/null
@@ -0,0 +1,150 @@
+/* IPNode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "IPNode.h"
+
+#define hex2dec(x) (isxdigit(x) ? (isdigit(x) ? x - '0' : (isupper(x) ? x - 'A' : x - 'a') + 10) : 0)
+
+static struct IPNode *parseIP(struct IPNode *ip, const char *org_ipstr);
+
+struct IPNode *createIPNode(const char *ipstr) {
+    struct IPNode *ip = malloc(sizeof(*ip));
+    if(!ip) return NULL;
+    ip->flags = 0;
+    if(!parseIP(ip, ipstr)) {
+        free(ip);
+        ip = NULL;
+    }
+    return ip;
+}
+
+static struct IPNode *parseIP(struct IPNode *ip, const char *org_ipstr) {
+    char ipstr[strlen(org_ipstr)+1];
+    strcpy(ipstr, org_ipstr);
+    if(strchr(ipstr, ':')) {
+        ip->flags |= IPNODE_IS_IPV6;
+        char *p1 = ipstr;
+        char *p2;
+        int blocks = 1;
+        while(*p1) {
+            if(p1[0] == ':' && p1[1] == ':') {
+                //double ::
+                p1++;
+            }
+            if(*p1 == ':')
+                blocks++;
+            if(*p1 == '/') {
+                *p1 = '\0';
+                break;
+            }
+            p1++;
+        }
+        if(blocks > 8) return 0;
+        char hexa[32];
+        int i;
+        for(i = 0; i < 32; i++)
+            hexa[i] = '0';
+        i = 0;
+        int p = 0;
+        int len;
+        while(*p1) {
+            if(p1[0] == ':' && p1[1] == ':') {
+                i = 8 - blocks;
+                p1 += 2;
+                p = 0;
+                p2 = p1;
+                len = 0;
+                while(*p2 && *p2 != ':') {
+                    len++;
+                    p2++;
+                }
+                continue;
+            }
+            else if(*p1 == ':') {
+                p1++;
+                i++;
+                p = 0;
+                len = 0;
+                while(*p2 && *p2 != ':') {
+                    len++;
+                    p2++;
+                }
+                continue;
+            }
+            if(p >= 4 || i >= 8) return NULL;
+            hexa[i*4 + ((4-len) + (p++))] = *p1;
+            p1++;
+        }
+        for(i = 0, p = 0; i < 32; i+=2, p++) {
+            ip->ipdata[p] = hex2dec(hexa[i]) << 4;
+            ip->ipdata[p] |= hex2dec(hexa[i+1]);
+        }
+    } else if(strchr(ipstr, '.')) {
+        char *ippart = ipstr;
+        char *next = strchr(ipstr, '/');
+        int i = 0;
+        if(next) {
+            *next = '\0';
+            next = NULL;
+        }
+        do {
+            next = strchr(ippart, '.');
+            if(next) *next = '\0';
+            if(i >= 4) return NULL;
+            ip->ipdata[12+(i++)] = (unsigned char) atoi(ippart);
+            if(next) {
+                ippart = next+1;
+                continue;
+            }
+            break;
+        } while(1);
+        if(i != 3) return NULL;
+    } else
+        return NULL;
+    return ip;
+}
+
+int ipmatch(struct IPNode *ip1, struct IPNode *ip2, int bits) {
+    if(!bits) return 0;
+    bits = (ip2->flags & IPNODE_IS_IPV6 ? bits : bits + (12*8));
+    if(bits > 128) return 1;
+    int sbit = (bits % 8);
+    int check = (bits / 8) + (sbit ? 1 : 0);
+    int i;
+    for(i = 0; i < check; i++) {
+        if(i == check-1 && sbit) {
+            int bitmask = 0;
+            int j = 0;
+            for(;j < sbit; j++) {
+                bitmask <<= 1;
+                bitmask |= 1;
+            }
+            for(;j < 8; j++) {
+                bitmask <<= 1;
+                bitmask |= 0;
+            }
+            if((ip1->ipdata[i] & bitmask) != (ip2->ipdata[i] & bitmask))
+                return 1;
+        } else if(ip1->ipdata[i] != ip2->ipdata[i])
+            return 1;
+    }
+    return 0;
+}
+
+void freeIPNode(struct IPNode *ip) {
+    free(ip);
+}
+
diff --git a/src/IPNode.h b/src/IPNode.h
new file mode 100644 (file)
index 0000000..737bf7f
--- /dev/null
@@ -0,0 +1,33 @@
+/* IPNode.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _IPNode_h
+#define _IPNode_h
+#include "main.h"
+
+#define IPNODE_IS_IPV6  0x01
+#define IPNODE_IS_LOCAL 0x02
+
+struct IPNode {
+    unsigned char flags;
+    unsigned char ipdata[16]; // 16 * 8bit = 128bit
+};
+
+struct IPNode *createIPNode(const char *ipstr);
+int ipmatch(struct IPNode *ip1, struct IPNode *ip2, int bits);
+void freeIPNode(struct IPNode *ip);
+
+#endif
\ No newline at end of file
diff --git a/src/IRCEvents.c b/src/IRCEvents.c
new file mode 100644 (file)
index 0000000..2eea34d
--- /dev/null
@@ -0,0 +1,237 @@
+/* IRCEvents.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "IRCEvents.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "ClientSocket.h"
+#include "mysqlConn.h"
+#include "log.h"
+
+struct binding {
+    void *func;
+    int module_id;
+    struct binding *next;
+};
+
+static void **binds = NULL;
+#define BIND_TYPE_JOIN       0
+#define BIND_TYPE_NICK       1
+#define BIND_TYPE_PART       2
+#define BIND_TYPE_QUIT       3
+#define BIND_TYPE_KICK       4
+#define BIND_TYPE_TOPIC      5
+#define BIND_TYPE_MODE       6
+#define BIND_TYPE_CHANMSG    7
+#define BIND_TYPE_PRIVMSG    8
+#define BIND_TYPE_CHANNOTICE 9
+#define BIND_TYPE_PRIVNOTICE 10
+#define BIND_TYPE_CHANCTCP   11
+#define BIND_TYPE_PRIVCTCP   12
+#define BIND_TYPE_INVITE     13
+#define BIND_TYPE_RAW        14
+#define BIND_TYPE_BOT_READY  15
+#define BIND_TYPE_REGISTERED 16
+#define BIND_TYPE_FREEUSER   17
+#define BIND_TYPE_FREECHAN   18
+#define BIND_TYPE_RELOAD     19
+#define BIND_TYPE_FREECLIENT 20
+
+#define TOTAL_BIND_TYPES     21
+
+void init_bind() {
+    if(binds)
+        return;
+    binds = calloc(TOTAL_BIND_TYPES, sizeof(*binds));
+}
+
+void free_bind() {
+    struct binding *cbind, *next;
+    int i;
+    for(i = 0; i < TOTAL_BIND_TYPES; i++) {
+        for(cbind = binds[i]; cbind; cbind = next) {
+            next = cbind->next;
+            free(cbind);
+        }
+    }
+    free(binds);
+    binds = NULL;
+}
+
+void unregister_module_events(int module_id) {
+    struct binding *cbind, *next, *prev;
+    int i;
+    for(i = 0; i < TOTAL_BIND_TYPES; i++) {
+        prev = NULL;
+        for(cbind = binds[i]; cbind; cbind = next) {
+            next = cbind->next;
+            if(cbind->module_id == module_id) {
+                if(prev)
+                    prev->next = next;
+                else
+                    binds[i] = next;
+                free(cbind);
+            } else
+                prev = cbind;
+        }
+    }
+}
+
+static int is_bound(unsigned char type, void *func) {
+    struct binding *cbind;
+    for(cbind = binds[type]; cbind; cbind = cbind->next) {
+        if(cbind->func == func) 
+            return 1;
+    }
+    return 0;
+}
+
+#define FUNC_BIND(NAME,FUNCTYPE,TYPE) \
+int bind_##NAME(FUNCTYPE *func, int module_id) { \
+    if(!is_bound(TYPE, func)) { \
+        struct binding *cbind = malloc(sizeof(*cbind)); \
+        if (!cbind) { \
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__); \
+            return 0; \
+        } \
+        cbind->func = func; \
+        cbind->module_id = module_id; \
+        cbind->next = binds[TYPE]; \
+        binds[TYPE] = cbind; \
+        return 1; \
+    } \
+    return 0; \
+}
+
+#define FUNC_UNBIND(NAME,FUNCTYPE,TYPE) \
+void unbind_##NAME(FUNCTYPE *func) { \
+    struct binding *cbind, *last = NULL, *next; \
+    for(cbind = binds[TYPE]; cbind; cbind = next) { \
+        next = cbind->next; \
+        if(cbind->func == func) { \
+            if(last) \
+                last->next = cbind->next; \
+            else \
+                binds[TYPE] = cbind->next; \
+            free(cbind); \
+        } else \
+            last = cbind; \
+    } \
+}
+
+#define FUNC_EVENT(NAME,FUNCTYPE,TYPE,PDECLARATION,PLIST) \
+int event_##NAME PDECLARATION { \
+    struct binding *cbind; \
+    pre_event(TYPE); \
+    for(cbind = binds[TYPE]; cbind; cbind = cbind->next) { \
+        FUNCTYPE *func = cbind->func; \
+        func PLIST; \
+    } \
+    post_event(TYPE); \
+    return 1; \
+}
+
+void pre_event(UNUSED_ARG(int type)) {
+
+}
+
+void post_event(UNUSED_ARG(int type)) {
+
+}
+
+//EVENTS
+
+FUNC_BIND(join, join_func_t, BIND_TYPE_JOIN)
+FUNC_UNBIND(join, join_func_t, BIND_TYPE_JOIN)
+FUNC_EVENT(join, join_func_t, BIND_TYPE_JOIN, (struct ChanUser *chanuser), (chanuser))
+
+FUNC_BIND(nick, nick_func_t, BIND_TYPE_NICK)
+FUNC_UNBIND(nick, nick_func_t, BIND_TYPE_NICK)
+FUNC_EVENT(nick, nick_func_t, BIND_TYPE_NICK, (struct UserNode *user, char *new_nick), (user, new_nick))
+
+FUNC_BIND(part, part_func_t, BIND_TYPE_PART)
+FUNC_UNBIND(part, part_func_t, BIND_TYPE_PART)
+FUNC_EVENT(part, part_func_t, BIND_TYPE_PART, (struct ChanUser *chanuser, int quit, char *reason), (chanuser, quit, reason))
+
+FUNC_BIND(kick, kick_func_t, BIND_TYPE_KICK)
+FUNC_UNBIND(kick, kick_func_t, BIND_TYPE_KICK)
+FUNC_EVENT(kick, kick_func_t, BIND_TYPE_KICK, (struct UserNode *user, struct ChanUser *target, char *reason), (user, target, reason))
+
+FUNC_BIND(topic, topic_func_t, BIND_TYPE_TOPIC)
+FUNC_UNBIND(topic, topic_func_t, BIND_TYPE_TOPIC)
+FUNC_EVENT(topic, topic_func_t, BIND_TYPE_TOPIC, (struct UserNode *user, struct ChanNode *chan, const char *new_topic), (user, chan, new_topic))
+
+FUNC_BIND(mode, mode_func_t, BIND_TYPE_MODE)
+FUNC_UNBIND(mode, mode_func_t, BIND_TYPE_MODE)
+FUNC_EVENT(mode, mode_func_t, BIND_TYPE_MODE, (struct UserNode *user, struct ChanNode *chan, char *modes, char **args, int argc), (user, chan, modes, args, argc))
+
+FUNC_BIND(chanmsg, chanmsg_func_t, BIND_TYPE_CHANMSG)
+FUNC_UNBIND(chanmsg, chanmsg_func_t, BIND_TYPE_CHANMSG)
+FUNC_EVENT(chanmsg, chanmsg_func_t, BIND_TYPE_CHANMSG, (struct UserNode *user, struct ChanNode *chan, char *message), (user, chan, message))
+
+FUNC_BIND(privmsg, privmsg_func_t, BIND_TYPE_PRIVMSG)
+FUNC_UNBIND(privmsg, privmsg_func_t, BIND_TYPE_PRIVMSG)
+FUNC_EVENT(privmsg, privmsg_func_t, BIND_TYPE_PRIVMSG, (struct UserNode *user, struct UserNode *target, char *message), (user, target, message))
+
+FUNC_BIND(channotice, channotice_func_t, BIND_TYPE_CHANNOTICE)
+FUNC_UNBIND(channotice, channotice_func_t, BIND_TYPE_CHANNOTICE)
+FUNC_EVENT(channotice, channotice_func_t, BIND_TYPE_CHANNOTICE, (struct UserNode *user, struct ChanNode *chan, char *message), (user, chan, message))
+
+FUNC_BIND(privnotice, privnotice_func_t, BIND_TYPE_PRIVNOTICE)
+FUNC_UNBIND(privnotice, privnotice_func_t, BIND_TYPE_PRIVNOTICE)
+FUNC_EVENT(privnotice, privnotice_func_t, BIND_TYPE_PRIVNOTICE, (struct UserNode *user, struct UserNode *target, char *message), (user, target, message))
+
+FUNC_BIND(chanctcp, chanctcp_func_t, BIND_TYPE_CHANCTCP)
+FUNC_UNBIND(chanctcp, chanctcp_func_t, BIND_TYPE_CHANCTCP)
+FUNC_EVENT(chanctcp, chanctcp_func_t, BIND_TYPE_CHANCTCP, (struct UserNode *user, struct ChanNode *chan, char *command, char *text), (user, chan, command, text))
+
+FUNC_BIND(privctcp, privctcp_func_t, BIND_TYPE_PRIVCTCP)
+FUNC_UNBIND(privctcp, privctcp_func_t, BIND_TYPE_PRIVCTCP)
+FUNC_EVENT(privctcp, privctcp_func_t, BIND_TYPE_PRIVCTCP, (struct UserNode *user, struct UserNode *target, char *command, char *text), (user, target, command, text))
+
+FUNC_BIND(invite, invite_func_t, BIND_TYPE_INVITE)
+FUNC_UNBIND(invite, invite_func_t, BIND_TYPE_INVITE)
+FUNC_EVENT(invite, invite_func_t, BIND_TYPE_INVITE, (struct ClientSocket *client, struct UserNode *user, char *channel), (client, user, channel))
+
+FUNC_BIND(raw, raw_func_t, BIND_TYPE_RAW)
+FUNC_UNBIND(raw, raw_func_t, BIND_TYPE_RAW)
+FUNC_EVENT(raw, raw_func_t, BIND_TYPE_RAW, (struct ClientSocket *client, char *from, char *cmd, char **argv, int argc), (client, from, cmd, argv, argc))
+
+FUNC_BIND(bot_ready, bot_ready_func_t, BIND_TYPE_BOT_READY)
+FUNC_UNBIND(bot_ready, bot_ready_func_t, BIND_TYPE_BOT_READY)
+FUNC_EVENT(bot_ready, bot_ready_func_t, BIND_TYPE_BOT_READY, (struct ClientSocket *client), (client))
+
+FUNC_BIND(registered, registered_func_t, BIND_TYPE_REGISTERED)
+FUNC_UNBIND(registered, registered_func_t, BIND_TYPE_REGISTERED)
+FUNC_EVENT(registered, registered_func_t, BIND_TYPE_REGISTERED, (struct UserNode *user, char *new_mask), (user, new_mask))
+
+FUNC_BIND(freeuser, freeuser_func_t, BIND_TYPE_FREEUSER)
+FUNC_UNBIND(freeuser, freeuser_func_t, BIND_TYPE_FREEUSER)
+FUNC_EVENT(freeuser, freeuser_func_t, BIND_TYPE_FREEUSER, (struct UserNode *user), (user))
+
+FUNC_BIND(freechan, freechan_func_t, BIND_TYPE_FREECHAN)
+FUNC_UNBIND(freechan, freechan_func_t, BIND_TYPE_FREECHAN)
+FUNC_EVENT(freechan, freechan_func_t, BIND_TYPE_FREECHAN, (struct ChanNode *chan), (chan))
+
+FUNC_BIND(reload, reload_func_t, BIND_TYPE_RELOAD)
+FUNC_UNBIND(reload, reload_func_t, BIND_TYPE_RELOAD)
+FUNC_EVENT(reload, reload_func_t, BIND_TYPE_RELOAD, (int initialization), (initialization))
+
+FUNC_BIND(freeclient, freeclient_func_t, BIND_TYPE_FREECLIENT)
+FUNC_UNBIND(freeclient, freeclient_func_t, BIND_TYPE_FREECLIENT)
+FUNC_EVENT(freeclient, freeclient_func_t, BIND_TYPE_FREECLIENT, (struct ClientSocket *client), (client))
diff --git a/src/IRCEvents.h b/src/IRCEvents.h
new file mode 100644 (file)
index 0000000..7a7ac3e
--- /dev/null
@@ -0,0 +1,173 @@
+/* IRCEvents.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _IRCEvents_h
+#define _IRCEvents_h
+
+#include "main.h"
+
+struct UserNode;
+struct ChanNode;
+struct ChanUser;
+struct ClientSocket;
+
+#ifndef DND_FUNCTIONS
+void init_bind();
+void free_bind();
+void unregister_module_events(int module_id);
+#endif
+
+typedef void join_func_t(struct ChanUser *chanuser);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_join(join_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_join(join_func_t *func);
+int event_join(struct ChanUser *chanuser);
+#endif
+
+typedef void nick_func_t(struct UserNode *user, char *new_nick);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_nick(nick_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_nick(nick_func_t *func);
+int event_nick(struct UserNode *user, char *new_nick);
+#endif
+
+typedef void part_func_t(struct ChanUser *chanuser, int quit, char *reason);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_part(part_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_part(part_func_t *func);
+int event_part(struct ChanUser *chanuser, int quit, char *reason);
+#endif
+
+typedef void kick_func_t(struct UserNode *user, struct ChanUser *target, char *reason);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_kick(kick_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_kick(kick_func_t *func);
+int event_kick(struct UserNode *user, struct ChanUser *target, char *reason);
+#endif
+
+typedef void topic_func_t(struct UserNode *user, struct ChanNode *chan, const char *new_topic);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_topic(topic_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_topic(topic_func_t *func);
+int event_topic(struct UserNode *user, struct ChanNode *chan, const char *new_topic);
+#endif
+
+typedef void mode_func_t(struct UserNode *user, struct ChanNode *chan, char *modes, char **argv, int argc);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_mode(mode_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_mode(mode_func_t *func);
+int event_mode(struct UserNode *user, struct ChanNode *chan, char *modes, char **argv, int argc);
+#endif
+
+typedef void chanmsg_func_t(struct UserNode *user, struct ChanNode *chan, char *message);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_chanmsg(chanmsg_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_chanmsg(chanmsg_func_t *func);
+int event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message);
+#endif
+
+typedef void privmsg_func_t(struct UserNode *user, struct UserNode *target, char *message);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_privmsg(privmsg_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_privmsg(privmsg_func_t *func);
+int event_privmsg(struct UserNode *user, struct UserNode *target, char *message);
+#endif
+
+typedef void channotice_func_t(struct UserNode *user, struct ChanNode *chan, char *message);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_channotice(channotice_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_channotice(channotice_func_t *func);
+int event_channotice(struct UserNode *user, struct ChanNode *chan, char *message);
+#endif
+
+typedef void privnotice_func_t(struct UserNode *user, struct UserNode *target, char *message);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_privnotice(privnotice_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_privnotice(privnotice_func_t *func);
+int event_privnotice(struct UserNode *user, struct UserNode *target, char *message);
+#endif
+
+typedef void chanctcp_func_t(struct UserNode *user, struct ChanNode *chan, char *command, char *text);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_chanctcp(chanctcp_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_chanctcp(chanctcp_func_t *func);
+int event_chanctcp(struct UserNode *user, struct ChanNode *chan, char *command, char *text);
+#endif
+
+typedef void privctcp_func_t(struct UserNode *user, struct UserNode *target, char *command, char *text);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_privctcp(privctcp_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_privctcp(privctcp_func_t *func);
+int event_privctcp(struct UserNode *user, struct UserNode *target, char *command, char *text);
+#endif
+
+typedef void invite_func_t(struct ClientSocket *client, struct UserNode *user, char *channel);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_invite(invite_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_invite(invite_func_t *func);
+int event_invite(struct ClientSocket *client, struct UserNode *user, char *channel);
+#endif
+
+typedef void raw_func_t(struct ClientSocket *client, char *from, char *cmd, char **argv, int argc);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_raw(raw_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_raw(raw_func_t *func);
+int event_raw(struct ClientSocket *client, char *from, char *cmd, char **argv, int argc);
+#endif
+
+typedef void bot_ready_func_t(struct ClientSocket *client);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_bot_ready(bot_ready_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_bot_ready(bot_ready_func_t *func);
+int event_bot_ready(struct ClientSocket *client);
+#endif
+
+typedef void registered_func_t(struct UserNode *user, char *new_mask);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_registered(registered_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_registered(registered_func_t *func);
+int event_registered(struct UserNode *user, char *new_mask);
+#endif
+
+typedef int freeuser_func_t(struct UserNode *user);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_freeuser(freeuser_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_freeuser(freeuser_func_t *func);
+int event_freeuser(struct UserNode *user);
+#endif
+
+typedef int freechan_func_t(struct ChanNode *chan);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_freechan(freechan_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_freechan(freechan_func_t *func);
+int event_freechan(struct ChanNode *chan);
+#endif
+
+typedef int reload_func_t(int initialization);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_reload(reload_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_reload(reload_func_t *func);
+int event_reload(int initialization);
+#endif
+
+typedef void freeclient_func_t(struct ClientSocket *client);
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int bind_freeclient(freeclient_func_t *func, int module_id);
+/* MODULAR ACCESSIBLE */ void unbind_freeclient(freeclient_func_t *func);
+int event_freeclient(struct ClientSocket *chan);
+#endif
+
+#endif
diff --git a/src/IRCParser.c b/src/IRCParser.c
new file mode 100644 (file)
index 0000000..89047f8
--- /dev/null
@@ -0,0 +1,1016 @@
+/* IRCParser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "IRCParser.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "IRCEvents.h"
+#include "ClientSocket.h"
+#include "WHOHandler.h"
+#include "lang.h"
+#include "DBHelper.h"
+#include "BanNode.h"
+#include "ModeNode.h"
+#include "tools.h"
+#include "bots.h"
+#include "timeq.h"
+#include "ConfigParser.h"
+#include "statistics.h"
+#include "log.h"
+
+struct irc_cmd *irc_commands = NULL;
+//static struct UserNode *registering_users = NULL;
+int statistics_privmsg = 0;
+int statistics_network_users = 0;
+int statistics_network_channels = 0;
+
+static void register_irc_function(char *command, irc_cmd_t *func);
+static void parse_raw(struct ClientSocket *client, char *from, char *cmd, char **argv, int argc);
+
+struct OplessRejoinUserbot {
+    char *nick;
+    char *auth;
+};
+
+struct OplessRejoinBot {
+    unsigned char is_client;
+    union {
+        struct ClientSocket *client;
+        struct OplessRejoinUserbot *userbot;
+    } bot;
+    struct OplessRejoinBot *next;
+};
+
+void parse_line(struct ClientSocket *client, char *line) {
+    int argc = 0;
+    char *argv[MAXNUMPARAMS];
+    #ifdef HAVE_THREADS
+    printf_log("main", LOG_IRCRAW, "[%d recv %lu] %s\n", getCurrentThreadID(), (unsigned long) strlen(line), line);
+    #else
+    printf_log("main", LOG_IRCRAW, "[recv %lu] %s\n", (unsigned long) strlen(line), line);
+    #endif
+    if(line[0] == ':')
+        line++;
+    else
+        argv[argc++] = NULL;
+    while(*line) {
+        //skip leading spaces
+        while (*line == ' ')
+            *line++ = 0;
+        if (*line == ':') {
+           //the rest is a single parameter
+           argv[argc++] = line + 1;
+           break;
+        }
+        argv[argc++] = line;
+        if (argc >= MAXNUMPARAMS)
+            break;
+        while (*line != ' ' && *line)
+            line++;
+    }
+    if(argc >= 2) {
+        parse_raw(client, argv[0], argv[1], argv+2, argc-2);
+    }
+}
+
+static void register_irc_function(char *command, irc_cmd_t *func) {
+    struct irc_cmd *irc_cmd = malloc(sizeof(*irc_cmd));
+    if (!irc_cmd)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    irc_cmd->cmd = command;
+    irc_cmd->func = func;
+    irc_cmd->next = irc_commands;
+    irc_commands = irc_cmd;
+}
+
+static void parse_raw(struct ClientSocket *client, char *from, char *cmd, char **argv, int argc) {
+    struct irc_cmd *irc_cmd;
+    int ret = 0;
+    for(irc_cmd = irc_commands; irc_cmd; irc_cmd = irc_cmd->next) {
+        if(!stricmp(irc_cmd->cmd, cmd)) {
+            ret = irc_cmd->func(client, from, argv, argc);
+            break;
+        }
+    }
+    if(!irc_cmd) {
+        event_raw(client, from, cmd, argv, argc);
+    } else if(!ret) {
+        printf_log("main", LOG_WARNING | LOG_IRCRAW, "PARSE ERROR: %s %s %s\n", (from ? from : "*"), cmd, merge_argv(argv, 0, argc));
+    }
+}
+
+static void increase_viscount_butone(struct ChanNode *chan, struct ChanUser *ignore) {
+    struct ChanUser *chanuser;
+    
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(chanuser == ignore)
+            continue;
+        chanuser->visCount++;
+    }
+}
+
+static void decrease_viscount_butone(struct ChanNode *chan, struct ChanUser *ignore) {
+    struct ChanUser *chanuser, *next_chanuser;
+    
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = next_chanuser) {
+        next_chanuser = getChannelUsers(chan, chanuser);
+        if(chanuser == ignore)
+            continue;
+        chanuser->visCount--;
+        if(chanuser->visCount <= 0)
+            delChanUser(chanuser, 1);
+    }
+}
+
+static USERLIST_CALLBACK(got_channel_userlist) {
+    struct ChanUser *chanuser = data;
+    
+    increase_viscount_butone(chanuser->chan, chanuser);
+    
+    event_join(chanuser);
+}
+
+static IRC_CMD(raw_002) { //fixed: ZNC fakes a 001 raw even if we're not connected!
+    struct UserNode *user = getUserByNick(argv[0]);
+    if(!user)
+        user = addUser(argv[0]);
+    client->user = user;
+    client->user->flags |= USERFLAG_ISBOT;
+    client->flags |= SOCKET_FLAG_READY;
+    event_bot_ready(client);
+    return 1;
+}
+
+static int check_userbot_rejoin(struct UserNode *user) {
+    if(!(user->flags & USERFLAG_ISAUTHED)) return 0;
+    char tmp[MAXLEN];
+    sprintf(tmp, "General.UserBots.%s.enabled", user->auth);
+    if(!get_int_field(tmp))
+        return 0;
+    sprintf(tmp, "General.UserBots.%s.nicks", user->auth);
+    char *nicks = get_string_field(tmp);
+    if(nicks) {
+        char *cnick = nicks;
+        int matching_nick = 0;
+        do {
+            nicks = strchr(cnick, ',');
+            if(nicks) 
+                *nicks = '\0';
+            if(!match(cnick, user->nick))
+                matching_nick = 1;
+            if(nicks) {
+                *nicks = ',';
+                nicks++;
+            }
+        } while((cnick = nicks) && !matching_nick);
+        if(!matching_nick)
+            return 0;
+    }
+    sprintf(tmp, "General.UserBots.%s.opless_part", user->auth);
+    if(!get_string_field(tmp))
+        return 0;
+    return 1;
+}
+
+static void free_rejoin_clients(struct ChanNode *chan, int rejoin) {
+    struct OplessRejoinBot *rejoin_bot, *next_rejoin_bot;
+    char tmp[MAXLEN];
+    int sourceid;
+    struct ClientSocket *bot;
+    for(rejoin_bot = chan->rejoin_bots; rejoin_bot; rejoin_bot = next_rejoin_bot) {
+        next_rejoin_bot = rejoin_bot->next;
+        if(rejoin) {
+            if(rejoin_bot->is_client) {
+                putsock(rejoin_bot->bot.client, "JOIN %s", chan->name);
+            } else {
+                sprintf(tmp, "General.UserBots.%s.sourcebot", rejoin_bot->bot.userbot->auth);
+                if(get_string_field(tmp)) {
+                    sourceid = resolve_botalias(get_string_field(tmp));
+                    if(sourceid == -1)
+                        sourceid = 0;
+                } else 
+                    sourceid = 0;
+                bot = getChannelBot(NULL, sourceid);
+                if(!bot)
+                    bot = getChannelBot(NULL, 0);
+                sprintf(tmp, "General.UserBots.%s.opless_join", rejoin_bot->bot.userbot->auth);
+                if(get_string_field(tmp))
+                    putsock(bot, get_string_field(tmp), rejoin_bot->bot.userbot->nick, chan->name);
+            }
+        }
+        if(!rejoin_bot->is_client) {
+            free(rejoin_bot->bot.userbot->nick);
+            free(rejoin_bot->bot.userbot->auth);
+            free(rejoin_bot->bot.userbot);
+        }
+        free(rejoin_bot);
+    }
+    if(chan->rejoin_timeout)
+        timeq_del(chan->rejoin_timeout);
+}
+
+static TIMEQ_CALLBACK(full_rejoin_timeout) {
+    struct ChanNode *chan = data;
+    chan->rejoin_timeout = NULL;
+    free_rejoin_clients(chan, 1);
+    chan->flags &= ~CHANFLAG_REJOINING;
+}
+
+static void check_full_rejoin(struct ChanNode *chan) {
+    struct ChanUser *chanuser;
+    char do_rejoin = 1;
+    int botcount = 0;
+    int userbots = 0;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if((chanuser->flags & CHANUSERFLAG_OPPED) || !(isBot(chanuser->user) || check_userbot_rejoin(chanuser->user))) {
+            do_rejoin = 0;
+            break;
+        }
+        botcount++;
+        if(!isBot(chanuser->user))
+            userbots++;
+    }
+    if(do_rejoin) {
+        struct OplessRejoinBot *rejoin_bot;
+        struct ClientSocket *bot, *chanbot = NULL;
+        chan->rejoin_bots = NULL;
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(!isUserOnChan(bot->user, chan))
+                continue;
+            if(!chanbot && ((bot->flags & SOCKET_FLAG_PREFERRED) || !getBots(SOCKET_FLAG_READY, bot)))
+                chanbot = bot;
+            else {
+                rejoin_bot = malloc(sizeof(*rejoin_bot));
+                rejoin_bot->is_client = 1;
+                rejoin_bot->bot.client = bot;
+                rejoin_bot->next = chan->rejoin_bots;
+                chan->rejoin_bots = rejoin_bot;
+                putsock(bot, "PART %s :rejoining", chan->name);
+            }
+        }
+        if(userbots) {
+            char tmp[MAXLEN];
+            int sourceid;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if(check_userbot_rejoin(chanuser->user)) {
+                    rejoin_bot = malloc(sizeof(*rejoin_bot));
+                    rejoin_bot->is_client = 0;
+                    rejoin_bot->bot.userbot = malloc(sizeof(struct OplessRejoinUserbot));
+                    rejoin_bot->bot.userbot->nick = strdup(chanuser->user->nick);
+                    rejoin_bot->bot.userbot->auth = strdup(chanuser->user->auth);
+                    rejoin_bot->next = chan->rejoin_bots;
+                    chan->rejoin_bots = rejoin_bot;
+                    sprintf(tmp, "General.UserBots.%s.sourcebot", chanuser->user->auth);
+                    if(get_string_field(tmp)) {
+                        sourceid = resolve_botalias(get_string_field(tmp));
+                        if(sourceid == -1)
+                            sourceid = 0;
+                    } else 
+                        sourceid = 0;
+                    bot = getChannelBot(NULL, sourceid);
+                    if(!bot)
+                        bot = getChannelBot(NULL, 0);
+                    sprintf(tmp, "General.UserBots.%s.opless_part", chanuser->user->auth);
+                    putsock(bot, get_string_field(tmp), chanuser->user->nick, chan->name);
+                }
+            }
+        }
+        
+        if(botcount == 1) {
+            //we're alone
+            free(chan->rejoin_bots);
+            putsock(chanbot, "PART %s :magic hop", chan->name);
+            putsock(chanbot, "JOIN %s", chan->name);
+        } else {
+            chan->flags |= CHANFLAG_REJOINING;
+            chan->rejoin_timeout = timeq_add(10, 0, full_rejoin_timeout, chan);
+        }
+    }
+}
+
+static IRC_CMD(raw_join) {
+    if(from == NULL || argc < 1) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    struct ChanNode *chan = getChanByName(argv[0]);
+    struct ChanUser *chanuser;
+    if(!chan && (!user || !(user->flags & USERFLAG_ISBOT))) {
+        //parse error if the channel is not known, yet and it's not a bot joining
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    if(user == NULL)
+        user = addUserMask(from);
+    if(chan == NULL) {
+        //new channel
+        chan = addChannel(argv[0]);
+        chanuser = addChanUser(chan, user);
+        chanuser->visCount = 1;
+        chan->botcount = 1;
+        get_userlist_with_invisible(chan, 0, got_channel_userlist, chanuser);
+        putsock(client, "MODE %s", chan->name);
+        putsock(client, "MODE %s +b", chan->name);
+    } else if((user->flags & USERFLAG_WAS_REGISTERING)) {
+        //user rejoined after registering (should still be present in the channel)
+        if(!(chanuser = getChanUser(user, chan)))
+            chanuser = addChanUser(chan, user);
+        chanuser->visCount++;
+        
+        event_registered(user, from);
+        user->flags &= ~USERFLAG_WAS_REGISTERING;
+        if(user->last_who > REWHO_TIMEOUT)
+            user->last_who -= REWHO_TIMEOUT;
+        
+        event_join(chanuser);
+    } else if(!(chan->flags & CHANFLAG_RECEIVED_USERLIST)) {
+        if(client->user != user) { //bots are allowed to add themselves
+            DESYNCHRONIZE(cache_sync);
+            return 1; //ignore join
+        }
+        
+        if(!(chanuser = getChanUser(user, chan))) {
+            chanuser = addChanUser(chan, user);
+        }
+        chanuser->visCount++;
+        chan->botcount++;
+        
+        if(isModeSet(chan->modes, 'D')) //if the bot joins a channel it could also be invisible
+            chanuser->flags |= CHANUSERFLAG_INVISIBLE;
+        
+        get_userlist_with_invisible(chan, 0, got_channel_userlist, chanuser);
+    } else if(!isUserOnChan(user, chan)) {
+        //join user to an existing channel
+        chanuser = addChanUser(chan, user);
+        chanuser->visCount = 1;
+        if(isBot(user) && client->user == user) {
+            if(isModeSet(chan->modes, 'D')) //if the bot joins a channel it could also be invisible
+                chanuser->flags |= CHANUSERFLAG_INVISIBLE;
+            increase_viscount_butone(chan, chanuser);
+            chan->botcount++;
+        }
+        
+        event_join(chanuser);
+        
+        if(!(user->flags & USERFLAG_ISBOT) && (chan->flags & CHANFLAG_REJOINING)) {
+            //ABORT AUTOMATIC REJOIN (security break)
+            free_rejoin_clients(chan, 1);
+            chan->flags &= ~CHANFLAG_REJOINING;
+        }
+    } else { 
+        //user is already in the channel
+        chanuser = getChanUser(user, chan);
+        chanuser->visCount++;
+        
+        if(isBot(user) && client->user == user) {
+            increase_viscount_butone(chan, chanuser);
+            chan->botcount++;
+        }
+        
+        if(chanuser->visCount > chan->botcount) {
+            printf_log("main", LOG_WARNING, "visCount (%d) bigger than botcount (%d) on channel %s (user %s).", chanuser->visCount, chan->botcount, chan->name, user->nick);
+            chanuser->visCount = chan->botcount;
+        }
+        
+        //if multiple bots see the user, it can't be invisible
+        chanuser->flags &= ~CHANUSERFLAG_INVISIBLE;
+    }
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_part) {
+    if(from == NULL || argc < 1) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    struct ChanNode *chan = getChanByName(argv[0]);
+    struct ChanUser *chanuser;
+    if(user == NULL || chan == NULL || !(chanuser = getChanUser(user, chan))) {
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    if(isBot(user) && user == client->user) {
+        decrease_viscount_butone(chan, chanuser);
+        chan->botcount--;
+    }
+    if(chanuser->flags & CHANUSERFLAG_PARTING)
+        chanuser->old_visCount--;
+    chanuser->visCount--;
+    if(chanuser->visCount == 0) {
+        delChanUser(chanuser, 0); //not free, yet!
+        event_part(chanuser, 0, (argc > 1 ? argv[1] : NULL));
+        freeChanUser(chanuser);
+    } else if(!(chanuser->flags & CHANUSERFLAG_PARTING)) {
+        chanuser->flags |= CHANUSERFLAG_PARTING;
+        chanuser->old_visCount = chanuser->visCount;
+    } else if(chanuser->old_visCount == 0) {
+        int visCount = chanuser->visCount;
+        delChanUser(chanuser, 0); //not free, yet!
+        event_part(chanuser, 0, (argc > 1 ? argv[1] : NULL));
+        freeChanUser(chanuser);
+        chanuser = addChanUser(chan, user);
+        chanuser->visCount = visCount;
+        event_join(chanuser);
+    }
+    
+    //check if channel is still present
+    int keep_channel = 1;
+    if(chan->usercount == 0) {
+        if((chan->flags & CHANFLAG_REJOINING)) {
+            free_rejoin_clients(chan, 1);
+            chan->flags &= ~CHANFLAG_REJOINING;
+        }
+        delChannel(chan, 1);
+        keep_channel = 0;
+    } else if(isBot(user)) //bot parted - check if theres another bot in the channel
+        keep_channel = checkChannelVisibility(chan);
+    
+    // free user if he/she is in no other channel
+    if(user->channel == NULL && !(user->flags & USERFLAG_ISBOT))
+        delUser(user, 1);
+
+    if(keep_channel && (chan->flags & CHANFLAG_RECEIVED_USERLIST) && !(chan->flags & CHANFLAG_REJOINING))
+        check_full_rejoin(chan);
+    else if(keep_channel && (chan->flags & CHANFLAG_REJOINING) && chan->usercount == 1) {
+        //bot is alone... rejoin!
+        struct ClientSocket *bot;
+        //find the last bot in the channel
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(isUserOnChan(bot->user, chan)) {
+                putsock(bot, "PART %s :magic hop", chan->name);
+                putsock(bot, "JOIN %s", chan->name);
+            }
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_kick) {
+    if(from == NULL || argc < 3) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    struct UserNode *target = getUserByNick(argv[1]);
+    struct ChanNode *chan = getChanByName(argv[0]);
+    struct ChanUser *chanuser;
+    if(chan == NULL || target == NULL || !(chanuser = getChanUser(target, chan))) {
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    if(isBot(target) && target == client->user) {
+        decrease_viscount_butone(chan, chanuser);
+        chan->botcount--;
+    }
+    chanuser->visCount--;
+    if(chanuser->visCount == 0) {
+        delChanUser(chanuser, 0); //not free, yet!
+        if(user)
+            event_kick(user, chanuser, argv[2]);
+        freeChanUser(chanuser);
+    }
+    
+    //check if channel is still present
+    int keep_channel = 1;
+    if(chan->usercount == 0) {
+        if((chan->flags & CHANFLAG_REJOINING)) {
+            free_rejoin_clients(chan, 1);
+            chan->flags &= ~CHANFLAG_REJOINING;
+        }
+        delChannel(chan, 1);
+        keep_channel = 0;
+    } else if(isBot(target)) //bot parted - check if theres another bot in the channel
+        keep_channel = checkChannelVisibility(chan);
+    
+    // free user if he/she is in no other channel
+    if(target->channel == NULL && !(target->flags & USERFLAG_ISBOT))
+        delUser(target, 1);
+
+    if(keep_channel && (chan->flags & CHANFLAG_RECEIVED_USERLIST) && !(chan->flags & CHANFLAG_REJOINING))
+        check_full_rejoin(chan);
+    else if(keep_channel && (chan->flags & CHANFLAG_REJOINING) && chan->usercount == 1) {
+        //bot is alone... rejoin!
+        struct ClientSocket *bot;
+        //find the last bot in the channel
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(isUserOnChan(bot->user, chan)) {
+                putsock(bot, "PART %s :magic hop", chan->name);
+                putsock(bot, "JOIN %s", chan->name);
+            }
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_quit) {
+    if(from == NULL || argc < 1) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    
+    if(client->user == user) {
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    
+    //check if user is just registering
+    if(!stricmp(argv[0], "Registered"))
+        user->flags |= USERFLAG_WAS_REGISTERING;
+    
+    //decrease visCount counter
+    struct ChanUser *chanuser, *next_chanuser;
+    for(chanuser = getUserChannels(user, NULL); chanuser; chanuser = next_chanuser) {
+        next_chanuser = getUserChannels(user, chanuser);
+        //decrease visCount counter only if client is in the channel
+        if(isUserOnChan(client->user, chanuser->chan)) {
+            chanuser->visCount--;
+            if(chanuser->visCount <= 0 && !(user->flags & USERFLAG_WAS_REGISTERING)) {
+                delChanUser(chanuser, 0); //not free, yet!
+                event_part(chanuser, 1, argv[0]);
+                if((chanuser->chan->flags & CHANFLAG_RECEIVED_USERLIST) && !(chanuser->chan->flags & CHANFLAG_REJOINING))
+                    check_full_rejoin(chanuser->chan);
+                freeChanUser(chanuser);
+            }
+        }
+    }
+    
+    if(user->channel == NULL) {
+        if(isBot(user)) {
+            //ASSERT
+            return 0;
+        }
+        if((user->flags & USERFLAG_WAS_REGISTERING)) {
+            //TODO: set a timeout or sth like that?
+        } else
+            delUser(user, 1);
+    }
+    
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+void bot_disconnect(struct ClientSocket *client) {
+    struct UserNode *user = client->user;
+    if(user) {
+        //just remove the bot mark from the user and handle a normal quit :)
+        user->flags &= ~USERFLAG_ISBOT;
+        client->user = NULL;
+        client->flags &= ~SOCKET_FLAG_READY;
+        
+        //decrease visCount counter
+        struct ChanUser *chanuser, *next_chanuser;
+        for(chanuser = getUserChannels(user, NULL); chanuser; chanuser = next_chanuser) {
+            next_chanuser = getUserChannels(user, chanuser);
+            decrease_viscount_butone(chanuser->chan, chanuser);
+            chanuser->chan->botcount--;
+            chanuser->visCount--;
+            if(chanuser->visCount <= 0) {
+                delChanUser(chanuser, 0); //not free, yet!
+                event_part(chanuser, 1, "QUIT");
+                if(chanuser->chan->flags & CHANFLAG_RECEIVED_USERLIST)
+                    checkChannelVisibility(chanuser->chan);
+                freeChanUser(chanuser);
+            }
+        }
+        
+        if(user->channel == NULL)
+            delUser(user, 1);
+    }
+}
+
+static struct ClientSocket *get_first_prefered_bot_in_channel(struct ChanNode *chan) {
+    struct ClientSocket *bot, *chanbot = NULL;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(!isUserOnChan(bot->user, chan))
+            continue;
+        if(bot->flags & SOCKET_FLAG_PREFERRED)
+            return bot;
+        chanbot = bot;
+    }
+    return chanbot;
+}
+
+static IRC_CMD(raw_topic) {
+    if(from == NULL || argc < 2) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    struct ChanNode *chan = getChanByName(argv[0]);
+    if(chan == NULL) {
+        DESYNCHRONIZE(cache_sync);
+        return 0;
+    }
+    if(client != get_first_prefered_bot_in_channel(chan)) {
+        DESYNCHRONIZE(cache_sync);
+        return 1; //just ignore it to prevent event duplicates
+    }
+    if(user == NULL) {
+        user = createTempUserMask(from);
+               if(!user) {
+            DESYNCHRONIZE(cache_sync);
+            return 0;
+        }
+        user->flags |= USERFLAG_ISTMPUSER;
+    }
+    event_topic(user, chan, argv[1]);
+    strcpy(chan->topic, argv[1]);
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_privmsg) {
+    if(from == NULL || argc < 2) return 0;
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        if(stricmplen(from, "***!", 4) == 0) return 1; /* ZNC Playback */
+        user = createTempUserMask(from);
+               if(!user) return 0;
+        user->flags |= USERFLAG_ISTMPUSER;
+    }
+    if(argv[0][0] == '#') { //Channel message
+        struct ChanNode *chan = getChanByName(argv[0]);
+        if(chan && client == get_first_prefered_bot_in_channel(chan)) {
+            statistics_privmsg++;
+            if(argv[1][0] == '\001') {
+                char *cmd = &argv[1][1];
+                char *text = strstr(cmd, " ");
+                if(text) {
+                    *text = '\0';
+                    text++;
+                    if(strlen(text) && text[strlen(text)-1] == '\001')
+                        text[strlen(text)-1] = '\0';
+                } else if(strlen(cmd) && cmd[strlen(cmd)-1] == '\001')
+                    cmd[strlen(cmd)-1] = '\0';
+                event_chanctcp(user, chan, cmd, text);
+            } else
+                event_chanmsg(user, chan, argv[1]);
+        }
+    } else {
+        struct UserNode *target = getUserByNick(argv[0]);
+        if(target) {
+            if(argv[1][0] == '\001') {
+                char *cmd = &argv[1][1];
+                char *text = strstr(cmd, " ");
+                if(text) {
+                    *text = '\0';
+                    text++;
+                    if(strlen(text) && text[strlen(text)-1] == '\001')
+                        text[strlen(text)-1] = '\0';
+                } else if(strlen(cmd) && cmd[strlen(cmd)-1] == '\001')
+                    cmd[strlen(cmd)-1] = '\0';
+                event_privctcp(user, target, cmd, text);
+            } else
+                event_privmsg(user, target, argv[1]);
+        }
+    }
+    return 1;
+}
+
+static IRC_CMD(raw_notice) {
+    if(from == NULL && argc && !stricmp(argv[0], "AUTH")) return 1; //NOTICE AUTH is NOT a parse error ;)
+    if(from == NULL || argc < 2) return 0;
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        user = createTempUserMask(from);
+               if(!user) return 0;
+        user->flags |= USERFLAG_ISTMPUSER;
+    }
+    if(argv[0][0] == '#') { //Channel notice
+        struct ChanNode *chan = getChanByName(argv[0]);
+        if(chan && client == get_first_prefered_bot_in_channel(chan))
+            event_channotice(user, chan, argv[1]);
+    } else {
+        struct UserNode *target = getUserByNick(argv[0]);
+        if(target)
+            event_privnotice(user, target, argv[1]);
+    }
+    return 1;
+}
+
+static void client_renamed(struct ClientSocket *client);
+
+static IRC_CMD(raw_nick) {
+    if(from == NULL || argc == 0) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        DESYNCHRONIZE(cache_sync);
+        return 1; //maybe already renamed - no parse error
+    }
+    if(isBot(user)) {
+        if(client->user != user) {
+            DESYNCHRONIZE(cache_sync);
+            return 1;
+        }
+        client_renamed(client);
+    }
+    else if(!strcmp(user->nick, argv[0])) {
+        DESYNCHRONIZE(cache_sync);
+        return 1; //user has already this nick (case sensitive)
+    }
+    event_nick(user, argv[0]);
+    renameUser(user, argv[0]);
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_ping) {
+    if(argc == 0) return 0;
+    putsock(client, "PONG :%s", argv[0]);
+    return 1;
+}
+
+static IRC_CMD(raw_354) {
+    recv_whohandler_354(client, argv, argc);
+    return 1;
+}
+
+static IRC_CMD(raw_315) {
+    recv_whohandler_315(client, argv, argc);
+    return 1;
+}
+
+static IRC_CMD(raw_324) { //MODE LIST
+    //Watchcat #pktest +stnzN
+    if(from == NULL || argc < 3) return 0;
+    struct ChanNode *chan = getChanByName(argv[1]);
+    if(chan == NULL) return 0;
+    parseModes(chan->modes, argv[2], argv+3, argc-3);
+    return 1;
+}
+
+static IRC_CMD(raw_invite) {
+    if(from == NULL || argc < 2) return 0;
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        user = createTempUserMask(from);
+               if(!user) return 0;
+        user->flags |= USERFLAG_ISTMPUSER;
+    }
+    event_invite(client, user, argv[1]);
+    return 1;
+}
+
+static IRC_CMD(raw_mode) {
+    if(from == NULL || argc < 2) return 0;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user = getUserByMask(from);
+    if(user == NULL) {
+        user = createTempUserMask(from);
+               if(!user) {
+            DESYNCHRONIZE(cache_sync);
+            return 0;
+        }
+        user->flags |= USERFLAG_ISTMPUSER;
+    }
+    if(argv[0][0] == '#') {
+        //ChannelMode
+        struct ChanNode *chan = getChanByName(argv[0]);
+        if(!chan) {
+            DESYNCHRONIZE(cache_sync);
+            return 0;
+        }
+        if(client != get_first_prefered_bot_in_channel(chan)) {
+            DESYNCHRONIZE(cache_sync);
+            return 1;
+        }
+        parseModes(chan->modes, argv[1], argv+2, argc-2);
+        event_mode(user, chan, argv[1], argv+2, argc-2);
+    } else {
+        //UserMode
+        if(stricmp(client->user->nick, argv[0])) {
+            DESYNCHRONIZE(cache_sync);
+            return 0;
+        }
+        parseUserModes(client->user, argv[1]);
+    }
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+static IRC_CMD(raw_367) {
+    //Watchcat #pktest pk911!*@* TestBot!~bot@pktest.user.WebGamesNet 1315863279
+    struct ChanNode *chan = getChanByName(argv[1]);
+    if(!chan) return 0;
+    struct BanNode *ban;
+    while((ban = getMatchingChannelBan(chan, argv[2]))) {
+        removeChannelBan(ban);
+    }
+    addChannelBan(chan, argv[2]);
+    return 1;
+}
+
+static IRC_CMD(raw_251) {
+    if(argc < 2) return 0;
+    char *total_user_str = argv[1];
+    char total_visible[20], total_invisible[20];
+    int i = 0, total_visible_pos = 0, total_invisible_pos = 0;
+    while(*total_user_str) {
+        if(*total_user_str == ' ') {
+            i++;
+        } else if(i == 2) {
+            if(total_visible_pos < 20)
+                total_visible[total_visible_pos++] = *total_user_str;
+        } else if(i == 5) {
+            if(total_invisible_pos < 20)
+                total_invisible[total_invisible_pos++] = *total_user_str;
+        }
+        total_user_str++;
+    }
+    total_visible[total_visible_pos] = '\0';
+    total_invisible[total_invisible_pos] = '\0';
+    statistics_network_users = atoi(total_visible) + atoi(total_invisible);
+    return 1;
+}
+
+static IRC_CMD(raw_254) {
+    if(argc < 3) return 0;
+    statistics_network_channels = atoi(argv[1]);
+    statistics_update();
+    return 1;
+}
+
+static IRC_CMD(raw_332) {
+    //Skynet #neonserv :topic
+    struct ChanNode *chan = getChanByName(argv[1]);
+    if(!chan) return 0;
+    strcpy(chan->topic, argv[2]);
+    return 1;
+}
+
+struct ClientRenamePartedChannel {
+    char channel[CHANNELLEN+1];
+    struct ClientRenamePartedChannel *next;
+};
+
+static IRC_CMD(raw_437) { //can NOT change nick
+    struct ClientRenamePartedChannel *partedchan = malloc(sizeof(*partedchan));
+    strcpy(partedchan->channel, argv[1]);
+    if((client->flags & SOCKET_FLAG_CHANGENICK))
+        partedchan->next = client->changenick_channels;
+    else
+        partedchan->next = NULL;
+    client->changenick_channels = partedchan;
+    client->flags |= SOCKET_FLAG_CHANGENICK;
+    putsock(client, "PART %s", argv[1]);
+    putsock(client, "NICK %s", client->nick);
+    return 1;
+}
+
+static void client_renamed(struct ClientSocket *client) {
+    if((client->flags & SOCKET_FLAG_CHANGENICK)) {
+        struct ClientRenamePartedChannel *partedchan, *nextchan;
+        for(partedchan = client->changenick_channels; partedchan; partedchan = nextchan) {
+            nextchan = partedchan->next;
+            putsock(client, "JOIN %s", partedchan->channel);
+            free(partedchan);
+        }
+        client->flags &= ~SOCKET_FLAG_CHANGENICK;
+    }
+}
+
+static void raw_005_network(struct ClientSocket *client, char *value) {
+    if(!value) return;
+    //check all other networknames
+    //if they are NOT simular to value throw a warning
+    SYNCHRONIZE(cache_sync); //all bots connect to the same time so there is a higher chance that this code is running on multiple threads at the same time
+    if(client->network_name)
+        free(client->network_name);
+    client->network_name = strdup(value);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot == client) continue;
+        if(!bot->network_name) continue;
+        if(stricmp(bot->network_name, value)) {
+            printf_log("main", LOG_ERROR, "WARNING: Network name '%s' (%s) differs from '%s' (%s)! Connecting to multiple IRC-Networks with one instance is NOT supported!\n", client->network_name, client->nick, bot->network_name, bot->nick);
+            break;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+}
+
+static IRC_CMD(raw_005) {
+    char *ptr1 = merge_argv(argv, 1, argc);
+    char *ptr2, *name, *value;
+    do {
+        ptr2 = strchr(ptr1, ' ');
+        if(ptr2)
+            *ptr2 = '\0';
+        name = ptr1;
+        if((value = strchr(ptr1, '='))) {
+            *value = '\0';
+            value++;
+        }
+        if(!stricmp(name, "NETWORK")) raw_005_network(client, value);
+        if(ptr2)
+            ptr1 = ptr2 + 1;
+    } while(ptr2);
+    return 1;
+}
+
+static IRC_CMD(raw_nojoin) {
+    if(from == NULL || argc < 3) return 0;
+    struct ChanNode *chan = getChanByName(argv[1]);
+    if(chan == NULL) return 0;
+    if(client->flags & SOCKET_FLAG_REQUEST_INVITE)
+        requestInvite(client->user, chan);
+    return 1;
+}
+
+void init_parser() {
+    //all the raws we receive...
+    register_irc_function("437", raw_437);
+    register_irc_function("002", raw_002);
+    register_irc_function("005", raw_005);
+    register_irc_function("251", raw_251);
+    register_irc_function("254", raw_254);
+    register_irc_function("324", raw_324);
+    register_irc_function("332", raw_332);
+    register_irc_function("367", raw_367);
+    register_irc_function("471", raw_nojoin);
+    register_irc_function("473", raw_nojoin);
+    register_irc_function("474", raw_nojoin);
+    register_irc_function("475", raw_nojoin);
+    register_irc_function("INVITE", raw_invite);
+    register_irc_function("NOTICE", raw_notice);
+    register_irc_function("TOPIC", raw_topic);
+    register_irc_function("KICK", raw_kick);
+    register_irc_function("PART", raw_part);
+    register_irc_function("QUIT", raw_quit);
+    register_irc_function("JOIN", raw_join);
+    register_irc_function("MODE", raw_mode);
+    register_irc_function("NICK", raw_nick);
+    register_irc_function("354", raw_354);
+    register_irc_function("315", raw_315);
+    register_irc_function("PING", raw_ping);
+    register_irc_function("PRIVMSG", raw_privmsg);
+}
+
+void free_parser() {
+    struct irc_cmd *cmd, *next;
+    for(cmd = irc_commands; cmd; cmd = next) {
+        next = cmd->next;
+        free(cmd);
+    }
+}
+
+void reply(struct ClientSocket *client, struct UserNode *user, const char *text, ...) {
+    const char *reply_format = get_language_string(user, text);
+    if(reply_format == NULL)
+        reply_format = text;
+    loadUserSettings(user);
+    char formatBuf[MAXLEN];
+    sprintf(formatBuf, "%s %s :%s", ((user->flags & USERFLAG_REPLY_PRIVMSG) ? "PRIVMSG" : "NOTICE"), user->nick, reply_format);
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!(client->flags & SOCKET_FLAG_CONNECTED)) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, formatBuf, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    write_socket(client, sendBuf, pos+1);
+}
+
+char* merge_argv(char **argv, int start, int end) {
+    return merge_argv_char(argv, start, end, ' ');
+}
+
+char* merge_argv_char(char **argv, int start, int end, char seperator) {
+    int i;
+    char *p = NULL;
+    for(i = start; i < end; i++) {
+        p = argv[i];
+        while(*p) p++;
+        if(i < end-1) {
+            while(p != argv[i+1]) {
+                *p++ = seperator;
+            }
+        } else
+            *p = seperator;
+    }
+    if(p) *p = '\0';
+    return argv[start];
+}
diff --git a/src/IRCParser.h b/src/IRCParser.h
new file mode 100644 (file)
index 0000000..7e06f8d
--- /dev/null
@@ -0,0 +1,47 @@
+/* IRCParser.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _IRCParser_h
+#define _IRCParser_h
+
+#include "main.h"
+
+struct ClientSocket;
+struct UserNode;
+
+#define IRC_CMD(NAME) int NAME(struct ClientSocket *client, UNUSED_ARG(char *from), UNUSED_ARG(char **argv), UNUSED_ARG(unsigned int argc))
+typedef IRC_CMD(irc_cmd_t);
+
+struct irc_cmd {
+    char *cmd;
+    irc_cmd_t *func;
+    struct irc_cmd *next;
+};
+
+#ifndef DND_FUNCTIONS
+extern int statistics_privmsg;
+extern int statistics_network_users;
+extern int statistics_network_channels;
+
+void parse_line(struct ClientSocket *client, char *line);
+void bot_disconnect(struct ClientSocket *client);
+void init_parser();
+void free_parser();
+/* MODULAR ACCESSIBLE */ void reply(struct ClientSocket *client, struct UserNode *user, const char *text, ...);
+/* MODULAR ACCESSIBLE */ char* merge_argv(char **argv, int start, int end);
+/* MODULAR ACCESSIBLE */ char* merge_argv_char(char **argv, int start, int end, char seperator);
+#endif
+#endif
diff --git a/src/IRCQueue.c b/src/IRCQueue.c
new file mode 100644 (file)
index 0000000..a7cc1a4
--- /dev/null
@@ -0,0 +1,266 @@
+/* IRCQueue.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "IRCQueue.h"
+#include "ClientSocket.h"
+#include "IOHandler.h"
+#include "tools.h"
+#include "log.h"
+
+#define MAXPENALTY 8 /* 4 messages */
+
+struct QueueEntry {
+    char *msg;
+    struct QueueEntry *next;
+};
+
+struct BotQueue {
+    struct ClientSocket *client;
+    struct IODescriptor *iofd;
+    int penalty : 8;
+    int rem_penalty : 8;
+    struct QueueEntry *fastqueue_first, *fastqueue_last;
+    struct QueueEntry *normalqueue_first, *normalqueue_last;
+    struct QueueEntry *textqueue_first, *textqueue_last;
+};
+
+static IOHANDLER_CALLBACK(queue_callback);
+
+static struct BotQueue *initialize_queue(struct ClientSocket *client) {
+    struct BotQueue *queue = malloc(sizeof(*queue));
+    if (!queue) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    queue->client = client;
+    client->queue = queue;
+    queue->iofd = NULL;
+    queue->penalty = 0;
+    queue->fastqueue_first = NULL;
+    queue->fastqueue_last = NULL;
+    queue->normalqueue_first = NULL;
+    queue->normalqueue_last = NULL;
+    queue->textqueue_first = NULL;
+    queue->textqueue_last = NULL;
+    return queue;
+}
+
+static int calculate_penalty(char *message) {
+    int msglen = strlen(message);
+    int penalty = (2 + msglen / 100);
+    return penalty;
+}
+
+int queue_add(struct ClientSocket *client, char* msg, int len) {
+    if(!client->queue)
+        client->queue = initialize_queue(client);
+    struct BotQueue *queue = client->queue;
+    char *args = strstr(msg, " ");
+    int type;
+    int add_queue = 0;
+    if(args) {
+        *args = '\0';
+        if(!stricmp(msg, "MODE")) 
+            type = 3;
+        else if(!stricmp(msg, "KICK")) 
+            type = 3;
+        else if(!stricmp(msg, "PONG")) 
+            type = 3;
+        else if(!stricmp(msg, "PRIVMSG")) 
+            type = 1;
+        else if(!stricmp(msg, "NOTICE")) 
+            type = 1;
+        else if(!stricmp(msg, "WHO")) 
+            type = 1;
+        else
+            type = 2;
+        *args = ' ';
+    } else
+        type = 2;
+    
+    //check if we need to queue
+    switch(type) {
+    case 1:
+        if(queue->textqueue_first) {
+            add_queue = 1;
+            break;
+        }
+    case 2:
+        if(queue->normalqueue_first) {
+            add_queue = 1;
+            break;
+        }
+    case 3:
+        if(queue->fastqueue_first) {
+            add_queue = 1;
+            break;
+        }
+    default:
+        if(queue->penalty >= MAXPENALTY)
+            add_queue = 1;
+        break;
+    }
+    
+    if(!add_queue) {
+        int penalty = calculate_penalty(msg);
+        write_socket_force(client, msg, len);
+        queue->penalty += penalty;
+        if(!queue->iofd) {
+            struct timeval timeout;
+            gettimeofday(&timeout, NULL);
+            if(queue->penalty >= MAXPENALTY)
+                queue->rem_penalty = (queue->penalty - MAXPENALTY)+1;
+            else
+                queue->rem_penalty = queue->penalty;
+            timeout.tv_sec += queue->rem_penalty;
+            queue->iofd = iohandler_timer(timeout, queue_callback);
+            queue->iofd->data = queue;
+        }
+    } else {
+        struct QueueEntry *entry = malloc(sizeof(*entry));
+        if (!entry) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return 0;
+        }
+        entry->msg = strdup(msg);
+        entry->next = NULL;
+        if(type == 1) { //low priority
+            if(queue->textqueue_last) {
+                queue->textqueue_last->next = entry;
+                queue->textqueue_last = entry;
+            } else {
+                queue->textqueue_last = entry;
+                queue->textqueue_first = entry;
+            }
+        } else if(type == 2) { //normal priority
+            if(queue->normalqueue_last) {
+                queue->normalqueue_last->next = entry;
+                queue->normalqueue_last = entry;
+            } else {
+                queue->normalqueue_last = entry;
+                queue->normalqueue_first = entry;
+            }
+        } else if(type == 3) { //high priority
+            if(queue->fastqueue_last) {
+                queue->fastqueue_last->next = entry;
+                queue->fastqueue_last = entry;
+            } else {
+                queue->fastqueue_last = entry;
+                queue->fastqueue_first = entry;
+            }
+        }
+    }
+    return 1;
+}
+
+static void dequeue_bot(struct ClientSocket *client) {
+    if(client->queue->penalty >= MAXPENALTY) return;
+    int penalty;
+    //try to send high priority messages
+    if(client->queue->fastqueue_first) {
+        do {
+            struct QueueEntry *entry = client->queue->fastqueue_first;
+            if(!entry->next)
+                client->queue->fastqueue_last = NULL;
+            client->queue->fastqueue_first = client->queue->fastqueue_first->next;
+            penalty = calculate_penalty(entry->msg);
+            write_socket_force(client, entry->msg, strlen(entry->msg));
+            client->queue->penalty += penalty;
+            free(entry->msg);
+            free(entry);
+        } while(client->queue->penalty < MAXPENALTY && client->queue->fastqueue_first);
+    }
+    if(client->queue->penalty >= MAXPENALTY) return;
+    //try to send normal priority messages
+    if(client->queue->normalqueue_first) {
+        do {
+            struct QueueEntry *entry = client->queue->normalqueue_first;
+            if(!entry->next)
+                client->queue->normalqueue_last = NULL;
+            client->queue->normalqueue_first = client->queue->normalqueue_first->next;
+            penalty = calculate_penalty(entry->msg);
+            write_socket_force(client, entry->msg, strlen(entry->msg));
+            client->queue->penalty += penalty;
+            free(entry->msg);
+            free(entry);
+        } while(client->queue->penalty < MAXPENALTY && client->queue->normalqueue_first);
+    }
+    if(client->queue->penalty >= MAXPENALTY) return;
+    //try to send low priority messages
+    if(client->queue->textqueue_first) {
+        do {
+            struct QueueEntry *entry = client->queue->textqueue_first;
+            if(!entry->next)
+                client->queue->textqueue_last = NULL;
+            client->queue->textqueue_first = client->queue->textqueue_first->next;
+            penalty = calculate_penalty(entry->msg);
+            write_socket_force(client, entry->msg, strlen(entry->msg));
+            client->queue->penalty += penalty;
+            free(entry->msg);
+            free(entry);
+        } while(client->queue->penalty < MAXPENALTY && client->queue->textqueue_first);
+    }
+}
+
+void queue_destroy(struct ClientSocket *client) {
+    if(!client->queue) return;
+    struct QueueEntry *entry, *next;
+    for(entry = client->queue->fastqueue_first; entry; entry = next) {
+        next = entry->next;
+        free(entry->msg);
+        free(entry);
+    }
+    for(entry = client->queue->normalqueue_first; entry; entry = next) {
+        next = entry->next;
+        free(entry->msg);
+        free(entry);
+    }
+    for(entry = client->queue->textqueue_first; entry; entry = next) {
+        next = entry->next;
+        free(entry->msg);
+        free(entry);
+    }
+    if(client->queue->iofd)
+        iohandler_close(client->queue->iofd);
+    free(client->queue);
+    client->queue = NULL;
+}
+
+static IOHANDLER_CALLBACK(queue_callback) {
+    struct BotQueue *queue = event->iofd->data;
+    switch(event->type) {
+    case IOEVENT_TIMEOUT:
+        queue->penalty -= queue->rem_penalty;
+        dequeue_bot(queue->client);
+        if(queue->penalty > 0) {
+            struct timeval timeout;
+            gettimeofday(&timeout, NULL);
+            if(queue->penalty >= MAXPENALTY)
+                queue->rem_penalty = (queue->penalty - MAXPENALTY)+1;
+            else
+                queue->rem_penalty = queue->penalty;
+            timeout.tv_sec += queue->rem_penalty;
+            queue->iofd = iohandler_timer(timeout, queue_callback);
+            queue->iofd->data = queue;
+        } else {
+            queue->iofd = NULL;
+            queue->penalty = 0;
+        }
+        break;
+    default:
+        break;
+    }
+}
diff --git a/src/IRCQueue.h b/src/IRCQueue.h
new file mode 100644 (file)
index 0000000..cd27b1c
--- /dev/null
@@ -0,0 +1,27 @@
+/* IRCQueue.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _IRCQueue_h
+#define _IRCQueue_h
+
+#include "main.h"
+
+struct ClientSocket;
+
+int queue_add(struct ClientSocket *client, char* msg, int len);
+void queue_destroy(struct ClientSocket *client);
+
+#endif
\ No newline at end of file
diff --git a/src/ModeNode.c b/src/ModeNode.c
new file mode 100644 (file)
index 0000000..f3579cd
--- /dev/null
@@ -0,0 +1,366 @@
+/* ModeNode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "ModeNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "UserNode.h"
+#include "BanNode.h"
+#include "log.h"
+
+static int modes_with_strarg, modes_with_intarg, modes_count;
+
+unsigned int valid_modes[] = { /* Thats our mode list :P */
+    1,  'b', CHANNEL_MODE_TYPE_A,
+    2,  'o', CHANNEL_MODE_TYPE_A,
+    3,  'h', CHANNEL_MODE_TYPE_A,
+    4,  'v', CHANNEL_MODE_TYPE_A,
+    5,  'k', CHANNEL_MODE_TYPE_B | CHANNEL_MODE_VALUE_STRING | CHANNEL_MODE_KEY,
+    6,  'a', CHANNEL_MODE_TYPE_C | CHANNEL_MODE_VALUE_INTEGER,
+    7,  'l', CHANNEL_MODE_TYPE_C | CHANNEL_MODE_VALUE_INTEGER,
+    8,  'f', CHANNEL_MODE_TYPE_C | CHANNEL_MODE_VALUE_STRING,
+    9,  'F', CHANNEL_MODE_TYPE_C | CHANNEL_MODE_VALUE_STRING,
+    10, 'c', CHANNEL_MODE_TYPE_D,
+    11, 'C', CHANNEL_MODE_TYPE_D,
+    12, 'i', CHANNEL_MODE_TYPE_D,
+    13, 'm', CHANNEL_MODE_TYPE_D,
+    14, 'M', CHANNEL_MODE_TYPE_D,
+    15, 'n', CHANNEL_MODE_TYPE_D,
+    16, 'N', CHANNEL_MODE_TYPE_D,
+    17, 'p', CHANNEL_MODE_TYPE_D,
+    18, 'r', CHANNEL_MODE_TYPE_D,
+    19, 's', CHANNEL_MODE_TYPE_D,
+    20, 'S', CHANNEL_MODE_TYPE_D,
+    21, 't', CHANNEL_MODE_TYPE_D,
+    22, 'u', CHANNEL_MODE_TYPE_D,
+    23, 'D', CHANNEL_MODE_TYPE_D,
+    24, 'd', CHANNEL_MODE_TYPE_D,
+    25, 'R', CHANNEL_MODE_TYPE_D,
+    26, 'z', CHANNEL_MODE_TYPE_D,
+//  ^ maximum is 32!!!
+    0x00, 0x00, 0x00
+};
+
+void init_ModeNode() {
+    unsigned int *mode, flag = 1;
+    modes_with_strarg = 0;
+    modes_with_intarg = 0;
+    modes_count = 0;
+    for (mode = valid_modes; mode[1]; mode += 3) {
+        if((mode[2] & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING) {
+            mode[2] |= modes_with_strarg << CHANNEL_MODE_VALUE_INDEX_SHIFT;
+            modes_with_strarg++;
+        }
+        if((mode[2] & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_INTEGER) {
+            mode[2] |= modes_with_intarg << CHANNEL_MODE_VALUE_INDEX_SHIFT;
+            modes_with_intarg++;
+        }
+        modes_count++;
+        mode[0] = flag;
+        flag = flag << 1;
+    }
+}
+
+struct ModeNode *createModeNode(struct ChanNode *chan) {
+    struct ModeNode *modes = malloc(sizeof(*modes));
+    if (!modes)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    modes->chan = chan;
+    modes->modes = 0;
+    modes->allmodes = 0;
+    modes->mode_str_args = calloc(modes_with_strarg, sizeof(char*));
+    modes->mode_int_args = calloc(modes_with_intarg, sizeof(int));
+    return modes;
+}
+
+void freeModeNode(struct ModeNode *modes) {
+    int i;
+    for(i = 0; i < modes_with_strarg; i++) {
+        if(modes->mode_str_args[i])
+            free(modes->mode_str_args[i]);
+    }
+    free(modes->mode_str_args);
+    free(modes->mode_int_args);
+    free(modes);
+}
+
+static unsigned int* getModeOptions(char mode) {
+    unsigned int *cmode;
+    for (cmode = valid_modes; cmode[1]; cmode += 3) {
+        if(cmode[1] == mode)
+            return cmode;
+    }
+    return NULL;
+}
+
+int isModeSet(struct ModeNode* modes, char modeChar) {
+    unsigned int *modeOpt = getModeOptions(modeChar);
+    return (modes->modes & modeOpt[0]);
+}
+
+int isModeAffected(struct ModeNode* modes, char modeChar) {
+    unsigned int *modeOpt = getModeOptions(modeChar);
+    return (modes->allmodes & modeOpt[0]);
+}
+
+void* getModeValue(struct ModeNode* modes, char modeChar) {
+    #define MODE_VALUE_INDEX (modeOpt[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    unsigned int *modeOpt = getModeOptions(modeChar);
+    if((modeOpt[2] & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING)
+        return modes->mode_str_args[MODE_VALUE_INDEX];
+    if((modeOpt[2] & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_INTEGER)
+        return &modes->mode_int_args[MODE_VALUE_INDEX];
+    return NULL;
+    #undef MODE_VALUE_INDEX
+}
+
+unsigned int getModeType(struct ModeNode* modes, char modeChar) {
+    #define MODE_VALUE_INDEX (modeOpt[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    unsigned int *modeOpt = getModeOptions(modeChar);
+    if(!modeOpt) return 0;
+    return modeOpt[2];
+    #undef MODE_VALUE_INDEX
+}
+
+static void parseModesUserPriv(struct ModeNode* modes, unsigned char flag, int add, char *nick) {
+    if(modes->chan == NULL) return;
+    struct UserNode *user = getUserByNick(nick);
+    if(user == NULL) return;
+    struct ChanUser *chanuser = getChanUser(user, modes->chan);
+    if(chanuser == NULL) return;
+    if(add)
+        chanuser->flags |= flag;
+    else
+        chanuser->flags &= ~flag;
+    if((chanuser->flags & CHANUSERFLAG_OPPED_OR_VOICED) && (chanuser->flags & CHANUSERFLAG_INVISIBLE))
+        chanuser->flags &= ~CHANUSERFLAG_INVISIBLE;
+}
+
+static void parseModesBan(struct ModeNode* modes, int add, char *mask) {
+    if(modes->chan == NULL) return;
+    if(add)
+        addChannelBan(modes->chan, mask);
+    else
+        removeChannelBanMask(modes->chan, mask);
+}
+
+void parseModes(struct ModeNode* modes, char *modeStr, char **argv, int argc) {
+    int i, argpos = 0, add = 1;
+    #define MODE_TYPE (modeOpt[2] & CHANNEL_MODE_TYPE)
+    #define MODE_VALUE (modeOpt[2] & CHANNEL_MODE_VALUE)
+    #define MODE_VALUE_INDEX (modeOpt[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    unsigned int *modeOpt;
+    for(i = 0; i < strlen(modeStr); i++) {
+        if(modeStr[i] == '+') {
+            add = 1;
+            continue;
+        }
+        if(modeStr[i] == '-') {
+            add = 0;
+            continue;
+        }
+        modeOpt = getModeOptions(modeStr[i]);
+        if(!modeOpt) continue; // unknown mode?
+        if(MODE_TYPE == CHANNEL_MODE_TYPE_A) {
+            if(argpos == argc) continue;
+            //special mode ;)
+            switch(modeStr[i]) {
+                case 'o':
+                    parseModesUserPriv(modes, CHANUSERFLAG_OPPED, add, argv[argpos]);
+                    break;
+                case 'h':
+                    parseModesUserPriv(modes, CHANUSERFLAG_HALFOPPED, add, argv[argpos]);
+                    break;
+                case 'v':
+                    parseModesUserPriv(modes, CHANUSERFLAG_VOICED, add, argv[argpos]);
+                    break;
+                case 'b':
+                    parseModesBan(modes, add, argv[argpos]);
+                    break;
+                default:
+                    //we have an unknown TYPE_A mode???
+                    break;
+            }
+            argpos++;
+            continue;
+        }
+        if(add) {
+            if(MODE_TYPE != CHANNEL_MODE_TYPE_D) { //all other types take parameters when set
+                if(argpos == argc) continue;
+                if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING) {
+                    if(modes->mode_str_args[MODE_VALUE_INDEX])
+                        free(modes->mode_str_args[MODE_VALUE_INDEX]);
+                    modes->mode_str_args[MODE_VALUE_INDEX] = strdup(argv[argpos++]);
+                } else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                    modes->mode_int_args[MODE_VALUE_INDEX] = atoi(argv[argpos++]);
+                else
+                    argpos++; //we simply don't know what to do with the argument...
+            }
+            modes->modes |= modeOpt[0];
+            modes->allmodes |= modeOpt[0];
+        } else {
+            modes->modes &= ~modeOpt[0];
+            modes->allmodes |= modeOpt[0];
+            if(MODE_TYPE == CHANNEL_MODE_TYPE_B) {
+                if(argpos == argc) continue;
+                if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING) {
+                    free(modes->mode_str_args[MODE_VALUE_INDEX]);
+                    modes->mode_str_args[MODE_VALUE_INDEX] = NULL;
+                } else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                    modes->mode_int_args[MODE_VALUE_INDEX] = 0;
+                argpos++; //we don't need the argument when unsetting a mode...
+            }
+        }
+    }
+    #undef MODE_TYPE
+    #undef MODE_VALUE
+    #undef MODE_VALUE_INDEX
+}
+
+void parseModeString(struct ModeNode* modes, char *modeStr) {
+    int argc = 0;
+    char *args[modes_count+1];
+    char *a, *b = modeStr;
+    do {
+        a = strstr(b, " ");
+        if(a) *a = '\0';
+        args[argc++] = b;
+        if(a) b = a+1;
+    } while(a);
+    parseModes(modes, args[0], args+1, argc-1);
+}
+
+int parseMode(struct ModeNode* modes, int add, char mode, char *param) {
+    #define MODE_TYPE (modeOpt[2] & CHANNEL_MODE_TYPE)
+    #define MODE_VALUE (modeOpt[2] & CHANNEL_MODE_VALUE)
+    #define MODE_VALUE_INDEX (modeOpt[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    unsigned int *modeOpt = getModeOptions(mode);
+    if(!modeOpt) return 0;
+    if(MODE_TYPE == CHANNEL_MODE_TYPE_A) {
+        if(!param) return 0;
+        //special mode ;)
+        switch(mode) {
+            case 'o':
+                parseModesUserPriv(modes, CHANUSERFLAG_OPPED, add, param);
+                break;
+            case 'v':
+                parseModesUserPriv(modes, CHANUSERFLAG_VOICED, add, param);
+                break;
+            case 'b':
+                parseModesBan(modes, add, param);
+                break;
+            default:
+                return 0; //we have an unknown TYPE_A mode???
+        }
+    }
+    if(add) {
+        if(MODE_TYPE != CHANNEL_MODE_TYPE_D) { //all other types take parameters when set
+            if(!param) return 0;
+            if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING) {
+                if(modes->mode_str_args[MODE_VALUE_INDEX])
+                    free(modes->mode_str_args[MODE_VALUE_INDEX]);
+                modes->mode_str_args[MODE_VALUE_INDEX] = strdup(param);
+            } else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                modes->mode_int_args[MODE_VALUE_INDEX] = atoi(param);
+        }
+        modes->modes |= modeOpt[0];
+        modes->allmodes |= modeOpt[0];
+    } else {
+        modes->modes &= ~modeOpt[0];
+        modes->allmodes |= modeOpt[0];
+        if(MODE_TYPE == CHANNEL_MODE_TYPE_B) {
+            if(!param && !(modeOpt[2] & CHANNEL_MODE_KEY)) return 0;
+            if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING) {
+                free(modes->mode_str_args[MODE_VALUE_INDEX]);
+                modes->mode_str_args[MODE_VALUE_INDEX] = NULL;
+            } else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                modes->mode_int_args[MODE_VALUE_INDEX] = 0;
+        }
+    }
+    #undef MODE_TYPE
+    #undef MODE_VALUE
+    #undef MODE_VALUE_INDEX
+    return 1;
+}
+
+void getModeString(struct ModeNode* modes, char *modesStr) {
+    #define MODE_TYPE (mode[2] & CHANNEL_MODE_TYPE)
+    #define MODE_VALUE (mode[2] & CHANNEL_MODE_VALUE)
+    #define MODE_VALUE_INDEX (mode[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    char paramStr[MAXLEN];
+    modesStr[0] = '+';
+    unsigned int *mode;
+    int modePos = 1;
+    int paramPos = 0;
+    for (mode = valid_modes; mode[1]; mode += 3) {
+        if(modes->modes & mode[0]) {
+            modesStr[modePos++] = (char) mode[1];
+            if(MODE_TYPE != CHANNEL_MODE_TYPE_D) {
+                if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING)
+                    paramPos += sprintf(paramStr + paramPos, " %s", modes->mode_str_args[MODE_VALUE_INDEX]);
+                else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                    paramPos += sprintf(paramStr + paramPos, " %d", modes->mode_int_args[MODE_VALUE_INDEX]);
+            }
+        }
+    }
+    paramStr[paramPos] = '\0';
+    strcpy(modesStr + modePos, paramStr);
+    #undef MODE_TYPE
+    #undef MODE_VALUE
+    #undef MODE_VALUE_INDEX
+}
+
+void getFullModeString(struct ModeNode* modes, char *modesStr) {
+    #define MODE_TYPE (mode[2] & CHANNEL_MODE_TYPE)
+    #define MODE_VALUE (mode[2] & CHANNEL_MODE_VALUE)
+    #define MODE_VALUE_INDEX (mode[2] & CHANNEL_MODE_VALUE_INDEX_MASK) >> CHANNEL_MODE_VALUE_INDEX_SHIFT
+    char addMode[modes_count+1];
+    int addModePos = 0;
+    char addParams[MAXLEN];
+    addParams[0] = '\0';
+    int addParamsPos = 0;
+    char delMode[modes_count+1];
+    int delModePos = 0;
+    unsigned int *mode;
+    for (mode = valid_modes; mode[1]; mode += 3) {
+        if(modes->allmodes & mode[0]) {
+            if(modes->modes & mode[0]) {
+                addMode[addModePos++] = (char) mode[1];
+                if(MODE_TYPE != CHANNEL_MODE_TYPE_D) {
+                    if(MODE_VALUE == CHANNEL_MODE_VALUE_STRING)
+                        addParamsPos += sprintf(addParams + addParamsPos, " %s", modes->mode_str_args[MODE_VALUE_INDEX]);
+                    else if(MODE_VALUE == CHANNEL_MODE_VALUE_INTEGER)
+                        addParamsPos += sprintf(addParams + addParamsPos, " %d", modes->mode_int_args[MODE_VALUE_INDEX]);
+                }
+            } else {
+                delMode[delModePos++] = (char) mode[1];
+            }
+        }
+    }
+    addMode[addModePos] = '\0';
+    delMode[delModePos] = '\0';
+    addParams[addParamsPos] = '\0';
+    sprintf(modesStr, "%s%s%s%s%s", (addModePos ? "+" : ""), addMode, (delModePos ? "-" : ""), delMode, addParams);
+    if(*modesStr == '\0') {
+        sprintf(modesStr, "+");
+    }
+    #undef MODE_TYPE
+    #undef MODE_VALUE
+    #undef MODE_VALUE_INDEX
+}
diff --git a/src/ModeNode.h b/src/ModeNode.h
new file mode 100644 (file)
index 0000000..0b8ab56
--- /dev/null
@@ -0,0 +1,61 @@
+/* ModeNode.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _ModeNode_h
+#define _ModeNode_h
+#include "main.h"
+
+struct ChanNode;
+
+//Types: http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
+#define CHANNEL_MODE_TYPE_A        0x01 /* ... special (addresses or users) ... */
+#define CHANNEL_MODE_TYPE_B        0x02 /* These modes always take a parameter. */
+#define CHANNEL_MODE_TYPE_C        0x03 /* These modes take a parameter only when set. */
+#define CHANNEL_MODE_TYPE_D        0x04 /* These modes never take a parameter. */
+#define CHANNEL_MODE_TYPE          0x07 /* bit mask to get the type */
+
+#define CHANNEL_MODE_VALUE_STRING  0x10
+#define CHANNEL_MODE_VALUE_INTEGER 0x20
+#define CHANNEL_MODE_VALUE         0x30 /* bit mask to get the value */
+
+#define CHANNEL_MODE_KEY           0x40 /* mode is a key - automatically add the current key as parameter on unset */
+
+#define CHANNEL_MODE_VALUE_INDEX_SHIFT 8
+#define CHANNEL_MODE_VALUE_INDEX_MASK  (0xff << CHANNEL_MODE_VALUE_INDEX_SHIFT) /* this "bitrange" is reserved for storing the array indexes of the mode values */
+
+struct ModeNode {
+    struct ChanNode *chan;
+    unsigned int modes;
+    unsigned int allmodes;
+    char **mode_str_args;
+    int *mode_int_args;
+};
+
+#ifndef DND_FUNCTIONS
+void init_ModeNode();
+/* MODULAR ACCESSIBLE */ struct ModeNode *createModeNode(struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ void freeModeNode(struct ModeNode *modes);
+/* MODULAR ACCESSIBLE */ int isModeSet(struct ModeNode* modes, char modeChar);
+/* MODULAR ACCESSIBLE */ int isModeAffected(struct ModeNode* modes, char modeChar);
+/* MODULAR ACCESSIBLE */ void* getModeValue(struct ModeNode* modes, char modeChar);
+/* MODULAR ACCESSIBLE */ unsigned int getModeType(struct ModeNode* modes, char modeChar);
+/* MODULAR ACCESSIBLE */ void parseModes(struct ModeNode* modes, char *modeStr, char **argv, int argc);
+/* MODULAR ACCESSIBLE */ void parseModeString(struct ModeNode* modes, char *modeStr);
+/* MODULAR ACCESSIBLE */ int parseMode(struct ModeNode* modes, int add, char mode, char *param);
+/* MODULAR ACCESSIBLE */ void getModeString(struct ModeNode* modes, char *modesStr);
+/* MODULAR ACCESSIBLE */ void getFullModeString(struct ModeNode* modes, char *modesStr);
+#endif
+#endif
diff --git a/src/ModuleFunctions.c b/src/ModuleFunctions.c
new file mode 100644 (file)
index 0000000..4e9a290
--- /dev/null
@@ -0,0 +1,90 @@
+/* ModuleFunctions.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "ModuleFunctions.h"
+#include "modules.h"
+#include "ChanNode.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
+#define FUNCTION_GLOBAL_CMD_REGISTER_NEONBACKUP   0
+#define FUNCTION_GLOBAL_CMD_UNREGISTER_NEONBACKUP 1
+#define FUNCTION_NEONBACKUP_RECOVER_CHAN          2
+
+#define TOTAL_FUNCTIONS                           3
+
+struct module_function {
+    struct ModuleInfo *module;
+    void *function;
+};
+
+static struct module_function module_functions[TOTAL_FUNCTIONS];
+
+void init_modulefunctions() {
+    int i;
+    for(i = 0; i < TOTAL_FUNCTIONS; i++) {
+        module_functions[i].function = NULL;
+    }
+}
+
+void scan_module(struct ModuleInfo *module) {
+    char *function_names[] = {
+        "global_cmd_register_neonbackup",   /* FUNCTION_GLOBAL_CMD_REGISTER_NEONBACKUP */
+        "global_cmd_unregister_neonbackup", /* FUNCTION_GLOBAL_CMD_UNREGISTER_NEONBACKUP */
+        "neonbackup_recover_chan"           /* FUNCTION_NEONBACKUP_RECOVER_CHAN */
+    };
+    int i;
+    for(i = 0; i < TOTAL_FUNCTIONS; i++) {
+        #ifndef WIN32
+        void* func = dlsym(module->module, function_names[i]);
+        #else
+        FARPROC func = GetProcAddress(module->module, function_names[i]);
+        #endif
+        if(func) {
+            module_functions[i].module = module;
+            module_functions[i].function = func;
+        }
+    }
+}
+
+void free_module_functions(struct ModuleInfo *module) {
+    int i;
+    for(i = 0; i < TOTAL_FUNCTIONS; i++) {
+        if(module_functions[i].module == module) {
+            module_functions[i].function = NULL;
+        }
+    }
+}
+
+/* function handlers */
+void module_global_cmd_register_neonbackup(char *chan) {
+    if(!module_functions[FUNCTION_GLOBAL_CMD_REGISTER_NEONBACKUP].function)
+        return;
+    ((void (*)(char *)) module_functions[FUNCTION_GLOBAL_CMD_REGISTER_NEONBACKUP].function)(chan);
+}
+
+void module_global_cmd_unregister_neonbackup(char *chan) {
+    if(!module_functions[FUNCTION_GLOBAL_CMD_UNREGISTER_NEONBACKUP].function)
+        return;
+    ((void (*)(char *)) module_functions[FUNCTION_GLOBAL_CMD_UNREGISTER_NEONBACKUP].function)(chan);
+}
+
+void module_neonbackup_recover_chan(struct ChanNode *chan) {
+    if(!module_functions[FUNCTION_NEONBACKUP_RECOVER_CHAN].function)
+        return;
+    ((void (*)(struct ChanNode *)) module_functions[FUNCTION_NEONBACKUP_RECOVER_CHAN].function)(chan);
+}
diff --git a/src/ModuleFunctions.h b/src/ModuleFunctions.h
new file mode 100644 (file)
index 0000000..9f26d0d
--- /dev/null
@@ -0,0 +1,34 @@
+/* ModuleFunctions.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _ModuleFunctions_h
+#define _ModuleFunctions_h
+#include "overall.h"
+
+#ifndef DND_FUNCTIONS
+struct ModuleInfo;
+struct ChanNode;
+
+void init_modulefunctions();
+void scan_module(struct ModuleInfo *module);
+void free_module_functions(struct ModuleInfo *module);
+
+/* MODULAR ACCESSIBLE */ void module_global_cmd_register_neonbackup(char *chan);
+/* MODULAR ACCESSIBLE */ void module_global_cmd_unregister_neonbackup(char *chan);
+/* MODULAR ACCESSIBLE */ void module_neonbackup_recover_chan(struct ChanNode *chan);
+
+#endif
+#endif
diff --git a/src/QServer.c b/src/QServer.c
new file mode 100644 (file)
index 0000000..ef382c9
--- /dev/null
@@ -0,0 +1,388 @@
+/* QServer.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "QServer.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ModeNode.h"
+#include "ChanUser.h"
+#include "ClientSocket.h"
+#include "WHOHandler.h"
+#include "ConfigParser.h"
+#include "bots.h"
+#include "IOHandler.h"
+#include "tools.h"
+
+#ifdef WIN32
+typedef uint32_t socklen_t;
+#endif
+
+#define QSERVER_TIMEOUT 30
+#define QSERVER_MAXCLIENTS 100
+
+#define QSERVER_FLAG_AUTHED     0x01
+#define QSERVER_FLAG_IN_USE     0x02
+
+struct QServerClient {
+    struct IODescriptor *iofd;
+    unsigned int flags;
+    int references;
+    struct QServerClient *next;
+};
+
+static struct IODescriptor *server_iofd = NULL;
+struct QServerClient *qserver_clients = NULL;
+static int qserver_clientcount = 0;
+
+static IOHANDLER_CALLBACK(qserver_callback);
+
+void qserver_init() {
+    if(get_int_field("QServer.enabled")) {
+        char *host = get_string_field("QServer.host");
+        if(!host)
+            host = "0.0.0.0";
+        int port = get_int_field("QServer.port");
+        if(!port)
+            port = 7499;
+        server_iofd = iohandler_listen(host, port, qserver_callback);
+    }
+}
+
+void qserver_free() {
+    if(!server_iofd)
+        return;
+    struct QServerClient *client, *next;
+    for (client = qserver_clients; client; client = next) {
+        next = client->next;
+        if(client->iofd)
+            iohandler_close(client->iofd);
+        free(client);
+    }
+    qserver_clients = NULL;
+    qserver_clientcount = 0;
+    iohandler_close(server_iofd);
+    server_iofd = NULL;
+}
+
+static int qserver_write(struct QServerClient *client, char* msg, int len) {
+    if (!client || !client->iofd) return 0;
+    if(!len)
+        len = strlen(msg);
+       iohandler_send(client->iofd, msg, len);
+    return 1;
+}
+
+static void qserver_put(struct QServerClient *client, const char *text, ...) {
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!client || !client->iofd) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    qserver_write(client, sendBuf, pos+1);
+}
+
+static void qserver_update_lastmsg(struct QServerClient *client) {
+    struct timeval timeout;
+    gettimeofday(&timeout, NULL);
+    timeout.tv_sec += QSERVER_TIMEOUT;
+    iohandler_set_timeout(client->iofd, &timeout);
+}
+
+static void qserver_parse_A(struct QServerClient *client, char **argv, int argc) {
+    if(client->flags & QSERVER_FLAG_AUTHED) {
+        qserver_put(client, "E :Already Authed");
+        return;
+    }
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    if(strcmp(argv[0], get_string_field("QServer.pass"))) {
+        qserver_put(client, "E :Wrong Password");
+        return;
+    }
+    client->flags |= QSERVER_FLAG_AUTHED;
+    qserver_update_lastmsg(client);
+    qserver_put(client, "A :Logged in");
+}
+
+#define QSERVER_COMMAND_HEADER {\
+    if(!(client->flags & QSERVER_FLAG_AUTHED)) {\
+        qserver_put(client, "E :Not Authed");\
+        return;\
+    }\
+    qserver_update_lastmsg(client);\
+}
+
+static void qserver_parse_U(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_C(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_AC(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_ACU(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_R(struct QServerClient *client, char **argv, int argc);
+
+static void qserver_parse(struct QServerClient *client, char *line) {
+    int argc = 0;
+    char *argv[MAXNUMPARAMS];
+    while(*line) {
+        //skip leading spaces
+        while (*line == ' ')
+            *line++ = 0;
+        if (*line == ':') {
+           //the rest is a single parameter
+           argv[argc++] = line + 1;
+           break;
+        }
+        argv[argc++] = line;
+        if (argc >= MAXNUMPARAMS)
+            break;
+        while (*line != ' ' && *line)
+            line++;
+    }
+    if(!stricmp(argv[0], "A")) //AUTH
+        qserver_parse_A(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "U")) //get User
+        qserver_parse_U(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "C")) //get Channel
+        qserver_parse_C(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "AC")) //get All Channels
+        qserver_parse_AC(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "ACU")) //get All ChannelUsers
+        qserver_parse_ACU(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "R")) //RAW
+        qserver_parse_R(client, argv+1, argc-1);
+    else
+        qserver_put(client, "E :Unknown Command");
+}
+
+static void qserver_accept(int sockfd) {
+    struct IODescriptor *client_iofd = iohandler_add(sockfd, IOTYPE_CLIENT, NULL, qserver_callback);
+    client_iofd->state = IO_CONNECTED;
+    iohandler_update(client_iofd);
+    if(qserver_clientcount >= QSERVER_MAXCLIENTS) {
+        iohandler_printf(client_iofd, "E :Maximum QServer Connections reached");
+        iohandler_close(client_iofd);
+        return;
+    }
+    struct QServerClient *client = malloc(sizeof(*client));
+    client->iofd = client_iofd;
+    client->references = 0;
+    client->next = qserver_clients;
+    qserver_clients = client;
+    qserver_clientcount++;
+}
+
+static void qserver_cleanup() {
+    struct QServerClient *client, *next, *prev = NULL;
+    for (client = qserver_clients; client; client = next) {
+        next = client->next;
+        if(client->iofd == NULL && !(client->flags & QSERVER_FLAG_IN_USE)) {
+            if(prev) 
+                prev->next = client->next;
+            else
+                qserver_clients = client->next;
+            qserver_clientcount--;
+            free(client);
+        }
+    }
+}
+
+static IOHANDLER_CALLBACK(qserver_callback) {
+    struct QServerClient *client = event->iofd->data;
+    switch(event->type) {
+    case IOEVENT_TIMEOUT:
+        qserver_put(client, "E :Timeout");
+        client->iofd = NULL;
+        break;
+    case IOEVENT_RECV:
+        qserver_parse(client, event->data.recv_str);
+        break;
+    case IOEVENT_CLOSED:
+        iohandler_close(client->iofd);
+        client->iofd = NULL;
+        break;
+    case IOEVENT_ACCEPT:
+        qserver_accept(event->data.accept_fd);
+        break;
+    default:
+        break;
+    }
+    qserver_cleanup();
+}
+
+/* 
+* Command functions
+*/
+
+static USERAUTH_CALLBACK(qserver_parse_U_async);
+static void qserver_parse_U(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct UserNode *cuser = getUserByNick(argv[0]);
+    if(!cuser) {
+        cuser = createTempUser(argv[0]);
+        if(!cuser) {
+            qserver_put(client, "U 0 :Unknown User");
+            return;
+        }
+        cuser->flags |= USERFLAG_ISTMPUSER;
+    }
+    client->references++;
+    client->flags |= QSERVER_FLAG_IN_USE;
+    get_userauth(cuser, 0, qserver_parse_U_async, client);
+}
+
+static USERAUTH_CALLBACK(qserver_parse_U_async) {
+    struct QServerClient *qclient = data;
+    qclient->references--;
+    if(!qclient->references)
+        qclient->flags &= ~QSERVER_FLAG_IN_USE;
+    if(!user) {
+        qserver_put(qclient, "U 0 :Unknown User");
+        return;
+    }
+    qserver_put(qclient, "U 1 %s %s %s %s :%s", user->nick, user->ident, user->host, ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "0"), user->realname);
+}
+
+static void qserver_parse_C(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ChanNode *chan = getChanByName(argv[0]);
+    if(!chan) {
+        qserver_put(client, "C 0 :Unknown Channel");
+        return;
+    }
+    char tmpStr[MAXLEN];
+    getModeString(chan->modes, tmpStr);
+    qserver_put(client, "C 1 %s %d %s :%s", chan->name, chan->usercount, tmpStr, chan->topic);
+}
+
+static void qserver_parse_AC(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    struct ChanNode *chan;
+    char tmpStr[MAXLEN];
+    for(chan = getAllChans(NULL); chan; chan = getAllChans(chan)) {
+        getModeString(chan->modes, tmpStr);
+        qserver_put(client, "AC %s %d %s :%s", chan->name, chan->usercount, tmpStr, chan->topic);
+    }
+    qserver_put(client, "ACE"); //AllChannelsEnd
+}
+
+static USERLIST_CALLBACK(qserver_parse_ACU_async);
+static void qserver_parse_ACU(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ChanNode *chan = getChanByName(argv[0]);
+    if(!chan) {
+        qserver_put(client, "ACUE 0 :Unknown Channel");
+        return;
+    }
+    if(argc > 1 && !stricmp(argv[1], "1")) {
+        client->references++;
+        client->flags |= QSERVER_FLAG_IN_USE;
+        get_userlist_if_invisible(chan, 0, qserver_parse_ACU_async, client);
+        return;
+    }
+    char tmpStr[6];
+    int tmpStrPos;
+    struct ChanUser *chanuser;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        tmpStrPos = 0;
+        if(chanuser->flags & CHANUSERFLAG_OPPED)
+            tmpStr[tmpStrPos++] = '@';
+        if(chanuser->flags & CHANUSERFLAG_HALFOPPED)
+            tmpStr[tmpStrPos++] = '%';
+        if(chanuser->flags & CHANUSERFLAG_VOICED)
+            tmpStr[tmpStrPos++] = '+';
+        if(chanuser->flags & CHANUSERFLAG_INVISIBLE)
+            tmpStr[tmpStrPos++] = '<';
+        tmpStr[tmpStrPos] = '\0';
+        qserver_put(client, "ACU %s %s %s", chanuser->user->nick, ((chanuser->user->flags & USERFLAG_ISAUTHED) ? chanuser->user->auth : "0"), tmpStr);
+    }
+    qserver_put(client, "ACUE 1");
+}
+
+static USERLIST_CALLBACK(qserver_parse_ACU_async) {
+    struct QServerClient *qclient = data;
+    qclient->references--;
+    if(!qclient->references)
+        qclient->flags &= ~QSERVER_FLAG_IN_USE;
+    char tmpStr[6];
+    int tmpStrPos;
+    struct ChanUser *chanuser;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        tmpStrPos = 0;
+        if(chanuser->flags & CHANUSERFLAG_OPPED)
+            tmpStr[tmpStrPos++] = '@';
+        if(chanuser->flags & CHANUSERFLAG_HALFOPPED)
+            tmpStr[tmpStrPos++] = '%';
+        if(chanuser->flags & CHANUSERFLAG_VOICED)
+            tmpStr[tmpStrPos++] = '+';
+        if(chanuser->flags & CHANUSERFLAG_INVISIBLE)
+            tmpStr[tmpStrPos++] = '<';
+        tmpStr[tmpStrPos] = '\0';
+        qserver_put(qclient, "ACU %s %s %s", chanuser->user->nick, ((chanuser->user->flags & USERFLAG_ISAUTHED) ? chanuser->user->auth : "0"), tmpStr);
+    }
+    qserver_put(qclient, "ACUE 1");
+}
+
+static void qserver_parse_R(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(argc < 3) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ClientSocket *bot;
+    if(!strcmp(argv[0], "1")) {
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(!stricmp(bot->user->nick, argv[1])) break;
+        }
+    } else {
+        struct ClientSocket *low_bot;
+        int botid = resolve_botalias(argv[1]);
+        if(botid == -1)
+            botid = atoi(argv[1]);
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->botid == botid) {
+                if(bot->flags & SOCKET_FLAG_PREFERRED) break;
+                low_bot = bot;
+            }
+        }
+        if(!bot)
+            bot = low_bot;
+    }
+    if(!bot) {
+        qserver_put(client, "R 0 :Bot not found");
+        return;
+    }
+    putsock(bot, "%s", argv[2]);
+    qserver_put(client, "R 1");
+}
diff --git a/src/QServer.h b/src/QServer.h
new file mode 100644 (file)
index 0000000..01289ba
--- /dev/null
@@ -0,0 +1,25 @@
+/* QServer.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _QServer_h
+#define _QServer_h
+
+#include "main.h"
+
+void qserver_init();
+void qserver_free();
+
+#endif
\ No newline at end of file
diff --git a/src/UserNode.c b/src/UserNode.c
new file mode 100644 (file)
index 0000000..cd8735e
--- /dev/null
@@ -0,0 +1,559 @@
+/* UserNode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "tools.h"
+#include "IRCEvents.h"
+#include "IPNode.h"
+#include "log.h"
+
+static struct UserNode **userList;
+
+unsigned int valid_user_modes[] = {
+    1,  'o',
+    2,  'O',
+    3,  'i',
+    4,  'w',
+    5,  's',
+    6,  'd',
+    7,  'k',
+    8,  'g',
+    9,  'n',
+    10, 'I',
+    11, 'X',
+    12, 'S',
+    13, 'H',
+    14, 'c',
+    15, 'W',
+    16, 't',
+    17, 'D',
+    18, 'x',
+//  ^ maximum is 32!!!
+    0x00, 0x00
+};
+
+void init_UserNode() {
+    unsigned int *mode, flag = 1;
+    userList = calloc(VALID_NICK_CHARS_FIRST_LEN+1, sizeof(*userList));
+    for (mode = valid_user_modes; mode[1]; mode += 2) {
+        mode[0] = flag;
+        flag = flag << 1;
+    }
+}
+
+void free_UserNode() {
+    //kamikaze free all users
+    //chanusers will be destroyed in free_ChanNode()
+    int i;
+    struct UserNode *user, *next;
+    for(i = 0; i < VALID_NICK_CHARS_FIRST_LEN+1; i++) {
+        for(user = userList[i]; user; user = next) {
+            next = user->next;
+            free(user);
+        }
+    }
+    free(userList);
+}
+
+int is_valid_nick(const char *nick) {
+    unsigned int i;
+    //first char must be one of: a-zA-Z{|}~[\]^_`
+    if (!strchr(VALID_NICK_CHARS_FIRST, *nick))
+        return 0;
+    //all other chars must be one of: a-zA-Z0-9{|}~[\]^_`
+    for (i = 0; nick[i]; ++i)
+        if (!strchr(VALID_NICK_CHARS, nick[i]))
+            return 0;
+    if (strlen(nick) > NICKLEN)
+        return 0;
+    return 1;
+}
+
+static int get_nicklist_entry(int nick) {
+    int i;
+    char *valid_chars = VALID_NICK_CHARS_FIRST;
+    for(i = 0; i < VALID_NICK_CHARS_FIRST_LEN; i++) {
+        if(valid_chars[i] == nick)
+            return i;
+    }
+    return -1; //ERROR!
+}
+
+static unsigned int* getUserModeOptions(char mode) {
+    unsigned int *cmode;
+    for (cmode = valid_user_modes; cmode[1]; cmode += 2) {
+        if(cmode[1] == mode)
+            return cmode;
+    }
+    return NULL;
+}
+
+int isUserModeSet(struct UserNode *user, char modeChar) {
+    unsigned int *modeOpt = getUserModeOptions(modeChar);
+    return (user->usermode & modeOpt[0]);
+}
+
+void parseUserModes(struct UserNode* user, char *modeStr) {
+    int i, add = 1;
+    unsigned int *modeOpt;
+    for(i = 0; i < strlen(modeStr); i++) {
+        if(modeStr[i] == '+') {
+            add = 1;
+            continue;
+        }
+        if(modeStr[i] == '-') {
+            add = 0;
+            continue;
+        }
+        modeOpt = getUserModeOptions(modeStr[i]);
+        if(!modeOpt) continue; // unknown mode?
+        if(add) {
+            user->usermode |= modeOpt[0];
+        } else {
+            user->usermode &= ~modeOpt[0];
+        }
+    }
+}
+
+
+struct UserNode* getUserByNick(const char *nick) { //case sensitive
+    int userListIndex = get_nicklist_entry(*nick);
+    if(userListIndex == -1 || userList[userListIndex] == NULL)
+        return NULL;
+    SYNCHRONIZE(cache_sync);
+    struct UserNode *user;
+    for(user = userList[userListIndex]; user; user = user->next) {
+        if(!stricmp(nick, user->nick)) {
+            DESYNCHRONIZE(cache_sync);
+            return user;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+struct UserNode* getUserByMask(const char *mask) { //case sensitive
+    SYNCHRONIZE(cache_sync);
+    char cmask[strlen(mask)+1];
+    strcpy(cmask, mask);
+    int i;
+    struct UserNode *user = NULL;
+    for(i = 0; i < strlen(mask); i++) {
+        if(cmask[i] == '!') {
+            cmask[i] = 0;
+            user = getUserByNick(&cmask[0]);
+            DESYNCHRONIZE(cache_sync);
+            return user;
+        } else if(cmask[i] == '.') {
+            //it's a server
+            DESYNCHRONIZE(cache_sync);
+            return NULL;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+struct UserNode* searchUserByNick(const char *nick) { //case insensitive
+    if(!isalpha(*nick)) 
+        return getUserByNick(nick);
+
+    SYNCHRONIZE(cache_sync);
+    int userListIndex;
+    struct UserNode *user;
+
+    //search in the lower case "section"
+    userListIndex = get_nicklist_entry(tolower(*nick));
+    if(userListIndex != -1 && userList[userListIndex] != NULL) {
+        for(user = userList[userListIndex]; user; user = user->next) {
+            if(!stricmp(nick, user->nick)) {
+                DESYNCHRONIZE(cache_sync);
+                return user;
+            }
+        }
+    }
+    //search in the upper case "section"
+    userListIndex = get_nicklist_entry(toupper(*nick));
+    if(userListIndex != -1 && userList[userListIndex] != NULL) {
+        for(user = userList[userListIndex]; user; user = user->next) {
+            if(!stricmp(nick, user->nick)) {
+                DESYNCHRONIZE(cache_sync);
+                return user;
+            }
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+int countUsersWithHost(char *host) {
+    SYNCHRONIZE(cache_sync);
+    int i, count = 0;
+    struct UserNode *user;
+    for(i = 0; i < VALID_NICK_CHARS_FIRST_LEN; i++) {
+        for(user = userList[i]; user; user = user->next) {
+            if(!strcmp(user->host, host)) {
+                count++;
+            }
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return count;
+}
+
+char *getAuthFakehost(char *auth) {
+    SYNCHRONIZE(cache_sync);
+    int i;
+    struct UserNode *user;
+    for(i = 0; i < VALID_NICK_CHARS_FIRST_LEN; i++) {
+        for(user = userList[i]; user; user = user->next) {
+            if((user->flags & USERFLAG_ISAUTHED) && !strcmp(user->auth, auth) && isFakeHost(user->host)) {
+                DESYNCHRONIZE(cache_sync);
+                return user->host;
+            }
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+struct UserNode* getAllUsers(struct UserNode *last) {
+    SYNCHRONIZE(cache_sync);
+    if(last == NULL || last->next == NULL) {
+        int cindex;
+        if(last == NULL)
+            cindex = 0;
+        else
+            cindex = get_nicklist_entry(last->nick[0]) + 1;
+        while(userList[cindex] == NULL && cindex < VALID_NICK_CHARS_FIRST_LEN)
+            cindex++;
+        DESYNCHRONIZE(cache_sync);
+        if(cindex >= VALID_NICK_CHARS_FIRST_LEN) return NULL;
+        return userList[cindex];
+    } else {
+        DESYNCHRONIZE(cache_sync);
+        return last->next;
+    }
+}
+
+struct UserNode* getUsersWithAuth(const char *auth, struct UserNode *last) {
+    SYNCHRONIZE(cache_sync);
+    int cindex = (last ? get_nicklist_entry(last->nick[0]) : 0);
+    struct UserNode *cuser = last;
+    while(cindex <= VALID_NICK_CHARS_FIRST_LEN) {
+        for(cuser = (cuser ? cuser->next : userList[cindex]); cuser; cuser = cuser->next) {
+            if((cuser->flags & USERFLAG_ISAUTHED) && !strcmp(cuser->auth, auth)) {
+                DESYNCHRONIZE(cache_sync);
+                return cuser;
+            }
+        }
+        cindex++;
+        cuser = NULL;
+    }
+    DESYNCHRONIZE(cache_sync);
+    return NULL;
+}
+
+int getUserCount() {
+    SYNCHRONIZE(cache_sync);
+    int i, count = 0;
+    struct UserNode *user;
+    for(i = 0; i < VALID_NICK_CHARS_FIRST_LEN; i++) {
+        for(user = userList[i]; user; user = user->next) {
+            count++;
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+    return count;
+}
+
+struct UserNode* addUser(const char *nick) {
+    int userListIndex = get_nicklist_entry(*nick);
+    if(userListIndex == -1 || !is_valid_nick(nick))
+        return NULL;
+    struct UserNode *user = malloc(sizeof(*user));
+    if (!user)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    strcpy(user->nick, nick);
+    user->created = time(0);
+    user->ident[0] = 0;
+    user->host[0] = 0;
+    user->ip = NULL;
+    user->realname[0] = 0;
+    user->flags = 0;
+    user->channel = NULL;
+    user->last_who = 0;
+    user->usermode = 0;
+    SYNCHRONIZE(cache_sync);
+    user->next = userList[userListIndex];
+    userList[userListIndex] = user;
+    DESYNCHRONIZE(cache_sync);
+    return user;
+}
+
+struct UserNode* addUserMask(const char *mask) {
+    char cmask[strlen(mask)+1];
+    strcpy(cmask, mask);
+    int i, ii = 0;
+    struct UserNode *user = NULL;
+    for(i = 0; i < strlen(mask)+1; i++) {
+        if(cmask[i] == '!') {
+            cmask[i] = 0;
+            user = addUser(cmask);
+            if(user == NULL) return NULL;
+            ii = i+1;
+        } else if(cmask[i] == '.' && !user) {
+            //it's a server
+            return NULL;
+        } else if(cmask[i] == '@') {
+            if(user == NULL) return NULL;
+            cmask[i] = 0;
+            strcpy(user->ident, &cmask[ii]);
+            ii = i+1;
+        } else if(cmask[i] == '\0') {
+            if(user == NULL) return NULL;
+            strcpy(user->host, &cmask[ii]);
+        }
+    }
+    return user;
+}
+
+struct UserNode* createTempUser(const char *nick) {
+    int already_on_list = 0;
+    struct UserNode *user = NULL;
+    if(!is_valid_nick(nick)) {
+        return NULL;
+    }
+    for(user = userList[TEMPUSER_LIST_INDEX]; user; user = user->next) {
+        if(!stricmp(user->nick, nick)) {
+            already_on_list = 1;
+            break;
+        }
+    }
+    if(!user) {
+        user = malloc(sizeof(*user));
+        if (!user) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return NULL;
+        }
+        user->ident[0] = 0;
+        user->host[0] = 0;
+        user->ip = NULL;
+        user->realname[0] = 0;
+        user->flags = 0;
+        user->channel = NULL;
+        user->usermode = 0;
+        user->last_who = 0;
+    } else
+        user->flags &= ~USERFLAG_FREETMPUSER;
+    user->created = time(0);
+    if(user->created - user->last_who > REWHO_TIMEOUT)
+        user->flags &= ~USERFLAG_ISAUTHED; //remove authed flag (security reasons)
+    strcpy(user->nick, nick);
+    if(!already_on_list) {
+        SYNCHRONIZE(cache_sync);
+        user->next = userList[TEMPUSER_LIST_INDEX];
+        userList[TEMPUSER_LIST_INDEX] = user;
+        DESYNCHRONIZE(cache_sync);
+    }
+    return user;
+}
+
+struct UserNode* createTempUserMask(const char *mask) {
+    //note: it could also be a server we have to create a temponary user for...
+    char cmask[strlen(mask)+1];
+    strcpy(cmask, mask);
+    int i, ii = 0;
+    int already_on_list = 0;
+    struct UserNode *user = NULL;
+    for(i = 0; i < strlen(mask)+1; i++) {
+        if(cmask[i] == '!') {
+            cmask[i] = 0;
+            if(!is_valid_nick(cmask)) {
+                return NULL;
+            }
+            for(user = userList[TEMPUSER_LIST_INDEX]; user; user = user->next) {
+                if(!stricmp(user->nick, cmask)) {
+                    already_on_list = 1;
+                    break;
+                }
+            }
+            if(!user) {
+                user = malloc(sizeof(*user));
+                if (!user) {
+                    printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                    return NULL;
+                }
+                user->ident[0] = 0;
+                user->host[0] = 0;
+                user->ip = NULL;
+                user->realname[0] = 0;
+                user->flags = 0;
+                user->channel = NULL;
+                user->usermode = 0;
+                user->last_who = 0;
+            } else
+                user->flags &= ~USERFLAG_FREETMPUSER;
+            user->created = time(0);
+            if(user->created - user->last_who > REWHO_TIMEOUT)
+                user->flags &= ~USERFLAG_ISAUTHED; //remove authed flag (security reasons)
+            strcpy(user->nick, cmask);
+            ii = i+1;
+        } else if(cmask[i] == '.' && !user) {
+            //it's a server
+            user = malloc(sizeof(*user));
+            if (!user)
+            {
+                printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return NULL;
+            }
+            strcpy(user->host, cmask);
+            strncpy(user->nick, cmask, NICKLEN);
+            user->nick[NICKLEN] = 0;
+            user->created = time(0);
+            user->ident[0] = 0;
+            user->host[0] = 0;
+            user->ip = NULL;
+            user->realname[0] = 0;
+            user->flags = USERFLAG_ISSERVER;
+            user->channel = NULL;
+            user->usermode = 0;
+            user->last_who = 0;
+            break;
+        } else if(cmask[i] == '@') {
+            if(user == NULL) return NULL;
+            cmask[i] = 0;
+            strcpy(user->ident, &cmask[ii]);
+            ii = i+1;
+        } else if(cmask[i] == '\0') {
+            if(user == NULL) {
+                //nick only
+                user = malloc(sizeof(*user));
+                if (!user)
+                {
+                    printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                    return NULL;
+                }
+                strcpy(user->nick, cmask);
+                user->created = time(0);
+                user->ident[0] = 0;
+                user->host[0] = 0;
+                user->ip = NULL;
+                user->realname[0] = 0;
+                user->flags = 0;
+                user->channel = NULL;
+                user->usermode = 0;
+                user->last_who = 0;
+                break;
+            }
+            strcpy(user->host, &cmask[ii]);
+        }
+    }
+    if(!already_on_list) {
+        SYNCHRONIZE(cache_sync);
+        user->next = userList[TEMPUSER_LIST_INDEX];
+        userList[TEMPUSER_LIST_INDEX] = user;
+        DESYNCHRONIZE(cache_sync);
+    }
+    return user;
+}
+
+int renameUser(struct UserNode* user, const char *new_nick) {
+    if(!is_valid_nick(new_nick))
+        return 0;
+    if(user->nick[0] == *new_nick) {
+        strcpy(user->nick, new_nick);
+        return 1;
+    }
+    //delUser(user, 0); //EPIC FAIL! This deletes the user from the channel Userlist -.-
+    //manually remove the user from the old userList
+    SYNCHRONIZE(cache_sync);
+    int userListIndex = get_nicklist_entry(user->nick[0]);
+    if(userListIndex != -1) {
+        struct UserNode *cuser, *last_user = NULL;
+        for(cuser = userList[userListIndex]; cuser; cuser = cuser->next) {
+            if(cuser == user) {
+                if(last_user)
+                    last_user->next = user->next;
+                else
+                    userList[userListIndex] = user->next;
+                break;
+            } else
+                last_user = cuser;
+        }
+    }
+    userListIndex = get_nicklist_entry(*new_nick);
+    strcpy(user->nick, new_nick);
+    user->next = userList[userListIndex];
+    userList[userListIndex] = user;
+    DESYNCHRONIZE(cache_sync);
+    return 1;
+}
+
+void delUser(struct UserNode* user, int freeUser) {
+    int userListIndex = ((user->flags & USERFLAG_ISTMPUSER) ? TEMPUSER_LIST_INDEX : get_nicklist_entry(user->nick[0]));
+    if(userListIndex == -1) return;
+    SYNCHRONIZE(cache_sync);
+    event_freeuser(user);
+    struct UserNode *cuser, *last_user = NULL;
+    for(cuser = userList[userListIndex]; cuser; cuser = cuser->next) {
+        if(cuser == user) {
+            if(last_user)
+                last_user->next = user->next;
+            else
+                userList[userListIndex] = user->next;
+            break;
+        } else
+            last_user = cuser;
+    }
+    if(freeUser && (user->flags & USERFLAG_IS_ON_WHO_QUEUE)) {
+        user->flags |= USERFLAG_FREE_AFTER_WHO;
+        freeUser = 0;
+    }
+    if(user->channel) {
+        struct ChanUser *chanUser, *next;
+        for(chanUser = user->channel; chanUser; chanUser = next) {
+            next = chanUser->next_chan;
+            removeChanUserFromLists(chanUser, 1, 0, freeUser);
+        }
+    }
+    if(freeUser) {
+        if(user->ip)
+            freeIPNode(user->ip);
+        free(user);
+    } else
+        user->next = NULL;
+    DESYNCHRONIZE(cache_sync);
+}
+
+void clearTempUsers() {
+    SYNCHRONIZE(cache_sync);
+    int userListIndex = TEMPUSER_LIST_INDEX;
+    struct UserNode *cuser, *next;
+    time_t now = time(0);
+    for(cuser = userList[userListIndex]; cuser; cuser = next) {
+        next = cuser->next;
+        if(cuser->flags & USERFLAG_FREETMPUSER || now - cuser->created >= 300) {
+            delUser(cuser, 1);
+        }
+    }
+    DESYNCHRONIZE(cache_sync);
+}
diff --git a/src/UserNode.h b/src/UserNode.h
new file mode 100644 (file)
index 0000000..ca6b70a
--- /dev/null
@@ -0,0 +1,85 @@
+/* UserNode.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _UserNode_h
+#define _UserNode_h
+#include "main.h"
+
+#define USERFLAG_ISBOT              0x0001
+#define USERFLAG_ISAUTHED           0x0002
+#define USERFLAG_ISIRCOP            0x0004
+#define USERFLAG_ISTMPUSER          0x0008
+#define USERFLAG_ISSERVER           0x0010
+#define USERFLAG_FREETMPUSER        0x0020
+#define USERFLAG_LOADED_SETTINGS    0x0040
+#define USERFLAG_REPLY_PRIVMSG      0x0080
+#define USERFLAG_GOD_MODE           0x0100
+#define USERFLAG_HAS_USERID         0x0200
+#define USERFLAG_IS_ON_WHO_QUEUE    0x0400 /* prevents the user struct from beeing freed too early */
+#define USERFLAG_FREE_AFTER_WHO     0x0800 /* user struct is no more referenced - free it after WHO */
+#define USERFLAG_QUITTING           0x1000 /* user is currently quitting... */
+
+#define USERFLAG_WAS_REGISTERING    0x20000000 /* only set for event_join if the quit reason was Registered */
+#define USERFLAG_SCRIPTFLAG1        0x40000000
+#define USERFLAG_SCRIPTFLAG2        0x80000000
+
+struct ChanUser;
+struct language;
+struct IPNode;
+
+struct UserNode {
+    char nick[NICKLEN+1];
+    char ident[USERLEN+1];
+    char host[HOSTLEN+1];
+    char realname[REALLEN+1];
+    char auth[AUTHLEN+1];
+    struct IPNode *ip;
+    unsigned int flags, usermode;
+    time_t created, last_who;
+    struct ChanUser *channel;
+    struct language *language;
+    
+    int user_id;
+    
+    struct UserNode *next;
+};
+
+#define isNetworkService(USER) (USER->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP | USERFLAG_ISSERVER))
+#define isBot(USER) (USER->flags & (USERFLAG_ISBOT))
+
+#ifndef DND_FUNCTIONS
+void init_UserNode();
+void free_UserNode();
+/* MODULAR ACCESSIBLE */ int isUserModeSet(struct UserNode *user, char modeChar);
+void parseUserModes(struct UserNode* user, char *modeStr);
+/* MODULAR ACCESSIBLE */ int is_valid_nick(const char *nick);
+/* MODULAR ACCESSIBLE */ struct UserNode* getUserByNick(const char *nick);
+/* MODULAR ACCESSIBLE */ struct UserNode* getUserByMask(const char *mask);
+/* MODULAR ACCESSIBLE */ int countUsersWithHost(char *host);
+/* MODULAR ACCESSIBLE */ char *getAuthFakehost(char *auth);
+/* MODULAR ACCESSIBLE */ struct UserNode* searchUserByNick(const char *nick);
+/* MODULAR ACCESSIBLE */ struct UserNode* getAllUsers(struct UserNode *last);
+/* MODULAR ACCESSIBLE */ struct UserNode* getUsersWithAuth(const char *auth, struct UserNode *last);
+/* MODULAR ACCESSIBLE */ int getUserCount();
+struct UserNode* addUser(const char *nick);
+struct UserNode* addUserMask(const char *mask);
+/* MODULAR ACCESSIBLE */ struct UserNode* createTempUser(const char *nick);
+struct UserNode* createTempUserMask(const char *mask);
+int renameUser(struct UserNode* user, const char *new_nick);
+void delUser(struct UserNode* user, int freeUser);
+void clearTempUsers();
+#endif
+#endif
diff --git a/src/WHOHandler.c b/src/WHOHandler.c
new file mode 100644 (file)
index 0000000..5db3db6
--- /dev/null
@@ -0,0 +1,395 @@
+/* WHOHandler.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "WHOHandler.h"
+#include "ChanNode.h"
+#include "UserNode.h"
+#include "ChanUser.h"
+#include "ModeNode.h"
+#include "ClientSocket.h"
+#include "IPNode.h"
+#include "modules.h"
+#include "log.h"
+
+#define WHOQUEUETYPE_ISONQUEUE 0x01
+#define WHOQUEUETYPE_USERLIST  0x02
+#define WHOQUEUETYPE_USERAUTH  0x04
+#define WHOQUEUETYPE_CHECKTYPE 0x07
+#define WHOQUEUETYPE_FOUND     0x08
+
+#define MAXCALLBACKS 10
+
+struct WHOQueueEntry {
+    char type;
+    int whoid;
+    struct ChanNode *chan;
+    struct UserNode *user;
+    struct WHOQueueEntry *next;
+    void *callback[MAXCALLBACKS];
+    int module_id[MAXCALLBACKS];
+    void *data[MAXCALLBACKS];
+};
+
+static int checkWHOID(struct ClientSocket *client, int whoid) {
+    struct WHOQueueEntry *entry;
+    for(entry = client->whoqueue_first; entry; entry = entry->next) {
+        if(entry->whoid == whoid)
+            return 1;
+    }
+    return 0;
+}
+
+static struct WHOQueueEntry* addWHOQueueEntry(struct ClientSocket *client) {
+    SYNCHRONIZE(whohandler_sync);
+    int whoid = 0;
+    do {
+        whoid++;
+    } while(checkWHOID(client, whoid) && whoid < 1000);
+    if(whoid == 1000) {
+        DESYNCHRONIZE(whohandler_sync);
+        return NULL;
+    }
+    struct WHOQueueEntry *entry = malloc(sizeof(*entry));
+    if (!entry)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        DESYNCHRONIZE(whohandler_sync);
+        return NULL;
+    }
+    entry->next = NULL;
+    entry->whoid = whoid;
+    if(client->whoqueue_last) {
+        client->whoqueue_last->next = entry;
+    } else {
+        client->whoqueue_first = entry;
+    }
+    client->whoqueue_last = entry;
+    DESYNCHRONIZE(whohandler_sync);
+    return entry;
+}
+
+static struct WHOQueueEntry* getNextWHOQueueEntry(struct ClientSocket *client, int whoid, int freeEntry) {
+    if(!client->whoqueue_first) return NULL;
+    SYNCHRONIZE(whohandler_sync);
+    struct WHOQueueEntry *entry;
+    for(entry = client->whoqueue_first; entry; entry = entry->next) {
+        if(entry->whoid == whoid)
+            break;
+    }
+    if(!entry) {
+        DESYNCHRONIZE(whohandler_sync);
+        return NULL;
+    }
+    if(freeEntry) {
+        client->whoqueue_first = entry->next;
+        if(entry == client->whoqueue_last) {
+            client->whoqueue_last = NULL;
+        }
+    }
+    DESYNCHRONIZE(whohandler_sync);
+    return entry;
+}
+
+void clear_whoqueue(struct ClientSocket *client) {
+    if(!client->whoqueue_first) return;
+    SYNCHRONIZE(whohandler_sync);
+    struct WHOQueueEntry *entry, *next;
+    for(entry = client->whoqueue_first; entry; entry = next) {
+        next = entry->next;
+        free(entry);
+    }
+    client->whoqueue_last = NULL;
+    client->whoqueue_first = NULL;
+    DESYNCHRONIZE(whohandler_sync);
+}
+
+void get_userlist(struct ChanNode *chan, int module_id, userlist_callback_t callback, void *data) {
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(isUserOnChan(bot->user, chan))
+            break;
+    }
+    if(bot == NULL) return;
+    //check if we really need to who the channel
+    int do_who = (!(chan->flags & CHANFLAG_RECEIVED_USERLIST));
+    if(!do_who) {
+        struct ChanUser *chanuser;
+        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+            if(!(chanuser->user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISIRCOP | USERFLAG_ISBOT)) && (time(0) - chanuser->user->last_who) > REWHO_TIMEOUT) {
+                do_who = 1;
+                break;
+            }
+        }
+    }
+    if(do_who) {
+        struct WHOQueueEntry* entry = addWHOQueueEntry(bot);
+        entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERLIST;
+        entry->chan = chan;
+        entry->callback[0] = callback;
+        entry->module_id[0] = module_id;
+        int i;
+        for(i = 1; i < MAXCALLBACKS; i++)
+            entry->callback[i] = NULL;
+        entry->data[0] = data;
+        for(i = 1; i < MAXCALLBACKS; i++)
+            entry->data[i] = NULL;
+        putsock(bot, "WHO %s,%d %%tuihnaf,%d", chan->name, entry->whoid, entry->whoid);
+    } else
+        callback(bot, chan, data);
+}
+
+void _get_userlist_with_invisible(struct ChanNode *chan, int module_id, userlist_callback_t callback, void *data, int force) {
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(isUserOnChan(bot->user, chan))
+            break;
+    }
+    if(bot == NULL) return;
+    //check if we really need to who the channel
+    //invisible users can only be present if chanmode +D or +d is set!
+    int do_who = (!(chan->flags & CHANFLAG_RECEIVED_USERLIST))  || (isModeSet(chan->modes, 'd') || isModeSet(chan->modes, 'D'));
+    if(!do_who && force) {
+        struct ChanUser *chanuser;
+        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+            if(!(chanuser->user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISIRCOP | USERFLAG_ISBOT)) && (time(0) - chanuser->user->last_who) > REWHO_TIMEOUT) {
+                do_who = 1;
+                break;
+            }
+        }
+    }
+    if(do_who) {
+        struct WHOQueueEntry* entry = addWHOQueueEntry(bot);
+        entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERLIST;
+        entry->chan = chan;
+        entry->callback[0] = callback;
+        entry->module_id[0] = module_id;
+        int i;
+        for(i = 1; i < MAXCALLBACKS; i++)
+            entry->callback[i] = NULL;
+        entry->data[0] = data;
+        for(i = 1; i < MAXCALLBACKS; i++)
+            entry->data[i] = NULL;
+        putsock(bot, "WHO %s,%d d%%tuihnaf,%d", chan->name, entry->whoid, entry->whoid);
+    } else
+        callback(bot, chan, data);
+}
+
+void get_userauth(struct UserNode *user, int module_id, userauth_callback_t callback, void *data) {
+    //check if we have already an active WHO for this user
+    struct ClientSocket *bot, *whobot = NULL;
+    struct WHOQueueEntry *entry;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        for(entry = bot->whoqueue_first; entry; entry = entry->next) {
+            if((entry->type & WHOQUEUETYPE_USERAUTH) && entry->user == user) {
+                int i = 0;
+                for(i = 1; i < MAXCALLBACKS; i++) {
+                    if(!entry->callback[i]) {
+                        entry->callback[i] = callback;
+                        entry->module_id[i] = module_id;
+                        entry->data[i] = data;
+                        return;
+                    }
+                }
+            }
+        }
+        if(bot->flags & SOCKET_FLAG_PREFERRED)
+            whobot = bot;
+    }
+    bot = whobot;
+    if(bot == NULL) bot = getBots(SOCKET_FLAG_READY, NULL);
+    //check if we really need to who the user
+    if(!is_valid_nick(user->nick)) {
+        callback(bot, user->nick, NULL, data);
+        return;
+    }
+    if((user->flags & (USERFLAG_ISAUTHED | USERFLAG_ISSERVER)) || (time(0) - user->last_who) <= REWHO_TIMEOUT) {
+        callback(bot, user->nick, user, data);
+        return;
+    }
+    entry = addWHOQueueEntry(bot);
+    entry->type = WHOQUEUETYPE_ISONQUEUE | WHOQUEUETYPE_USERAUTH;
+    entry->user = user;
+    user->flags |= USERFLAG_IS_ON_WHO_QUEUE;
+    entry->callback[0] = callback;
+    entry->module_id[0] = module_id;
+    int i;
+    for(i = 1; i < MAXCALLBACKS; i++)
+        entry->callback[i] = NULL;
+    entry->data[0] = data;
+    for(i = 1; i < MAXCALLBACKS; i++)
+            entry->data[i] = NULL;
+    //WHO ".$user->getNick().",".$id." %tuhna,".$id
+    putsock(bot, "WHO %s,%d %%tuhna,%d", user->nick, entry->whoid, entry->whoid);
+}
+
+static void _recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc);
+void recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc) {
+    _recv_whohandler_354(client, argv, argc);
+}
+
+static void _recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc) {
+    int i;
+    if(argc < 2) return;
+    int type = atoi(argv[1]);
+    if(!type) return;
+    struct WHOQueueEntry* entry = getNextWHOQueueEntry(client, type, 0);
+    if(entry == NULL) return;
+    #ifdef HAVE_THREADS
+    unsigned int tid = (unsigned int) pthread_self_tid();
+    while(!clientsocket_parseorder_top(tid)) {
+        usleep(1000); //1ms
+    }
+    #endif
+    if(entry->type & WHOQUEUETYPE_USERLIST) {
+        if(argc < 7) return;
+        //:OGN2.OnlineGamesNet.net 354 skynet 1 pk910 2001:41d0:2:1d3b::babe skynet H@ pk910
+        struct ChanNode *chan = entry->chan;
+        //add the user toe the channel if he isn't added, yet and update its user data
+        //parse flags
+        int userflags = 0;
+        int chanuserflags = 0;
+        for(i = 0; i < strlen(argv[6]); i++) {
+            switch (argv[6][i]) {
+                case '@':
+                    chanuserflags |= CHANUSERFLAG_OPPED;
+                    break;
+                case '%':
+                    chanuserflags |= CHANUSERFLAG_HALFOPPED;
+                    break;
+                case '+':
+                    chanuserflags |= CHANUSERFLAG_VOICED;
+                    break;
+                case '*':
+                    userflags |= USERFLAG_ISIRCOP;
+                    break;
+                case '<':
+                    chanuserflags |= CHANUSERFLAG_INVISIBLE;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        struct UserNode *user = getUserByNick(argv[5]);
+        struct ChanUser *chanuser;
+        if((chanuserflags & CHANUSERFLAG_INVISIBLE) && (!user || (user && !isBot(user)))) {
+            user = createTempUser(argv[5]); //always add a temponary user to prevent cache problems when the user joins right now (while it's stored in our cache as being invisible)
+            user->flags |= USERFLAG_ISTMPUSER;
+            chan->flags |= CHANFLAG_HAVE_INVISIBLES;
+            chanuser = addInvisibleChanUser(chan, user);
+            chanuser->flags = (chanuser->flags & ~CHANUSERFLAG_OPPED_OR_VOICED) | chanuserflags;
+        } else {
+            if(user == NULL) {
+                user = addUser(argv[5]);
+            }
+            if(!(chanuser = getChanUser(user, chan))) {
+                chanuser = addChanUser(chan, user);
+            }
+            chanuser->flags = (chanuser->flags & ~(CHANUSERFLAG_OPPED_OR_VOICED | CHANUSERFLAG_INVISIBLE)) | chanuserflags;
+        }
+        user->flags = (user->flags & ~USERFLAG_ISIRCOP) | userflags;
+        user->last_who = time(0);
+        if(!*user->ident)
+            strcpy(user->ident, argv[2]);
+        if(!user->ip)
+            user->ip = createIPNode(argv[3]);
+        if(!*user->host)
+            strcpy(user->host, argv[4]);
+        if(!(user->flags & USERFLAG_ISAUTHED) && strcmp(argv[7], "0")) {
+            strcpy(user->auth, argv[7]);
+            user->flags |= USERFLAG_ISAUTHED;
+        }
+    } else if((entry->type & WHOQUEUETYPE_USERAUTH) && !(entry->type & WHOQUEUETYPE_FOUND)) {
+        //:OGN2.OnlineGamesNet.net 354 Skynet 1 pk910 2001:41d0:2:1d3b::babe Skynet pk910
+        entry->type |= WHOQUEUETYPE_FOUND;
+        entry->user->last_who = time(0);
+        if(strcmp(argv[5], "0") && !(entry->user->flags & USERFLAG_ISAUTHED)) {
+            strcpy(entry->user->auth, argv[5]);
+            entry->user->flags |= USERFLAG_ISAUTHED;
+        }
+        for(i = 0; i < MAXCALLBACKS; i++) {
+            userauth_callback_t *callback = entry->callback[i];
+            if(!callback) break;
+            if(!entry->module_id[i] || module_loaded(entry->module_id[i]))
+                callback(client, entry->user->nick, entry->user, entry->data[i]);
+        }
+    }
+}
+
+static void _recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc);
+void recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc) {
+    _recv_whohandler_315(client, argv, argc);
+}
+
+static void _recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc) {
+    if(argc < 2) return;
+    char *typestr = strstr(argv[1], ",");
+    if(!typestr) return;
+    typestr++;
+    int type = atoi(typestr);
+    struct WHOQueueEntry* entry = getNextWHOQueueEntry(client, type, 0);
+    if(entry == NULL) return;
+    #ifdef HAVE_THREADS
+    unsigned int tid = (unsigned int) pthread_self_tid();
+    while(!clientsocket_parseorder_top(tid)) {
+        usleep(1000); //1ms
+    }
+    #endif
+    getNextWHOQueueEntry(client, type, 1);
+    if(entry->type & WHOQUEUETYPE_USERLIST) {
+        //:OGN2.OnlineGamesNet.net 315 skynet #pk910,1 :End of /WHO list.
+        entry->chan->flags |= CHANFLAG_RECEIVED_USERLIST;
+        userlist_callback_t *callback;
+        int i;
+        for(i = 0; i < MAXCALLBACKS; i++) {
+            callback = entry->callback[i];
+            if(!callback) break;
+            if(!entry->module_id[i] || module_loaded(entry->module_id[i]))
+                callback(client, entry->chan, entry->data[i]);
+        }
+        if(entry->chan->flags & CHANFLAG_HAVE_INVISIBLES) {
+            //remove all invisible users again
+            struct ChanUser *chanuser, *next;
+            for(chanuser = getChannelUsers(entry->chan, NULL); chanuser; chanuser = next) {
+                next = getChannelUsers(entry->chan, chanuser);
+                if((chanuser->flags & CHANUSERFLAG_INVISIBLE) && !isBot(chanuser->user)) {
+                    delChanUser(chanuser, 1);
+                }
+            }
+        }
+    } else if(entry->type & WHOQUEUETYPE_USERAUTH) {
+        if(!(entry->type & WHOQUEUETYPE_FOUND)) {
+            userauth_callback_t *callback;
+            int i;
+            for(i = 0; i < MAXCALLBACKS; i++) {
+                callback = entry->callback[i];
+                if(!callback) break;
+                if(!entry->module_id[i] || module_loaded(entry->module_id[i]))
+                    callback(client, entry->user->nick, NULL, entry->data[i]);
+            }
+        }
+        entry->user->flags &= ~USERFLAG_IS_ON_WHO_QUEUE;
+        if(entry->user->flags & USERFLAG_FREE_AFTER_WHO) {
+            delUser(entry->user, 1);
+        }
+    }
+    free(entry);
+}
+
+void free_whoqueue() {
+    
+}
diff --git a/src/WHOHandler.h b/src/WHOHandler.h
new file mode 100644 (file)
index 0000000..03e30d1
--- /dev/null
@@ -0,0 +1,44 @@
+/* WHOHandler.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _WHOHandler_h
+#define _WHOHandler_h
+
+#include "main.h"
+
+struct ClientSocket;
+struct ChanNode;
+struct UserNode;
+
+#define USERLIST_CALLBACK(NAME) void NAME(UNUSED_ARG(struct ClientSocket *client), UNUSED_ARG(struct ChanNode *chan), UNUSED_ARG(void *data))
+typedef USERLIST_CALLBACK(userlist_callback_t);
+
+#define USERAUTH_CALLBACK(NAME) void NAME(UNUSED_ARG(struct ClientSocket *client), UNUSED_ARG(char *user_nick), UNUSED_ARG(struct UserNode *user), UNUSED_ARG(void *data))
+typedef USERAUTH_CALLBACK(userauth_callback_t);
+
+#define get_userlist_if_invisible(CHAN, MODID, CALLBACK, DATA) _get_userlist_with_invisible(CHAN, MODID, CALLBACK, DATA, 0)
+#define get_userlist_with_invisible(CHAN, MODID, CALLBACK, DATA) _get_userlist_with_invisible(CHAN, MODID, CALLBACK, DATA, 1)
+
+#ifndef DND_FUNCTIONS
+void clear_whoqueue(struct ClientSocket *client);
+void recv_whohandler_354(struct ClientSocket *client, char **argv, unsigned int argc);
+void recv_whohandler_315(struct ClientSocket *client, char **argv, unsigned int argc);
+/* MODULAR ACCESSIBLE */ void get_userlist(struct ChanNode *chan, int module_id, userlist_callback_t callback, void *data);
+/* MODULAR ACCESSIBLE */ void _get_userlist_with_invisible(struct ChanNode *chan, int module_id, userlist_callback_t callback, void *data, int force);
+/* MODULAR ACCESSIBLE */ void get_userauth(struct UserNode *user, int module_id, userauth_callback_t callback, void *data);
+void free_whoqueue();
+#endif
+#endif
diff --git a/src/bots.c b/src/bots.c
new file mode 100644 (file)
index 0000000..7da678c
--- /dev/null
@@ -0,0 +1,304 @@
+/* bots.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "bots.h"
+#include "timeq.h"
+#include "mysqlConn.h"
+#include "ClientSocket.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "version.h"
+#include "modcmd.h"
+#include "DBHelper.h"
+#include "IRCEvents.h"
+#include "tools.h"
+#include "log.h"
+
+struct cmd_bot_alias {
+    int botid;
+    char *alias;
+    struct cmd_bot_alias *next;
+};
+
+static struct cmd_bot_alias *bot_aliases = NULL;
+
+static void start_zero_bots() {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '0' AND `active` = '1'");
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+        client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+        client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+        client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+        client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+        client->botid = 0;
+        client->clientid = atoi(row[7]);
+        connect_socket(client);
+        
+        printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d'", client->clientid);
+        res2 = mysql_use();
+        while ((row = mysql_fetch_row(res2)) != NULL) {
+            if(bind_botwise_cmd_to_command(0, client->clientid, row[0], row[1])) {
+                if(row[2] && strcmp(row[2], "")) {
+                    bind_botwise_set_parameters(0, client->clientid, row[0], row[2]);
+                }
+                if(row[3]) {
+                    bind_botwise_set_global_access(0, client->clientid, row[0], atoi(row[3]));
+                }
+                if(row[4]) {
+                    bind_botwise_set_channel_access(0, client->clientid, row[0], row[4]);
+                }
+                if(strcmp(row[5], "0"))
+                    bind_botwise_set_bind_flags(0, client->clientid, row[0], atoi(row[5]));
+            }
+        }
+        bind_botwise_unbound_required_functions(0, client->clientid);
+    }
+}
+
+static void zero_bots_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "+");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '0' AND `botid` = '%d'", chan->channel_id, clientid);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "+"));
+}
+
+static void zero_bots_bot_ready(struct ClientSocket *client) {
+    if(client->botid != 0)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static TIMEQ_CALLBACK(load_timed_bans) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    //load all timed bans for the next 7 days
+    printf_mysql_query("SELECT `ban_id`, `ban_timeout` FROM `bans` WHERE `ban_timeout` > 0 AND `ban_timeout` < (UNIX_TIMESTAMP() + (86400 * 7))");
+    res = mysql_use();
+    char nameBuf[20];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(atol(row[1]) - time(0) > 0) {
+            sprintf(nameBuf, "ban_%s", row[0]);
+            timeq_add_name(nameBuf, atol(row[1]) - time(0), 0, channel_ban_timeout, strdup(row[0]));
+        } else {
+            //timed out
+            printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[0]);
+        }
+    }
+    timeq_add(86400*7, 0, load_timed_bans, NULL);
+}
+
+void init_bots() {
+    set_bot_alias(0, "0");
+    start_zero_bots();
+    set_trigger_callback(0, 0, zero_bots_trigger_callback);
+       bind_bot_ready(zero_bots_bot_ready, 0);
+    
+    timeq_add(10, 0, load_timed_bans, NULL);
+    
+}
+
+struct ClientSocket *getChannelBot(struct ChanNode *chan, int botid) {
+    struct ClientSocket *bot, *use_bot = NULL, *second_bot = NULL, *third_bot = NULL;
+    struct ChanUser *chanuser;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(botid && bot->botid != botid) continue;
+        if(!chan || (chanuser = getChanUser(bot->user, chan)) != NULL) {
+            if(chan && (chanuser->flags & CHANUSERFLAG_OPPED)) {
+                use_bot = bot;
+                if(bot->flags & SOCKET_FLAG_PREFERRED) break;
+            } else if(bot->flags & SOCKET_FLAG_PREFERRED)
+                second_bot = bot;
+            else
+                third_bot = bot;
+        }
+    }
+    if(!use_bot) use_bot = second_bot;
+    if(!use_bot) use_bot = third_bot;
+    return use_bot;
+}
+
+void requestOp(struct UserNode *user, struct ChanNode *chan) {
+    struct ClientSocket *bot, *userbot = NULL;
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    char opped = 0;
+    if(!chanuser) return;
+    if((chanuser->flags & CHANUSERFLAG_OPPED)) return;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(!opped && (chanuser = getChanUser(bot->user, chan)) != NULL && (chanuser->flags & CHANUSERFLAG_OPPED)) {
+            opped = 1;
+            putsock(bot, "MODE %s +o %s", chan->name, user->nick);
+        }
+        if(bot->user == user) {
+            userbot = bot;
+        }
+    }
+    if(!opped) {
+        if(userbot && (isUserModeSet(user, 'o') || isUserModeSet(user, 'O') || isUserModeSet(user, 'k') || isUserModeSet(user, 'X'))) {
+            putsock(userbot, "MODE %s +o %s", chan->name, user->nick);
+            putsock(userbot, "OPMODE %s +o %s", chan->name, user->nick);
+        }
+    }
+}
+
+void requestInvite(struct UserNode *user, struct ChanNode *chan) {
+    struct ClientSocket *bot;
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    char invited = 0;
+    if(chanuser) return;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(!invited && (chanuser = getChanUser(bot->user, chan)) != NULL && (chanuser->flags & CHANUSERFLAG_OPPED)) {
+            invited = 1;
+            putsock(bot, "INVITE %s %s", user->nick, chan->name);
+        }
+    }
+}
+
+TIMEQ_CALLBACK(channel_ban_timeout) {
+    char *str_banid = data;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `ban_mask`, `channel_name` FROM `bans` LEFT JOIN `channels` ON `ban_channel` = `channel_id` WHERE `ban_id` = '%s'", str_banid);
+    res = mysql_use();
+    struct ChanNode *chan;
+    if((row = mysql_fetch_row(res)) != NULL && (chan = getChanByName(row[1])) != NULL) {
+        struct ClientSocket *use_bot = getChannelBot(chan, 0);
+        if(use_bot) {
+            putsock(use_bot, "MODE %s -b %s", chan->name, row[0]);
+        }
+        printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", str_banid);
+    }
+    free(str_banid);
+}
+
+static int general_ctcp(char *buffer, char *command, char *text);
+
+void general_event_privctcp(struct UserNode *user, struct UserNode *target, char *command, char *text) {
+    char ctcpBuf[MAXLEN];
+    if(general_ctcp(ctcpBuf, command, text)) {
+        struct ClientSocket *bot;
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->user == target) break;
+        }
+        if(bot)
+            putsock(bot, "NOTICE %s :\001%s\001", user->nick, ctcpBuf);
+    }
+}
+
+static int general_ctcp(char *buffer, char *command, char *text) {
+    if(!stricmp(command, "VERSION")) {
+        sprintf(buffer, "VERSION NeonServ v%s.%d by pk910 (%s)", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"));
+        return 1;
+    }
+    if(!stricmp(command, "FINGER")) {
+        sprintf(buffer, "FINGER NeonServ v%s.%d (%s) build %s lines C code using " COMPILER " (see +netinfo)", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"), codelines);
+        return 1;
+    }
+    if(!stricmp(command, "PING")) {
+        sprintf(buffer, "PING %s", (text ? text : "0"));
+        return 1;
+    }
+    if(!stricmp(command, "TIME")) {
+        time_t rawtime;
+        struct tm *timeinfo;
+        char timeBuf[80];
+        time(&rawtime);
+        timeinfo = localtime(&rawtime);
+        strftime(timeBuf, 80, "%c", timeinfo);
+        sprintf(buffer, "TIME %s", timeBuf);
+        return 1;
+    }
+    return 0;
+}
+
+void set_bot_alias(int botid, char *alias) {
+    struct cmd_bot_alias *botalias, *existing_alias = NULL;
+    for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+        if(!stricmp(botalias->alias, alias))
+            return;
+        if(botalias->botid == botid)
+            existing_alias = botalias;
+    }
+    if(existing_alias) {
+        free(existing_alias->alias);
+        existing_alias->alias = strdup(alias);
+        return;
+    }
+    botalias = malloc(sizeof(*botalias));
+    if (!botalias) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    botalias->botid = botid;
+    botalias->alias = strdup(alias);
+    botalias->next = bot_aliases;
+    bot_aliases = botalias;
+}
+
+const char *resolve_botid(int botid) {
+    struct cmd_bot_alias *botalias;
+    for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+        if(botalias->botid == botid)
+            return botalias->alias;
+    }
+    return NULL;
+}
+
+int resolve_botalias(const char *alias) {
+    struct cmd_bot_alias *botalias;
+    for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+        if(!stricmp(botalias->alias, alias))
+            return botalias->botid;
+    }
+    return -1;
+}
diff --git a/src/bots.h b/src/bots.h
new file mode 100644 (file)
index 0000000..fd1eac2
--- /dev/null
@@ -0,0 +1,39 @@
+/* bots.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bots_h
+#define _bots_h
+
+#include "main.h"
+#include "timeq.h"
+
+struct UserNode;
+struct ChanNode;
+struct ClientSocket;
+
+#ifndef DND_FUNCTIONS
+void init_bots();
+
+/* MODULAR ACCESSIBLE */ struct ClientSocket *getChannelBot(struct ChanNode *chan, int botid);
+/* MODULAR ACCESSIBLE */ void requestOp(struct UserNode *user, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ void requestInvite(struct UserNode *user, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ TIMEQ_CALLBACK(channel_ban_timeout);
+/* MODULAR ACCESSIBLE */ void general_event_privctcp(struct UserNode *user, struct UserNode *target, char *command, char *text);
+/* MODULAR ACCESSIBLE */ void set_bot_alias(int botid, char *alias);
+/* MODULAR ACCESSIBLE */ const char *resolve_botid(int botid);
+/* MODULAR ACCESSIBLE */ int resolve_botalias(const char *alias);
+#endif
+#endif
\ No newline at end of file
diff --git a/src/lang.c b/src/lang.c
new file mode 100644 (file)
index 0000000..7d1532b
--- /dev/null
@@ -0,0 +1,228 @@
+/* lang.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "lang.h"
+#include "UserNode.h"
+#include "DBHelper.h"
+#include "mysqlConn.h"
+#include "tools.h"
+#include "log.h"
+
+#define DEFAULT_LANG_TAG "EN"
+#define DEFAULT_LANG_NAME "English"
+
+static struct language **langdict;
+static struct language *lang_c;
+
+void init_lang() {
+    langdict = calloc(MAXLANGUAGES, sizeof(*langdict));
+}
+
+void free_lang() {
+    
+}
+
+void load_languages() {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `lang`, `text` FROM `language` WHERE `ident` = 'name'");
+    res = mysql_use();
+    while((row = mysql_fetch_row(res)) != NULL) {
+        load_language(row[0], row[1]);
+    }
+}
+
+static struct language* add_language(char *langtag, char *langname) {
+    int cindex;
+    for(cindex = 0; cindex < MAXLANGUAGES; cindex++) {
+        if(langdict[cindex] == NULL) break;
+        if(!strcmp(langdict[cindex]->langname, langname) || !strcmp(langdict[cindex]->langtag, langtag))
+            return langdict[cindex];
+    }
+    if(cindex == MAXLANGUAGES) return NULL;
+    struct language *lang = malloc(sizeof(*lang));
+    if (!lang) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    lang->langtag = strdup(langtag);
+    lang->langname = strdup(langname);
+    struct language_table **entrys = calloc(27, sizeof(*entrys));
+    lang->entrys = entrys;
+    langdict[cindex] = lang;
+    return lang;
+}
+
+static int get_entry_index(const char *ident) {
+    const char *underscore = strstr(ident, "_");
+    if(!underscore || !(underscore[1] >= 65 && underscore[1] <= 90)) return 26;
+    return (underscore[1] - 'A');
+}
+
+void load_language(char *tag, char *name) {
+    struct language *lang = get_language_by_tag(tag);
+    if(lang == get_default_language()) return;
+    if(lang) {
+        //remove all entrys
+        int cindex;
+        struct language_table *entry, *next;
+        for(cindex = 0; cindex < 27; cindex++) {
+            for(entry = lang->entrys[cindex]; entry; entry = next) {
+                next = entry->next;
+                free(entry->ident);
+                free(entry->text);
+                free(entry);
+            }
+            lang->entrys[cindex] = NULL;
+        }
+    } else
+        lang = add_language(tag, name);
+    if(!lang) return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `ident`, `text` FROM `language` WHERE `lang` = '%s' AND `ident` != 'name'", escape_string(tag));
+    res = mysql_use();
+    while((row = mysql_fetch_row(res)) != NULL) {
+        register_language_string(lang, row[0], row[1]);
+    }
+}
+
+struct language* get_language_by_tag(char *tag) {
+    int cindex;
+    for(cindex = 0; cindex < MAXLANGUAGES; cindex++) {
+        if(langdict[cindex] == NULL) break;
+        if(!stricmp(langdict[cindex]->langtag, tag))
+            return langdict[cindex];
+    }
+    return NULL;
+}
+
+struct language* get_language_by_name(char *name) {
+    int cindex;
+    for(cindex = 0; cindex < MAXLANGUAGES; cindex++) {
+        if(langdict[cindex] == NULL) break;
+        if(!stricmp(langdict[cindex]->langname, name))
+            return langdict[cindex];
+    }
+    return NULL;
+}
+
+struct language* get_default_language() {
+    if(lang_c == NULL) 
+        lang_c = add_language(DEFAULT_LANG_TAG, DEFAULT_LANG_NAME);
+    return lang_c;
+}
+
+void register_default_language_table(const struct default_language_entry *msgtab) {
+    if(lang_c == NULL) 
+        lang_c = add_language(DEFAULT_LANG_TAG, DEFAULT_LANG_NAME);
+    while(msgtab->ident) {
+        register_language_string(lang_c, msgtab->ident, msgtab->text);
+        msgtab++;
+    }
+}
+
+void register_language_string(struct language *lang, char *ident, char *text) {
+    int cindex = get_entry_index(ident);
+    struct language_table *lang_entry;
+    for(lang_entry = lang->entrys[cindex]; lang_entry; lang_entry = lang_entry->next) {
+        if(!strcmp(lang_entry->ident, ident)) break;
+    }
+    if(!lang_entry) {
+        lang_entry = malloc(sizeof(*lang_entry));
+        if (!lang_entry) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        lang_entry->ident = strdup(ident);
+        lang_entry->next = lang->entrys[cindex];
+        lang->entrys[cindex] = lang_entry;
+    } else
+        free(lang_entry->text); //free old text (new one will be set below)
+    //replace all:
+    //$b to \002
+    //$k to \003
+    char txt[MAXLEN];
+    strcpy(txt, text);
+    char tmp[MAXLEN];
+    int tmppos = 0;
+    char *a, *b = txt;
+    do {
+        a = strstr(b, "$");
+        if(a) *a = '\0';
+        tmppos += sprintf(tmp + tmppos, "%s", b);
+        if(a) {
+            switch(a[1]) {
+                case 'b':
+                    tmp[tmppos++] = 2;
+                    break;
+                case 'k':
+                    tmp[tmppos++] = 3;
+                    break;
+                case 'u':
+                    tmp[tmppos++] = 31;
+                    break;
+                default:
+                    //unknown - just write it
+                    tmppos += sprintf(tmp + tmppos, "$%c", a[1]);
+            }
+            b = a+2;
+        }
+    } while(a);
+    lang_entry->text = strdup(tmp);
+}
+
+char *get_language_string(struct UserNode *user, const char* msg_ident) {
+    struct language* lang;
+    if(user && (user->flags & USERFLAG_ISAUTHED)) {
+        loadUserSettings(user);
+        lang = user->language;
+    } else
+        lang = lang_c;
+    int cindex = get_entry_index(msg_ident);
+    struct language_table* entry;
+    for(entry = lang->entrys[cindex]; entry; entry = entry->next) {
+        if(!strcmp(entry->ident, msg_ident))
+            return entry->text;
+    }
+    if(lang == lang_c) return NULL;
+    for(entry = lang_c->entrys[cindex]; entry; entry = entry->next) {
+        if(!strcmp(entry->ident, msg_ident))
+            return entry->text;
+    }
+    return NULL;
+}
+
+char *build_language_string(struct UserNode *user, char *buffer, const char *msg_ident, ...) {
+    char *formatStr = get_language_string(user, msg_ident);
+    if(!formatStr) return NULL;
+    if(buffer == NULL) {
+        buffer = (char *)malloc((MAXLEN+1) * sizeof(char));
+        if (!buffer) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return NULL;
+        }
+    }
+    int pos;
+    va_list arg_list;
+    buffer[0] = '\0';
+    va_start(arg_list, msg_ident);
+    pos = vsnprintf(buffer, MAXLEN - 2, formatStr, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    buffer[pos] = '\0';
+    return buffer;
+}
diff --git a/src/lang.h b/src/lang.h
new file mode 100644 (file)
index 0000000..64652ed
--- /dev/null
@@ -0,0 +1,55 @@
+/* lang.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _lang_h
+#define _lang_h
+
+#include "main.h"
+
+struct UserNode;
+
+struct default_language_entry {
+    char *ident;
+    char *text;
+};
+
+struct language_table {
+    char *ident;
+    char *text;
+    
+    struct language_table *next;
+};
+
+struct language {
+    char *langtag;
+    char *langname;
+    struct language_table **entrys;
+};
+
+#ifndef DND_FUNCTIONS
+void init_lang();
+void free_lang();
+/* MODULAR ACCESSIBLE */ struct language* get_language_by_tag(char *tag);
+/* MODULAR ACCESSIBLE */ struct language* get_language_by_name(char *name);
+/* MODULAR ACCESSIBLE */ struct language* get_default_language();
+void load_languages();
+/* MODULAR ACCESSIBLE */ void load_language(char *tag, char *name);
+void register_language_string(struct language *lang, char *ident, char *text);
+/* MODULAR ACCESSIBLE */ void register_default_language_table(const struct default_language_entry *msgtab);
+/* MODULAR ACCESSIBLE */ char *get_language_string(struct UserNode *user, const char* msg_ident);
+/* MODULAR ACCESSIBLE */ char *build_language_string(struct UserNode *user, char *buffer, const char *msg_ident, ...);
+#endif
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644 (file)
index 0000000..44d63df
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,297 @@
+/* log.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "log.h"
+#include "ChanNode.h"
+#include "IRCEvents.h"
+#include "ClientSocket.h"
+#include "bots.h"
+#include "IOHandler.h"
+#include "tools.h"
+#include "ConfigParser.h"
+
+#define MAXLOGLEN 1024
+
+static void write_log(const char *module, const int section, const char *line, int len);
+static void load_log_targets();
+static void unload_log_targets();
+static int reload_log_targets(int init);
+
+#ifdef HAVE_THREADS
+static pthread_mutex_t log_sync;
+#endif
+
+#define LOG_TARGET_TYPE_FILE   1
+#define LOG_TARGET_TYPE_STDOUT 2
+#define LOG_TARGET_TYPE_STDERR 3
+#define LOG_TARGET_TYPE_IRC    4
+
+static const struct {
+    int id;
+    char *name;
+} log_sections[] = {
+    {LOG_INFO, "info"},
+    {LOG_DEBUG, "debug"},
+    {LOG_IRCRAW, "raw"},
+    {LOG_MYSQL, "mysql"},
+    {LOG_OVERRIDE, "override"},
+    {LOG_WARNING, "warning"},
+    {LOG_ERROR, "error"},
+    {LOG_FATAL, "fatal"},
+    {0, NULL}
+};
+
+struct log_target {
+    char *module;
+    int section;
+    int type : 8;
+    union {
+        char *channel;
+        FILE *fptr;
+    } target;
+    struct log_target *next;
+};
+
+static struct log_target *log_targets = NULL;
+
+static char *get_section_name(int section_id) {
+    int i = -1;
+    while(log_sections[++i].id) {
+        if(log_sections[i].id == section_id)
+            return log_sections[i].name;
+    }
+    return NULL;
+}
+
+static int get_section_id(char *section_name) {
+    int i = -1;
+    while(log_sections[++i].id) {
+        if(!stricmp(log_sections[i].name, section_name))
+            return log_sections[i].id;
+    }
+    return 0;
+}
+
+
+void printf_log(const char *module, const int section, const char *text, ...) {
+    va_list arg_list;
+    char logBuf[MAXLOGLEN];
+    int pos;
+    logBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(logBuf, MAXLOGLEN - 1, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLOGLEN - 1)) pos = MAXLOGLEN - 1;
+    logBuf[pos] = '\0';
+    write_log(module, section, logBuf, pos);
+}
+
+static void write_log(const char *module, const int section, const char *line, int len) {
+    if(!log_targets)
+        return;
+    SYNCHRONIZE(log_sync);
+    struct log_target *target;
+    char lineBuf[MAXLOGLEN];
+    char timeBuf[80];
+    int lineBufPos, timeBufPos;
+    time_t rawtime;
+    struct tm *timeinfo;
+    int i, j;
+    
+    time(&rawtime);
+    timeinfo = localtime(&rawtime);
+    timeBufPos = strftime(timeBuf, 80, "[%X %x] ", timeinfo);
+    
+    lineBufPos = sprintf(lineBuf, "(%s:", module);
+    for(i = 0, j = 0; i < LOG_SECTIONS; i++) {
+        if((section & (1 << i))) {
+            char *section_name = get_section_name((1 << i));
+            if(!section_name)
+                continue;
+            if(j++)
+                lineBuf[lineBufPos++] = ',';
+            lineBufPos += sprintf(lineBuf + lineBufPos, "%s", section_name);
+        }
+    }
+    lineBufPos += sprintf(lineBuf + lineBufPos, ") %s", line);
+    //cut off \n \r
+    while(lineBuf[lineBufPos-1] == '\r' || lineBuf[lineBufPos-1] == '\n') {
+        lineBuf[lineBufPos-1] = '\0';
+        lineBufPos--;
+    }
+    
+    j = 0;
+    for(target = log_targets; target; target = target->next) {
+        if(strcmp(target->module, "*") && stricmp(target->module, module))
+            continue;
+        if(!(target->section & section))
+            continue;
+        if(target->type == LOG_TARGET_TYPE_IRC) {
+            if(section == LOG_IRCRAW || !stricmp(module, "iohandler"))
+                continue; //endless loop ;)
+            struct ChanNode *channel = getChanByName(target->target.channel);
+            struct ClientSocket *client;
+            if(channel && (client = getChannelBot(channel, 0))) {
+                putsock(client, "PRIVMSG %s :%s", channel->name, lineBuf);
+            }
+        } else if(target->type == LOG_TARGET_TYPE_FILE) {
+            fwrite(timeBuf, 1, timeBufPos, target->target.fptr);
+            fwrite(lineBuf, 1, lineBufPos, target->target.fptr);
+            fwrite("\n", 1, 1, target->target.fptr);
+        } else if(target->type == LOG_TARGET_TYPE_STDOUT || target->type == LOG_TARGET_TYPE_STDERR) {
+            if(process_state.daemonized)
+                continue; //block stdout / stderr as daemon
+            fprintf((target->type == LOG_TARGET_TYPE_STDOUT ? stdout : stderr), "%s%s\n", timeBuf, lineBuf);
+            j = 1;
+        }
+    }
+    if((process_state.loglevel & section) && !process_state.daemonized && !j) {
+        fprintf(stdout, "%s%s\n", timeBuf, lineBuf);
+    }
+    
+    DESYNCHRONIZE(log_sync);
+}
+
+static void load_log_targets() {
+    if(log_targets)
+        return;
+    char **targetlist = get_all_fieldnames("logs");
+    if(!targetlist) return;
+    int i = 0;
+    char tmp[MAXLEN];
+    struct log_target *ctarget;
+    while(targetlist[i]) {
+        sprintf(tmp, "logs.%s", targetlist[i]);
+        char *type = get_string_field(tmp);
+        char *target = strchr(type, ':');
+        char *module = targetlist[i];
+        char *section = strchr(module, ':');
+        if(!target || !section) {
+            i++;
+            continue;
+        }
+        *target = '\0';
+        target++;
+        *section = '\0';
+        section++;
+        ctarget = malloc(sizeof(*ctarget));
+        if(!stricmp(type, "file")) {
+            ctarget->type = LOG_TARGET_TYPE_FILE;
+            ctarget->target.fptr = fopen(target, "a");
+            if(!ctarget->target.fptr) {
+                free(ctarget);
+                ctarget = NULL;
+            }
+        } else if(!stricmp(type, "std")) {
+            if(!stricmp(target, "out"))
+                ctarget->type = LOG_TARGET_TYPE_STDOUT;
+            else if(!stricmp(target, "err"))
+                ctarget->type = LOG_TARGET_TYPE_STDERR;
+            else {
+                free(ctarget);
+                ctarget = NULL;
+            }
+        } else if(!stricmp(type, "irc")) {
+            if(is_valid_chan(target)) {
+                ctarget->type = LOG_TARGET_TYPE_IRC;
+                ctarget->target.channel = strdup(target);
+            } else {
+                free(ctarget);
+                ctarget = NULL;
+            }
+        } else {
+            free(ctarget);
+            ctarget = NULL;
+        }
+        if(ctarget) {
+            ctarget->module = strdup(module);
+            ctarget->section = 0;
+            char *csection = section;
+            while(1) {
+                char *next_section = strchr(csection, ',');
+                if(next_section)
+                    *next_section = '\0';
+                if(!strcmp(csection, "*"))
+                    ctarget->section |= LOG_ALL;
+                else
+                    ctarget->section |= get_section_id(csection);
+                if(next_section) {
+                    *next_section = ',';
+                    csection = next_section + 1;
+                } else
+                    break;
+            }
+            ctarget->next = log_targets;
+            log_targets = ctarget;
+        }
+        target--;
+        *target = ':';
+        section--;
+        *section = ':';
+        i++;
+    }
+}
+
+static void unload_log_targets() {
+    struct log_target *target, *next_target;
+    for(target = log_targets; target; target = next_target) {
+        next_target = target->next;
+        if(target->type == LOG_TARGET_TYPE_IRC)
+            free(target->target.channel);
+        else if(target->type == LOG_TARGET_TYPE_FILE)
+            fclose(target->target.fptr);
+        free(target);
+    }
+    log_targets = NULL;
+}
+
+static int reload_log_targets(int init) {
+    unload_log_targets();
+    load_log_targets();
+    return 1;
+}
+
+static IOHANDLER_LOG_BACKEND(log_iohandler_backend) {
+    int log_level;
+    switch(type) {
+        case IOLOG_DEBUG:
+            log_level = LOG_DEBUG;
+            break;
+        case IOLOG_WARNING:
+            log_level = LOG_WARNING;
+            break;
+        case IOLOG_ERROR:
+            log_level = LOG_ERROR;
+            break;
+        case IOLOG_FATAL:
+            log_level = LOG_FATAL;
+            break;
+        default:
+            log_level = 0;
+    }
+    write_log("iohandler", log_level, line, strlen(line));
+}
+
+void init_log() {
+    #if HAVE_THREADS
+    THREAD_MUTEX_INIT(log_sync);
+    #endif
+    load_log_targets();
+    bind_reload(reload_log_targets, 0);
+    iolog_backend = log_iohandler_backend;
+    printf_log("log", LOG_INFO, "initialized log system.");
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..657e527
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,39 @@
+/* log.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _log_h
+#define _log_h
+#include "main.h"
+
+#define LOG_INFO     0x01
+#define LOG_DEBUG    0x02
+#define LOG_IRCRAW   0x04
+#define LOG_MYSQL    0x08
+#define LOG_OVERRIDE 0x10
+#define LOG_WARNING  0x20
+#define LOG_ERROR    0x40
+#define LOG_FATAL    0x80
+
+#define LOG_ALL      0xff
+#define LOG_SECTIONS 8
+
+#ifndef DND_FUNCTIONS
+
+void printf_log(const char *module, const int section, const char *text, ...);
+void init_log();
+
+#endif
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..fb16300
--- /dev/null
@@ -0,0 +1,600 @@
+/* main.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#define DEFAULT_PID_FILE "neonserv.pid"
+#define DEFAULT_CONF_FILE "neonserv.conf"
+
+#include "main.h"
+#include "signal.h"
+#include "ClientSocket.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "IRCEvents.h"
+#include "IRCParser.h"
+#include "modcmd.h"
+#include "WHOHandler.h"
+#include "bots.h"
+#include "mysqlConn.h"
+#include "HandleInfoHandler.h"
+#include "lang.h"
+#include "tools.h"
+#include "timeq.h"
+#include "EventLogger.h"
+#include "ModeNode.h"
+#include "IRCQueue.h"
+#include "DBHelper.h"
+#include "ConfigParser.h"
+#include "QServer.h"
+#include "version.h"
+#include "modules.h"
+#include "module_commands.h"
+#include "ModuleFunctions.h"
+#include "IOHandler.h"
+#include "statistics.h"
+#include "log.h"
+
+struct ProcessState process_state;
+
+#ifdef HAVE_THREADS
+pthread_mutex_t cache_sync;
+pthread_mutex_t whohandler_sync, whohandler_mass_sync;
+static pthread_t *current_threads = NULL;
+#endif
+
+static void *main_tread(void *empty);
+static TIMEQ_CALLBACK(clear_cache);
+static TIMEQ_CALLBACK(main_checkauths);
+static void check_firstrun();
+
+
+static void main_parse_arguments() {
+    int c;
+    struct option options[] = {
+        {"show", 1, 0, 's'},
+        {"foreground", 0, 0, 'f'},
+        {"config", 1, 0, 'c'},
+        {"pid", 1, 0, 'p'},
+        {"help", 0, 0, 'h'},
+        {"version", 0, 0, 'v'},
+        {0, 0, 0, 0}
+    };
+    while ((c = getopt_long(process_state.argc, process_state.argv, "s:fvh", options, NULL)) != -1) {
+        switch (c) {
+        case 'c':
+            strncpy(process_state.config, optarg, MAXLEN-1);
+            break;
+        case 'p':
+            strncpy(process_state.pidfile, optarg, MAXLEN-1);
+            break;
+        case 's':
+            process_state.loglevel = atoi(optarg);
+            break;
+        case 'f':
+            process_state.run_as_daemon = 0;
+            break;
+        case 'v':
+            printf("Version: %s.%d (%s)\n", NEONSERV_VERSION, patchlevel, (strcmp(revision, "") ? revision : "-"));
+            printf("Build: #%s %s (%s lines, " COMPILER ")\n", compilation, creation, codelines);
+            exit(0);
+            break;
+        case 'h':
+            printf("Usage: ./neonserv [-c neonserv.conf] [-p neonserv.pid] [-s loglevel] [-f] [-h|-v]\n");
+            printf(" -c, --config         use this configuration file.\n");
+            printf(" -f, --foreground     run NeonServ in the foreground.\n");
+            printf(" -h, --help           prints this usage message.\n");
+            printf(" -p, --pid            use this pid file.\n");
+            printf(" -s, --show           show log lines matching loglevel in stdout.\n");
+            printf(" -v, --version        prints this program's version.\n");
+            exit(0);
+            break;
+        }
+    }
+}
+
+static void main_daemon_exit() {
+    remove(process_state.pidfile);
+}
+
+static void main_daemonize() {
+    #ifndef WIN32
+    /* Attempt to fork into the background if daemon mode is on. */
+    pid_t pid = fork();
+    if (pid < 0) {
+        fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
+    } else if (pid > 0) {
+        printf("Forking into the background (pid: %d)...\n", pid);
+        printf_log("main", LOG_INFO, "Forking into the background (pid: %d)...\n", pid);
+        exit(0);
+    }
+    setsid();
+    process_state.daemonized = 1;
+    atexit(main_daemon_exit);
+    FILE *pidfile = fopen(process_state.pidfile, "w");
+    if (pidfile == NULL) {
+        fprintf(stderr, "Unable to create PID file: %s\n", strerror(errno));
+        printf_log("main", LOG_ERROR, "Unable to create PID file: %s\n", strerror(errno));
+    } else {
+        fprintf(pidfile, "%i\n", (int)getpid());
+        fclose(pidfile);
+    }
+    FILE *retn;
+    fclose(stdin); retn = fopen("/dev/null", "r");
+    fclose(stdout); retn = fopen("/dev/null", "w");
+    fclose(stderr); retn = fopen("/dev/null", "w");
+    #endif
+}
+
+static int reload_configuration() {
+    printf_log("main", LOG_DEBUG, "reloading configuration file: %s", process_state.config);
+    if(!loadConfig(process_state.config)) {
+        printf_log("main", LOG_ERROR, "could not reload configuration file: %s", process_state.config);
+        return 1;
+    }
+    if(process_state.loaded_config) {
+        if(!reload_mysql())
+            return 2;
+        char **modulelist = get_all_fieldnames("modules");
+        if(!modulelist || !modulelist[0]) {
+            free(modulelist);
+            return 3;
+        }
+        free(modulelist);
+        event_reload(0);
+    }
+    process_state.loaded_config = 1;
+    return 0;
+}
+
+
+/* INITIALISATION OF SUBSYSTEMS */
+void initialize_subsystems() {
+    init_bind();
+    init_log();
+    printf_log("main", LOG_INFO, "starting up NeonServ %s subsystems...", NEONSERV_VERSION);
+    init_lang();
+    init_parser();
+    init_UserNode();
+    init_ChanNode();
+    init_ModeNode();
+       init_modcmd();
+    register_module_commands();
+    init_handleinfohandler();
+    init_tools();
+    init_modulefunctions();
+    loadModules();
+    init_bots();
+    init_DBHelper();
+    qserver_init();
+    load_languages();
+    init_statistics();
+}
+
+void shutdown_subsystems() {
+    printf_log("main", LOG_INFO, "stopping NeonServ subsystems...");
+    free_sockets(1);
+    //wait 50ms (run iohandler)
+    {
+        struct timeval timeout, ctime1, ctime2;
+        gettimeofday(&ctime1, NULL);
+        ctime1.tv_usec += 50000;
+        if(ctime1.tv_usec > 1000000) {
+            ctime1.tv_usec -= 1000000;
+            ctime1.tv_sec++;
+        }
+        do {
+            timeout.tv_sec = 0;
+            timeout.tv_usec = 10000;
+            iohandler_poll_timeout(timeout);
+            gettimeofday(&ctime2, NULL);
+        } while(timeval_is_bigger(ctime1, ctime2));
+    }
+    stop_modules();
+    free_sockets(0);
+    qserver_free();
+    free_parser();
+    free_UserNode();
+    free_ChanNode();
+    free_bind();
+    free_modcmd();
+    free_whoqueue();
+    free_mysql();
+    free_handleinfohandler();
+    free_lang();
+}
+
+/* THREAD CONTROL */
+#ifdef HAVE_THREADS
+int getCurrentThreadID() {
+    if(!current_threads) return 0;
+    int i;
+    unsigned int my_tid = (unsigned int) pthread_self_tid();
+    for(i = 0; i < process_state.running_threads; i++) {
+        #ifdef WIN32
+        if((unsigned int) current_threads[i].p == my_tid)
+        #else
+        if((unsigned int) current_threads[i] == my_tid)
+        #endif
+            return i+1;
+    }
+    return 0;
+}
+#endif
+
+int getRunningThreads() {
+       #ifdef HAVE_THREADS
+    return process_state.running_threads;
+       #else
+       return 1;
+       #endif
+}
+
+static void main_start_threads() {
+    int worker_threads = get_int_field("General.worker_threads");
+    if(!worker_threads) worker_threads = 1;
+    #ifdef HAVE_THREADS
+    int tid_id = 0;
+    {
+        current_threads = calloc(worker_threads, sizeof(*current_threads));
+        for(tid_id = 0; tid_id < worker_threads; tid_id++) {
+            process_state.running_threads++;
+            pthread_create(&current_threads[tid_id], NULL, main_tread, NULL);
+        }
+    }
+    #endif
+    main_tread(NULL);
+    #ifdef HAVE_THREADS
+    {
+        for(tid_id = 0; tid_id < worker_threads; tid_id++) {
+            pthread_join(current_threads[tid_id], NULL);
+        }
+        process_state.running_threads = 0;
+    }
+    #endif
+}
+
+/* MAIN FUNCTION(S) */
+
+static void *main_tread(void *empty) {
+    while(process_state.running) {
+        iohandler_poll();
+    }
+    return NULL;
+}
+
+static void main_restart_process() {
+    /* Append a NULL to the end of argv[]. */
+    char **restart_argv = (char **)alloca((process_state.argc + 1) * sizeof(char *));
+    memcpy(restart_argv, process_state.argv, process_state.argc * sizeof(char *));
+    restart_argv[process_state.argc] = NULL;
+    #ifdef WIN32
+    execv(process_state.argv[0], (const char * const*)restart_argv);
+    #else
+    execv(process_state.argv[0], restart_argv);
+    #endif
+}
+
+int main(int argc, char *argv[]) {
+    memset(&process_state, 0, sizeof(process_state));
+    printf("NeonServ v%s\n\n", NEONSERV_VERSION);
+    
+    process_state.argv = argv;
+    process_state.argc = argc;
+    process_state.run_as_daemon = 1;
+    strcpy(process_state.config, DEFAULT_CONF_FILE);
+    strcpy(process_state.pidfile, DEFAULT_PID_FILE);
+    
+    //parse argv
+    main_parse_arguments();
+    
+    //initialize memory debugger BEFORE allocating memory
+    #ifdef ENABLE_MEMORY_DEBUG
+    initMemoryDebug();
+    #endif
+    
+    //initialize mutex debugger BEFORE using any mutexes
+    #ifdef ENABLE_MUTEX_DEBUG
+    initMutexDebug();
+    #endif
+    
+    //deny root startup
+    #ifndef WIN32
+    if(geteuid() == 0 || getuid() == 0) {
+        fprintf(stderr, "NeonServ may not be run with super user privileges.\n");
+        exit(0);
+    }
+    #endif
+    
+    //load configuration
+    int errid;
+    if((errid = reload_configuration())) {
+        fprintf(stderr, "Unable to load configuration file `%s`. (errid: %d)\n", process_state.config, errid);
+        exit(0);
+    }
+    
+    //check mysql configuration
+    if(!reload_mysql()) {
+        fprintf(stderr, "Unable to load MySQL configuration.\n");
+        exit(0);
+    }
+    
+    //check module configuration
+    char **modulelist = get_all_fieldnames("modules");
+    if(!modulelist || !modulelist[0]) {
+        fprintf(stderr, "Unable to load Module configuration.\n");
+        exit(0);
+    }
+    free(modulelist);
+    
+    #if HAVE_THREADS
+    THREAD_MUTEX_INIT(cache_sync);
+    THREAD_MUTEX_INIT(whohandler_sync);
+    THREAD_MUTEX_INIT(whohandler_mass_sync);
+    #endif
+    
+    //connect to mysql and check if it's the frst bot startup
+    init_mysql();
+    check_firstrun();
+    
+    //deamonize if wanted
+    if(process_state.run_as_daemon)
+        main_daemonize();
+    
+    //set signal handlers
+    signal(SIGABRT, sighandler);
+    signal(SIGFPE, sighandler);
+    signal(SIGILL, sighandler);
+    signal(SIGINT, sighandler);
+    signal(SIGSEGV, sighandler);
+    signal(SIGTERM, sighandler);
+    
+    //set start time and initialize other code parts
+    process_state.running = 1;
+    process_state.start_time = time(0);
+    initialize_subsystems();
+    
+    //start timers
+    timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL);
+    timeq_add(90, 0, main_checkauths, NULL);
+    
+    //start worker threads
+    main_start_threads();  //BLOCKING
+    
+    //shutdown sequence...
+    shutdown_subsystems();
+    if(process_state.restart)
+        main_restart_process(); //terminates the current process on success
+    
+    //eop (end of program :P)
+    //trust me, thats the end!
+    exit(0);
+}
+
+/* BOT INFORMATION */
+time_t getStartTime() {
+    return process_state.start_time;
+}
+
+/* BOT CONTROL */
+void restart_bot(int crash) {
+    if(crash) {
+        main_daemon_exit();
+        main_restart_process();
+    } else {
+        process_state.restart = 1;
+        process_state.running = 0;
+    }
+}
+
+void stop_bot() {
+    process_state.running = 0;
+}
+
+void reload_config() {
+    reload_configuration();
+}
+
+/* TIMER FUNCTIONS */
+
+static TIMEQ_CALLBACK(clear_cache) {
+    timeq_add(CLEAR_CACHE_INTERVAL, 0, clear_cache, NULL);
+    clearTempUsers();
+    destroyEvents();
+    mysql_free();
+}
+
+static AUTHLOOKUP_CALLBACK(main_checkauths_callback) {
+    //check if registered is still valid
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_id`, `user_registered` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        int diff = registered - atoi(row[1]);
+        if(diff < 0)
+            diff *= -1;
+        if(!exists || (strcmp(row[1], "0") && diff > 86400)) {
+            //User is no longer valid! Delete it...
+            deleteUser(atoi(row[0]));
+            char *alertchan = get_string_field("General.CheckAuths.alertchan");
+            if(alertchan) {
+                char reason[MAXLEN];
+                if(!exists) {
+                    strcpy(reason, "USER_NOT_EXISTS");
+                } else {
+                    sprintf(reason, "USER_REGISTERED_MISSMATCH: %lu, expected %d (diff: %d)", (unsigned long) registered, atoi(row[1]), diff);
+                }
+                struct ChanNode *alertchan_chan = getChanByName(alertchan);
+                struct ClientSocket *alertclient;
+                if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
+                    putsock(alertclient, "PRIVMSG %s :Deleted User %s (%s)", alertchan_chan->name, auth, reason);
+                }
+            }
+        } else if(exists && !strcmp(row[1], "0")) {
+            printf_mysql_query("UPDATE `users` SET `user_registered` = '%lu', `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", (unsigned long) registered, row[0]);
+        } else {
+            printf_mysql_query("UPDATE `users` SET `user_lastcheck` = UNIX_TIMESTAMP() WHERE `user_id` = '%s'", row[0]);
+        }
+    }
+}
+
+static TIMEQ_CALLBACK(main_checkauths) {
+    int next_call = 600;
+    if(get_int_field("General.CheckAuths.enabled")) {
+        int check_start_time = get_int_field("General.CheckAuths.start_time") * 3600;
+        int duration = get_int_field("General.CheckAuths.duration") * 60;
+        int now = getCurrentSecondsOfDay();
+        if(now < check_start_time && check_start_time+duration >= 86400) {
+            check_start_time -= 86400;
+        }
+        if(now >= check_start_time && now < (check_start_time + duration)) {
+            next_call = get_int_field("General.CheckAuths.interval");
+            //get the "longest-unchecked-user"
+            MYSQL_RES *res;
+            MYSQL_ROW row;
+            int lastcheck;
+            time_t unixtime = time(0);
+            int min_unckecked = get_int_field("General.CheckAuths.min_unckecked");
+            printf_mysql_query("SELECT `user_user`, `user_lastcheck` FROM `users` ORDER BY `user_lastcheck` ASC LIMIT 1");
+            res = mysql_use();
+            if ((row = mysql_fetch_row(res)) != NULL) {
+                lastcheck = atoi(row[1]);
+                if(!lastcheck || unixtime - lastcheck >= min_unckecked) {
+                    lookup_authname(row[0], 0, main_checkauths_callback, NULL);
+                } else 
+                    next_call = 300;
+            }
+        } else {
+            int pending;
+            if(now > check_start_time)
+                pending = 86400 - now + check_start_time;
+            else
+                pending = check_start_time - now;
+            if(pending < 600)
+                next_call = pending;
+        }
+        
+    }
+    timeq_add(next_call, 0, main_checkauths, NULL);
+}
+
+/* INSTALLATION SCRIPT */
+
+static void check_firstrun() {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_access` = 1000 LIMIT 1");
+    res = mysql_use();
+    if (mysql_fetch_row(res) == NULL) {
+        //first run!
+        printf("No superuser found...\n");
+        check_firstrun_admin:
+        printf("AuthServ account name of admin user: ");
+        char *ptr;
+        char adminuser[31];
+        ptr = fgets(adminuser, 30, stdin);
+        for(ptr = adminuser; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(strlen(adminuser) < 2) goto check_firstrun_admin;
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(adminuser));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL)
+            printf_mysql_query("UPDATE `users` SET `user_access` = 1000 WHERE `user_id` = '%s'", row[0]);
+        else
+            printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`) VALUES ('%s', 1000)", escape_string(adminuser));
+    }
+    printf_mysql_query("SELECT `id` FROM `bots` WHERE `active` = 1 LIMIT 1");
+    res = mysql_use();
+    if (mysql_fetch_row(res) == NULL) {
+        //no bot active
+        printf("No active bot found...\n\n");
+        printf("ADD NEW BOT\n");
+        char *ptr;
+        char bot_nick[31];
+        check_firstrun_bot_nick:
+        printf("Nick: ");
+        ptr = fgets(bot_nick, 30, stdin);
+        for(ptr = bot_nick; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(strlen(bot_nick) < 2) goto check_firstrun_bot_nick;
+        char bot_ident[16];
+        check_firstrun_bot_ident:
+        printf("Ident: ");
+        ptr = fgets(bot_ident, 15, stdin);
+        for(ptr = bot_ident; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(strlen(bot_ident) < 2) goto check_firstrun_bot_ident;
+        char bot_realname[101];
+        check_firstrun_bot_realname:
+        printf("Realname: ");
+        ptr = fgets(bot_realname, 100, stdin);
+        for(ptr = bot_realname; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(strlen(bot_realname) < 2) goto check_firstrun_bot_realname;
+        char bot_server[101];
+        check_firstrun_bot_server:
+        printf("Server: [irc.onlinegamesnet.net] ");
+        ptr = fgets(bot_server, 100, stdin);
+        for(ptr = bot_server; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(*bot_server && strlen(bot_nick) < 5) goto check_firstrun_bot_server;
+        if(!*bot_server)
+            strcpy(bot_server, "irc.onlinegamesnet.net");
+        int bot_port;
+        char bot_port_buf[7];
+        printf("Port: [6667] ");
+        ptr = fgets(bot_port_buf, 6, stdin);
+        for(ptr = bot_port_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(!*bot_port_buf)
+            bot_port = 6667;
+        else
+            bot_port = atoi(bot_port_buf);
+        int bot_ssl;
+        char bot_ssl_buf[5];
+        check_firstrun_bot_ssl:
+        printf("SSL: [y/N] ");
+        ptr = fgets(bot_ssl_buf, 4, stdin);
+        for(ptr = bot_ssl_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(!*bot_ssl_buf || tolower(*bot_ssl_buf) == 'n')
+            bot_ssl = 0;
+        else if(tolower(*bot_ssl_buf) == 'y')
+            bot_ssl = 1;
+        else
+            goto check_firstrun_bot_ssl;
+        char bot_pass[101];
+        printf("Server Password: [] ");
+        ptr = fgets(bot_pass, 100, stdin);
+        for(ptr = bot_pass; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        int bot_maxchan;
+        char bot_maxchan_buf[5];
+        printf("MaxChannel: [20] ");
+        ptr = fgets(bot_maxchan_buf, 5, stdin);
+        for(ptr = bot_maxchan_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(*bot_maxchan_buf)
+            bot_maxchan = atoi(bot_maxchan_buf);
+        else
+            bot_maxchan = 20;
+        int bot_queue;
+        char bot_queue_buf[5];
+        check_firstrun_bot_queue:
+        printf("Queue (prevents excess floods): [Y/n] ");
+        ptr = fgets(bot_queue_buf, 4, stdin);
+        for(ptr = bot_queue_buf; *ptr; ptr++) { if(*ptr == '\n' || *ptr == '\r') *ptr = '\0'; }
+        if(!*bot_queue_buf || tolower(*bot_queue_buf) == 'y')
+            bot_queue = 1;
+        else if(tolower(*bot_queue_buf) == 'n')
+            bot_queue = 0;
+        else
+            goto check_firstrun_bot_queue;
+        printf_mysql_query("INSERT INTO `bots` (`active`, `nick`, `server`, `port`, `pass`, `ssl`, `ident`, `realname`, `botclass`, `textbot`, `queue`, `defaulttrigger`, `max_channels`) VALUES ('1', '%s', '%s', '%d', '%s', '%d', '%s', '%s', '1', '1', '%d', '+', '%d')", escape_string(bot_nick), escape_string(bot_server), bot_port, escape_string(bot_pass), bot_ssl, escape_string(bot_ident), escape_string(bot_realname), bot_queue, bot_maxchan);
+    }
+}
+
diff --git a/src/main.h b/src/main.h
new file mode 100644 (file)
index 0000000..1f1d0f3
--- /dev/null
@@ -0,0 +1,56 @@
+/* main.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _main_h
+#define _main_h
+#include "overall.h"
+
+struct ProcessState {
+    time_t start_time;
+    int running : 1;
+    int restart : 1;
+    int run_as_daemon : 1;
+    int daemonized : 1;
+    int loglevel : 8;
+    int loaded_config : 1;
+    int running_threads : 8;
+    
+    int argc;
+    char **argv;
+    
+    char config[MAXLEN];
+    char pidfile[MAXLEN];
+};
+
+#ifndef DND_FUNCTIONS
+
+extern struct ProcessState process_state;
+
+#ifdef HAVE_THREADS
+extern pthread_mutex_t cache_sync;
+extern pthread_mutex_t whohandler_sync, whohandler_mass_sync;
+
+/* MODULAR ACCESSIBLE */ int getCurrentThreadID();
+#endif
+
+/* MODULAR ACCESSIBLE */ time_t getStartTime();
+/* MODULAR ACCESSIBLE */ int getRunningThreads();
+
+/* MODULAR ACCESSIBLE */ void restart_bot(int crash);
+/* MODULAR ACCESSIBLE */ void stop_bot();
+/* MODULAR ACCESSIBLE */ void reload_config();
+#endif
+#endif
\ No newline at end of file
diff --git a/src/memoryDebug.c b/src/memoryDebug.c
new file mode 100644 (file)
index 0000000..d4e828f
--- /dev/null
@@ -0,0 +1,215 @@
+/* memoryDebug.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "main.h"
+#include "memoryDebug.h"
+#include "memoryInfo.h"
+#include "tools.h"
+
+#define FILE_NAME_LENGTH  256
+#define OUTPUT_FILE "leak_info.txt"
+
+#undef malloc
+#undef calloc
+#undef strdup
+#undef free
+
+struct MemoryNode {
+void *address;
+unsigned int size;
+char file_name[FILE_NAME_LENGTH];
+unsigned int line;
+};
+
+struct MemoryLeak {
+struct MemoryNode mem_info;
+struct MemoryLeak *next;
+};
+
+#ifdef HAVE_THREADS
+static pthread_mutex_t synchronized;
+#endif
+
+static struct MemoryLeak *ptr_start = NULL;
+
+static unsigned int own_allocated_memleaks = 0;
+
+static void add_mem_info(void * mem_ref, unsigned int size, const char *file, unsigned int line);
+static void remove_mem_info(void * mem_ref);
+
+void *xmalloc(unsigned int size, const char *file, unsigned int line) {
+    void *ptr = malloc(size);
+    if (ptr != NULL) {
+        add_mem_info(ptr, size, file, line);
+    }
+    return ptr;
+}
+
+void *xcalloc(unsigned int elements, unsigned int size, const char *file, unsigned int line) {
+    void *ptr = calloc(elements, size);
+    if(ptr != NULL) {
+        add_mem_info(ptr, (elements * size), file, line);
+    }
+    return ptr;
+}
+
+char *xstrdup(const char *data, const char *file, unsigned int line) {
+    int len = strlen(data)+1;
+    char *ptr = malloc(len);
+    if(ptr != NULL) {
+        strcpy(ptr, data);
+        add_mem_info(ptr, len, file, line);
+    }
+    return ptr;
+}
+
+void xfree(void *mem_ref) {
+    remove_mem_info(mem_ref);
+    free(mem_ref);
+}
+
+static void add_mem_info(void *mem_ref, unsigned int size, const char *file, unsigned int line) {
+    #ifdef HAVE_THREADS
+    pthread_mutex_lock(&synchronized);
+    #endif
+    struct MemoryLeak *mem_leak_info = malloc(sizeof(*mem_leak_info));
+    own_allocated_memleaks++;
+    mem_leak_info->mem_info.address = mem_ref;
+    mem_leak_info->mem_info.size = size;
+    strcpy(mem_leak_info->mem_info.file_name, file);    
+    mem_leak_info->mem_info.line = line;
+    mem_leak_info->next = ptr_start;
+    ptr_start = mem_leak_info;
+    #ifdef HAVE_THREADS
+    pthread_mutex_unlock(&synchronized);
+    #endif
+}
+
+static void remove_mem_info(void *mem_ref) {
+    #ifdef HAVE_THREADS
+    pthread_mutex_lock(&synchronized);
+    #endif
+    struct MemoryLeak *leak_info, *next, *prev = NULL;
+    for(leak_info = ptr_start; leak_info; leak_info = next) {
+        next = leak_info->next;
+        if (leak_info->mem_info.address == mem_ref) {
+            if(prev)
+                prev->next = next;
+            else
+                ptr_start = next;
+            own_allocated_memleaks--;
+            free(leak_info);
+            break;
+        } else 
+            prev = leak_info;
+    }
+    #ifdef HAVE_THREADS
+    pthread_mutex_unlock(&synchronized);
+    #endif
+}
+
+void initMemoryDebug() {
+    THREAD_MUTEX_INIT(synchronized);
+}
+
+struct memoryInfoFiles *getMemoryInfoFiles() {
+    #ifdef HAVE_THREADS
+    pthread_mutex_lock(&synchronized);
+    #endif
+    struct MemoryLeak *leak_info;
+    struct memoryInfoFiles *list = NULL, *element;
+    for(leak_info = ptr_start; leak_info != NULL; leak_info = leak_info->next) {
+        for(element = list; element; element = element->next) {
+            if(!strcmp(leak_info->mem_info.file_name, element->filename)) {
+                break;
+            }
+        }
+        if(!element) {
+            element = malloc(sizeof(*element));
+            element->filename = strdup(leak_info->mem_info.file_name);
+            element->allocations = 0;
+            element->allocated = 0;
+            element->next = list;
+            list = element;
+        }
+        element->allocations += 1;
+        element->allocated += leak_info->mem_info.size;
+    }
+    element = malloc(sizeof(*element));
+    element->filename = strdup(__FILE__);
+    element->allocations = own_allocated_memleaks;
+    element->allocated = own_allocated_memleaks * sizeof(struct MemoryLeak);
+    element->next = list;
+    list = element;
+    #ifdef HAVE_THREADS
+    pthread_mutex_unlock(&synchronized);
+    #endif
+    return list;
+}
+
+void freeMemoryInfoFiles(struct memoryInfoFiles *files) {
+    struct memoryInfoFiles *next;
+    for(;files;files = next) {
+        next = files->next;
+        free(files->filename);
+        free(files);
+    }
+}
+
+struct memoryInfoLines *getMemoryInfoLines(const char *filename) {
+    #ifdef HAVE_THREADS
+    pthread_mutex_lock(&synchronized);
+    #endif
+    struct MemoryLeak *leak_info;
+    struct memoryInfoLines *list = NULL, *element;
+    for(leak_info = ptr_start; leak_info != NULL; leak_info = leak_info->next) {
+        if(stricmp(leak_info->mem_info.file_name, filename)) continue;
+        for(element = list; element; element = element->next) {
+            if(element->line == leak_info->mem_info.line && element->allocate == leak_info->mem_info.size) {
+                break;
+            }
+        }
+        if(!element) {
+            element = malloc(sizeof(*element));
+            element->line = leak_info->mem_info.line;
+            element->allocations = 0;
+            element->allocate = leak_info->mem_info.size;
+            element->next = list;
+            list = element;
+        }
+        element->allocations++;
+    }
+    if(!stricmp(filename, __FILE__)) {
+        element = malloc(sizeof(*element));
+        element->line = 0;
+        element->allocations = own_allocated_memleaks;
+        element->allocate = sizeof(struct MemoryLeak);
+        element->next = list;
+        list = element;
+    }
+    #ifdef HAVE_THREADS
+    pthread_mutex_unlock(&synchronized);
+    #endif
+    return list;
+}
+
+void freeMemoryInfoLines(struct memoryInfoLines *lines) {
+    struct memoryInfoLines *next;
+    for(;lines;lines = next) {
+        next = lines->next;
+        free(lines);
+    }
+}
diff --git a/src/memoryDebug.h b/src/memoryDebug.h
new file mode 100644 (file)
index 0000000..456ed2a
--- /dev/null
@@ -0,0 +1,33 @@
+/* memoryDebug.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _memorydebug_h
+#define _memorydebug_h
+
+#define malloc(size) xmalloc (size, __FILE__, __LINE__)
+#define calloc(elements, size) xcalloc (elements, size, __FILE__, __LINE__)
+#define strdup(data) xstrdup (data, __FILE__, __LINE__)
+#define free(mem_ref) xfree(mem_ref)
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ void *xmalloc(unsigned int size, const char *file, unsigned int line);
+/* MODULAR ACCESSIBLE */ void *xcalloc(unsigned int elements, unsigned int size, const char *file, unsigned int line);
+/* MODULAR ACCESSIBLE */ char *xstrdup(const char *data, const char *file, unsigned int line);
+/* MODULAR ACCESSIBLE */ void xfree(void *mem_ref);
+
+void initMemoryDebug();
+#endif
+#endif
diff --git a/src/memoryInfo.h b/src/memoryInfo.h
new file mode 100644 (file)
index 0000000..4ed52fa
--- /dev/null
@@ -0,0 +1,44 @@
+/* memoryInfo.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _memoryinfo_h
+#define _memoryinfo_h
+
+struct memoryInfoFiles {
+    char *filename;
+    unsigned int allocations;
+    unsigned int allocated;
+    struct memoryInfoFiles *next;
+};
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ struct memoryInfoFiles *getMemoryInfoFiles();
+/* MODULAR ACCESSIBLE */ void freeMemoryInfoFiles(struct memoryInfoFiles *files);
+#endif
+
+struct memoryInfoLines {
+    unsigned int line;
+    unsigned int allocations;
+    unsigned int allocate;
+    struct memoryInfoLines *next;
+};
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ struct memoryInfoLines *getMemoryInfoLines(const char *filename);
+/* MODULAR ACCESSIBLE */ void freeMemoryInfoLines(struct memoryInfoLines *lines);
+#endif
+
+#endif
diff --git a/src/modcmd.c b/src/modcmd.c
new file mode 100644 (file)
index 0000000..8ec2517
--- /dev/null
@@ -0,0 +1,1093 @@
+/* modcmd.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "modcmd.h"
+#include "IRCEvents.h"
+#include "IRCParser.h"
+#include "ClientSocket.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "WHOHandler.h"
+#include "lang.h"
+#include "mysqlConn.h"
+#include "DBHelper.h"
+#include "EventLogger.h"
+#include "tools.h"
+#include "log.h"
+
+struct trigger_callback {
+    int botid;
+    int module_id;
+    trigger_callback_t *func;
+    
+    struct trigger_callback *next;
+};
+
+struct cmd_bot_alias {
+    int botid;
+    char *alias;
+    
+    struct cmd_bot_alias *next;
+};
+
+struct command_check_user_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan, *sent_chan;
+    char **argv;
+    int argc;
+    char *message, *args_buffer;
+    struct cmd_binding *cbind;
+};
+
+static struct cmd_binding **cmd_binds;
+static struct cmd_function *cmd_functions = NULL;
+static struct trigger_callback *trigger_callbacks = NULL;
+static struct cmd_bot_alias *bot_aliases = NULL;
+static int total_triggered = 0;
+int statistics_commands = 0;
+
+static const struct default_language_entry msgtab[] = {
+    {"MODCMD_LESS_PARAM_COUNT", "This command requires more parameters."},
+    {"MODCMD_CHAN_REQUIRED",    "You must provide the name of a channel that exists and the bot is on."},
+    {"MODCMD_AUTH_REQUIRED",    "You need to be authenticated with AuthServ to use this command."},
+    {"MODCMD_CHAN_SUSPENDED",   "This channel is currently suspended."},
+    {"MODCMD_PRIVILEGED",       "$b%s$b is a privileged command."}, /* {ARGS: "god"} */
+    {"MODCMD_PUBCMD",           "Public commands in $b%s$b are restricted."}, /* {ARGS: "#TestChan"} */
+    {"MODCMD_ACCESS_DENIED",    "Access denied."},
+    {"MODCMD_SUBCOMMANDS",      "Subcommands of %s: %s"}, /* {ARGS: "bot", "ADD, DEL, EDIT"} */
+    {"MODCMD_CROSSCHAN",        "You must be in %s (or on its userlist) to use this command."}, /* {ARGS: "#TestChan"} */
+    {"MODCMD_UNKNOWN",          "$b%s$b is an unknown command."}, /* {ARGS: "bla"} */
+    {NULL, NULL}
+};
+
+static int get_binds_index(char first_char) {
+    if(tolower(first_char) >= 'a' && tolower(first_char) <= 'z') {
+        return tolower(first_char) - 'a';
+    }
+    return 26;
+}
+
+struct ClientSocket* get_botwise_prefered_bot(int botid, int clientid) {
+    struct ClientSocket *client, *lowbot = NULL;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->botid == botid && (botid || clientid == client->clientid)) {
+            if((client->flags & SOCKET_FLAG_PREFERRED))
+                return client;
+            else
+                lowbot = client;
+        }
+    }
+    return lowbot;
+}
+
+static char* get_channel_trigger(int botid, int clientid, struct ChanNode *chan) {
+    struct trigger_cache *trigger;
+    for(trigger = chan->trigger; trigger; trigger = trigger->next) {
+        if(trigger->botid == botid && (botid || trigger->clientid == clientid))
+            return trigger->trigger;
+    }
+    struct trigger_callback *cb;
+    for(cb = trigger_callbacks; cb; cb = cb->next) {
+        if(cb->botid == botid)
+            break;
+    }
+    char triggerStr[TRIGGERLEN];
+    if(cb)
+        cb->func(clientid, chan, triggerStr);
+    else
+        triggerStr[0] = '\0';
+    trigger = malloc(sizeof(*trigger));
+    if (!trigger) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return 0;
+    }
+    trigger->botid = botid;
+    trigger->clientid = clientid;
+    trigger->trigger = (triggerStr[0] ? strdup(triggerStr) : NULL);
+    trigger->next = chan->trigger;
+    chan->trigger = trigger;
+    return trigger->trigger;
+}
+
+static void handle_command_async(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct ChanNode *sent_chan, struct cmd_binding *cbind, char **argv, int argc);
+
+static USERAUTH_CALLBACK(command_checked_auth) {
+    struct command_check_user_cache *cache = data;
+    if(user)
+        handle_command_async(cache->client, cache->textclient, user, cache->chan, cache->sent_chan, cache->cbind, cache->argv, cache->argc);
+    free(cache->message);
+    if(cache->args_buffer)
+        free(cache->args_buffer);
+    free(cache->argv);
+    free(cache);
+}
+
+static CMD_BIND(modcmd_linker) {
+    //fake command for subcommands
+    //just empty
+}
+
+static struct cmd_binding *modcmd_linker_command(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, int bind_index, char **args_ptr) {
+    //links subcommands
+    char command[MAXLEN];
+    struct cmd_binding *parent_bind = cbind;
+    char *args = *args_ptr;
+    if(args) {
+        char *subcmd = args;
+        args = strstr(args, " ");
+        if(args) {
+            *args = '\0';
+            args++;
+        }
+        sprintf(command, "%s %s", parent_bind->cmd, subcmd);
+        for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+            if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmp(cbind->cmd, command) == 0)
+                break;
+        }
+        *args_ptr = args;
+        if(cbind && cbind->func->func == modcmd_linker) {
+            return modcmd_linker_command(client, textclient, user, cbind, bind_index, args_ptr);
+        }
+        return cbind;
+    } else {
+        //list all sub commands
+        int commandlen = sprintf(command, "%s ", parent_bind->cmd);
+        char subcommands[MAXLEN];
+        int subcompos = 0;
+        for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+            if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmplen(cbind->cmd, command, commandlen) == 0) {
+                if(strstr(cbind->cmd + commandlen, " ")) continue; //ignore if there is another space in the command (sub-sub-command :D)
+                subcompos += sprintf(subcommands + subcompos, (subcompos ? ", %s" : "%s"), cbind->cmd + commandlen);
+            }
+        }
+        reply(textclient, user, "MODCMD_SUBCOMMANDS", parent_bind->cmd, (subcompos ? subcommands : "\1dnone\1d"));
+        return NULL;
+    }
+}
+
+static struct cmd_binding *modcmd_sub_linker_command(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, int bind_index, char **args_ptr) {
+    //links subcommands
+    char command[MAXLEN];
+    struct cmd_binding *parent_bind = cbind;
+    char *args = *args_ptr;
+    if(args) {
+        char *subcmd = args;
+        args = strchr(args, ' ');
+        if(args) {
+            *args = '\0';
+            args++;
+        }
+        sprintf(command, "%s %s", parent_bind->cmd, subcmd);
+        if(args)
+            args[-1] = ' ';
+        for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+            if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmp(cbind->cmd, command) == 0)
+                break;
+        }
+        if(cbind) {
+            *args_ptr = args;
+            if(cbind->func->func == modcmd_linker)
+                parent_bind = modcmd_linker_command(client, textclient, user, cbind, bind_index, args_ptr);
+            else
+                parent_bind = cbind;
+        }
+    }
+    return parent_bind;
+}
+
+static void handle_command(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message) {
+    struct ChanNode *sent_chan = chan;
+    if(message[0] == '#') {
+        char *chanName = message;
+        message = strstr(message, " ");
+        if(!message) return;
+        *message = '\0';
+        message++;
+        struct ChanNode *chan2 = getChanByName(chanName);
+        if(chan2)
+            chan = chan2;
+    }
+    message = strdup(message);
+    int bind_index = get_binds_index(message[0]);
+    char *args = strchr(message, ' ');
+    char *args_buffer = NULL; //we need this to save a possible pointer to a allocation we need to free
+    if(args) {
+        *args = '\0';
+        args++;
+    }
+    struct cmd_binding *cbind;
+    int found_cmd = 0;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && stricmp(cbind->cmd, message) == 0) {
+            found_cmd = 1;
+            //get a text bot
+            struct ClientSocket *textclient = get_botwise_prefered_bot(client->botid, (client->botid == 0 ? client->clientid : 0));
+            if(cbind->func->func == modcmd_linker) {
+                cbind = modcmd_linker_command(client, textclient, user, cbind, bind_index, &args);
+                if(cbind == NULL) break;
+            } else if(cbind->flags & CMDFLAG_SUB_LINKER)
+                cbind = modcmd_sub_linker_command(client, textclient, user, cbind, bind_index, &args);
+            statistics_commands++;
+            total_triggered++;
+            cbind->triggered++;
+            if((BIND_FLAGS(cbind) & CMDFLAG_FUNCMD)) {
+                if(!sent_chan)
+                    break;
+                chan = sent_chan;
+            }
+            //parse the arguments...
+            char *arga[MAXNUMPARAMS];
+            char **argv;
+            int argc = 0;
+            int escape = 0;
+            int offset = 0;
+            if(args) {
+                while(*args) {
+                    //skip leading spaces
+                    while (*args == ' ')
+                        *args++ = 0;
+                    arga[argc++] = args;
+                    if (argc >= MAXNUMPARAMS)
+                        break;
+                    while ((escape || *args != ' ') && *args) {
+                        if((BIND_FLAGS(cbind) & CMDFLAG_ESCAPE_ARGS) && *args == '\\') {
+                            escape = 1;
+                            offset++;
+                        } else if(escape)
+                            escape = 0;
+                        if(!escape && offset) {
+                            args[0 - offset] = args[0];
+                        }
+                        args++;
+                        
+                    }
+                    if(offset) {
+                        args[0-offset] = '\0';
+                        offset = 0;
+                    }
+                }
+            }
+            argv = arga;
+            if(cbind->paramcount) {
+                //userdefined parameters...
+                args_buffer = malloc(MAXLEN * 2 * sizeof(*args_buffer));
+                int args_pos = 0;
+                char *uargs[MAXNUMPARAMS];
+                int uargc = 0;
+                char *b, *c;
+                int i;
+                int allargs, argi;
+                for(i = 0; i < cbind->paramcount; i++) {
+                    b = cbind->parameters[i];
+                    if(b[0] == '%') {
+                        b++;
+                        c = b;
+                        while(*c && *c != ' ') {
+                            if(*c == '|') break;
+                            c++;
+                        }
+                        if(!*c || *c == ' ') c = NULL;
+                        else {
+                            *c = '\0';
+                            c++;
+                        }
+                        if(b[strlen(b)-1] == '-') {
+                            allargs = strlen(b)-1;
+                            b[allargs] = '\0';
+                            argi = atoi(b);
+                            b[allargs] = '-';
+                            allargs = 1;
+                        } else if(b[strlen(b)-1] == '+') {
+                            allargs = strlen(b)-1;
+                            b[allargs] = '\0';
+                            argi = atoi(b);
+                            b[allargs] = '+';
+                            allargs = 2;
+                        } else {
+                            allargs = 0;
+                            argi = atoi(b);
+                        }
+                        if(argi > 0) {
+                            if(argi <= argc) {
+                                uargs[uargc++] = args_buffer + args_pos;
+                                if(allargs == 0) {
+                                    args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                } else if(allargs == 1) {
+                                    args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                    for(argi++; argi <= argc; argi++) {
+                                        uargs[uargc++] = args_buffer + args_pos;
+                                        args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                    }
+                                } else if(allargs == 2) {
+                                    for(;argi <= argc; argi++) {
+                                        args_pos += sprintf(args_buffer + args_pos, (allargs ? "%s" : " %s"), argv[argi-1]);
+                                        allargs = 0;
+                                    }
+                                    args_pos++;
+                                }
+                            } else if((BIND_FLAGS(cbind) & CMDFLAG_EMPTY_ARGS)) {
+                                uargs[uargc++] = args_buffer + args_pos;
+                                args_buffer[args_pos++] = '\0';
+                            } else if(c) {
+                                uargs[uargc++] = args_buffer + args_pos;
+                                args_pos += sprintf(args_buffer + args_pos, "%s", c) + 1;
+                            }
+                        } else if(!strcmp(b, "c")) {
+                            uargs[uargc++] = args_buffer + args_pos;
+                            args_pos += sprintf(args_buffer + args_pos, "%s", (chan ? chan->name : "")) + 1;
+                        } else if(!strcmp(b, "n")) {
+                            uargs[uargc++] = args_buffer + args_pos;
+                            args_pos += sprintf(args_buffer + args_pos, "%s", user->nick) + 1;
+                        }
+                        if(c) c[-1] = '|'; //reset \0 to |
+                    } else {
+                        uargs[uargc++] = args_buffer + args_pos;
+                        args_pos += sprintf(args_buffer + args_pos, "%s", b) + 1;
+                    }
+                }
+                argv = uargs;
+                argc = uargc;
+            }
+            if(argc != 0 && argv[0][0] == '#' && !(BIND_FLAGS(cbind) & CMDFLAG_CHAN_PARAM)) {
+                struct ChanNode *chan2 = getChanByName(argv[0]);
+                if(chan2) {
+                    argv += 1;
+                    argc -= 1;
+                    chan = chan2;
+                }
+            }
+            if(argc < cbind->func->paramcount) {
+                reply(textclient, user, "MODCMD_LESS_PARAM_COUNT");
+                break;
+            }
+            if((BIND_FLAGS(cbind) & CMDFLAG_REQUIRE_CHAN) && !chan) {
+                reply(textclient, user, "MODCMD_CHAN_REQUIRED");
+                break;
+            }
+            if(((BIND_FLAGS(cbind) & CMDFLAG_CHECK_AUTH) || (chan && chan != sent_chan && !isUserOnChan(user, chan))) && !(user->flags & USERFLAG_ISAUTHED)) {
+                //check auth...
+                struct command_check_user_cache *data = malloc(sizeof(*data));
+                char **temp_argv = malloc(argc*sizeof(*temp_argv));
+                if (!data || !temp_argv) {
+                    printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                    break;
+                }
+                memcpy(temp_argv, argv, argc*sizeof(*temp_argv));
+                data->argv = temp_argv;
+                data->argc = argc;
+                data->client = client;
+                data->user = user;
+                data->chan = chan;
+                data->sent_chan = sent_chan;
+                data->message = message;
+                data->args_buffer = args_buffer;
+                data->cbind = cbind;
+                data->textclient = textclient;
+                get_userauth(user, 0, command_checked_auth, data);
+                return;
+            } else
+                handle_command_async(client, textclient, user, chan, sent_chan, cbind, argv, argc);
+            break;
+        }
+    }
+    if(!found_cmd && !sent_chan && !(client->flags & SOCKET_FLAG_SILENT))
+        reply(get_botwise_prefered_bot(client->botid, (client->botid == 0 ? client->clientid : 0)), user, "MODCMD_UNKNOWN", message);
+    free(message);
+    if(args_buffer)
+        free(args_buffer);
+}
+
+static void handle_command_async(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct ChanNode *sent_chan, struct cmd_binding *cbind, char **argv, int argc) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int uaccess;
+    char requested_uaccess = 0;
+    int eventflags = (BIND_FLAGS(cbind) & (CMDFLAG_LOG | CMDFLAG_OPLOG));
+    if((BIND_FLAGS(cbind) & CMDFLAG_REQUIRE_AUTH) && !(user->flags & USERFLAG_ISAUTHED)) {
+        reply(textclient, user, "MODCMD_AUTH_REQUIRED");
+        return;
+    }
+    if(chan && sent_chan != chan && (BIND_FLAGS(cbind) & CMDFLAG_NO_CROSSCHAN) && !isUserOnChan(user, chan)) {
+        char user_in_chan = 0;
+        if((user->flags & USERFLAG_ISAUTHED)) {
+            //maybe there's another user authed to user->auth on the channel...
+            struct ChanUser *cchanuser;
+            for(cchanuser = getChannelUsers(chan, NULL); cchanuser; cchanuser = getChannelUsers(chan, cchanuser)) {
+                if((cchanuser->user->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cchanuser->user->auth)) {
+                    user_in_chan = 1;
+                    break;
+                }
+            }
+        }
+        if(!user_in_chan) {
+            //check if we are allowed to execute commands in this channel
+            requested_uaccess = 1;
+            uaccess = getChannelAccess(user, chan);
+            if(!uaccess) {
+                if(isGodMode(user)) {
+                    eventflags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "MODCMD_CROSSCHAN", chan->name);
+                    return;
+                }
+            }
+        }
+    }
+    if(sent_chan && sent_chan != chan) {
+        //check pubcmd of this channel
+        printf_mysql_query("SELECT `channel_pubcmd` FROM `channels` WHERE `channel_name` = '%s'", escape_string(sent_chan->name));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            int saccess = getChannelAccess(user, sent_chan);
+            if(row[0] && saccess < atoi(row[0]) && !isGodMode(user)) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
+                reply(textclient, user, "MODCMD_PUBCMD", sent_chan->name);
+                return;
+            }
+        }
+    }
+    int global_access = ((cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS) ? cbind->global_access : cbind->func->global_access);
+    if(global_access > 0) {
+        int user_global_access = 0;
+        printf_mysql_query("SELECT `user_access` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            user_global_access = atoi(row[0]);
+        }
+        if(user_global_access < global_access) {
+            if(!user_global_access)
+                reply(textclient, user, "MODCMD_PRIVILEGED", cbind->cmd);
+            else
+                reply(textclient, user, "MODCMD_ACCESS_DENIED");
+            return;
+        }
+    }
+    if((BIND_FLAGS(cbind) & CMDFLAG_REGISTERED_CHAN)) {
+        MYSQL_ROW defaults = NULL;
+        char access_list[256];
+        int access_pos = 0;
+        int access_count = 0;
+        int minaccess = 0;
+        char *str_a, *str_b = cbind->func->channel_access, *str_c;
+        if(cbind->flags & CMDFLAG_OVERRIDE_CHANNEL_ACCESS)
+            str_b = cbind->channel_access;
+        access_list[0] = '\0';
+        if(str_b) {
+            struct ChanUser *chanuser = getChanUser(user, chan);
+            str_c = strdup(str_b);
+            str_b = str_c;
+            while((str_a = str_b)) {
+                str_b = strstr(str_a, ",");
+                if(str_b) {
+                    *str_b = '\0';
+                    str_b++;
+                }
+                if(*str_a == '@' || *str_a == '+') {
+                    //privs can override this access requirement
+                    int priv = 0;
+                    if(*str_a == '@') priv = CHANUSERFLAG_OPPED;
+                    else if(*str_a == '%') priv = CHANUSERFLAG_HALFOPPED;
+                    else if(*str_a == '+') priv = CHANUSERFLAG_VOICED;
+                    if(chanuser && (chanuser->flags & priv)) continue;
+                    str_a++;
+                }
+                if(*str_a == '#') {
+                    str_a++;
+                    access_pos += sprintf(access_list+access_pos, ", `%s`", str_a);
+                    access_count++;
+                } else {
+                    if(atoi(str_a) > minaccess)
+                        minaccess = atoi(str_a);
+                }
+            }
+            free(str_c);
+        }
+        if(!(chan->flags & CHANFLAG_REQUESTED_CHANINFO) || (sent_chan && sent_chan == chan) || access_count || minaccess) {
+            printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = '%s'", access_list, escape_string(chan->name));
+            res = mysql_use();
+            if ((row = mysql_fetch_row(res)) != NULL) {
+                chan->flags |= CHANFLAG_CHAN_REGISTERED;
+                chan->channel_id = atoi(row[0]);
+                if((sent_chan && sent_chan == chan) || access_count || minaccess) {
+                    if(!requested_uaccess) uaccess = getChannelAccess(user, chan);
+                    if(uaccess < minaccess && isGodMode(user)) {
+                        eventflags |= CMDFLAG_OPLOG;
+                    } else if(uaccess < minaccess) {
+                        //ACCESS DENIED
+                        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+                        return;
+                    }
+                    if(!row[1] && !defaults) {
+                        printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
+                        defaults = mysql_fetch_row(mysql_use());
+                    }
+                    if(sent_chan && (sent_chan == chan) && uaccess < (row[1] ? atoi(row[1]) : atoi(defaults[1]))) {
+                        if(isGodMode(user)) {
+                            eventflags |= CMDFLAG_OPLOG;
+                        } else {
+                            //PUBCMD
+                            reply(textclient, user, "MODCMD_PUBCMD", chan->name);
+                            return;
+                        }
+                    }
+                    int i;
+                    for(i = 0; i < access_count; i++) {
+                        if(!row[2+i] && !defaults) {
+                            printf_mysql_query("SELECT `channel_id`, `channel_pubcmd` %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
+                            defaults = mysql_fetch_row(mysql_use());
+                        }
+                        if(uaccess < (row[2+i] ? atoi(row[2+i]) : atoi(defaults[2+i]))) {
+                            if(isGodMode(user)) {
+                                eventflags |= CMDFLAG_OPLOG;
+                            } else {
+                                reply(textclient, user, "MODCMD_ACCESS_DENIED");
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+            chan->flags |= CHANFLAG_REQUESTED_CHANINFO;
+        }
+        if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+            reply(textclient, user, "MODCMD_CHAN_REQUIRED");
+            return;
+        }
+        printf_mysql_query("SELECT `botid`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, client->botid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) == NULL) {
+            reply(textclient, user, "MODCMD_CHAN_REQUIRED");
+            return;
+        } else if(!strcmp(row[1], "1")) {
+            reply(textclient, user, "MODCMD_CHAN_SUSPENDED");
+            return;
+        }
+    }
+    if((BIND_FLAGS(cbind) & CMDFLAG_REQUIRE_GOD) && !isGodMode(user)) {
+        reply(textclient, user, "MODCMD_PRIVILEGED", cbind->cmd);
+        return;
+    }
+    struct Event *event = createEvent(client, user, chan, cbind, argv, argc, eventflags);
+    cbind->func->func(client, textclient, user, chan, argv, argc, event);
+}
+
+static void got_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
+    fd_set fds, fds2;
+    char *trigger;
+    struct ClientSocket *client;
+    FD_ZERO(&fds);
+    FD_ZERO(&fds2);
+    got_chanmsg_loop1:
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(isUserOnChan(client->user, chan) && (client->flags & SOCKET_FLAG_PREFERRED) && ((client->botid == 0 && !FD_ISSET(client->clientid, &fds)) || (client->botid && !FD_ISSET(client->botid, &fds2))))  {
+            FD_SET(client->clientid, &fds);
+            FD_SET(client->botid, &fds2);
+            trigger = get_channel_trigger(client->botid, client->clientid, chan);
+            if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
+                handle_command(client, user, chan, message + strlen(trigger));
+                goto got_chanmsg_loop1; //Thats really really bad, i know... But we can't count on the "getBots" list anymore after executing a command
+            }
+        }
+    }
+    got_chanmsg_loop2:
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(isUserOnChan(client->user, chan) && ((client->botid == 0 && !FD_ISSET(client->clientid, &fds)) || (client->botid && !FD_ISSET(client->botid, &fds2)))) {
+            FD_SET(client->clientid, &fds);
+            FD_SET(client->botid, &fds2);
+            trigger = get_channel_trigger(client->botid, client->clientid, chan);
+            if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
+                handle_command(client, user, chan, message + strlen(trigger));
+                goto got_chanmsg_loop2; //Thats really really bad, i know... But we can't count on the "getBots" list anymore after executing a command
+            }
+        }
+    }
+}
+
+static void got_privmsg(struct UserNode *user, struct UserNode *target, char *message) {
+    struct ClientSocket *client;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->user == target) {
+            handle_command(client, user, NULL, message);
+        }
+    }
+}
+
+int register_command(int botid, char *name, int module_id, cmd_bind_t *func, int paramcount, char *channel_access, int global_access, unsigned int flags) {
+    struct cmd_function *cmdfunc;
+    for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
+        if((cmdfunc->botid == botid || cmdfunc->botid == 0) && !stricmp(cmdfunc->name, name))
+            return 0;
+    }
+    cmdfunc = malloc(sizeof(*cmdfunc));
+    if (!cmdfunc) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return 0;
+    }
+    cmdfunc->botid = botid;
+    cmdfunc->name = strdup(name);
+    cmdfunc->module_id = module_id;
+    cmdfunc->func = func;
+    cmdfunc->flags = flags;
+    cmdfunc->paramcount = paramcount;
+    cmdfunc->channel_access = channel_access;
+    cmdfunc->global_access = global_access;
+    cmdfunc->next = cmd_functions;
+    cmd_functions = cmdfunc;
+    return 1;
+}
+
+int set_trigger_callback(int botid, int module_id, trigger_callback_t *func) {
+    static struct trigger_callback *cb = NULL;
+    for(cb = trigger_callbacks; cb; cb = cb->next) {
+        if(cb->botid == botid)
+            break;
+    }
+    if(!cb) {
+        cb = malloc(sizeof(*cb));
+        if (!cb) {
+            printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return 0;
+        }
+        cb->botid = botid;
+        cb->next = trigger_callbacks;
+        trigger_callbacks = cb;
+    }
+    cb->module_id = module_id;
+    cb->func = func;
+    return 1;
+}
+
+int flush_trigger_cache(int botid, int clientid) {
+    struct ChanNode *chan;
+    struct trigger_cache *trigger, *last;
+    for(chan = getAllChans(NULL); chan; chan = getAllChans(chan)) {
+        last = NULL;
+        for(trigger = chan->trigger; trigger; trigger = trigger->next) {
+            if(trigger->botid == botid && (botid || trigger->clientid == clientid)) {
+                if(last)
+                    last->next = trigger->next;
+                else
+                    chan->trigger = trigger->next;
+                free(trigger->trigger);
+                free(trigger);
+                break;
+            } else
+                last = trigger;
+        }
+    }
+    return 1;
+}
+
+int changeBotwiseChannelTrigger(int botid, int clientid, struct ChanNode *chan, char *new_trigger) {
+    struct trigger_cache *trigger;
+    for(trigger = chan->trigger; trigger; trigger = trigger->next) {
+        if(trigger->botid == botid && (botid || trigger->clientid == clientid)) {
+            free(trigger->trigger);
+            trigger->trigger = strdup(new_trigger);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int bind_botwise_cmd_to_function(int botid, int clientid, char *cmd, struct cmd_function *func) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(((botid && cbind->botid == botid) || (botid == 0 && clientid == cbind->clientid)) && !stricmp(cbind->cmd, cmd))
+            return 0;
+    }
+    cbind = malloc(sizeof(*cbind));
+    if (!cbind) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return 0;
+    }
+    cbind->botid = botid;
+    cbind->clientid = clientid;
+    cbind->cmd = strdup(cmd);
+    cbind->func = func;
+    cbind->paramcount = 0;
+    cbind->global_access = 0;
+    cbind->channel_access = NULL;
+    cbind->flags = 0;
+    cbind->triggered = 0;
+    cbind->next = cmd_binds[bind_index];
+    cmd_binds[bind_index] = cbind;
+    return 1;
+}
+
+int bind_botwise_cmd_to_command(int botid, int clientid, char *cmd, char *func) {
+    struct cmd_function *cmdfunc;
+    int fbotid = botid;
+    char *c;
+    if((c = strstr(func, "."))) {
+        *c = '\0';
+        struct cmd_bot_alias *botalias;
+        for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+            if(!stricmp(botalias->alias, func)) {
+                fbotid = botalias->botid;
+                break;
+            }
+        }
+        *c = '.';
+        func = c+1;
+    }
+    for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
+        if((cmdfunc->botid == fbotid || cmdfunc->botid == 0) && !stricmp(cmdfunc->name, func))
+            break;
+    }
+    if(!cmdfunc) return 0;
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(((botid && cbind->botid == botid) || (botid == 0 && clientid == cbind->clientid)) && !stricmp(cbind->cmd, cmd))
+            return 0;
+    }
+    cbind = malloc(sizeof(*cbind));
+    if (!cbind) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return 0;
+    }
+    cbind->botid = botid;
+    cbind->clientid = clientid;
+    cbind->cmd = strdup(cmd);
+    cbind->func = cmdfunc;
+    cbind->next = cmd_binds[bind_index];
+    cbind->paramcount = 0;
+    cbind->global_access = 0;
+    cbind->channel_access = NULL;
+    cbind->flags = 0;
+    cbind->triggered = 0;
+    cmd_binds[bind_index] = cbind;
+    return 1;
+}
+
+int unbind_botwise_cmd(int botid, int clientid, char *cmd) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind, *last = NULL;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            if(last)
+                last->next = cbind->next;
+            else
+                cmd_binds[bind_index] = cbind->next;
+            free(cbind->cmd);
+            if(cbind->paramcount) {
+                int i;
+                for(i = 0; i < cbind->paramcount; i++)
+                    free(cbind->parameters[i]);
+            }
+            if(cbind->channel_access)
+                free(cbind->channel_access);
+            free(cbind);
+            return 1;
+        } else
+            last = cbind;
+    }
+    return 0;
+}
+
+int unbind_botwise_allcmd(int botid, int clientid) {
+    int i;
+    for(i = 0; i < 27; i++) {
+        struct cmd_binding *cbind, *next, *last = NULL;
+        for(cbind = cmd_binds[i]; cbind; cbind = next) {
+            next = cbind->next;
+            if((botid == 0 && clientid == cbind->clientid) || (botid && botid == cbind->botid)) {
+                if(last)
+                    last->next = cbind->next;
+                else
+                    cmd_binds[i] = cbind->next;
+                free(cbind->cmd);
+                if(cbind->paramcount) {
+                    int j;
+                    for(j = 0; j < cbind->paramcount; j++)
+                        free(cbind->parameters[j]);
+                }
+                if(cbind->channel_access)
+                    free(cbind->channel_access);
+                free(cbind);
+            } else
+                last = cbind;
+        }
+    }
+    return 1;
+}
+
+struct cmd_function *find_cmd_function(int botid, char *name) {
+    struct cmd_function *cmdfunc;
+    char *c;
+    if((c = strstr(name, "."))) {
+        *c = '\0';
+        struct cmd_bot_alias *botalias;
+        for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+            if(!stricmp(botalias->alias, name)) {
+                botid = botalias->botid;
+                break;
+            }
+        }
+        *c = '.';
+        name = c+1;
+    }
+    for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
+        if((cmdfunc->botid == botid || cmdfunc->botid == 0) && stricmp(cmdfunc->name, name) == 0)
+            break;
+    }
+    return cmdfunc;
+}
+
+void init_modcmd() {
+    cmd_binds = calloc(27, sizeof(*cmd_binds));
+    bind_chanmsg(got_chanmsg, 0);
+    bind_privmsg(got_privmsg, 0);
+    register_default_language_table(msgtab);
+    register_command(0, "linker", 0, modcmd_linker, 0, 0, 0, 0); //fake command for subcommands
+}
+
+void free_modcmd() {
+    int i;
+    for(i = 0; i < 27; i++) {
+        struct cmd_binding *cbind, *next;
+        for(cbind = cmd_binds[i]; cbind; cbind = next) {
+            next = cbind->next;
+            free(cbind->cmd);
+            if(cbind->paramcount) {
+                int j;
+                for(j = 0; j < cbind->paramcount; j++)
+                    free(cbind->parameters[j]);
+            }
+            if(cbind->channel_access)
+                free(cbind->channel_access);
+            free(cbind);
+        }
+    }
+    free(cmd_binds);
+    struct cmd_function *cmdfunct, *next;
+    for(cmdfunct = cmd_functions; cmdfunct; cmdfunct = next) {
+        next = cmdfunct->next;
+        free(cmdfunct->name);
+        free(cmdfunct);
+    }
+    struct trigger_callback *cb, *next_cb;
+    for(cb = trigger_callbacks; cb; cb = next_cb) {
+        next_cb = cb->next;
+        free(next_cb);
+    }
+    struct cmd_bot_alias *botalias, *next_botalias;
+    for(botalias = bot_aliases; botalias; botalias = next_botalias) {
+        next_botalias = botalias->next;
+        free(botalias->alias);
+        free(botalias);
+    }
+    cmd_functions = NULL;
+    trigger_callbacks = NULL;
+    bot_aliases = NULL;
+}
+
+void bind_botwise_set_parameters(int botid, int clientid, char *cmd, char *parameters) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            if(cbind->paramcount) {
+                int i;
+                for(i = 0; i < cbind->paramcount; i++)
+                    free(cbind->parameters[i]);
+                cbind->paramcount = 0;
+            }
+            if(parameters) {
+                char *a, *b = parameters;
+                do {
+                    a = strstr(b, " ");
+                    if(a) *a = '\0';
+                    cbind->parameters[cbind->paramcount++] = strdup(b);
+                    if(a) b = a+1;
+                } while(a);
+            }
+            return;
+        }
+    }
+}
+
+void bind_botwise_set_global_access(int botid, int clientid, char *cmd, int gaccess) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            if(gaccess > -1) {
+                cbind->global_access = gaccess;
+                cbind->flags |= CMDFLAG_OVERRIDE_GLOBAL_ACCESS;
+            } else {
+                cbind->flags &= ~CMDFLAG_OVERRIDE_GLOBAL_ACCESS;
+            }
+            return;
+        }
+    }
+}
+
+void bind_botwise_set_channel_access(int botid, int clientid, char *cmd, char *chanaccess) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            if(cbind->channel_access)
+                free(cbind->channel_access);
+            if(chanaccess) {
+                cbind->channel_access = strdup(chanaccess);
+                cbind->flags |= CMDFLAG_OVERRIDE_CHANNEL_ACCESS;
+            } else {
+                cbind->channel_access = NULL;
+                cbind->flags &= ~CMDFLAG_OVERRIDE_CHANNEL_ACCESS;
+            }
+            return;
+        }
+    }
+}
+
+void bind_botwise_set_bind_flags(int botid, int clientid, char *cmd, unsigned int flags) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            cbind->flags |= flags;
+            return;
+        }
+    }
+}
+
+struct cmd_binding *find_botwise_cmd_binding(int botid, int clientid, char *cmd) {
+    int bind_index = get_binds_index(cmd[0]);
+    struct cmd_binding *cbind;
+    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+        if(cbind->botid == botid && (botid || clientid == cbind->clientid) && !stricmp(cbind->cmd, cmd)) {
+            return cbind;
+        }
+    }
+    return NULL;
+}
+
+void bind_botwise_unbound_required_functions(int botid, int clientid) {
+    struct cmd_function *cmdfunc;
+    int i, found;
+    struct cmd_binding *cbind;
+    for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
+        if((cmdfunc->flags & CMDFLAG_REQUIRED)) {
+            found = 0;
+            for(i = 0; i < 27; i++) {
+                for(cbind = cmd_binds[i]; cbind; cbind = cbind->next) {
+                    if(cbind->botid == botid && (botid || clientid == cbind->clientid) && cbind->func == cmdfunc) {
+                        found = 1;
+                        break;
+                    }
+                }
+                if(found)
+                    break;
+            }
+            if(!found && bind_botwise_cmd_to_function(botid, clientid, cmdfunc->name, cmdfunc)) {
+                cbind = find_botwise_cmd_binding(botid, clientid, cmdfunc->name);
+                cbind->flags |= CMDFLAG_TEMPONARY_BIND;
+            }
+        }
+    }
+}
+
+void register_command_alias(int botid, char *alias) {
+    struct cmd_bot_alias *botalias;
+    for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+        if(!stricmp(botalias->alias, alias))
+            return;
+    }
+    botalias = malloc(sizeof(*botalias));
+    if (!botalias) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    botalias->botid = botid;
+    botalias->alias = strdup(alias);
+    botalias->next = bot_aliases;
+    bot_aliases = botalias;
+}
+
+struct cmd_binding *getAllBinds(struct cmd_binding *last) {
+    int bind_index;
+    if(last) {
+        if(last->next)
+            return last->next;
+        bind_index = get_binds_index(last->cmd[0]) + 1;
+        if(bind_index > 26)
+            return NULL;
+    } else
+        bind_index = 0;
+    do {
+        if(cmd_binds[bind_index])
+            return cmd_binds[bind_index];
+        bind_index++;
+    } while(bind_index < 27);
+    return NULL;
+}
+
+void unregister_module_commands(int module_id) {
+    struct cmd_function *cmdfunct, *nextfunct, *prevfunct = NULL;
+    int i;
+    for(cmdfunct = cmd_functions; cmdfunct; cmdfunct = nextfunct) {
+        nextfunct = cmdfunct->next;
+        if(cmdfunct->module_id == module_id) {
+            for(i = 0; i < 27; i++) {
+                struct cmd_binding *cbind, *next, *last = NULL;
+                for(cbind = cmd_binds[i]; cbind; cbind = next) {
+                    next = cbind->next;
+                    if(cmdfunct == cbind->func) {
+                        if(last)
+                            last->next = cbind->next;
+                        else
+                            cmd_binds[i] = cbind->next;
+                        free(cbind->cmd);
+                        if(cbind->paramcount) {
+                            int j;
+                            for(j = 0; j < cbind->paramcount; j++)
+                                free(cbind->parameters[j]);
+                        }
+                        if(cbind->channel_access)
+                            free(cbind->channel_access);
+                        free(cbind);
+                    } else
+                        last = cbind;
+                }
+            }
+            if(prevfunct) 
+                prevfunct->next = nextfunct;
+            else
+                cmd_functions = nextfunct;
+            free(cmdfunct->name);
+            free(cmdfunct);
+        } else
+            prevfunct = cmdfunct;
+    }
+    struct trigger_callback *cb, *prevcb = NULL, *nextcb;
+    for(cb = trigger_callbacks; cb; cb = nextcb) {
+        nextcb = cb->next;
+        if(cb->module_id == module_id) {
+            if(prevcb)
+                prevcb->next = nextcb;
+            else
+                trigger_callbacks = nextcb;
+            free(cb);
+        } else
+            prevcb = cb;
+    }
+}
diff --git a/src/modcmd.h b/src/modcmd.h
new file mode 100644 (file)
index 0000000..214a096
--- /dev/null
@@ -0,0 +1,132 @@
+/* modcmd.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _modcmd_h
+#define _modcmd_h
+#include "main.h"
+
+#define MAXPARAMETERS 50
+
+#define CMDFLAG_REQUIRE_CHAN            0x00001
+#define CMDFLAG_REQUIRE_AUTH            0x00002
+#define CMDFLAG_REQUIRE_GOD             0x00004
+#define CMDFLAG_CHECK_AUTH              0x00008
+#define CMDFLAG_REGISTERED_CHAN         0x00010
+#define CMDFLAG_OVERRIDE_GLOBAL_ACCESS  0x00020
+#define CMDFLAG_OVERRIDE_CHANNEL_ACCESS 0x00040
+#define CMDFLAG_CHAN_PARAM              0x00080
+#define CMDFLAG_LOG                     0x00100
+#define CMDFLAG_OPLOG                   0x00200
+#define CMDFLAG_EMPTY_ARGS              0x00400
+#define CMDFLAG_REQUIRED                0x00800
+#define CMDFLAG_TEMPONARY_BIND          0x01000
+#define CMDFLAG_FUNCMD                  0x02000
+#define CMDFLAG_ESCAPE_ARGS             0x04000
+#define CMDFLAG_NO_CROSSCHAN            0x08000
+#define CMDFLAG_SUB_LINKER              0x10000
+
+struct ClientSocket;
+struct UserNode;
+struct ChanNode;
+struct Event;
+
+#define CMD_BIND(NAME) void NAME(UNUSED_ARG(struct ClientSocket *client), UNUSED_ARG(struct ClientSocket *textclient), UNUSED_ARG(struct UserNode *user), UNUSED_ARG(struct ChanNode *chan), UNUSED_ARG(char **argv), UNUSED_ARG(char argc), UNUSED_ARG(struct Event *event))
+typedef void cmd_bind_t(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char **argv, char argc, struct Event *event);
+typedef void trigger_callback_t(int clientid, struct ChanNode *chan, char *trigger);
+
+struct cmd_function {
+    char *name;
+    int botid;
+    int module_id;
+    cmd_bind_t *func;
+    unsigned int flags;
+    int paramcount;
+    int global_access;
+    char *channel_access;
+    
+    struct cmd_function *next;
+};
+
+struct cmd_binding {
+    char *cmd;
+    int botid;
+    int clientid;
+    struct cmd_function *func;
+    unsigned int flags;
+    char *parameters[MAXPARAMETERS];
+    int paramcount;
+    int global_access;
+    char *channel_access;
+    int triggered;
+    
+    struct cmd_binding *next;
+};
+
+#define BIND_FLAGS(BIND) (BIND->flags | BIND->func->flags)
+
+struct trigger_cache {
+    int botid;
+    int clientid;
+    char *trigger;
+    
+    struct trigger_cache *next;
+};
+
+#define get_prefered_bot(BOTID) get_botwise_prefered_bot(BOTID, 0)
+#define changeChannelTrigger(BOTID,CHAN,NEW) changeBotwiseChannelTrigger(BOTID, 0, CHAN, NEW)
+#define bind_cmd_to_function(BOTID,CMD,FUNC) bind_botwise_cmd_to_function(BOTID, 0, CMD, FUNC)
+#define bind_cmd_to_command(BOTID,CMD,FUNC) bind_botwise_cmd_to_command(BOTID, 0, CMD, FUNC)
+#define unbind_cmd(BOTID,CMD) unbind_botwise_cmd(BOTID, 0, CMD)
+#define unbind_allcmd(BOTID) unbind_botwise_allcmd(BOTID, 0)
+#define bind_set_parameters(BOTID,CMD,PARAMETERS) bind_botwise_set_parameters(BOTID, 0, CMD, PARAMETERS)
+#define bind_set_global_access(BOTID,CMD,GACCESS) bind_botwise_set_global_access(BOTID, 0, CMD, GACCESS)
+#define bind_set_channel_access(BOTID,CMD,CACCESS) bind_botwise_set_channel_access(BOTID, 0, CMD, CACCESS)
+#define bind_set_bind_flags(BOTID,CMD,FLAGS) bind_botwise_set_bind_flags(BOTID, 0, CMD, FLAGS)
+#define find_cmd_binding(BOTID,CMD) find_botwise_cmd_binding(BOTID, 0, CMD)
+#define bind_unbound_required_functions(BOTID) bind_botwise_unbound_required_functions(BOTID, 0)
+
+
+#ifndef DND_FUNCTIONS
+extern int statistics_commands;
+
+void init_modcmd();
+void free_modcmd();
+
+/* MODULAR ACCESSIBLE */ struct ClientSocket* get_botwise_prefered_bot(int botid, int clientid);
+
+/* MODULAR ACCESSIBLE */ int register_command(int botid, char *name, int module_id, cmd_bind_t *func, int paramcount, char *channel_access, int global_access, unsigned int flags);
+/* MODULAR ACCESSIBLE */ int set_trigger_callback(int botid, int module_id, trigger_callback_t *func);
+
+/* MODULAR ACCESSIBLE */ int flush_trigger_cache(int botid, int clientid);
+/* MODULAR ACCESSIBLE */ int changeBotwiseChannelTrigger(int botid, int clientid, struct ChanNode *chan, char *new_trigger);
+/* MODULAR ACCESSIBLE */ int bind_botwise_cmd_to_function(int botid, int clientid, char *cmd, struct cmd_function *func);
+/* MODULAR ACCESSIBLE */ int bind_botwise_cmd_to_command(int botid, int clientid, char *cmd, char *func);
+/* MODULAR ACCESSIBLE */ int unbind_botwise_cmd(int botid, int clientid, char *cmd);
+/* MODULAR ACCESSIBLE */ int unbind_botwise_allcmd(int botid, int clientid);
+/* MODULAR ACCESSIBLE */ void bind_botwise_set_parameters(int botid, int clientid, char *cmd, char *parameters);
+/* MODULAR ACCESSIBLE */ void bind_botwise_set_global_access(int botid, int clientid, char *cmd, int gaccess);
+/* MODULAR ACCESSIBLE */ void bind_botwise_set_channel_access(int botid, int clientid, char *cmd, char *chanaccess);
+/* MODULAR ACCESSIBLE */ void bind_botwise_set_bind_flags(int botid, int clientid, char *cmd, unsigned int flags);
+/* MODULAR ACCESSIBLE */ struct cmd_binding *find_botwise_cmd_binding(int botid, int clientid, char *cmd);
+/* MODULAR ACCESSIBLE */ void bind_botwise_unbound_required_functions(int botid, int clientid);
+/* MODULAR ACCESSIBLE */ struct cmd_function *find_cmd_function(int botid, char *name);
+/* MODULAR ACCESSIBLE */ void register_command_alias(int botid, char *alias);
+/* MODULAR ACCESSIBLE */ struct cmd_binding *getAllBinds(struct cmd_binding *last);
+
+void unregister_module_commands(int module_id);
+
+#endif
+#endif
diff --git a/src/module_commands.c b/src/module_commands.c
new file mode 100644 (file)
index 0000000..7d3b4e1
--- /dev/null
@@ -0,0 +1,86 @@
+/* module_commands.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "modcmd.h"
+#include "IRCParser.h"
+#include "modules.h"
+#include "tools.h"
+#include "lang.h"
+
+static const struct default_language_entry msgtab[] = {
+    {"MODULE_LOADED", "Module %s loaded."}, /* {ARGS: "NeonServ"} */
+    {"MODULE_UNLOADED", "Module %s unloaded."}, /* {ARGS: "NeonServ"} */
+    {"MODULE_RELOADED", "Module %s reloaded."}, /* {ARGS: "NeonServ"} */
+    {"MODULE_ERROR", "An error occured when performing this module operation. View error log for more information."},
+    {"MODULE_LIST", "Loaded Modules: %s"},
+    {NULL, NULL}
+};
+
+static CMD_BIND(module_cmd_load);
+static CMD_BIND(module_cmd_unload);
+static CMD_BIND(module_cmd_reload);
+static CMD_BIND(module_cmd_modules);
+
+void register_module_commands() {
+    //Module Commands
+    #define OPER_COMMAND(NAME,FUNCTION,PARAMCOUNT,GACCESS,FLAGS) register_command(0, NAME, 0, FUNCTION, PARAMCOUNT, NULL, GACCESS, FLAGS)
+    //            NAME            FUNCTION            PARAMS  ACCS  FLAGS
+    OPER_COMMAND("loadmod",      module_cmd_load,      1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("unloadmod",    module_cmd_unload,    1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("reloadmod",    module_cmd_reload,    1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("modules",      module_cmd_modules,   0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    #undef OPER_COMMAND
+    
+    register_default_language_table(msgtab);
+}
+
+static CMD_BIND(module_cmd_load) {
+    if(ext_load_module(argv[0]))
+        reply(textclient, user, "MODULE_LOADED", argv[0]);
+    else
+        reply(textclient, user, "MODULE_ERROR");
+}
+
+static CMD_BIND(module_cmd_unload) {
+    if(ext_unload_module(argv[0]))
+        reply(textclient, user, "MODULE_UNLOADED", argv[0]);
+    else
+        reply(textclient, user, "MODULE_ERROR");
+}
+
+static CMD_BIND(module_cmd_reload) {
+    if(ext_reload_module(argv[0]))
+        reply(textclient, user, "MODULE_RELOADED", argv[0]);
+    else
+        reply(textclient, user, "MODULE_ERROR");
+}
+
+static CMD_BIND(module_cmd_modules) {
+    char modules[MAXLEN];
+    int modpos = 0;
+    int modcount = 0;
+    struct ModuleInfo *modinfo;
+    for(modinfo = ext_get_modules(NULL); modinfo; modinfo = ext_get_modules(modinfo)) {
+        if(modpos + strlen(modinfo->name) > 450) {
+            reply(textclient, user, "MODULE_LIST", modules);
+            modpos = 0;
+        }
+        modcount++;
+        modpos += sprintf(modules + modpos, (modpos ? ", %s" : "%s"), modinfo->name);
+    }
+    if(!modcount || modpos)
+        reply(textclient, user, "MODULE_LIST", (modpos ? modules : "none"));
+}
diff --git a/src/module_commands.h b/src/module_commands.h
new file mode 100644 (file)
index 0000000..c7f285a
--- /dev/null
@@ -0,0 +1,24 @@
+/* module_commands.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _module_commands_h
+#define _module_commands_h
+#ifndef DND_FUNCTIONS
+
+void register_module_commands();
+
+#endif
+#endif
diff --git a/src/modules.c b/src/modules.c
new file mode 100644 (file)
index 0000000..f48c41a
--- /dev/null
@@ -0,0 +1,512 @@
+/* modules.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "modules.h"
+#ifndef WIN32
+#include <dlfcn.h>
+#endif
+
+/* 000-001 */ #include "main.h"
+/* 002-004 */ #include "tools.h"
+#ifdef ENABLE_MUTEX_DEBUG
+/* 005-006 */ #include "mutexDebug.h"
+#endif
+/* 007-009 */ /* main.h */
+/* 010     */ #include "log.h"
+/* 011     */ /* main.h */
+/* 012     */ #include "BanNode.h"
+/* 013-019 */ #include "bots.h"
+/* 020-025 */ #include "ChanNode.h"
+/* 026-029 */ #include "ChanUser.h"
+/* 030-036 */ #include "ClientSocket.h"
+/* 037-038 */ #include "ConfigParser.h"
+/* 039-048 */ #include "DBHelper.h"
+/* 049     */ #include "EventLogger.h"
+/* 050     */ #include "HandleInfoHandler.h"
+/* 051-088 */ #include "IRCEvents.h"
+/* 089-091 */ #include "IRCParser.h"
+/* 092-098 */ #include "lang.h"
+#ifdef ENABLE_MEMORY_DEBUG
+/* 099-102 */ #include "memoryDebug.h"
+#endif
+/* 103-106 */ #include "memoryInfo.h"
+/* 107-122 */ #include "modcmd.h"
+/* 123     */ /* deprecated */
+/* 124-125 */ /* modcmd.h */
+/* 126-136 */ #include "ModeNode.h"
+/* 137-142 */ #include "mysqlConn.h"
+/* 143-149 */ #include "timeq.h"
+/* 150-169 */ /* tools.h */
+/* 170-180 */ #include "UserNode.h"
+/* 181-183 */ #include "WHOHandler.h"
+/* 184-188 */ #include "version.h"
+/* 189     */ /* modules.h */
+/* 190     */ /* UserNode.h */
+/* 191-193 */ #include "ModuleFunctions.h"
+/* 194     */ /* bots.h */
+/* 195-196 */ /* version.h */
+/* 197-198 */ /* IRCEvents.h */
+
+#define Function void *
+
+void *global_functions[] = {
+/* 000 */ (Function) getStartTime,
+/* 001 */ (Function) getRunningThreads,
+/* 002 */ (Function) getCurrentSecondsOfDay,
+/* 003 */ (Function) stricmp,
+/* 004 */ (Function) stricmplen,
+#ifdef ENABLE_MUTEX_DEBUG
+/* 005 */ (Function) xmutex,
+/* 006 */ (Function) mutex_debug,
+#else
+/* 005 */ (Function) NULL,
+/* 006 */ (Function) NULL,
+#endif
+/* 007 */ (Function) restart_bot,
+/* 008 */ (Function) stop_bot,
+/* 009 */ (Function) reload_config,
+/* 010 */ (Function) printf_log,
+#ifdef HAVE_THREADS
+/* 011 */ (Function) getCurrentThreadID,
+#else
+/* 011 */ (Function) NULL,
+#endif
+/* 012 */ (Function) getMatchingChannelBan,
+/* 013 */ (Function) getChannelBot,
+/* 014 */ (Function) requestOp,
+/* 015 */ (Function) channel_ban_timeout,
+/* 016 */ (Function) general_event_privctcp,
+/* 017 */ (Function) set_bot_alias,
+/* 018 */ (Function) resolve_botid,
+/* 019 */ (Function) resolve_botalias,
+/* 020 */ (Function) is_valid_chan,
+/* 021 */ (Function) getAllChans,
+/* 022 */ (Function) getChanByName,
+/* 023 */ (Function) getChannelCount,
+/* 024 */ (Function) getChanUserCount,
+/* 025 */ (Function) getChanBanCount,
+/* 026 */ (Function) isUserOnChan,
+/* 027 */ (Function) getChanUser,
+/* 028 */ (Function) getChannelUsers,
+/* 029 */ (Function) getUserChannels,
+/* 030 */ (Function) create_socket,
+/* 031 */ (Function) connect_socket,
+/* 032 */ (Function) close_socket,
+/* 033 */ (Function) destroy_socket,
+/* 034 */ (Function) write_socket,
+/* 035 */ (Function) putsock,
+/* 036 */ (Function) getBots,
+/* 037 */ (Function) get_int_field,
+/* 038 */ (Function) get_string_field,
+/* 039 */ (Function) _loadUserSettings,
+/* 040 */ (Function) isGodMode,
+/* 041 */ (Function) getChanDefault,
+/* 042 */ (Function) getChannelAccess,
+/* 043 */ (Function) checkChannelAccess,
+/* 044 */ (Function) _loadChannelSettings,
+/* 045 */ (Function) isUserProtected,
+/* 046 */ (Function) getBanAffectingMask,
+/* 047 */ (Function) renameAccount,
+/* 048 */ (Function) deleteUser,
+/* 049 */ (Function) logEvent,
+/* 050 */ (Function) lookup_authname,
+/* 051 */ (Function) bind_join,
+/* 052 */ (Function) unbind_join,
+/* 053 */ (Function) bind_nick,
+/* 054 */ (Function) unbind_nick,
+/* 055 */ (Function) bind_part,
+/* 056 */ (Function) unbind_part,
+/* 057 */ (Function) bind_reload,
+/* 058 */ (Function) unbind_reload,
+/* 059 */ (Function) bind_kick,
+/* 060 */ (Function) unbind_kick,
+/* 061 */ (Function) bind_topic,
+/* 062 */ (Function) unbind_topic,
+/* 063 */ (Function) bind_mode,
+/* 064 */ (Function) unbind_mode,
+/* 065 */ (Function) bind_chanmsg,
+/* 066 */ (Function) unbind_chanmsg,
+/* 067 */ (Function) bind_privmsg,
+/* 068 */ (Function) unbind_privmsg,
+/* 069 */ (Function) bind_channotice,
+/* 070 */ (Function) unbind_channotice,
+/* 071 */ (Function) bind_privnotice,
+/* 072 */ (Function) unbind_privnotice,
+/* 073 */ (Function) bind_chanctcp,
+/* 074 */ (Function) unbind_chanctcp,
+/* 075 */ (Function) bind_privctcp,
+/* 076 */ (Function) unbind_privctcp,
+/* 077 */ (Function) bind_invite,
+/* 078 */ (Function) unbind_invite,
+/* 079 */ (Function) bind_raw,
+/* 080 */ (Function) unbind_raw,
+/* 081 */ (Function) bind_bot_ready,
+/* 082 */ (Function) unbind_bot_ready,
+/* 083 */ (Function) bind_registered,
+/* 084 */ (Function) unbind_registered,
+/* 085 */ (Function) bind_freeuser,
+/* 086 */ (Function) unbind_freeuser,
+/* 087 */ (Function) bind_freechan,
+/* 088 */ (Function) unbind_freechan,
+/* 089 */ (Function) reply,
+/* 090 */ (Function) merge_argv,
+/* 091 */ (Function) merge_argv_char,
+/* 092 */ (Function) get_language_by_tag,
+/* 093 */ (Function) get_language_by_name,
+/* 094 */ (Function) get_default_language,
+/* 095 */ (Function) load_language,
+/* 096 */ (Function) register_default_language_table,
+/* 097 */ (Function) get_language_string,
+/* 098 */ (Function) build_language_string,
+#ifdef ENABLE_MEMORY_DEBUG
+/* 099 */ (Function) xmalloc,
+/* 100 */ (Function) xcalloc,
+/* 101 */ (Function) xstrdup,
+/* 102 */ (Function) xfree,
+#else
+/* 099 */ (Function) NULL,
+/* 100 */ (Function) NULL,
+/* 101 */ (Function) NULL,
+/* 102 */ (Function) NULL,
+#endif
+/* 103 */ (Function) getMemoryInfoFiles,
+/* 104 */ (Function) freeMemoryInfoFiles,
+/* 105 */ (Function) getMemoryInfoLines,
+/* 106 */ (Function) freeMemoryInfoLines,
+/* 107 */ (Function) get_botwise_prefered_bot,
+/* 108 */ (Function) register_command,
+/* 109 */ (Function) set_trigger_callback,
+/* 110 */ (Function) flush_trigger_cache,
+/* 111 */ (Function) changeBotwiseChannelTrigger,
+/* 112 */ (Function) bind_botwise_cmd_to_function,
+/* 113 */ (Function) bind_botwise_cmd_to_command,
+/* 114 */ (Function) unbind_botwise_cmd,
+/* 115 */ (Function) unbind_botwise_allcmd,
+/* 116 */ (Function) bind_botwise_set_parameters,
+/* 117 */ (Function) bind_botwise_set_global_access,
+/* 118 */ (Function) bind_botwise_set_channel_access,
+/* 119 */ (Function) bind_botwise_set_bind_flags,
+/* 120 */ (Function) find_botwise_cmd_binding,
+/* 121 */ (Function) bind_botwise_unbound_required_functions,
+/* 122 */ (Function) find_cmd_function,
+/* 123 */ (Function) NULL, /* deprecated */
+/* 124 */ (Function) register_command_alias,
+/* 125 */ (Function) getAllBinds,
+/* 126 */ (Function) createModeNode,
+/* 127 */ (Function) freeModeNode,
+/* 128 */ (Function) isModeSet,
+/* 129 */ (Function) isModeAffected,
+/* 130 */ (Function) getModeValue,
+/* 131 */ (Function) getModeType,
+/* 132 */ (Function) parseModes,
+/* 133 */ (Function) parseModeString,
+/* 134 */ (Function) parseMode,
+/* 135 */ (Function) getModeString,
+/* 136 */ (Function) getFullModeString,
+/* 137 */ (Function) mysql_use,
+/* 138 */ (Function) mysql_free,
+/* 139 */ (Function) printf_mysql_query,
+/* 140 */ (Function) printf_long_mysql_query,
+/* 141 */ (Function) escape_string,
+/* 142 */ (Function) get_mysql_conn,
+/* 143 */ (Function) timeq_add,
+/* 144 */ (Function) timeq_uadd,
+/* 145 */ (Function) timeq_add_name,
+/* 146 */ (Function) timeq_uadd_name,
+/* 147 */ (Function) timeq_del,
+/* 148 */ (Function) timeq_del_name,
+/* 149 */ (Function) timeq_name_exists,
+/* 150 */ (Function) match,
+/* 151 */ (Function) table_init,
+/* 152 */ (Function) table_add,
+/* 153 */ (Function) table_change,
+/* 154 */ (Function) table_change_field,
+/* 155 */ (Function) table_set_bold,
+/* 156 */ (Function) table_end,
+/* 157 */ (Function) table_free,
+/* 158 */ (Function) timeToStr,
+/* 159 */ (Function) strToTime,
+/* 160 */ (Function) initModeBuffer,
+/* 161 */ (Function) modeBufferSet,
+/* 162 */ (Function) flushModeBuffer,
+/* 163 */ (Function) freeModeBuffer,
+/* 164 */ (Function) is_ircmask,
+/* 165 */ (Function) generate_banmask,
+/* 166 */ (Function) make_banmask,
+/* 167 */ (Function) isFakeHost,
+/* 168 */ (Function) mask_match,
+/* 169 */ (Function) crc32,
+/* 170 */ (Function) is_valid_nick,
+/* 171 */ (Function) getUserByNick,
+/* 172 */ (Function) getUserByMask,
+/* 173 */ (Function) countUsersWithHost,
+/* 174 */ (Function) getAuthFakehost,
+/* 175 */ (Function) searchUserByNick,
+/* 176 */ (Function) getAllUsers,
+/* 177 */ (Function) getUsersWithAuth,
+/* 178 */ (Function) getUserCount,
+/* 179 */ (Function) createTempUser,
+/* 180 */ (Function) createTempUserMask,
+/* 181 */ (Function) get_userlist,
+/* 182 */ (Function) _get_userlist_with_invisible,
+/* 183 */ (Function) get_userauth,
+/* 184 */ (Function) get_compilation,
+/* 185 */ (Function) get_creation,
+/* 186 */ (Function) get_revision,
+/* 187 */ (Function) get_codelines,
+/* 188 */ (Function) get_patchlevel,
+/* 189 */ (Function) get_module_name,
+/* 190 */ (Function) isUserModeSet,
+/* 191 */ (Function) module_global_cmd_register_neonbackup,
+/* 192 */ (Function) module_global_cmd_unregister_neonbackup,
+/* 193 */ (Function) module_neonbackup_recover_chan,
+/* 194 */ (Function) requestInvite,
+/* 195 */ (Function) is_stable_revision,
+/* 196 */ (Function) get_dev_revision,
+/* 197 */ (Function) bind_freeclient,
+/* 198 */ (Function) unbind_freeclient
+};
+
+static int module_id_counter = 1;
+static struct ModuleInfo *modules = NULL;
+
+static void unregister_module_hooks(int module_id);
+
+void loadModules() {
+    char **modulelist = get_all_fieldnames("modules");
+    if(!modulelist) return;
+    int i = 0;
+    char tmp[MAXLEN];
+    struct ModuleInfo *modinfo;
+    while(modulelist[i]) {
+        sprintf(tmp, "modules.%s.enabled", modulelist[i]);
+        if(get_int_field(tmp)) {
+            modinfo = loadModule(modulelist[i]);
+            sprintf(tmp, "modules.%s.protected", modulelist[i]);
+            if(get_int_field(tmp))
+                modinfo->state |= MODINFO_STATE_PROTECTED;
+        }
+        i++;
+    }
+    free(modulelist);
+    start_modules();
+}
+
+struct ModuleInfo *loadModule(char *name) {
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(!stricmp(modinfo->name, name)) return NULL;
+    }
+    char fname[256];
+    #ifndef WIN32
+    sprintf(fname, "%s.so", name);
+    void* module = dlopen(fname, RTLD_LAZY);
+    if(!module) {
+        sprintf(fname, "./%s.so", name);
+        module = dlopen(fname, RTLD_LAZY);
+    }
+    if(!module) {
+        sprintf(fname, ".libs/%s.so", name);
+        module = dlopen(fname, RTLD_LAZY);
+    }
+    if(!module) {
+        printf_log("main", LOG_ERROR, "Error loading module '%s': %s not found.\n", name, fname);
+        return NULL;
+    }
+    void* initfunc = dlsym(module, "init_module");
+    void* startfunc = dlsym(module, "start_module");
+    void* stopfunc = dlsym(module, "stop_module");
+    void* modversion = dlsym(module, "modversion");
+    #else
+    sprintf(fname, "%s.dll", name);
+    HMODULE module = LoadLibrary(fname);
+    if(!module) {
+        printf_log("main", LOG_ERROR, "Error loading module '%s': %s not found.\n", name, fname);
+        return NULL;
+    }
+    FARPROC initfunc = GetProcAddress(module, "init_module");
+    FARPROC startfunc = GetProcAddress(module, "start_module");
+    FARPROC stopfunc = GetProcAddress(module, "stop_module");
+    FARPROC modversion = GetProcAddress(module, "modversion");
+    #endif
+    if(!startfunc || !stopfunc || !modversion) {
+        printf_log("main", LOG_ERROR, "Error loading module '%s': required symbols not found.\n", name);
+        return NULL;
+    }
+    int version = ((int (*)(void)) modversion)();
+    if(version != MODULE_VERSION) {
+        printf_log("main", LOG_ERROR, "Error loading module '%s': version mismatch ('%d' main code, '%d' module)\n", name, MODULE_VERSION, version);
+        return NULL;
+    }
+    //start module
+    int errid;
+    int module_id = module_id_counter++;
+    if((errid = ((int (*)(void **, int)) initfunc)(global_functions, module_id))) {
+        printf_log("main", LOG_ERROR, "Error loading module '%s': module reported error (errid: %d)\n", name, errid);
+        return NULL;
+    }
+    modinfo = malloc(sizeof(*modinfo));
+    if(!modinfo) {
+        unregister_module_hooks(module_id);
+        return NULL;
+    }
+    modinfo->name = strdup(name);
+    modinfo->module_id = module_id;
+    modinfo->module = module;
+    modinfo->startfunc = startfunc;
+    modinfo->stopfunc = stopfunc;
+    modinfo->state = 0;
+    modinfo->next = modules;
+    modules = modinfo;
+    scan_module(modinfo);
+    return modinfo;
+}
+
+#ifndef WIN32
+static void closemodule(void *module) {
+    dlclose(module);
+}
+#else
+static void closemodule(HMODULE module) {
+    FreeLibrary(module);
+}
+#endif
+
+int ext_load_module(char *name) {
+    if(!loadModule(name)) return 0;
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(!(modinfo->state & MODINFO_STATE_STARTED)) {
+            modinfo->state |= MODINFO_STATE_STARTED;
+            ((void (*)(int)) modinfo->startfunc)(MODSTATE_STARTSTOP);
+        } else
+            ((void (*)(int)) modinfo->startfunc)(MODSTATE_REBIND);
+    }
+    return 1;
+}
+
+int ext_unload_module(char *name) {
+    struct ModuleInfo *old_modinfo, *old_prev = NULL;
+    for(old_modinfo = modules; old_modinfo; old_modinfo = old_modinfo->next) {
+        if(!stricmp(old_modinfo->name, name)) {
+            if(old_modinfo->state & MODINFO_STATE_PROTECTED) {
+                return 0;
+            }
+            if(old_prev)
+                old_prev->next = old_modinfo->next;
+            else
+                modules = old_modinfo->next;
+            unregister_module_hooks(old_modinfo->module_id);
+            ((void (*)(int)) old_modinfo->stopfunc)(MODSTATE_STARTSTOP);
+            closemodule(old_modinfo->module);
+            free_module_functions(old_modinfo);
+            free(old_modinfo->name);
+            free(old_modinfo);
+            return 1;
+        } else
+            old_prev = old_modinfo;
+    }
+    return 0;
+}
+
+int ext_reload_module(char *name) {
+    char libname[256];
+    struct ModuleInfo *old_modinfo, *old_prev = NULL;
+    for(old_modinfo = modules; old_modinfo; old_modinfo = old_modinfo->next) {
+        if(!stricmp(old_modinfo->name, name)) {
+            strcpy(libname, old_modinfo->name);
+            if(old_prev)
+                old_prev->next = old_modinfo->next;
+            else
+                modules = old_modinfo->next;
+            unregister_module_hooks(old_modinfo->module_id);
+            ((void (*)(int)) old_modinfo->stopfunc)(MODSTATE_RELOAD);
+            closemodule(old_modinfo->module);
+            free_module_functions(old_modinfo);
+            free(old_modinfo->name);
+            free(old_modinfo);
+            break;
+        } else
+            old_prev = old_modinfo;
+    }
+    if(!loadModule(libname)) return 0;
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(!(modinfo->state & MODINFO_STATE_STARTED)) {
+            modinfo->state |= MODINFO_STATE_STARTED;
+            ((void (*)(int)) modinfo->startfunc)(MODSTATE_RELOAD);
+        } else
+            ((void (*)(int)) modinfo->startfunc)(MODSTATE_REBIND);
+    }
+    return 1;
+}
+
+struct ModuleInfo *ext_get_modules(struct ModuleInfo *last) {
+    return (last ? last->next : modules);
+}
+
+void start_modules() {
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(!(modinfo->state & MODINFO_STATE_STARTED)) {
+            modinfo->state |= MODINFO_STATE_STARTED;
+            ((void (*)(int)) modinfo->startfunc)(MODSTATE_STARTSTOP);
+        }
+    }
+}
+
+void stop_modules() {
+    struct ModuleInfo *modinfo, *next;
+    for(modinfo = modules; modinfo; modinfo = next) {
+        next = modinfo->next;
+        unregister_module_hooks(modinfo->module_id);
+        ((void (*)(int)) modinfo->stopfunc)(MODSTATE_STARTSTOP);
+        closemodule(modinfo->module);
+        free_module_functions(modinfo);
+        free(modinfo->name);
+        free(modinfo);
+    }
+    modules = NULL;
+}
+
+static void unregister_module_hooks(int module_id) {
+    unregister_module_commands(module_id);
+    unregister_module_events(module_id);
+    unregister_module_timers(module_id);
+    
+}
+
+int module_loaded(int module_id) {
+    if(!module_id) return 1;
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(modinfo->module_id == module_id)
+            return 1;
+    }
+    return 0;
+}
+
+char *get_module_name(int module_id) {
+    if(!module_id) return NULL;
+    struct ModuleInfo *modinfo;
+    for(modinfo = modules; modinfo; modinfo = modinfo->next) {
+        if(modinfo->module_id == module_id)
+            return modinfo->name;
+    }
+    return NULL;
+}
+
+
diff --git a/src/modules.h b/src/modules.h
new file mode 100644 (file)
index 0000000..7600f6e
--- /dev/null
@@ -0,0 +1,52 @@
+/* modules.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _modules_h
+#define _modules_h
+#include "main.h"
+
+#define MODINFO_STATE_STARTED   0x01
+#define MODINFO_STATE_PROTECTED 0x02
+
+struct ModuleInfo {
+    char *name;
+    int module_id;
+    #ifndef WIN32
+    void *module;
+    #else
+    HMODULE module;
+    #endif
+    int state;
+    void *startfunc;
+    void *stopfunc;
+    struct ModuleInfo *next;
+};
+
+#ifndef DND_FUNCTIONS
+void loadModules();
+struct ModuleInfo *loadModule(char *name);
+void start_modules();
+void stop_modules();
+int module_loaded(int module_id);
+
+/* MODULAR ACCESSIBLE */ char *get_module_name(int module_id);
+
+int ext_load_module(char *name);
+int ext_unload_module(char *name);
+int ext_reload_module(char *name);
+struct ModuleInfo *ext_get_modules(struct ModuleInfo *last);
+#endif
+#endif
diff --git a/src/modules/DummyServ.mod/bot_DummyServ.c b/src/modules/DummyServ.mod/bot_DummyServ.c
new file mode 100644 (file)
index 0000000..4121bc8
--- /dev/null
@@ -0,0 +1,170 @@
+/* bot_DummyServ.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_DummyServ.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+
+#define BOTID DUMMYSERV_BOTID
+#define BOTALIAS "DummyServ"
+
+static void dummyserv_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void dummyserv_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    //this bot doesn't have a trigger
+    strcpy(trigger, "");
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_SILENT;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+static void dummyserv_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(!(chan && isUserOnChan(bot->user, chan)))
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+void init_DummyServ(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(dummyserv_bot_ready, module_id);
+       bind_invite(dummyserv_event_invite, module_id);
+    
+    set_trigger_callback(BOTID, module_id, dummyserv_trigger_callback);
+}
+
+void free_DummyServ(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/DummyServ.mod/bot_DummyServ.h b/src/modules/DummyServ.mod/bot_DummyServ.h
new file mode 100644 (file)
index 0000000..335b1c8
--- /dev/null
@@ -0,0 +1,25 @@
+/* bot_DummyServ.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_DummyServ_h
+#define _bot_DummyServ_h
+
+#include "../../main.h"
+
+void init_DummyServ(int type);
+void free_DummyServ(int type);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/DummyServ.mod/module.c b/src/modules/DummyServ.mod/module.c
new file mode 100644 (file)
index 0000000..cb7937f
--- /dev/null
@@ -0,0 +1,32 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_DummyServ.h"
+
+static int module_initialize() {
+    return 0;
+}
+
+static void module_start(int type) {
+    init_DummyServ(type);
+}
+
+static void module_stop(int type) {
+    free_DummyServ(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonBackup.mod/bot_NeonBackup.c b/src/modules/NeonBackup.mod/bot_NeonBackup.c
new file mode 100644 (file)
index 0000000..a886da3
--- /dev/null
@@ -0,0 +1,221 @@
+/* bot_NeonBackup.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_NeonBackup.h"
+#include "../../modcmd.h"
+#include "cmd_neonbackup.h"
+#include "../../lang.h"
+#include "../../mysqlConn.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../IRCEvents.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../DBHelper.h"
+#include "../../WHOHandler.h"
+
+#define BOTID NEONBACKUP_BOTID
+#define BOTALIAS "NeonBackup"
+
+static const struct default_language_entry msgtab[] = {
+    {NULL, NULL}
+};
+
+static void neonbackup_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonbackup_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "!");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "+");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "+"));
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_REQUEST_INVITE | SOCKET_FLAG_REQUEST_OP;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+void neonbackup_recover_chan(struct ChanNode *chan) {
+    struct ClientSocket *bot = getChannelBot(chan, BOTID); // prefer backup bot ;)
+    struct ChanUser *chanuser = (bot ? getChanUser(bot->user, chan) : NULL);
+    if(!chanuser || !(chanuser->flags & CHANUSERFLAG_OPPED)) {
+        //search an opped bot
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if((chanuser = getChanUser(bot->user, chan)) && (chanuser->flags & CHANUSERFLAG_OPPED))
+                break;
+        }
+    }
+    if(!bot) //no opped bots present... channel can't be recovered
+        return;
+    struct ClientSocket *target;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(bot, chan);
+    for(target = getBots(SOCKET_FLAG_READY, NULL); target; target = getBots(SOCKET_FLAG_READY, target)) {
+        if((target->flags & SOCKET_FLAG_REQUEST_OP) && (chanuser = getChanUser(target->user, chan)) && !(chanuser->flags & CHANUSERFLAG_OPPED)) {
+            modeBufferOp(modeBuf, target->user->nick);
+        }
+    }
+    freeModeBuffer(modeBuf);
+}
+
+static void neonbackup_event_join(struct ChanUser *chanuser) {
+    struct ClientSocket *client = getChannelBot(chanuser->chan, BOTID);
+    if(!client) return; //we can't "see" this event
+    if(chanuser->user == client->user) {
+        requestOp(client->user, chanuser->chan);
+        neonbackup_recover_chan(chanuser->chan);
+    }
+}
+
+static void neonbackup_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(client, user, "MODCMD_CHAN_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(chan && isUserOnChan(bot->user, chan)) {
+            reply(client, user, "NS_INVITE_ON_CHAN", bot->user->nick, chan->name);
+        } else
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+void init_NeonBackup(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonbackup_bot_ready, module_id);
+    bind_join(neonbackup_event_join, module_id);
+       bind_invite(neonbackup_event_invite, module_id);
+    
+    set_trigger_callback(BOTID, module_id, neonbackup_trigger_callback);
+    
+    register_default_language_table(msgtab);
+}
+
+void free_NeonBackup(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonBackup.mod/bot_NeonBackup.h b/src/modules/NeonBackup.mod/bot_NeonBackup.h
new file mode 100644 (file)
index 0000000..336ba82
--- /dev/null
@@ -0,0 +1,25 @@
+/* bot_NeonBackup.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_NeonBackup_h
+#define _bot_NeonBackup_h
+
+#include "../../main.h"
+
+void init_NeonBackup(int type);
+void free_NeonBackup(int type);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonBackup.mod/cmd_neonbackup.c b/src/modules/NeonBackup.mod/cmd_neonbackup.c
new file mode 100644 (file)
index 0000000..f78bf37
--- /dev/null
@@ -0,0 +1,31 @@
+/* cmd_neonbackup.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_neonbackup.h"
+#include "../../modcmd.h"
+#include "../../ConfigParser.h"
+
+void register_commands() {
+    //NeonBackup Commands
+    register_command_alias(6, "NeonBackup");
+    
+    #define OPER_COMMAND(NAME,FUNCTION,PARAMCOUNT,GACCESS,FLAGS) register_command(6, NAME, module_id, FUNCTION, PARAMCOUNT, NULL, GACCESS, FLAGS)
+    //            NAME            FUNCTION              PARAMS  ACCS  FLAGS
+    OPER_COMMAND("recover",      neonbackup_cmd_recover, 0,     900,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    #undef OPER_COMMAND
+    
+}
\ No newline at end of file
diff --git a/src/modules/NeonBackup.mod/cmd_neonbackup.h b/src/modules/NeonBackup.mod/cmd_neonbackup.h
new file mode 100644 (file)
index 0000000..d6f1763
--- /dev/null
@@ -0,0 +1,37 @@
+/* cmd_neonbackup.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_neonbackup_h
+#define _cmd_neonbackup_h
+#include "../module.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../mysqlConn.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../DBHelper.h"
+#include "../../IRCParser.h"
+#include "bot_NeonBackup.h"
+#include "../../lang.h"
+#include "../../tools.h"
+
+void register_commands();
+
+CMD_BIND(neonbackup_cmd_recover);
+
+#endif
diff --git a/src/modules/NeonBackup.mod/cmd_neonbackup_recover.c b/src/modules/NeonBackup.mod/cmd_neonbackup_recover.c
new file mode 100644 (file)
index 0000000..dc71276
--- /dev/null
@@ -0,0 +1,56 @@
+/* cmd_neonbackup_recover.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonbackup.h"
+
+/*
+* no argv
+*/
+static struct ClientSocket *neonbackup_cmd_recover_get_bot(int clientid) {
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == clientid)
+            return bot;
+    }
+    return NULL;
+}
+
+CMD_BIND(neonbackup_cmd_recover) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channels`.`channel_name`, `bots`.`id` FROM `bot_channels`, `bots`, `channels` WHERE `bots`.`id` = `botid` AND `channel_id` = `chanid` AND `active` = 1");
+    res = mysql_use();
+    
+    struct ClientSocket *bot;
+    struct ChanUser *chanuser;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        chan = getChanByName(row[0]);
+        if(!chan)
+            continue;
+        bot = neonbackup_cmd_recover_get_bot(atoi(row[1]));
+        if(!bot)
+            continue;
+        if(!(chanuser = getChanUser(bot->user, chan))) {
+            requestInvite(bot->user, chan);
+            reply(textclient, user, "NS_INVITE_DONE", bot->user->nick, chan->name);
+        } else if(!chanuser->flags & CHANUSERFLAG_OPPED) {
+            requestOp(bot->user, chan);
+            reply(textclient, user, "NS_OP_DONE", chan->name);
+        }
+    }
+    logEvent(event);
+}
diff --git a/src/modules/NeonBackup.mod/module.c b/src/modules/NeonBackup.mod/module.c
new file mode 100644 (file)
index 0000000..3fe1d39
--- /dev/null
@@ -0,0 +1,34 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonBackup.h"
+#include "cmd_neonbackup.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonBackup(type);
+}
+
+static void module_stop(int type) {
+    free_NeonBackup(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonFun.mod/bot_NeonFun.c b/src/modules/NeonFun.mod/bot_NeonFun.c
new file mode 100644 (file)
index 0000000..44df0c3
--- /dev/null
@@ -0,0 +1,329 @@
+/* bot_NeonFun.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_NeonFun.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+#include "game_uno.h"
+#include "game_4wins.h"
+#include "game_blackjack.h"
+
+#define BOTID NEONFUN_BOTID
+#define BOTALIAS "NeonFun"
+
+static void neonfun_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonfun_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "+");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "+");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "~"));
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_SILENT;
+            client->flags |= SOCKET_FLAG_REQUEST_INVITE | SOCKET_FLAG_REQUEST_OP;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+static void neonfun_parted(struct ChanUser *chanuser, int quit, char *reason) {
+    uno_event_part(chanuser);
+    fourwins_event_part(chanuser);
+    bj_event_part(chanuser);
+}
+
+static int neonfun_freechan(struct ChanNode *chan) {
+    uno_event_freechan(chan);
+    fourwins_event_freechan(chan);
+    bj_event_freechan(chan);
+    return 0;
+}
+
+static void neonfun_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(client, user, "MODCMD_CHAN_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(chan && isUserOnChan(bot->user, chan)) {
+            reply(client, user, "NS_INVITE_ON_CHAN", bot->user->nick, chan->name);
+        } else
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+void init_NeonFun(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonfun_bot_ready, module_id);
+    bind_part(neonfun_parted, module_id);
+    bind_freechan(neonfun_freechan, module_id);
+    bind_invite(neonfun_event_invite, module_id);
+       
+    set_trigger_callback(BOTID, module_id, neonfun_trigger_callback);
+}
+
+void free_NeonFun(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+char* getSetting(struct UserNode *user, struct ChanNode *chan, const char *setting) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        return row[0];
+    } else
+        return NULL;
+}
+
+void setSetting(struct UserNode *user, struct ChanNode *chan, const char *setting, const char *value) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `id`, `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(strcmp(row[1], value))
+            printf_mysql_query("UPDATE `fundata` SET `value` = '%s' WHERE `id` = '%s'", escape_string(value), row[0]);
+    } else
+        printf_mysql_query("INSERT INTO `fundata` (`user`, `cid`, `name`, `value`) VALUES ('%s', '%d', '%s', '%s')", escape_string(uname), cid, escape_string(setting), escape_string(value));
+}
+
+void uno_reply(struct uno_game *game, struct UserNode *user, const char *text, ...) {
+    struct ClientSocket *client = game->textbot;
+    const char *reply_format = get_language_string(user, text);
+    if(reply_format == NULL)
+        reply_format = text;
+    char formatBuf[MAXLEN];
+    sprintf(formatBuf, "PRIVMSG %s :[UNO] %s", game->channel->name, reply_format);
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!(client->flags & SOCKET_FLAG_CONNECTED)) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, formatBuf, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    write_socket(client, sendBuf, pos+1);
+}
+
+void fourwins_reply(struct fourwins_game *game, const char *text, ...) {
+    struct ClientSocket *client = game->textbot;
+    struct fourwins_guest *guest;
+    int guest_count = 0;
+    for(guest = game->guests; guest; guest = guest->next) {
+        guest_count++;
+    }
+    struct ChanUser *chanusers[guest_count + 2];
+    chanusers[0] = game->player[0];
+    chanusers[1] = game->player[1];
+    guest_count = 0;
+    for(guest = game->guests; guest; guest = guest->next) {
+        chanusers[2 + (guest_count++)] = guest->chanuser;
+    }
+    int i;
+    guest_count += 2;
+    for(i = 0; i < guest_count; i++) {
+        const char *reply_format = get_language_string(chanusers[i]->user, text);
+        if(reply_format == NULL)
+            reply_format = text;
+        char formatBuf[MAXLEN];
+        sprintf(formatBuf, "NOTICE %s :[4WINS] %s", chanusers[i]->user->nick, reply_format);
+        va_list arg_list;
+        char sendBuf[MAXLEN];
+        int pos;
+        if (!(client->flags & SOCKET_FLAG_CONNECTED)) return;
+        sendBuf[0] = '\0';
+        va_start(arg_list, text);
+        pos = vsnprintf(sendBuf, MAXLEN - 2, formatBuf, arg_list);
+        va_end(arg_list);
+        if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+        sendBuf[pos] = '\n';
+        sendBuf[pos+1] = '\0';
+        write_socket(client, sendBuf, pos+1);
+    }
+}
+
+void bj_reply(struct bj_game *game, struct UserNode *user, const char *text, ...) {
+    struct ClientSocket *client = game->textbot;
+    const char *reply_format = get_language_string(user, text);
+    if(reply_format == NULL)
+        reply_format = text;
+    char formatBuf[MAXLEN];
+    sprintf(formatBuf, "PRIVMSG %s :[BJ] %s", game->channel->name, reply_format);
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!(client->flags & SOCKET_FLAG_CONNECTED)) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, formatBuf, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    write_socket(client, sendBuf, pos+1);
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonFun.mod/bot_NeonFun.h b/src/modules/NeonFun.mod/bot_NeonFun.h
new file mode 100644 (file)
index 0000000..a2a9ea4
--- /dev/null
@@ -0,0 +1,37 @@
+/* bot_NeonFun.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_NeonFun_h
+#define _bot_NeonFun_h
+
+#include "../../main.h"
+
+void init_NeonFun(int type);
+void free_NeonFun(int type);
+
+struct uno_game;
+struct fourwins_game;
+struct bj_game;
+struct ChanNode;
+struct UserNode;
+
+char* getSetting(struct UserNode *user, struct ChanNode *chan, const char *setting);
+void setSetting(struct UserNode *user, struct ChanNode *chan, const char *setting, const char *value);
+void uno_reply(struct uno_game *game, struct UserNode *user, const char *text, ...);
+void fourwins_reply(struct fourwins_game *game, const char *text, ...);
+void bj_reply(struct bj_game *game, struct UserNode *user, const char *text, ...);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonFun.mod/cmd_neonfun.c b/src/modules/NeonFun.mod/cmd_neonfun.c
new file mode 100644 (file)
index 0000000..9bf3b4b
--- /dev/null
@@ -0,0 +1,121 @@
+/* cmd_neonfun.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+
+#include "cmd_neonfun.h"
+#include "../../modcmd.h"
+#include "../../lang.h"
+#include "../../ConfigParser.h"
+
+static const struct default_language_entry msgtab[] = {
+    {"NF_UNO_ALREADY_RUNNING", "There is already a UNO game running in %s."}, /* {ARGS: "#channel"} */
+    {"NF_UNO_ALREADY_JOINED", "You have already joined this UNO game."},
+    {"NF_UNO_USER_JOINED", "$b%s$b has joined the game."}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_CREATED", "$b%s$b started an UNO game. Type $b+uno$b to join the game."}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_ERROR", "UNO program error. Please contact a bot administrator. (ErrCode: %d)"}, /* {ARGS: 0} */
+    {"NF_UNO_START", "GAME START"},
+    {"NF_UNO_USER_HURRY_UP", "$b%s$b, it's your turn!"}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_USER_TOOK_CARD", "$b%s$b took another card."}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_USER_TOOK_CARDS", "$b%s$b took $b%d$b cards."}, /* {ARGS: "TestUser", 4} */
+    {"NF_UNO_YOUR_CARDS", "Your cards: %s"}, /* {ARGS: "[+2] [+4] [R5] ..."} */
+    {"NF_UNO_TOP_CARD", "upper card: %s"}, /* {ARGS: "[+4]"} */
+    {"NF_UNO_NOT_YOUR_TURN", "Wait your turn!"},
+    {"NF_UNO_UNKNOWN_CARD", "Unknown card [%s]."}, /* {ARGS: "xy"} */
+    {"NF_UNO_CARD_NOT_IN_DECK", "You don't have this card."},
+    {"NF_UNO_CARD_YELLOW", "yellow"},
+    {"NF_UNO_CARD_BLUE", "blue"},
+    {"NF_UNO_CARD_RED", "red"},
+    {"NF_UNO_CARD_GREEN", "green"},
+    {"NF_UNO_DEFINE_COLOR", "Please define the color you want to set as a parameter to the command (eg. $b+unoplay +4 RED$b)"},
+    {"NF_UNO_CARD_NOT_POSSIBLE", "You can't play this card."},
+    {"NF_UNO_ONE_CARD", "$b%s$b has only one card! $k4U$k$k9N$k$k12O$k"}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_USER_WIN", "$b%s$b has 0 cards! $k4U$k$k9N$k$k12O$k $k4U$k$k9N$k$k12O$k!!!"}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_TIMEOUT", "game stopped (no more active players)."},
+    {"NF_UNO_USER_SKIP", "skipped $b%s$b."}, /* {ARGS: "TestUser"} */
+    {"NF_UNO_GAME_FINISHED", "The UNO Game has been finished!"},
+    {"NF_UNO_ADD_CARD", "$b%s$b has to take up %d cards. There is still the possibility that he/she has another card of this type...?"},
+    {"NF_UNO_RANK", "Rank"},
+    {"NF_UNO_NAME", "Name"},
+    {"NF_UNO_WON_GAMES", "Won Games"},
+    {"NF_UNO_TOTAL_WON_GAMES", "Total won Games"},
+    {"NF_UNO_LESS_PLAYERS", "There are not enough players to start the game."},
+    
+    {"NF_4WINS_NOT_YOUR_TURN", "Wait your turn!"},
+    {"NF_4WINS_INVALID_COLUMN", "invalid column!"},
+    {"NF_4WINS_COLUMN_FULL", "This column is full!"},
+    {"NF_4WINS_USER_HURRY_UP", "$b%s$b, it's your turn! To put a stone into a column use: 4stone <colomn>"}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_USER_WON", "$b%s$b won the game!"}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_ENTER_OPPONENT", "Please enter the nick of the opponent you want to play against as a parameter of 4wins (eg. $b4wins TestUser$b)"},
+    {"NF_4WINS_OPPONENT_NOT_IN_CHAN", "$b%s$b needs to be in this channel to start a game against him/her."}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_DRAW", "Nobody has won this game."},
+    {"NF_4WINS_REQUEST", "$b%s$b wants to play a game against you. use $b4wins$b to start the game."}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_REQUESTED", "waiting for $b%s$b accepting the game..."}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_START", "%s accepted the challenge! game starts:"}, /* {ARGS: "TestUser"} */
+    {"NF_4WINS_APPEND_PLAYER", "Please append the nick of a player playing the game you want to view. (eg. $b4view TestUser$b)"},
+    {"NF_4WINS_NO_GAME_FOUND", "Couldn't find a game with this user in this channel."},
+    {"NF_4WINS_VIEWING_ANOTHER_GAME", "You are already viewing another game."},
+    {"NF_4WINS_VIEWING_GAME", "You are now viewing the game."},
+    {"NF_4WINS_GAME_CLOSED", "Game aborted."},
+    {"NF_4WINS_TIMEOUT", "Game aborted (timeout)."},
+    {"NF_4WINS_SELF", "You may not play against yourself."},
+    
+    {"NF_BJ_ALREADY_RUNNING", "There is already a BlackJack game running in %s."}, /* {ARGS: "#channel"} */
+    {"NF_BJ_ALREADY_JOINED", "You have already joined this BlackJack game."},
+    {"NF_BJ_USER_JOINED", "$b%s$b has joined the game."}, /* {ARGS: "TestUser"} */
+    {"NF_BJ_CREATED", "$b%s$b started a BlackJack game. Type $b+bj$b to join the game."}, /* {ARGS: "TestUser"} */
+    {"NF_BJ_ERROR", "BlackJack program error. Please contact a bot administrator. (ErrCode: %d)"}, /* {ARGS: 0} */
+    {"NF_BJ_NOT_YOUR_TURN", "Wait your turn!"},
+    {"NF_BJ_START", "GAME START"},
+    {"NF_BJ_USER_HURRY_UP", "$b%s$b, it's your turn! You can take a card with $bbjtake$b or stop playing with $bbjenough$b"}, /* {ARGS: "TestUser"} */
+    {"NF_BJ_TAKE", "You took another card: %s (%d points)."}, /* {ARGS: "[&#9824;A]", 11} */
+    {"NF_BJ_YOUR_CARDS", "You have %d points! Your cards: %s"}, /* {ARGS: 18, "[&#9824;A] [&#9827;7] ..."} */
+    {"NF_BJ_LESS_PLAYERS", "There are not enough players to start the game."},
+    {"NF_BJ_USER_TIMEOUT", "It seems that %s doesn't want another card... (Timeout)"}, /* {ARGS: "TestUser"} */
+    {"NF_BJ_POINTS_EXCEEDED", "You have more than 21 points... please use $bbjenough$b to finish."},
+    {"NF_BJ_GAME_FINISHED", "Game Finished! Showdown:"},
+    {"NF_BJ_RANK", "Rank"},
+    {"NF_BJ_NAME", "Name"},
+    {"NF_BJ_POINTS", "Points"},
+    {"NF_BJ_CARDS", "Cards"},
+    
+    {NULL, NULL}
+};
+
+void register_commands() {
+    //NeonFun Commands
+    register_default_language_table(msgtab);
+    register_command_alias(5, "NeonFun");
+    
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,PRIVS,FLAGS) register_command(5, NAME, module_id, FUNCTION, PARAMCOUNT, PRIVS, 0, FLAGS)
+    //           NAME            FUNCTION              PARAMS  PRIVS     FLAGS
+    USER_COMMAND("uno",          neonfun_cmd_uno,      0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("unotake",      neonfun_cmd_unotake,  0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("unoplay",      neonfun_cmd_unoplay,  1,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    
+    USER_COMMAND("4wins",        neonfun_cmd_4wins,    0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("4stone",       neonfun_cmd_4stone,   1,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("4view",        neonfun_cmd_4view,    0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    
+    USER_COMMAND("blackjack",    neonfun_cmd_blackjack,0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("bjtake",       neonfun_cmd_bjtake,   0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    USER_COMMAND("bjenough",     neonfun_cmd_bjenough, 0,      NULL,     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN);
+    
+    #undef USER_COMMAND
+    
+}
+
+
diff --git a/src/modules/NeonFun.mod/cmd_neonfun.h b/src/modules/NeonFun.mod/cmd_neonfun.h
new file mode 100644 (file)
index 0000000..fdccf0f
--- /dev/null
@@ -0,0 +1,57 @@
+/* cmd_neonfun.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_neonfun_h
+#define _cmd_neonfun_h
+#include "../module.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+#include "../../ConfigParser.h"
+#include "bot_NeonFun.h"
+
+void register_commands();
+
+CMD_BIND(neonfun_cmd_uno);
+CMD_BIND(neonfun_cmd_unotake);
+CMD_BIND(neonfun_cmd_unoplay);
+
+CMD_BIND(neonfun_cmd_4wins);
+CMD_BIND(neonfun_cmd_4stone);
+CMD_BIND(neonfun_cmd_4view);
+
+CMD_BIND(neonfun_cmd_blackjack);
+CMD_BIND(neonfun_cmd_bjtake);
+CMD_BIND(neonfun_cmd_bjenough);
+
+#endif
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_4stone.c b/src/modules/NeonFun.mod/cmd_neonfun_4stone.c
new file mode 100644 (file)
index 0000000..334d98c
--- /dev/null
@@ -0,0 +1,107 @@
+/* cmd_neonfun_4stone.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_4wins.h"
+
+CMD_BIND(neonfun_cmd_4stone) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct fourwins_game *game;
+    for(game = fourwins_active_games; game; game = game->next) {
+        if(chanuser == game->player[0] || chanuser == game->player[1]) {
+            if(game->state == FOURWINS_STATE_WAITING)
+                return;
+            else 
+                break;
+        }
+    }
+    if(game) {
+        //check if it's the player's turn
+        if(game->player[game->active_player] != chanuser) {
+            reply(textclient, user, "NF_4WINS_NOT_YOUR_TURN");
+            return;
+        }
+        int x = atoi(argv[0])-1;
+        if(x < 0 || x >= FOURWINS_MATRIX_WIDTH) {
+            reply(textclient, user, "NF_4WINS_INVALID_COLUMN");
+            return;
+        }
+        int y = fourwins_next_free_y(game, x);
+        if(y < 0) {
+            reply(textclient, user, "NF_4WINS_COLUMN_FULL");
+            return;
+        }
+        timeq_del(game->timer);
+        game->timer = NULL;
+        game->matrix[x][y].field = game->active_player+1;
+        fourwins_show_matrix(game);
+        if(fourwins_check_win(game, x, y)) {
+            fourwins_reply(game, "NF_4WINS_USER_WON", game->player[game->active_player]->user->nick);
+            if((game->player[game->active_player]->user->flags & USERFLAG_ISAUTHED)) {
+                char *tmp;
+                int win_count = ((tmp = getSetting(game->player[game->active_player]->user, chan, "4wins_win")) ? atoi(tmp) : 0);
+                int total_win_count = ((tmp = getSetting(game->player[game->active_player]->user, NULL, "4wins_win")) ? atoi(tmp) : 0);
+                win_count++;
+                total_win_count++;
+                char buf[10];
+                sprintf(buf, "%d", win_count);
+                setSetting(game->player[game->active_player]->user, chan, "4wins_win", buf);
+                sprintf(buf, "%d", total_win_count);
+                setSetting(game->player[game->active_player]->user, NULL, "4wins_win", buf);
+            }
+            fourwins_free_game(game);
+        } else {
+            game->total_stones++;
+            if(game->total_stones == FOURWINS_MAX_STONES) {
+                fourwins_reply(game, "NF_4WINS_DRAW");
+                if((game->player[0]->user->flags & USERFLAG_ISAUTHED)) {
+                    char *tmp;
+                    int win_count = ((tmp = getSetting(game->player[0]->user, chan, "4wins_draw")) ? atoi(tmp) : 0);
+                    int total_win_count = ((tmp = getSetting(game->player[0]->user, NULL, "4wins_draw")) ? atoi(tmp) : 0);
+                    win_count++;
+                    total_win_count++;
+                    char buf[10];
+                    sprintf(buf, "%d", win_count);
+                    setSetting(game->player[0]->user, chan, "4wins_draw", buf);
+                    sprintf(buf, "%d", total_win_count);
+                    setSetting(game->player[0]->user, NULL, "4wins_draw", buf);
+                }
+                if((game->player[1]->user->flags & USERFLAG_ISAUTHED)) {
+                    char *tmp;
+                    int win_count = ((tmp = getSetting(game->player[1]->user, chan, "4wins_draw")) ? atoi(tmp) : 0);
+                    int total_win_count = ((tmp = getSetting(game->player[1]->user, NULL, "4wins_draw")) ? atoi(tmp) : 0);
+                    win_count++;
+                    total_win_count++;
+                    char buf[10];
+                    sprintf(buf, "%d", win_count);
+                    setSetting(game->player[1]->user, chan, "4wins_draw", buf);
+                    sprintf(buf, "%d", total_win_count);
+                    setSetting(game->player[1]->user, NULL, "4wins_draw", buf);
+                }
+                fourwins_free_game(game);
+                return;
+            }
+            if(game->active_player)
+                game->active_player = 0;
+            else
+                game->active_player = 1;
+            fourwins_reply(game, "NF_4WINS_USER_HURRY_UP", game->player[game->active_player]->user->nick);
+            game->timer = timeq_add(120, module_id, fourwins_timeout, game);
+        }
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_4view.c b/src/modules/NeonFun.mod/cmd_neonfun_4view.c
new file mode 100644 (file)
index 0000000..b912e78
--- /dev/null
@@ -0,0 +1,57 @@
+/* cmd_neonfun_4view.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_4wins.h"
+
+CMD_BIND(neonfun_cmd_4view) {
+    struct UserNode *gameuser;
+    struct ChanUser *chanuser;
+    if(!argc || !(gameuser = getUserByNick(argv[0])) || !(chanuser = getChanUser(gameuser, chan))) {
+        reply(textclient, user, "NF_4WINS_APPEND_PLAYER");
+        return;
+    }
+    struct fourwins_game *game, *fgame = NULL;
+    struct fourwins_guest *guest;
+    for(game = fourwins_active_games; game; game = game->next) {
+        for(guest = game->guests; guest; guest = guest->next) {
+            if(guest->chanuser->user == user) {
+                reply(textclient, user, "NF_4WINS_VIEWING_ANOTHER_GAME");
+                return;
+            }
+        }
+        if(game->player[0]->user == user || game->player[1]->user == user) {
+            reply(textclient, user, "NF_4WINS_VIEWING_ANOTHER_GAME");
+            return;
+        }
+        if(chanuser == game->player[0] || chanuser == game->player[1]) {
+            fgame = game;
+        }
+    }
+    game = fgame;
+    if(game) {
+        guest = malloc(sizeof(*guest));
+        chanuser = getChanUser(user, chan);
+        if(!chanuser) return;
+        guest->chanuser = chanuser;
+        guest->next = game->guests;
+        game->guests = guest;
+        reply(textclient, user, "NF_4WINS_VIEWING_GAME");
+    } else {
+        reply(textclient, user, "NF_4WINS_NO_GAME_FOUND");
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_4wins.c b/src/modules/NeonFun.mod/cmd_neonfun_4wins.c
new file mode 100644 (file)
index 0000000..47acf18
--- /dev/null
@@ -0,0 +1,102 @@
+/* cmd_neonfun_4wins.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_4wins.h"
+
+CMD_BIND(neonfun_cmd_4wins) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct fourwins_game *game;
+    for(game = fourwins_active_games; game; game = game->next) {
+        if(chanuser == game->player[0]) {
+            return;
+        } else if(chanuser == game->player[1]) {
+            if(game->state == FOURWINS_STATE_WAITING)
+                break;
+            else 
+                return;
+        }
+    }
+    if(game) {
+        timeq_del(game->timer);
+        game->timer = NULL;
+        game->state = FOURWINS_STATE_RUNNING;
+        if((game->player[0]->user->flags & USERFLAG_ISAUTHED) && (game->player[1]->user->flags & USERFLAG_ISAUTHED) && !stricmp(game->player[0]->user->auth, game->player[1]->user->auth)) {
+            fourwins_reply(game, "NF_4WINS_SELF");
+            fourwins_free_game(game);
+            return;
+        }
+        fourwins_reply(game, "NF_4WINS_START", user->nick);
+        if((game->player[0]->user->flags & USERFLAG_ISAUTHED)) {
+            char *tmp;
+            int win_count = ((tmp = getSetting(game->player[0]->user, chan, "4wins_games")) ? atoi(tmp) : 0);
+            int total_win_count = ((tmp = getSetting(game->player[0]->user, NULL, "4wins_games")) ? atoi(tmp) : 0);
+            win_count++;
+            total_win_count++;
+            char buf[10];
+            sprintf(buf, "%d", win_count);
+            setSetting(game->player[0]->user, chan, "4wins_games", buf);
+            sprintf(buf, "%d", total_win_count);
+            setSetting(game->player[0]->user, NULL, "4wins_games", buf);
+        }
+        if((game->player[1]->user->flags & USERFLAG_ISAUTHED)) {
+            char *tmp;
+            int win_count = ((tmp = getSetting(game->player[1]->user, chan, "4wins_games")) ? atoi(tmp) : 0);
+            int total_win_count = ((tmp = getSetting(game->player[1]->user, NULL, "4wins_games")) ? atoi(tmp) : 0);
+            win_count++;
+            total_win_count++;
+            char buf[10];
+            sprintf(buf, "%d", win_count);
+            setSetting(game->player[1]->user, chan, "4wins_games", buf);
+            sprintf(buf, "%d", total_win_count);
+            setSetting(game->player[1]->user, NULL, "4wins_games", buf);
+        }
+        fourwins_show_matrix(game);
+        fourwins_reply(game, "NF_4WINS_USER_HURRY_UP", game->player[game->active_player]->user->nick);
+        game->timer = timeq_add(120, module_id, fourwins_timeout, game);
+    } else {
+        if(!argc) {
+            reply(textclient, user, "NF_4WINS_ENTER_OPPONENT");
+            return;
+        }
+        struct UserNode *opp_user = getUserByNick(argv[0]);
+        if(!opp_user) {
+            reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+            return;
+        }
+        if(opp_user == user) {
+            reply(textclient, user, "NF_4WINS_SELF");
+            return;
+        }
+        struct ChanUser *opponent = getChanUser(opp_user, chan);
+        if(!opponent) {
+            reply(textclient, user, "NF_4WINS_OPPONENT_NOT_IN_CHAN", opp_user->nick);
+            return;
+        }
+        game = calloc(1,sizeof(*game));
+        game->player[0] = chanuser;
+        game->player[1] = opponent;
+        game->textbot = textclient;
+        game->state = FOURWINS_STATE_WAITING;
+        game->timer = timeq_add(120, module_id, fourwins_timeout, game);
+        game->next = fourwins_active_games;
+        fourwins_active_games = game;
+        reply(textclient, user, "NF_4WINS_REQUESTED", opp_user->nick);
+        reply(textclient, opp_user, "NF_4WINS_REQUEST", user->nick);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_bjenough.c b/src/modules/NeonFun.mod/cmd_neonfun_bjenough.c
new file mode 100644 (file)
index 0000000..da1ff9b
--- /dev/null
@@ -0,0 +1,41 @@
+/* cmd_neonfun_bjenough.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_blackjack.h"
+
+CMD_BIND(neonfun_cmd_bjenough) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(chan == game->channel) {
+            if(game->state == BJ_STATE_WAITING)
+                return;
+            else 
+                break;
+        }
+    }
+    if(game) {
+        //check if it's the player's turn
+        if(game->active_player->chanuser != chanuser) {
+            reply(textclient, user, "NF_BJ_NOT_YOUR_TURN");
+            return;
+        }
+        bj_action_next_player(game, game->active_player);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_bjtake.c b/src/modules/NeonFun.mod/cmd_neonfun_bjtake.c
new file mode 100644 (file)
index 0000000..91b3f09
--- /dev/null
@@ -0,0 +1,41 @@
+/* cmd_neonfun_bjtake.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_blackjack.h"
+
+CMD_BIND(neonfun_cmd_bjtake) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(chan == game->channel) {
+            if(game->state == BJ_STATE_WAITING)
+                return;
+            else 
+                break;
+        }
+    }
+    if(game) {
+        //check if it's the player's turn
+        if(game->active_player->chanuser != chanuser) {
+            reply(textclient, user, "NF_BJ_NOT_YOUR_TURN");
+            return;
+        }
+        bj_action_take_card(game, game->active_player);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_blackjack.c b/src/modules/NeonFun.mod/cmd_neonfun_blackjack.c
new file mode 100644 (file)
index 0000000..31fe45c
--- /dev/null
@@ -0,0 +1,69 @@
+/* cmd_neonfun_blackjack.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_blackjack.h"
+
+CMD_BIND(neonfun_cmd_blackjack) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(chan == game->channel) 
+            break;
+    }
+    if(game) {
+        //check if player has already joined
+        struct bj_player *player, *last_player = NULL;
+        for(player = game->player; player; player = player->next) {
+            if(player->chanuser == chanuser) {
+                reply(textclient, user, "NF_BJ_ALREADY_JOINED");
+                return;
+            } else
+                last_player = player;
+        }
+        if(!last_player) return; //error (game without players?)
+        player = malloc(sizeof(*player));
+        player->chanuser = chanuser;
+        player->count = 0;
+        player->cards = NULL;
+        player->prev = last_player;
+        player->next = NULL;
+        last_player->next = player;
+        game->players++;
+        bj_reply(game, user, "NF_BJ_USER_JOINED", user->nick);
+    } else {
+        game = malloc(sizeof(*game));
+        game->channel = chan;
+        game->textbot = textclient;
+        game->state = BJ_STATE_WAITING;
+        game->deck = NULL;
+        struct bj_player *player = malloc(sizeof(*player));
+        player->chanuser = chanuser;
+        player->count = 0;
+        player->cards = NULL;
+        player->prev = NULL;
+        player->next = NULL;
+        game->player = player;
+        game->active_player = NULL;
+        game->players = 1;
+        game->timer = timeq_add(30, module_id, bj_game_wait_timeout, game);
+        game->next = bj_active_games;
+        bj_active_games = game;
+        bj_reply(game, user, "NF_BJ_CREATED", user->nick);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_uno.c b/src/modules/NeonFun.mod/cmd_neonfun_uno.c
new file mode 100644 (file)
index 0000000..5742fa0
--- /dev/null
@@ -0,0 +1,81 @@
+/* cmd_neonfun_uno.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_uno.h"
+
+CMD_BIND(neonfun_cmd_uno) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct uno_game *game;
+    for(game = uno_active_games; game; game = game->next) {
+        if(chan == game->channel) {
+            if(game->state == UNO_STATE_WAITING)
+                break;
+            else {
+                reply(textclient, user, "NF_UNO_ALREADY_RUNNING", chan->name);
+                return;
+            }
+        }
+    }
+    if(game) {
+        //check if player has already joined
+        struct uno_player *player, *last_player = NULL;
+        for(player = game->player; player; player = player->next) {
+            if(player->chanuser == chanuser) {
+                reply(textclient, user, "NF_UNO_ALREADY_JOINED");
+                return;
+            } else
+                last_player = player;
+        }
+        if(!last_player) return; //error (game without players?)
+        player = malloc(sizeof(*player));
+        player->chanuser = chanuser;
+        player->count = 0;
+        player->cards = NULL;
+        player->timeout = 0;
+        player->prev = last_player;
+        player->next = NULL;
+        last_player->next = player;
+        game->players++;
+        uno_reply(game, user, "NF_UNO_USER_JOINED", user->nick);
+    } else {
+        game = malloc(sizeof(*game));
+        game->channel = chan;
+        game->textbot = textclient;
+        game->state = UNO_STATE_WAITING;
+        game->reverse_direction = 0;
+        game->take_cards_pending = 0;
+        game->deck = NULL;
+        game->top_card = NULL;
+        struct uno_player *player = malloc(sizeof(*player));
+        player->chanuser = chanuser;
+        player->count = 0;
+        player->cards = NULL;
+        player->timeout = 0;
+        player->prev = NULL;
+        player->next = NULL;
+        game->player = player;
+        game->winner = NULL;
+        game->active_player = NULL;
+        game->players = 1;
+        game->timer = timeq_add(30, module_id, uno_game_wait_timeout, game);
+        game->next = uno_active_games;
+        uno_active_games = game;
+        uno_reply(game, user, "NF_UNO_CREATED", user->nick);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_unoplay.c b/src/modules/NeonFun.mod/cmd_neonfun_unoplay.c
new file mode 100644 (file)
index 0000000..fcb92ef
--- /dev/null
@@ -0,0 +1,68 @@
+/* cmd_neonfun_unoplay.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_uno.h"
+
+CMD_BIND(neonfun_cmd_unoplay) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct uno_game *game;
+    for(game = uno_active_games; game; game = game->next) {
+        if(chan == game->channel) {
+            if(game->state == UNO_STATE_WAITING)
+                return;
+            else 
+                break;
+        }
+    }
+    if(game) {
+        //check if it's the player's turn
+        if(game->active_player->chanuser != chanuser) {
+            reply(textclient, user, "NF_UNO_NOT_YOUR_TURN");
+            return;
+        }
+        game->active_player->timeout = 0;
+        struct uno_card *card = uno_parse_card(game, game->active_player, argv[0]);
+        if(!card) return;
+        unsigned char new_color = card->color;
+        if(card->card == UNO_CARD_ADD_4 || card->card == UNO_CARD_COLOR) {
+            if(argc < 2) {
+                reply(game->textbot, user, "NF_UNO_DEFINE_COLOR");
+                return;
+            }
+            if(!stricmp(argv[1], "RED") || !stricmp(argv[1], "R") || !stricmp(argv[1], get_language_string(user, "NF_UNO_CARD_RED"))) {
+                new_color = UNO_COLOR_RED;
+            } else if(!stricmp(argv[1], "BLUE") || !stricmp(argv[1], "B") || !stricmp(argv[1], get_language_string(user, "NF_UNO_CARD_BLUE"))) {
+                new_color = UNO_COLOR_BLUE;
+            } else if(!stricmp(argv[1], "GREEN") || !stricmp(argv[1], "G") || !stricmp(argv[1], get_language_string(user, "NF_UNO_CARD_GREEN"))) {
+                new_color = UNO_COLOR_GREEN;
+            } else if(!stricmp(argv[1], "YELLOW") || !stricmp(argv[1], "Y") || !stricmp(argv[1], get_language_string(user, "NF_UNO_CARD_YELLOW"))) {
+                new_color = UNO_COLOR_YELLOW;
+            } else {
+                reply(game->textbot, user, "NF_UNO_DEFINE_COLOR");
+                return;
+            }
+        }
+        if(uno_check_card_valid(game, card)) {
+            reply(game->textbot, user, "NF_UNO_CARD_NOT_POSSIBLE");
+            return;
+        }
+        card->color = new_color;
+        uno_play_card(game, game->active_player, card);
+    }
+}
diff --git a/src/modules/NeonFun.mod/cmd_neonfun_unotake.c b/src/modules/NeonFun.mod/cmd_neonfun_unotake.c
new file mode 100644 (file)
index 0000000..e66a34c
--- /dev/null
@@ -0,0 +1,42 @@
+/* cmd_neonfun_unotake.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonfun.h"
+#include "game_uno.h"
+
+CMD_BIND(neonfun_cmd_unotake) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return;
+    struct uno_game *game;
+    for(game = uno_active_games; game; game = game->next) {
+        if(chan == game->channel) {
+            if(game->state == UNO_STATE_WAITING)
+                return;
+            else 
+                break;
+        }
+    }
+    if(game) {
+        //check if it's the player's turn
+        if(game->active_player->chanuser != chanuser) {
+            reply(textclient, user, "NF_UNO_NOT_YOUR_TURN");
+            return;
+        }
+        game->active_player->timeout = 0;
+        uno_action_take_card(game, game->active_player);
+    }
+}
diff --git a/src/modules/NeonFun.mod/game_4wins.c b/src/modules/NeonFun.mod/game_4wins.c
new file mode 100644 (file)
index 0000000..7bcd873
--- /dev/null
@@ -0,0 +1,190 @@
+/* game_4wins.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "game_4wins.h"
+#include "bot_NeonFun.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../UserNode.h"
+#include "../../ChanUser.h"
+#include "../../tools.h"
+#include "../botid.h"
+
+struct fourwins_game *fourwins_active_games = NULL;
+
+int fourwins_next_free_y(struct fourwins_game *game, int x) {
+    int y;
+    for(y = 0; y < FOURWINS_MATRIX_HEIGHT; y++) {
+        if(!game->matrix[x][y].field)
+            return y;
+    }
+    return -1;
+}
+
+int fourwins_check_win(struct fourwins_game *game, int x, int y) {
+    int field = game->matrix[x][y].field;
+    if(!field) return 0;
+    //horizontal
+    {
+        int fc = 0;
+        int i;
+        for(i = x; i < FOURWINS_MATRIX_WIDTH; i++) {
+            if(game->matrix[i][y].field == field)
+                fc++;
+            else
+                break;
+        }
+        for(i = x-1; i >= 0; i--) {
+            if(game->matrix[i][y].field == field)
+                fc++;
+            else
+                break;
+        }
+        if(fc >= 4) return 1;
+    }
+    //senkrecht
+    if(y >= 3) {
+        int fc = 0;
+        int i;
+        for(i = y; i >= 0; i--) {
+            if(game->matrix[i][y].field == field)
+                fc++;
+            else
+                break;
+        }
+        if(fc >= 4) return 1;
+    }
+    //diagonal 1
+    {
+        int fc = 0;
+        int ix, iy;
+        for(ix = x, iy = y; ix < FOURWINS_MATRIX_WIDTH && iy < FOURWINS_MATRIX_HEIGHT; ix++, iy++) {
+            if(game->matrix[ix][iy].field == field)
+                fc++;
+            else
+                break;
+        }
+        for(ix = x-1, iy = y-1; ix >= 0 && iy >= 0; ix--, iy--) {
+            if(game->matrix[ix][iy].field == field)
+                fc++;
+            else
+                break;
+        }
+        if(fc >= 4) return 1;
+    }
+    //diagonal 2
+    {
+        int fc = 0;
+        int ix, iy;
+        for(ix = x, iy = y; ix < FOURWINS_MATRIX_WIDTH && iy >= 0; ix++, iy--) {
+            if(game->matrix[ix][iy].field == field)
+                fc++;
+            else
+                break;
+        }
+        for(ix = x-1, iy = y+1; ix >= 0 && iy < FOURWINS_MATRIX_HEIGHT; ix--, iy++) {
+            if(game->matrix[ix][iy].field == field)
+                fc++;
+            else
+                break;
+        }
+        if(fc >= 4) return 1;
+    }
+    return 0;
+}
+
+void fourwins_show_matrix(struct fourwins_game *game) {
+    int x,y;
+    char lineBuf[MAXLEN];
+    int linePos = 0;
+    for(x = 0; x < FOURWINS_MATRIX_WIDTH; x++) {
+        linePos += sprintf(lineBuf+linePos, (x ? "| %d " : " %d "), x+1);
+    }
+    fourwins_reply(game, lineBuf);
+    for(y = FOURWINS_MATRIX_HEIGHT-1; y >= 0; y--) {
+        linePos = 0;
+        for(x = 0; x < FOURWINS_MATRIX_WIDTH; x++) {
+            char *field = " ";
+            if(game->matrix[x][y].field == 1)
+                field = "\0034o\003";
+            if(game->matrix[x][y].field == 2)
+                field = "\00312o\003";
+            linePos += sprintf(lineBuf+linePos, (x ? " (%s)" : "(%s)"), field);
+        }
+        fourwins_reply(game, lineBuf);
+    }
+}
+
+TIMEQ_CALLBACK(fourwins_timeout) {
+    struct fourwins_game *game = data;
+    game->timer = NULL;
+    fourwins_reply(game, "NF_4WINS_TIMEOUT");
+    fourwins_free_game(game);
+}
+
+void fourwins_free_game(struct fourwins_game *game) {
+    struct fourwins_guest *guest, *next_guest;
+    for(guest = game->guests; guest; guest = next_guest) {
+        next_guest = guest->next;
+        free(guest);
+    }
+    struct fourwins_game *cgame, *prev = NULL;
+    for(cgame = fourwins_active_games; cgame; cgame = cgame->next) {
+        if(cgame == game) {
+            if(prev)
+                prev->next = game->next;
+            else
+                fourwins_active_games = game->next;
+            break;
+        } else
+            prev = cgame;
+    }
+    free(game);
+}
+
+void fourwins_event_part(struct ChanUser *chanuser) {
+    struct fourwins_game *game;
+    for(game = fourwins_active_games; game; game = game->next) {
+        if(game->player[0] == chanuser || game->player[1] == chanuser) {
+            fourwins_reply(game, "NF_4WINS_GAME_CLOSED");
+            fourwins_free_game(game);
+            return;
+        }
+        struct fourwins_guest *guest, *prev_guest = NULL;
+        for(guest = game->guests; guest; guest = guest->next) {
+            if(guest->chanuser == chanuser) {
+                if(prev_guest)
+                    prev_guest->next = guest->next;
+                else
+                    game->guests = guest->next;
+                free(guest);
+                break;
+            } else
+                prev_guest = guest;
+        }
+    }
+}
+
+void fourwins_event_freechan(struct ChanNode *chan) {
+    struct fourwins_game *game;
+    for(game = fourwins_active_games; game; game = game->next) {
+        if(game->player[0]->chan == chan) {
+            fourwins_free_game(game);
+            return;
+        }
+    }
+}
diff --git a/src/modules/NeonFun.mod/game_4wins.h b/src/modules/NeonFun.mod/game_4wins.h
new file mode 100644 (file)
index 0000000..986f859
--- /dev/null
@@ -0,0 +1,62 @@
+/* game_4wins.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _game_4wins_h
+#define _game_4wins_h
+#include "../../timeq.h"
+
+#define FOURWINS_STATE_WAITING 1
+#define FOURWINS_STATE_RUNNING 2
+
+#define FOURWINS_MATRIX_WIDTH 7
+#define FOURWINS_MATRIX_HEIGHT 6
+
+#define FOURWINS_MAX_STONES (FOURWINS_MATRIX_WIDTH * FOURWINS_MATRIX_HEIGHT)
+
+struct UserNode;
+struct ChanNode;
+struct ChanUser;
+struct ClientSocket;
+extern struct fourwins_game *fourwins_active_games;
+
+struct fourwins_guest {
+    struct ChanUser *chanuser;
+    struct fourwins_guest *next;
+};
+
+struct fourwins_game {
+    struct ChanUser *player[2];
+    int active_player;
+    struct fourwins_guest *guests;
+    struct ClientSocket *textbot;
+    int state : 3;
+    struct {
+        unsigned int field:2; /* 0,1,2 */
+    } matrix[FOURWINS_MATRIX_WIDTH][FOURWINS_MATRIX_HEIGHT];
+    int total_stones;
+    struct timeq_entry *timer;
+    struct fourwins_game *next;
+};
+
+int fourwins_next_free_y(struct fourwins_game *game, int x);
+int fourwins_check_win(struct fourwins_game *game, int x, int y);
+void fourwins_show_matrix(struct fourwins_game *game);
+TIMEQ_CALLBACK(fourwins_timeout);
+void fourwins_free_game(struct fourwins_game *game);
+void fourwins_event_part(struct ChanUser *chanuser);
+void fourwins_event_freechan(struct ChanNode *chan);
+
+#endif
diff --git a/src/modules/NeonFun.mod/game_blackjack.c b/src/modules/NeonFun.mod/game_blackjack.c
new file mode 100644 (file)
index 0000000..f2576dc
--- /dev/null
@@ -0,0 +1,421 @@
+/* game_blackjack.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "game_blackjack.h"
+#include "bot_NeonFun.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../UserNode.h"
+#include "../../ChanUser.h"
+#include "../../tools.h"
+#include "../botid.h"
+
+struct bj_game *bj_active_games = NULL;
+
+static int bj_ctype[] =         {BJ_CTYPE_HEARTS, BJ_CTYPE_SPADES, BJ_CTYPE_DIAMONDS, BJ_CTYPE_CLUBS};
+static int bj_irc_colors[] =    {4,               1,               4,                 1};
+static char *bj_color_chars[] = {"\xE2\x9D\xA4",  "\xE2\x99\xA0",  "\xE2\x99\xA6",    "\xE2\x99\xA3"};
+
+static const struct {
+    const char *name;
+    unsigned char type;
+    unsigned int points : 8;
+} bj_card_types[] = {
+    {"2",  BJ_CARD_NUMBER_2,  2},
+    {"3",  BJ_CARD_NUMBER_3,  3},
+    {"4",  BJ_CARD_NUMBER_4,  4},
+    {"5",  BJ_CARD_NUMBER_5,  5},
+    {"6",  BJ_CARD_NUMBER_6,  6},
+    {"7",  BJ_CARD_NUMBER_7,  7},
+    {"8",  BJ_CARD_NUMBER_8,  8},
+    {"9",  BJ_CARD_NUMBER_9,  9},
+    {"10", BJ_CARD_NUMBER_10, 10},
+    {"J",  BJ_CARD_JACK,      10},
+    {"Q",  BJ_CARD_QUEEN,     10},
+    {"K",  BJ_CARD_KING,      10},
+    {"A",  BJ_CARD_ACE,       11},
+    {NULL, 0}
+};
+
+struct bj_card_deck *bj_shuffle_deck() {
+    struct bj_card *card, *last_card = NULL;
+    int card_count = 0;
+    #define ADD_CARD(ctype,ccard) \
+    card = malloc(sizeof(*card)); \
+    card->type = ctype; \
+    card->card = ccard; \
+    card->prev = NULL; \
+    card->next = last_card; \
+    if(last_card) \
+        last_card->prev = card; \
+    last_card = card; \
+    card_count++;
+    int typecount = BJ_CTYPE_COUNT;
+    for(typecount--; typecount >= 0; typecount--) {
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_2);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_3);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_4);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_5);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_6);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_7);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_8);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_9);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_10);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_JACK);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_QUEEN);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_KING);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_ACE);
+    }
+    #undef ADD_CARD
+    struct bj_card_deck *deck = malloc(sizeof(*deck));
+    deck->cards = last_card;
+    deck->count = card_count;
+    return deck;
+}
+
+struct bj_card *bj_get_card(struct bj_card_deck *deck) {
+    if(!deck->count) return NULL;
+    int card_id = (rand() % deck->count);
+    int i = 0;
+    struct bj_card *card;
+    for(card = deck->cards; card; card = card->next) {
+        if(i == card_id) {
+            if(card->prev)
+                card->prev->next = card->next;
+            else
+                deck->cards = card->next;
+            if(card->next)
+                card->next->prev = card->prev;
+            deck->count--;
+            card->next = NULL;
+            card->prev = NULL;
+            return card;
+        }
+        i++;
+    }
+    return NULL;
+}
+
+void bj_free_deck(struct bj_card_deck *deck) {
+    struct bj_card *card, *next_card;
+    if(deck->count) {
+        for(card = deck->cards; card; card = next_card) {
+            next_card = card->next;
+            free(card);
+        }
+    }
+    free(deck);
+}
+
+void bj_free_player(struct bj_player *player, struct bj_card_deck *deck) {
+    if(player->count) {
+        struct bj_card *card, *next_card;
+        for(card = player->cards; card; card = next_card) {
+            next_card = card->next;
+            if(deck) {
+                card->next = deck->cards;
+                deck->cards->prev = card;
+                deck->cards = card;
+                deck->count++;
+            } else
+                free(card);
+        }
+    }
+    if(player->prev)
+        player->prev->next = player->next;
+    if(player->next)
+        player->next->prev = player->prev;
+    free(player);
+}
+
+void bj_free_game(struct bj_game *game) {
+    struct bj_player *player, *next_player;
+    for(player = game->player; player; player = next_player) {
+        next_player = player->next;
+        bj_free_player(player, NULL);
+    }
+    if(game->deck)
+        bj_free_deck(game->deck);
+    if(game->timer)
+        timeq_del(game->timer);
+    struct bj_game *cgame, *pgame = NULL;
+    for(cgame = bj_active_games; cgame; cgame = cgame->next) {
+        if(cgame == game) {
+            if(pgame)
+                pgame->next = game->next;
+            else
+                bj_active_games = game->next;
+            break;
+        } else
+            pgame = cgame;
+    }
+    free(game);
+}
+
+struct bj_player *bj_get_next_player(struct bj_game *game) {
+    struct bj_player *player = game->active_player;
+    player = player->next;
+    return player;
+}
+
+int bj_get_player_card_points(struct bj_player *player) {
+    int i, points = 0, ace_count = 0;
+    struct bj_card *card;
+    for(card = player->cards; card; card = card->next) {
+        if(card->card == BJ_CARD_ACE) {
+            ace_count++; //add these points later ;)
+            continue;
+        }
+        i = 0;
+        while(bj_card_types[i].name) {
+            if(bj_card_types[i].type == card->card) {
+                points += bj_card_types[i].points;
+                break;
+            }
+            i++;
+        }
+    }
+    while((ace_count--) > 0) {
+        if(points <= 10)
+            points += 11;
+        else
+            points += 1;
+    }
+    return points;
+}
+
+static void bj_print_player_cards(struct bj_player *player, char *cards_buf) {
+    struct bj_card *card;
+    int cards_bufpos = 0;
+    cards_buf[0] = '\0';
+    for(card = player->cards; card; card = card->next) {
+        int cardcolor = 1;
+        char *cardchar = "";
+        int i;
+        for(i = 0; i < BJ_CTYPE_COUNT; i++) {
+            if(bj_ctype[i] == card->type) {
+                cardcolor = bj_irc_colors[i];
+                cardchar = bj_color_chars[i];
+                break;
+            }
+        }
+        i = 0;
+        while(bj_card_types[i].name) {
+            if(bj_card_types[i].type == card->card) {
+                cards_bufpos += sprintf(cards_buf + cards_bufpos, "%s[\003%d%s%s\003]", (cards_bufpos ? " " : ""), cardcolor, cardchar, bj_card_types[i].name);
+                break;
+            }
+            i++;
+        }
+    }
+}
+
+void bj_show_player_cards(struct bj_game *game, struct bj_player *player) {
+    char cards_buf[MAXLEN];
+    bj_print_player_cards(player, cards_buf);
+    int card_points = bj_get_player_card_points(player);
+    reply(game->textbot, player->chanuser->user, "NF_BJ_YOUR_CARDS", card_points, cards_buf);
+}
+
+TIMEQ_CALLBACK(bj_game_wait_timeout) {
+    struct bj_game *game = data;
+    game->timer = NULL;
+    if(game->players == 1) {
+        bj_reply(game, NULL, "NF_BJ_LESS_PLAYERS");
+        bj_free_game(game);
+        return;
+    }
+    game->deck = bj_shuffle_deck();
+    bj_reply(game, NULL, "NF_BJ_START");
+    game->state = BJ_STATE_RUNNING;
+    game->active_player = game->player; //active player
+    bj_reply(game, NULL, "NF_BJ_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+}
+
+TIMEQ_CALLBACK(bj_player_timeout) {
+    struct bj_game *game = data;
+    game->timer = NULL;
+    //player timeout (next player)
+    struct bj_player *player = game->active_player;
+    bj_reply(game, NULL, "NF_BJ_USER_TIMEOUT", player->chanuser->user->nick);
+    bj_action_next_player(game, player);
+}
+
+void bj_action_take_card(struct bj_game *game, struct bj_player *player) {
+    int points = bj_get_player_card_points(player);
+    if(points > 21) {
+        reply(game->textbot, player->chanuser->user, "NF_BJ_POINTS_EXCEEDED");
+        return;
+    }
+    if(game->timer) {
+        timeq_del(game->timer);
+        game->timer = NULL;
+    }
+    struct bj_card *card = bj_get_card(game->deck);
+    if(!card) {
+        bj_free_deck(game->deck);
+        game->deck = bj_shuffle_deck();
+        card = bj_get_card(game->deck);
+    }
+    struct bj_card *last_card;
+    for(last_card = player->cards; last_card; last_card = last_card->next) {
+        if(last_card->next == NULL)
+            break;
+    }
+    card->prev = last_card;
+    if(last_card)
+        last_card->next = card;
+    else
+        player->cards = card;
+    player->count++;
+    char cardbuf[16];
+    int cardcolor = 1;
+    char *cardchar = "";
+    int i, cardpoints = 0;
+    cardbuf[0] = '\0';
+    for(i = 0; i < BJ_CTYPE_COUNT; i++) {
+        if(bj_ctype[i] == card->type) {
+            cardcolor = bj_irc_colors[i];
+            cardchar = bj_color_chars[i];
+            break;
+        }
+    }
+    i = 0;
+    while(bj_card_types[i].name) {
+        if(bj_card_types[i].type == card->card) {
+            sprintf(cardbuf, "[\003%d%s%s\003]", cardcolor, cardchar, bj_card_types[i].name);
+            cardpoints = bj_card_types[i].points;
+            if(bj_card_types[i].type == BJ_CARD_ACE && points > 10)
+                cardpoints = 1;
+            break;
+        }
+        i++;
+    }
+    reply(game->textbot, player->chanuser->user, "NF_BJ_TAKE", cardbuf, cardpoints);
+    bj_show_player_cards(game, player);
+    points = bj_get_player_card_points(player);
+    if(points > 21)
+        reply(game->textbot, player->chanuser->user, "NF_BJ_POINTS_EXCEEDED");
+    game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+}
+
+void bj_action_next_player(struct bj_game *game, struct bj_player *player) {
+    if(game->timer) {
+        timeq_del(game->timer);
+        game->timer = NULL;
+    }
+    if(player)
+        bj_show_player_cards(game, player);
+    game->active_player = bj_get_next_player(game);
+    if(game->active_player) {
+        bj_reply(game, NULL, "NF_BJ_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+        game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+    } else {
+        bj_game_end(game);
+        bj_free_game(game);
+    }
+}
+
+void bj_event_part(struct ChanUser *chanuser) {
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(chanuser->chan == game->channel) {
+            struct bj_player *player;
+            for(player = game->player; player; player = player->next) {
+                if(player->chanuser == chanuser) {
+                    if(game->active_player == player) {
+                        if(bj_get_next_player(game) == NULL) {
+                            bj_game_end(game);
+                            bj_free_game(game);
+                            return;
+                        } else
+                            bj_action_next_player(game, NULL);
+                    }
+                    game->players--;
+                    bj_free_player(player, game->deck);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+void bj_event_freechan(struct ChanNode *chan) {
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(game->channel == chan) {
+            bj_free_game(game);
+            return;
+        }
+    }
+}
+
+static int bj_highscore_sort(const void *a, const void *b) {
+    const struct bj_player *player_a = *((struct bj_player * const *) a);
+    const struct bj_player *player_b = *((struct bj_player * const *) b); 
+    int points_a, points_b;
+    if(player_a->count > 21)
+        points_a = player_a->count * -1;
+    else
+        points_a = player_a->count;
+    if(player_b->count > 21)
+        points_b = player_b->count * -1;
+    else
+        points_b = player_b->count;
+    return points_b - points_a;
+}
+
+void bj_game_end(struct bj_game *game) {
+    bj_reply(game, NULL, "NF_BJ_GAME_FINISHED");
+    struct Table *table;
+    table = table_init(4, game->players+1, 0);
+    char *content[4];
+    content[0] = get_language_string(NULL, "NF_BJ_RANK");
+    content[1] = get_language_string(NULL, "NF_BJ_NAME");
+    content[2] = get_language_string(NULL, "NF_BJ_POINTS");
+    content[3] = get_language_string(NULL, "NF_BJ_CARDS");
+    table_add(table, content);
+    //sort users
+    struct bj_player *players[game->players];
+    struct bj_player *player;
+    int i = 0;
+    for(player = game->player; player; player = player->next) {
+        player->count = bj_get_player_card_points(player);
+        players[i++] = player;
+    }
+    qsort(players, game->players, sizeof(struct bj_player *), bj_highscore_sort);
+    char rankbuf[12];
+    char pointbuf[12];
+    char cardsbuf[MAXLEN];
+    for(i = 0; i < game->players; i++) {
+        player = players[i];
+        sprintf(rankbuf, "#%d", i+1);
+        content[0] = rankbuf;
+        content[1] = player->chanuser->user->nick;
+        sprintf(pointbuf, "%d", player->count);
+        content[2] = pointbuf;
+        bj_print_player_cards(player, cardsbuf);
+        content[3] = cardsbuf;
+        table_add(table, content);
+    }
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        bj_reply(game, NULL, table_lines[i]);
+    }
+    table_free(table);
+}
diff --git a/src/modules/NeonFun.mod/game_blackjack.h b/src/modules/NeonFun.mod/game_blackjack.h
new file mode 100644 (file)
index 0000000..0e643ae
--- /dev/null
@@ -0,0 +1,102 @@
+/* game_blackjack.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _game_blackjack_h
+#define _game_blackjack_h
+#include "../../timeq.h"
+
+#define BJ_STATE_WAITING 1
+#define BJ_STATE_RUNNING 2
+
+#define BJ_CTYPE_HEARTS   0
+#define BJ_CTYPE_SPADES   1
+#define BJ_CTYPE_DIAMONDS 2
+#define BJ_CTYPE_CLUBS    3
+
+#define BJ_CTYPE_COUNT    4
+
+#define BJ_CARD_NUMBER_2  0
+#define BJ_CARD_NUMBER_3  1
+#define BJ_CARD_NUMBER_4  2
+#define BJ_CARD_NUMBER_5  3
+#define BJ_CARD_NUMBER_6  4
+#define BJ_CARD_NUMBER_7  5
+#define BJ_CARD_NUMBER_8  6
+#define BJ_CARD_NUMBER_9  7
+#define BJ_CARD_NUMBER_10 8
+#define BJ_CARD_JACK      9
+#define BJ_CARD_QUEEN     10
+#define BJ_CARD_KING      11
+#define BJ_CARD_ACE       12
+
+struct UserNode;
+struct ChanNode;
+struct ChanUser;
+struct ClientSocket;
+extern struct bj_game *bj_active_games;
+
+struct bj_card {
+    unsigned char type;
+    unsigned char card;
+    struct bj_card *prev, *next;
+};
+
+struct bj_card_deck {
+    struct bj_card *cards;
+    int count;
+};
+
+struct bj_player {
+    struct ChanUser *chanuser;
+    struct bj_card *cards;
+    int count;
+    struct bj_player *prev, *next;
+};
+
+struct bj_game {
+    struct ChanNode *channel;
+    struct ClientSocket *textbot;
+    int state : 3;
+    struct bj_card_deck *deck;
+    struct bj_player *player;
+    struct bj_player *active_player;
+    int players;
+    
+    struct timeq_entry *timer;
+    struct bj_game *next;
+};
+
+struct bj_card_deck *bj_shuffle_deck();
+struct bj_card *bj_get_card(struct bj_card_deck *deck);
+void bj_free_deck(struct bj_card_deck *deck);
+void bj_free_player(struct bj_player *player, struct bj_card_deck *deck);
+void bj_free_game(struct bj_game *game);
+struct bj_player *bj_get_next_player(struct bj_game *game);
+int bj_get_player_card_points(struct bj_player *player);
+void bj_show_player_cards(struct bj_game *game, struct bj_player *player);
+
+TIMEQ_CALLBACK(bj_game_wait_timeout);
+TIMEQ_CALLBACK(bj_player_timeout);
+
+void bj_action_take_card(struct bj_game *game, struct bj_player *player);
+void bj_action_next_player(struct bj_game *game, struct bj_player *player);
+
+void bj_event_part(struct ChanUser *chanuser);
+void bj_event_freechan(struct ChanNode *chan);
+
+void bj_game_end(struct bj_game *game);
+
+#endif
diff --git a/src/modules/NeonFun.mod/game_uno.c b/src/modules/NeonFun.mod/game_uno.c
new file mode 100644 (file)
index 0000000..2e67eda
--- /dev/null
@@ -0,0 +1,668 @@
+/* game_uno.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "game_uno.h"
+#include "bot_NeonFun.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../UserNode.h"
+#include "../../ChanUser.h"
+#include "../../tools.h"
+#include "../botid.h"
+
+struct uno_game *uno_active_games = NULL;
+
+#define UNO_COLOR_COUNT 4
+static int uno_colors[] =        {UNO_COLOR_RED, UNO_COLOR_BLUE, UNO_COLOR_GREEN, UNO_COLOR_YELLOW};
+static int uno_irc_colors[] =    {4,             12,             3,               7};
+static char *uno_color_chars[] = {"R",           "B",            "G",             "Y"};
+
+static const struct {
+    const char *name;
+    unsigned char type;
+} uno_card_types[] = {
+    {"0", UNO_CARD_NUMBER_0},
+    {"1", UNO_CARD_NUMBER_1},
+    {"2", UNO_CARD_NUMBER_2},
+    {"3", UNO_CARD_NUMBER_3},
+    {"4", UNO_CARD_NUMBER_4},
+    {"5", UNO_CARD_NUMBER_5},
+    {"6", UNO_CARD_NUMBER_6},
+    {"7", UNO_CARD_NUMBER_7},
+    {"8", UNO_CARD_NUMBER_8},
+    {"9", UNO_CARD_NUMBER_9},
+    {"X", UNO_CARD_SKIP},
+    {"><", UNO_CARD_DIRECTION},
+    {"+2", UNO_CARD_ADD_2},
+    {"+4", UNO_CARD_ADD_4},
+    {"COLOR", UNO_CARD_COLOR},
+    {NULL, 0}
+};
+
+struct uno_card_deck *uno_shuffle_deck() {
+    struct uno_card *card, *last_card = NULL;
+    int card_count = 0;
+    #define ADD_CARD(ccolor,ctype) \
+    card = malloc(sizeof(*card)); \
+    card->color = ccolor; \
+    card->card = ctype; \
+    card->prev = NULL; \
+    card->next = last_card; \
+    if(last_card) \
+        last_card->prev = card; \
+    last_card = card; \
+    card_count++;
+    int colorcount = UNO_COLOR_COUNT;
+    for(colorcount--; colorcount >= 0; colorcount--) {
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_0);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_0);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_1);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_1);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_2);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_2);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_3);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_3);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_4);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_4);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_5);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_5);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_6);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_6);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_7);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_7);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_8);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_8);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_9);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_NUMBER_9);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_SKIP);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_SKIP);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_DIRECTION);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_DIRECTION);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_ADD_2);
+        ADD_CARD(uno_colors[colorcount], UNO_CARD_ADD_2);
+    }
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_ADD_4);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_ADD_4);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_ADD_4);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_ADD_4);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_COLOR);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_COLOR);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_COLOR);
+    ADD_CARD(UNO_COLOR_BLACK, UNO_CARD_COLOR);
+    #undef ADD_CARD
+    struct uno_card_deck *deck = malloc(sizeof(*deck));
+    deck->cards = last_card;
+    deck->count = card_count;
+    return deck;
+}
+
+struct uno_card *uno_get_card(struct uno_card_deck *deck) {
+    if(!deck->count) return NULL;
+    int card_id = (rand() % deck->count);
+    int i = 0;
+    struct uno_card *card;
+    for(card = deck->cards; card; card = card->next) {
+        if(i == card_id) {
+            if(card->prev)
+                card->prev->next = card->next;
+            else
+                deck->cards = card->next;
+            if(card->next)
+                card->next->prev = card->prev;
+            deck->count--;
+            card->next = NULL;
+            card->prev = NULL;
+            return card;
+        }
+        i++;
+    }
+    return NULL;
+}
+
+void uno_free_deck(struct uno_card_deck *deck) {
+    struct uno_card *card, *next_card;
+    if(deck->count) {
+        for(card = deck->cards; card; card = next_card) {
+            next_card = card->next;
+            free(card);
+        }
+    }
+    free(deck);
+}
+
+void uno_free_player(struct uno_player *player, struct uno_card_deck *deck) {
+    if(player->count) {
+        struct uno_card *card, *next_card;
+        for(card = player->cards; card; card = next_card) {
+            next_card = card->next;
+            if(deck) {
+                card->next = deck->cards;
+                deck->cards->prev = card;
+                deck->cards = card;
+                deck->count++;
+            } else
+                free(card);
+        }
+    }
+    if(player->prev)
+        player->prev->next = player->next;
+    if(player->next)
+        player->next->prev = player->prev;
+    free(player);
+}
+
+void uno_free_topcard(struct uno_card *card) {
+    struct uno_card *next_card;
+    for(; card; card = next_card) {
+        next_card = card->prev;
+        free(card);
+    }
+}
+
+void uno_free_game(struct uno_game *game) {
+    struct uno_player *player, *next_player;
+    for(player = game->player; player; player = next_player) {
+        next_player = player->next;
+        uno_free_player(player, NULL);
+    }
+    for(player = game->winner; player; player = next_player) {
+        next_player = player->next;
+        uno_free_player(player, NULL);
+    }
+    if(game->deck)
+        uno_free_deck(game->deck);
+    if(game->top_card)
+        uno_free_topcard(game->top_card);
+    if(game->timer)
+        timeq_del(game->timer);
+    struct uno_game *cgame, *pgame = NULL;
+    for(cgame = uno_active_games; cgame; cgame = cgame->next) {
+        if(cgame == game) {
+            if(pgame)
+                pgame->next = game->next;
+            else
+                uno_active_games = game->next;
+            break;
+        } else
+            pgame = cgame;
+    }
+    free(game);
+}
+
+struct uno_player *uno_get_next_player(struct uno_game *game) {
+    struct uno_player *player = game->active_player;
+    if(!game->reverse_direction) {
+        player = player->next;
+        if(!player)
+            player = game->player;
+    } else {
+        player = player->prev;
+        if(!player) {
+            for(player = game->player; player->next; player = player->next) {
+                //loop to the last player
+            }
+        }
+    }
+    return player;
+}
+
+void uno_show_player_cards(struct uno_game *game, struct uno_player *player) {
+    struct uno_card *card;
+    char cards_buf[MAXLEN];
+    int cards_bufpos = 0;
+    for(card = player->cards; card; card = card->next) {
+        int cardcolor = 1;
+        char *cardchar = "";
+        int i;
+        for(i = 0; i < UNO_COLOR_COUNT; i++) {
+            if(uno_colors[i] == card->color) {
+                cardcolor = uno_irc_colors[i];
+                cardchar = uno_color_chars[i];
+                break;
+            }
+        }
+        i = 0;
+        while(uno_card_types[i].name) {
+            if(uno_card_types[i].type == card->card) {
+                cards_bufpos += sprintf(cards_buf + cards_bufpos, "%s[\003%d%s%s\003]", (cards_bufpos ? " " : ""), cardcolor, cardchar, uno_card_types[i].name);
+                break;
+            }
+            i++;
+        }
+    }
+    reply(game->textbot, player->chanuser->user, "NF_UNO_YOUR_CARDS", cards_buf);
+}
+
+void uno_show_top_card(struct uno_game *game) {
+    struct uno_card *card = game->top_card;
+    char card_buf[50];
+    int cardcolor = 1;
+    char *cardchar = "";
+    int i;
+    for(i = 0; i < UNO_COLOR_COUNT; i++) {
+        if(uno_colors[i] == card->color) {
+            cardcolor = uno_irc_colors[i];
+            cardchar = uno_color_chars[i];
+            break;
+        }
+    }
+    i = 0;
+    while(uno_card_types[i].name) {
+        if(uno_card_types[i].type == card->card) {
+            if(card->card == UNO_CARD_ADD_4 || card->card == UNO_CARD_COLOR)
+                cardchar = "";
+            sprintf(card_buf, "[\003%d%s%s\003]", cardcolor, cardchar, uno_card_types[i].name);
+            break;
+        }
+        i++;
+    }
+    uno_reply(game, NULL, "NF_UNO_TOP_CARD", card_buf);
+}
+
+TIMEQ_CALLBACK(uno_game_wait_timeout) {
+    struct uno_game *game = data;
+    game->timer = NULL;
+    if(game->players == 1) {
+        uno_reply(game, NULL, "NF_UNO_LESS_PLAYERS");
+        uno_free_game(game);
+        return;
+    }
+    game->deck = uno_shuffle_deck();
+    uno_reply(game, NULL, "NF_UNO_START");
+    struct uno_player *player;
+    for(player = game->player; player; player = player->next) {
+        //give 7 cards
+        int i;
+        for(i = 0; i < 7; i++) {
+            if(!game->deck->count)
+                game->deck = uno_shuffle_deck();
+            struct uno_card *card = uno_get_card(game->deck);
+            if(!card) {
+                uno_reply(game, NULL, "NF_UNO_ERROR", 1);
+                uno_free_game(game);
+                return;
+            }
+            card->next = player->cards;
+            if(player->cards)
+                player->cards->prev = card;
+            player->cards = card;
+            player->count++;
+        }
+        uno_show_player_cards(game, player);
+        if((player->chanuser->user->flags & USERFLAG_ISAUTHED)) {
+            char *tmp;
+            int game_count = ((tmp = getSetting(player->chanuser->user, game->channel, "uno_games")) ? atoi(tmp) : 0);
+            int total_game_count = ((tmp = getSetting(player->chanuser->user, NULL, "uno_games")) ? atoi(tmp) : 0);
+            game_count++;
+            total_game_count++;
+            char buf[10];
+            sprintf(buf, "%d", game_count);
+            setSetting(player->chanuser->user, game->channel, "uno_games", buf);
+            sprintf(buf, "%d", total_game_count);
+            setSetting(player->chanuser->user, NULL, "uno_games", buf);
+        }
+    }
+    if(!game->deck->count)
+        game->deck = uno_shuffle_deck();
+    struct uno_card *card = uno_get_card(game->deck);
+    if(!card) {
+        uno_reply(game, NULL, "NF_UNO_ERROR", 1);
+        uno_free_game(game);
+        return;
+    }
+    game->top_card = card;
+    game->state = UNO_STATE_RUNNING;
+    uno_show_top_card(game);
+    game->active_player = game->player; //active player
+    uno_reply(game, NULL, "NF_UNO_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    game->timer = timeq_add(40, module_id, uno_player_timeout, game);
+}
+
+TIMEQ_CALLBACK(uno_player_timeout) {
+    struct uno_game *game = data;
+    game->timer = NULL;
+    //player timeout (take another card)
+    struct uno_player *player = game->active_player, *next_player = uno_get_next_player(game);
+    //add a card to the players deck
+    int ccount;
+    if(game->take_cards_pending) {
+        //count cards to take
+        struct uno_card *card;
+        ccount = 0;
+        for(card = game->top_card; card; card = card->prev) {
+            if(card->card == UNO_CARD_ADD_2)
+                ccount += 2;
+            else if(card->card == UNO_CARD_ADD_4)
+                ccount += 4;
+        }
+    } else
+        ccount = 1;
+    int i;
+    for(i = 0; i < ccount; i++) {
+        if(!game->deck->count)
+            game->deck = uno_shuffle_deck();
+        struct uno_card *card = uno_get_card(game->deck);
+        if(!card) {
+            uno_reply(game, NULL, "NF_UNO_ERROR", 2);
+            uno_free_game(game);
+            return;
+        }
+        card->next = player->cards;
+        if(player->cards)
+            player->cards->prev = card;
+        player->cards = card;
+        player->count++;
+    }
+    player->timeout = 1;
+    game->active_player = next_player;
+    if(game->take_cards_pending) {
+        game->take_cards_pending = 0;
+        uno_reply(game, NULL, "NF_UNO_USER_TOOK_CARDS", player->chanuser->user->nick, ccount);
+    } else
+        uno_reply(game, NULL, "NF_UNO_USER_TOOK_CARD", player->chanuser->user->nick);
+    struct uno_player *cplayer;
+    for(cplayer = game->player; cplayer; cplayer = cplayer->next) {
+        if(!cplayer->timeout)
+            break;
+    }
+    if(!cplayer) {
+        uno_reply(game, NULL, "NF_UNO_TIMEOUT");
+        uno_free_game(game);
+        return;
+    }
+    uno_show_player_cards(game, player);
+    uno_show_top_card(game);
+    uno_reply(game, NULL, "NF_UNO_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    uno_show_player_cards(game, game->active_player);
+    game->timer = timeq_add(40, module_id, uno_player_timeout, game);
+}
+
+void uno_action_take_card(struct uno_game *game, struct uno_player *player) {
+    timeq_del(game->timer);
+    game->timer = NULL;
+    struct uno_player *next_player = uno_get_next_player(game);
+    //add a card to the players deck
+    int ccount;
+    if(game->take_cards_pending) {
+        //count cards to take
+        struct uno_card *card;
+        ccount = 0;
+        for(card = game->top_card; card; card = card->prev) {
+            if(card->card == UNO_CARD_ADD_2)
+                ccount += 2;
+            else if(card->card == UNO_CARD_ADD_4)
+                ccount += 4;
+        }
+    } else
+        ccount = 1;
+    int i;
+    for(i = 0; i < ccount; i++) {
+        if(!game->deck->count)
+            game->deck = uno_shuffle_deck(game->deck);
+        struct uno_card *card = uno_get_card(game->deck);
+        if(!card) {
+            uno_reply(game, NULL, "NF_UNO_ERROR", 2);
+            uno_free_game(game);
+            return;
+        }
+        card->next = player->cards;
+        if(player->cards)
+            player->cards->prev = card;
+        player->cards = card;
+        player->count++;
+    }
+    game->active_player = next_player;
+    if(game->take_cards_pending) {
+        game->take_cards_pending = 0;
+        uno_reply(game, NULL, "NF_UNO_USER_TOOK_CARDS", player->chanuser->user->nick, ccount);
+    } else
+        uno_reply(game, NULL, "NF_UNO_USER_TOOK_CARD", player->chanuser->user->nick);
+    uno_show_player_cards(game, player);
+    uno_show_top_card(game);
+    uno_reply(game, NULL, "NF_UNO_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    uno_show_player_cards(game, game->active_player);
+    game->timer = timeq_add(40, module_id, uno_player_timeout, game);
+}
+
+
+
+struct uno_card *uno_parse_card(struct uno_game *game, struct uno_player *player, char *arg1) {
+    char *b = arg1;
+    int a = 0;
+    while(*b) {
+        switch (*b) {
+        case '\003':
+            if(isdigit(b[1]))
+                b[1] = '\002';
+            if(isdigit(b[2]))
+                b[2] = '\002';
+        case '[':
+        case '\002':
+        case ']':
+            *b = '\0';
+            break;
+        default:
+            if(!a) {
+                a = 1;
+                arg1 = b;
+            }
+        }
+        b++;
+    }
+    unsigned char ctype = 255, ccolor = 0;
+    int i = 0, j;
+    char tmpbuf[50];
+    while(uno_card_types[i].name) {
+        if(!stricmp(uno_card_types[i].name, arg1)) {
+            ctype = uno_card_types[i].type;
+            break;
+        }
+        for(j = 0; j < UNO_COLOR_COUNT; j++) {
+            sprintf(tmpbuf, "%s%s", uno_color_chars[j], uno_card_types[i].name);
+            if(!stricmp(tmpbuf, arg1)) {
+                ccolor = uno_colors[j];
+                ctype = uno_card_types[i].type;
+                break;
+            }
+        }
+        i++;
+    }
+    if(ctype == 255) {
+        reply(game->textbot, player->chanuser->user, "NF_UNO_UNKNOWN_CARD", arg1);
+        return NULL;
+    }
+    struct uno_card *card;
+    for(card = player->cards; card; card = card->next) {
+        if(card->card == ctype && card->color == ccolor)
+            break;
+    }
+    if(!card) {
+        reply(game->textbot, player->chanuser->user, "NF_UNO_CARD_NOT_IN_DECK");
+        return NULL;
+    }
+    return card;
+}
+
+int uno_check_card_valid(struct uno_game *game, struct uno_card *card) {
+    if(game->take_cards_pending && card->card != game->top_card->card)
+        return 1;
+    if(card->color == UNO_COLOR_BLACK || game->top_card->color == UNO_COLOR_BLACK)
+        return 0;
+    if(card->color != game->top_card->color && card->card != game->top_card->card)
+        return 1;
+    return 0;
+}
+
+void uno_play_card(struct uno_game *game, struct uno_player *player, struct uno_card *card) {
+    timeq_del(game->timer);
+    game->timer = NULL;
+    if(card->prev)
+        card->prev->next = card->next;
+    else
+        player->cards = card->next;
+    if(card->next)
+        card->next->prev = card->prev;
+    player->count--;
+    if(!game->take_cards_pending) {
+        uno_free_topcard(game->top_card);
+        card->prev = NULL;
+    } else
+        card->prev = game->top_card;
+    card->next = NULL;
+    game->top_card = card;
+    uno_show_top_card(game);
+    if(player->count == 1) {
+        uno_reply(game, NULL, "NF_UNO_ONE_CARD", game->active_player->chanuser->user->nick);
+    } else if(player->count == 0) {
+        uno_reply(game, NULL, "NF_UNO_USER_WIN", game->active_player->chanuser->user->nick);
+        if(player->prev)
+            player->prev->next = player->next;
+        else
+            game->player = player->next;
+        if(player->next)
+            player->next->prev = player->prev;
+        player->next = NULL;
+        player->prev = NULL;
+        struct uno_player *cplayer;
+        int winner_count = 0;
+        if(game->winner) {
+            for(cplayer = game->winner; cplayer->next; cplayer = cplayer->next) {
+                winner_count++;
+            }
+        } else
+            cplayer = NULL;
+        if(cplayer) {
+            cplayer->next = player;
+        } else {
+            game->winner = player;
+            if((player->chanuser->user->flags & USERFLAG_ISAUTHED)) {
+                char *tmp;
+                int win_count = ((tmp = getSetting(player->chanuser->user, game->channel, "uno_win")) ? atoi(tmp) : 0);
+                int total_win_count = ((tmp = getSetting(player->chanuser->user, NULL, "uno_win")) ? atoi(tmp) : 0);
+                win_count++;
+                total_win_count++;
+                char buf[10];
+                sprintf(buf, "%d", win_count);
+                setSetting(player->chanuser->user, game->channel, "uno_win", buf);
+                sprintf(buf, "%d", total_win_count);
+                setSetting(player->chanuser->user, NULL, "uno_win", buf);
+            }
+        }
+        player->prev = cplayer;
+        game->players--;
+        if(game->players <= 1) {
+            //game finished
+            uno_reply(game, NULL, "NF_UNO_GAME_FINISHED");
+            struct Table *table;
+            table = table_init(4, winner_count + 3, 0);
+            char *content[4];
+            content[0] = get_language_string(NULL, "NF_UNO_RANK");
+            content[1] = get_language_string(NULL, "NF_UNO_NAME");
+            content[2] = get_language_string(NULL, "NF_UNO_WON_GAMES");
+            content[3] = get_language_string(NULL, "NF_UNO_TOTAL_WON_GAMES");
+            table_add(table, content);
+            winner_count = 1;
+            char rank_buf[20], won_buf[50], total_won_buf[50];
+            char *tmp, *tmp2;
+            for(cplayer = game->winner; cplayer; cplayer = cplayer->next) {
+                sprintf(rank_buf, "%d", winner_count++);
+                content[0] = rank_buf;
+                content[1] = cplayer->chanuser->user->nick;
+                if((cplayer->chanuser->user->flags & USERFLAG_ISAUTHED)) {
+                    sprintf(won_buf, "%d/%d", ((tmp = getSetting(cplayer->chanuser->user, game->channel, "uno_win")) ? atoi(tmp) : 0), ((tmp2 = getSetting(cplayer->chanuser->user, game->channel, "uno_games")) ? atoi(tmp2) : 0));
+                    content[2] = won_buf;
+                    sprintf(total_won_buf, "%d/%d", ((tmp = getSetting(cplayer->chanuser->user, NULL, "uno_win")) ? atoi(tmp) : 0), ((tmp2 = getSetting(cplayer->chanuser->user, NULL, "uno_games")) ? atoi(tmp2) : 0));
+                    content[3] = total_won_buf;
+                } else {
+                    content[2] = "?";
+                    content[3] = "?";
+                }
+                table_add(table, content);
+            }
+            cplayer = game->player;
+            sprintf(rank_buf, "%d", winner_count++);
+            content[0] = rank_buf;
+            content[1] = cplayer->chanuser->user->nick;
+            if((cplayer->chanuser->user->flags & USERFLAG_ISAUTHED)) {
+                sprintf(won_buf, "%d/%d", ((tmp = getSetting(cplayer->chanuser->user, game->channel, "uno_win")) ? atoi(tmp) : 0), ((tmp2 = getSetting(cplayer->chanuser->user, game->channel, "uno_games")) ? atoi(tmp2) : 0));
+                content[2] = won_buf;
+                sprintf(total_won_buf, "%d/%d", ((tmp = getSetting(cplayer->chanuser->user, NULL, "uno_win")) ? atoi(tmp) : 0), ((tmp2 = getSetting(cplayer->chanuser->user, NULL, "uno_games")) ? atoi(tmp2) : 0));
+                content[3] = total_won_buf;
+            } else {
+                content[2] = "?";
+                content[3] = "?";
+            }
+            table_add(table, content);
+            char **table_lines = table_end(table);
+            int i;
+            for(i = 0; i < table->entrys; i++) {
+                uno_reply(game, NULL, table_lines[i]);
+            }
+            table_free(table);
+            uno_free_game(game);
+            return;
+        }
+    }
+    if(card->card == UNO_CARD_DIRECTION)
+        game->reverse_direction = (game->reverse_direction ? 0 : 1);
+    struct uno_player *next_player = uno_get_next_player(game);
+    game->active_player = next_player;
+    if(card->card == UNO_CARD_SKIP) {
+        uno_reply(game, NULL, "NF_UNO_USER_SKIP", game->active_player->chanuser->user->nick);
+        next_player = uno_get_next_player(game);
+        game->active_player = next_player;
+    }
+    if(card->card == UNO_CARD_ADD_2 || card->card == UNO_CARD_ADD_4) {
+        int ccount = 0;
+        for(card = game->top_card; card; card = card->prev) {
+            if(card->card == UNO_CARD_ADD_2)
+                ccount += 2;
+            else if(card->card == UNO_CARD_ADD_4)
+                ccount += 4;
+        }
+        uno_reply(game, NULL, "NF_UNO_ADD_CARD", game->active_player->chanuser->user->nick, ccount);
+        game->take_cards_pending = 1;
+    }
+    uno_reply(game, NULL, "NF_UNO_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    uno_show_player_cards(game, game->active_player);
+    game->timer = timeq_add(40, module_id, uno_player_timeout, game);
+}
+
+void uno_event_part(struct ChanUser *chanuser) {
+    struct uno_game *game;
+    for(game = uno_active_games; game; game = game->next) {
+        if(chanuser->chan == game->channel) {
+            struct uno_player *player;
+            for(player = game->player; player; player = player->next) {
+                if(player->chanuser == chanuser) {
+                    uno_free_player(player, game->deck);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+void uno_event_freechan(struct ChanNode *chan) {
+    struct uno_game *game;
+    for(game = uno_active_games; game; game = game->next) {
+        if(game->channel == chan) {
+            uno_free_game(game);
+            return;
+        }
+    }
+}
diff --git a/src/modules/NeonFun.mod/game_uno.h b/src/modules/NeonFun.mod/game_uno.h
new file mode 100644 (file)
index 0000000..04a02a7
--- /dev/null
@@ -0,0 +1,111 @@
+/* game_uno.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _game_uno_h
+#define _game_uno_h
+#include "../../timeq.h"
+
+#define UNO_STATE_WAITING 1
+#define UNO_STATE_RUNNING 2
+
+#define UNO_COLOR_BLACK  0
+#define UNO_COLOR_RED    1
+#define UNO_COLOR_BLUE   2
+#define UNO_COLOR_GREEN  3
+#define UNO_COLOR_YELLOW 4
+
+#define UNO_CARD_NUMBER_0  0
+#define UNO_CARD_NUMBER_1  1
+#define UNO_CARD_NUMBER_2  2
+#define UNO_CARD_NUMBER_3  3
+#define UNO_CARD_NUMBER_4  4
+#define UNO_CARD_NUMBER_5  5
+#define UNO_CARD_NUMBER_6  6
+#define UNO_CARD_NUMBER_7  7
+#define UNO_CARD_NUMBER_8  8
+#define UNO_CARD_NUMBER_9  9
+#define UNO_CARD_SKIP      10
+#define UNO_CARD_DIRECTION 11
+#define UNO_CARD_ADD_2     12
+#define UNO_CARD_ADD_4     13
+#define UNO_CARD_COLOR     14
+
+#define UNO_IS_NUMBER_CARD(ccard) (ccard->card <= 9)
+
+struct UserNode;
+struct ChanNode;
+struct ChanUser;
+struct ClientSocket;
+extern struct uno_game *uno_active_games;
+
+struct uno_card {
+    unsigned char color;
+    unsigned char card;
+    struct uno_card *prev, *next;
+};
+
+struct uno_card_deck {
+    struct uno_card *cards;
+    int count;
+};
+
+struct uno_player {
+    struct ChanUser *chanuser;
+    struct uno_card *cards;
+    int count;
+    int timeout;
+    struct uno_player *prev, *next;
+};
+
+struct uno_game {
+    struct ChanNode *channel;
+    struct ClientSocket *textbot;
+    int state : 3;
+    int reverse_direction : 1;
+    int take_cards_pending : 1;
+    struct uno_card_deck *deck;
+    struct uno_card *top_card;
+    struct uno_player *player;
+    struct uno_player *winner;
+    struct uno_player *active_player;
+    int players;
+    
+    struct timeq_entry *timer;
+    struct uno_game *next;
+};
+
+struct uno_card_deck *uno_shuffle_deck();
+struct uno_card *uno_get_card(struct uno_card_deck *deck);
+void uno_free_deck(struct uno_card_deck *deck);
+void uno_free_player(struct uno_player *player, struct uno_card_deck *deck);
+void uno_free_topcard(struct uno_card *card);
+void uno_free_game(struct uno_game *game);
+struct uno_player *uno_get_next_player(struct uno_game *game);
+void uno_show_player_cards(struct uno_game *game, struct uno_player *player);
+void uno_show_top_card(struct uno_game *game);
+
+TIMEQ_CALLBACK(uno_game_wait_timeout);
+TIMEQ_CALLBACK(uno_player_timeout);
+
+void uno_action_take_card(struct uno_game *game, struct uno_player *player);
+struct uno_card *uno_parse_card(struct uno_game *game, struct uno_player *player, char *arg1);
+int uno_check_card_valid(struct uno_game *game, struct uno_card *card);
+void uno_play_card(struct uno_game *game, struct uno_player *player, struct uno_card *card);
+
+void uno_event_part(struct ChanUser *chanuser);
+void uno_event_freechan(struct ChanNode *chan);
+
+#endif
diff --git a/src/modules/NeonFun.mod/module.c b/src/modules/NeonFun.mod/module.c
new file mode 100644 (file)
index 0000000..b6d2e62
--- /dev/null
@@ -0,0 +1,35 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonFun.h"
+#include "cmd_neonfun.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonFun(type);
+    srand(time(NULL));
+}
+
+static void module_stop(int type) {
+    free_NeonFun(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonHelp.mod/NeonHelpHistory.php b/src/modules/NeonHelp.mod/NeonHelpHistory.php
new file mode 100644 (file)
index 0000000..1c05d08
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+$db['host'] = "localhost";
+$db['user'] = "neonserv";
+$db['pass'] = "";
+$db['base'] = "neonserv";
+
+$db['conn'] = @mysql_connect($db['host'], $db['user'], $db['pass']) or die(mysql_error());
+if($db['conn']) {
+    $mysql_connection=TRUE;
+    mysql_select_db($db['base'], $db['conn']) OR $mysql_connection=FALSE;
+}
+
+if(!$mysql_connection)
+    die("no connection to the MySQL Server.");
+
+$id = $_GET['id'];
+$key = $_GET['key'];
+if(!is_numeric($id) || strlen($key) != 32)
+    die("Access denied");
+
+$result = mysql_query("SELECT `nick`, `hand`, `host`, `time`, `status`, `text`, `log`, `supporter`, `users`.`user_user`, MD5(CONCAT(`id`, `host`, `time`, `supporter`)) FROM `helpserv_requests` LEFT JOIN `users` ON `supporter` = `user_id` WHERE `id` = '".mysql_real_escape_string($id)."';");
+if(mysql_num_rows($result) == 0)
+    die("Access denied");
+$row = mysql_fetch_row($result);
+if(strtolower($row[9]) != strtolower($key))
+    die("Access denied");
+
+?>
+<table border="1" width="600">
+    <tr>
+        <td width="150">Nick:</td>
+        <td><?php echo$row[0]; ?></td>
+    </tr>
+    <tr>
+        <td width="150">Hostmask:</td>
+        <td><?php echo$row[2]; ?> (auth: <?php echo$row[1]; ?>)</td>
+    </tr>
+    <tr>
+        <td width="150">Time</td>
+        <td><?php echo date("d.m.Y H:i:s", $row[3]); ?></td>
+    </tr>
+    <tr>
+        <td width="150">State</td>
+        <td>
+        <?php 
+        if($row[4] == 0)
+            echo"pending";
+        else if($row[4] == 1)
+            echo "active (".$row[8].")";
+        else if($row[4] == 2)
+            echo "closed (".($row[7] == 0 ? "*" : $row[8]).")";
+        ?>
+        </td>
+    </tr>
+    <tr>
+        <td colspan="2"><span style="font-size:10pt; font-family:Courier New;"><?php echo str_replace("\n", "<br>", $row[5]); ?></span></td>
+    </tr>
+    <tr>
+        <td colspan="2"><span style="font-size:10pt; font-family:Courier New;"><?php echo str_replace("\n", "<br>", $row[6]); ?></span></td>
+    </tr>
+</table>
\ No newline at end of file
diff --git a/src/modules/NeonHelp.mod/bot_NeonHelp.c b/src/modules/NeonHelp.mod/bot_NeonHelp.c
new file mode 100644 (file)
index 0000000..5d4aecb
--- /dev/null
@@ -0,0 +1,597 @@
+/* bot_NeonHelp.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_NeonHelp.h"
+#include "../../modcmd.h"
+#include "cmd_neonhelp.h"
+#include "../../lang.h"
+#include "../../mysqlConn.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../IRCEvents.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../DBHelper.h"
+#include "../../WHOHandler.h"
+
+#define BOTID NEONHELP_BOTID
+#define BOTALIAS "NeonHelp"
+
+static const struct default_language_entry msgtab[] = {
+    {"NH_NOT_ON_CHAN_1", "You cannot open this request as you are not in %s."}, /* {ARGS: "#test"} */
+    {"NH_NOT_ON_CHAN_2", "You cannot open this request as you are not in %s or %s."}, /* {ARGS: "test", "#test-support"} */
+    {"NH_REQUEST_RECORDED", "Your message has been recorded and assigned request ID#%d A helper should contact you shortly."}, /* {ARGS: 5} */
+    {"NH_REQUEST_OTHERS_0", "There are no other unhandled requests."},
+    {"NH_REQUEST_OTHERS_1", "There is 1 other unhandled request."},
+    {"NH_REQUEST_OTHERS_2", "There are %d other unhandled requests."}, /* {ARGS: 1337} */
+    {"NH_REQUEST_FOOTER_1", "Everything you tell me until you are helped (or you leave %1$s) will be recorded. If you part %1$s, your request will be lost."}, /* {ARGS: "#test"} */
+    {"NH_REQUEST_FOOTER_2", "Everything you tell me until you are helped (or you leave %1$s or %2$s) will be recorded. If you part %1$s or %2$s, your request will be lost."}, /* {ARGS: "#test", "#test-support"} */
+    {"NH_NEW_REQUEST", "New request #%d by %s: %s"}, /* {ARGS: 5, "pk910", "Help, I've fallen and I can't get up!"} */
+    {"NH_NEXT_NONE", "No more requests."},
+    {"NH_NEXT_NOT_FOUND", "No request found."},
+    {"NH_NEXT_HEADER", "$bNext request: #%d %s$b"}, /* {ARGS: 5, "pk910"} */
+    {"NH_NEXT_HELPER", "Your helper for request ID#%d is %s (Current nick: %s)."}, /* {ARGS: 5, "pk910", "Skynet"} */
+    {"NH_NEXT_JOIN", "Please /join %s now."}, /* {ARGS: "#test-support"} */
+    {"NH_DELETED", "Your request ID#%d has been deleted."}, /* {ARGS: 5} */
+    {"NH_DELETED_STAFF", "Request deleted: #%d (%s)"}, /* {ARGS: 5, "pk910"} */
+    {"NH_REMIND_OPEN_REQUESTS_1", "There is %d unhandled request!"}, /* {ARGS: 1} */
+    {"NH_REMIND_OPEN_REQUESTS_2", "There are %d unhandled requests!"}, /* {ARGS: 4} */
+    {"NH_REQUESTS_HEADER_ID", "ID"},
+    {"NH_REQUESTS_HEADER_STATUS", "State"},
+    {"NH_REQUESTS_HEADER_NICK", "Nick"},
+    {"NH_REQUESTS_HEADER_TIME", "Time"},
+    {"NH_REQUESTS_HEADER_STATUS", "State"},
+    {"NH_REQUESTS_HEADER_AUTH", "Auth"},
+    {"NH_REQUESTS_HEADER_MASK", "Mask"},
+    {"NH_REQUESTS_HEADER_REQUEST", "Question"},
+    {"NH_REQUESTS_STATE_ACTIVE", "active"},
+    {"NH_REQUESTS_STATE_PENDING", "pending"},
+    {"NH_REQUESTS_STATE_CLOSED", "closed"},
+    {"NH_REQUESTS_STATE_ERROR", "ERROR"},
+    {"NH_STATS_HEADER_USER", "User"},
+    {"NH_STATS_HEADER_LAST_24H", "last 24h"},
+    {"NH_STATS_HEADER_LAST_7DAY", "last 7d"},
+    {"NH_STATS_HEADER_LAST_30DAY", "last 30d"},
+    {NULL, NULL}
+};
+
+static void neonhelp_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonhelp_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "!");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "!");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "!"));
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_SILENT;
+            client->flags |= SOCKET_FLAG_REQUEST_INVITE | SOCKET_FLAG_REQUEST_OP;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+            //close old, still opened requests
+            printf_mysql_query("UPDATE `helpserv_requests` SET `status` = '2' WHERE `botid` = '%d'", client->clientid);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+static void neonhelp_event_privmsg_async(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, char *message);
+static USERAUTH_CALLBACK(neonhelp_event_privmsg_nick_lookup);
+struct neonhelp_event_privmsg_cache {
+    struct ClientSocket *client;
+    struct UserNode *user, *target;
+    char *message;
+};
+
+static void neonhelp_event_privmsg(struct UserNode *user, struct UserNode *target, char *message) {
+    struct ClientSocket *client;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->user == target) {
+            if(client->botid != BOTID) return;
+            break;
+        }
+    }
+    if(!client) return; //we got the message but we have no client that could receive it???
+    if(user->flags & USERFLAG_ISAUTHED) {
+        neonhelp_event_privmsg_async(client, user, target, message);
+    } else {
+        struct neonhelp_event_privmsg_cache *cache = malloc(sizeof(*cache));
+        if(!cache) return;
+        cache->client = client;
+        cache->user = user;
+        cache->target = target;
+        cache->message = strdup(message);
+        get_userauth(user, module_id, neonhelp_event_privmsg_nick_lookup, cache);
+    }
+}
+
+static USERAUTH_CALLBACK(neonhelp_event_privmsg_nick_lookup) {
+    struct neonhelp_event_privmsg_cache *cache = data;
+    neonhelp_event_privmsg_async(cache->client, cache->user, cache->target, cache->message);
+    free(cache->message);
+    free(cache);
+}
+
+static TIMEQ_CALLBACK(neonhelp_remind_open_requests);
+
+static void neonhelp_event_privmsg_async(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, char *message) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    char setting[128];
+    sprintf(setting, "modules.%s.need_auth", get_module_name(module_id));
+    if(get_int_field(setting) && !(user->flags & USERFLAG_ISAUTHED)) {
+        sprintf(setting, "modules.%s.need_auth_message", get_module_name(module_id));
+        char *reply_message;
+        if(!(reply_message = get_string_field(setting)))
+            reply_message = get_language_string(user, "MODCMD_AUTH_REQUIRED");
+        reply(client, user, "%s", reply_message);
+        return;
+    }
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern`, `helpserv_intern_announce` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        int caccess = 0;
+        int userid;
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+        if(caccess) return; //ignore messages from supporters
+    }
+    //check if the user is in one of the bot's channels
+    struct ChanUser *chanuser;
+    struct ChanNode *chan;
+    for(chanuser = getUserChannels(target, NULL); chanuser; chanuser = getUserChannels(target, chanuser)) {
+        chan = chanuser->chan;
+        if((!stricmp(chan->name, row[0]) || (row[1] && !stricmp(chan->name, row[1]))) && isUserOnChan(user, chan))
+            break;
+    }
+    if(!chanuser) {
+        if(row[1])
+            reply(client, user, "NH_NOT_ON_CHAN_2", row[0], row[1]);
+        else
+            reply(client, user, "NH_NOT_ON_CHAN_1", row[0]);
+        return;
+    }
+    //check if there is already a support request
+    int others = 0;
+    if(client->flags & SOCKET_HAVE_HELPNODE) {
+        struct NeonHelpNode *helpnode;
+        for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+            if(helpnode->user == user) {
+                //simply append the message to the database
+                printf_mysql_query("SELECT `text` FROM `helpserv_requests` WHERE `id` = %d", helpnode->suppid);
+                res = mysql_use();
+                if ((row2 = mysql_fetch_row(res)) != NULL) {
+                    char *old_msg = escape_string(row2[0]);
+                    char *new_msg = escape_string(message);
+                    printf_long_mysql_query(1024 + strlen(old_msg) + strlen(new_msg), "UPDATE `helpserv_requests` SET `text` = '%s\n%s' WHERE `id` = %d", old_msg, new_msg, helpnode->suppid);
+                }
+                return;
+            }
+            others++;
+        }
+    }
+    //add new request
+    struct NeonHelpNode *helpnode = malloc(sizeof(*helpnode));
+    if(!helpnode) return;
+    helpnode->user = user;
+    helpnode->logchan = getChanByName(row[0]);
+    helpnode->status = 0;
+    helpnode->announce = (row[2] && strcmp(row[3], "0") ? 1 : 0);
+    if(helpnode->announce) {
+        char nameBuf[30];
+        sprintf(nameBuf, "neonhelp_%d", client->clientid);
+        if(!timeq_name_exists(nameBuf)) {
+            int *cidptr = malloc(sizeof(int));
+            *cidptr = client->clientid;
+            timeq_add_name(nameBuf, 300, module_id, neonhelp_remind_open_requests, cidptr);
+        }
+    }
+    printf_mysql_query("INSERT INTO `helpserv_requests` (`botid`, `host`, `hand`, `nick`, `status`, `supporter`, `time`, `text`) VALUES ('%d', '%s@%s', '%s', '%s', '0', '-1', UNIX_TIMESTAMP(), '%s')", client->clientid, escape_string(user->ident), escape_string(user->host), ((user->flags & USERFLAG_ISAUTHED) ? escape_string(user->auth) : "*"), escape_string(user->nick), escape_string(message));
+    helpnode->suppid = (int) mysql_insert_id(get_mysql_conn());
+    helpnode->log = NULL;
+    helpnode->next = ((client->flags & SOCKET_HAVE_HELPNODE) ? client->botclass_helpnode : NULL);
+    client->botclass_helpnode = helpnode;
+    client->flags |= SOCKET_HAVE_HELPNODE;
+    //build the user reply...
+    char user_reply[MAXLEN];
+    int user_reply_pos = 0;
+    char reply_buff[MAXLEN];
+    //1st part: NH_REQUEST_RECORDED
+    strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_RECORDED", helpnode->suppid));
+    user_reply_pos += strlen(reply_buff);
+    //2nd part: NH_REQUEST_OTHERS_0 / NH_REQUEST_OTHERS_1 / NH_REQUEST_OTHERS_2
+    user_reply[user_reply_pos++] = ' ';
+    if(others <= 1)
+        strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, (others ? "NH_REQUEST_OTHERS_1" : "NH_REQUEST_OTHERS_0")));
+    else
+        strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_OTHERS_2", others));
+    user_reply_pos += strlen(reply_buff);
+    //3th part: NH_REQUEST_FOOTER_1 / NH_REQUEST_FOOTER_2
+    user_reply[user_reply_pos++] = ' ';
+    if(row[1])
+        strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_FOOTER_2", row[0], row[1]));
+    else
+        strcpy(user_reply + user_reply_pos, build_language_string(user, reply_buff, "NH_REQUEST_FOOTER_1", row[0]));
+    user_reply_pos += strlen(reply_buff);
+    reply(client, user, "%s", user_reply);
+    //sent a message to the internal channel / onotice to supp channel
+    build_language_string(user, reply_buff, "NH_NEW_REQUEST", helpnode->suppid, user->nick, message);
+    if(row[2]) {
+        putsock(client, "PRIVMSG %s :%s", row[2], reply_buff);
+    } else {
+        putsock(client, "NOTICE @%s :%s", row[0], reply_buff);
+    }
+}
+
+static TIMEQ_CALLBACK(neonhelp_remind_open_requests) {
+    int clientid = *((int*)data);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern`, `helpserv_intern_announce` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res)) || !row[2]) {
+        free(data);
+        return;
+    }
+    struct ClientSocket *client;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->clientid == clientid)
+            break;
+    }
+    if(!client) {
+        free(data);
+        return;
+    }
+    //count open requests
+    int requests = 0;
+    struct NeonHelpNode *helpnode;
+    if(client->flags & SOCKET_HAVE_HELPNODE) {
+        for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+            if(helpnode->status == 0) {
+                requests++;
+            }
+        }
+    }
+    if(requests) {
+        char nameBuf[30];
+        sprintf(nameBuf, "neonhelp_%d", client->clientid);
+        if(!timeq_name_exists(nameBuf)) {
+            timeq_add_name(nameBuf, 300, module_id, neonhelp_remind_open_requests, data);
+        }
+        char replybuf[MAXLEN];
+        build_language_string(NULL, replybuf, (requests == 1 ? "NH_REMIND_OPEN_REQUESTS_1" : "NH_REMIND_OPEN_REQUESTS_2"), requests);
+        putsock(client, "PRIVMSG %s :%s", row[2], replybuf);
+    } else
+        free(data);
+}
+
+static void neonhelp_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
+    char logline[MAXLEN];
+    sprintf(logline, "<%s> %s", user->nick, message);
+    struct ClientSocket *client;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->botid == BOTID) {
+            struct NeonHelpNode *helpnode;
+            if(client->flags & SOCKET_HAVE_HELPNODE) {
+                for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+                    if(helpnode->logchan == chan && helpnode->status == 1) {
+                        if(!helpnode->log) {
+                            helpnode->log = calloc(LOGBUFFERLINES, sizeof(char*));
+                            if(!helpnode->log) return;
+                        }
+                        int i;
+                        for(i = 0; i < LOGBUFFERLINES; i++) {
+                            if(!helpnode->log[i]) {
+                                helpnode->log[i] = strdup(logline);
+                                break;
+                            }
+                        }
+                        if(i == LOGBUFFERLINES) {
+                            //write buffer to database
+                            char logbuff[MAXLEN * LOGBUFFERLINES];
+                            int len = 0;
+                            for(i = 0; i < LOGBUFFERLINES; i++) {
+                                len += sprintf(logbuff + len, "%s\n", helpnode->log[i]);
+                                free(helpnode->log[i]);
+                                helpnode->log[i] = NULL;
+                            }
+                            printf_long_mysql_query(1024 + len, "UPDATE `helpserv_requests` SET `log` = CONCAT(`log`, '%s') WHERE `id` = %d", escape_string(logbuff), helpnode->suppid);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+void neonhelp_destroy_support_request(struct ClientSocket *client, struct NeonHelpNode *helpnode, int do_reply) {
+    //write buffer to database
+    char logbuff[MAXLEN * LOGBUFFERLINES];
+    int len = 0;
+    int i;
+    if(helpnode->log) {
+        for(i = 0; i < LOGBUFFERLINES; i++) {
+            if(!helpnode->log[i]) break;
+            len += sprintf(logbuff + len, "%s\n", helpnode->log[i]);
+            free(helpnode->log[i]);
+            helpnode->log[i] = NULL;
+        }
+        free(helpnode->log);
+    } else
+        logbuff[0] = '\0';
+    printf_long_mysql_query(1024 + len, "UPDATE `helpserv_requests` SET `status`='2', `log` = CONCAT(`log`, '%s') WHERE `id` = %d", escape_string(logbuff), helpnode->suppid);
+    if(do_reply) {
+        reply(client, helpnode->user, "NH_DELETED", helpnode->suppid);
+    }
+    free(helpnode);
+}
+
+void neonhelp_invite_active_requests(struct ClientSocket *client, struct ChanNode *support, struct ChanNode *public, struct UserNode *ignore_user) {
+    struct NeonHelpNode *helpnode;
+    if(public && client->flags & SOCKET_HAVE_HELPNODE) {
+        for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+            if(helpnode->status == 1 && helpnode->user != ignore_user && !isUserOnChan(helpnode->user, support)) {
+                putsock(client, "INVITE %s %s", helpnode->user->nick, support->name);
+            }
+        }
+    }
+}
+
+static void neonhelp_event_kick(struct UserNode *user, struct ChanUser *target, char *reason) {
+    struct ClientSocket *client;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct ChanNode *support, *public;
+    int userHasRequest;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->botid == BOTID && isUserOnChan(client->user, target->chan)) {
+            userHasRequest = 0;
+            struct NeonHelpNode *helpnode, *prev_helpnode = NULL;
+            if(client->flags & SOCKET_HAVE_HELPNODE) {
+                for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+                    if(helpnode->user == target->user) {
+                        userHasRequest = 1;
+                        break;
+                    } else
+                        prev_helpnode = helpnode;
+                }
+            }
+            if(!userHasRequest) continue;
+            printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+            res = mysql_use();
+            if (!(row = mysql_fetch_row(res))) continue;
+            support = getChanByName(row[0]);
+            public = (row[1] ? getChanByName(row[1]) : NULL);
+            if(target->chan == support || !((support && isUserOnChan(target->user, support)) || (public && isUserOnChan(target->user, public)))) {
+                if(helpnode->status == 1 && target->chan != support) {
+                    putsock(client, "MODE %s -i", support->name); //clear invite list
+                    if(isModeSet(support->modes, 'i'))
+                        putsock(client, "MODE %s +i", support->name);
+                    neonhelp_invite_active_requests(client, support, public, target->user);
+                }
+                //free the user's support request
+                if(prev_helpnode)
+                    prev_helpnode->next = helpnode->next;
+                else
+                    client->botclass_helpnode = helpnode->next;
+                neonhelp_destroy_support_request(client, helpnode, 1);
+            }
+        }
+    }
+}
+
+static void neonhelp_event_part(struct ChanUser *target, int quit, char *reason) {
+    struct ClientSocket *client;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct ChanNode *support, *public;
+    int userHasRequest;
+    for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+        if(client->botid == BOTID && isUserOnChan(client->user, target->chan)) {
+            userHasRequest = 0;
+            struct NeonHelpNode *helpnode, *prev_helpnode = NULL;
+            if(client->flags & SOCKET_HAVE_HELPNODE) {
+                for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+                    if(helpnode->user == target->user) {
+                        userHasRequest = 1;
+                        break;
+                    } else
+                        prev_helpnode = helpnode;
+                }
+            }
+            if(!userHasRequest) continue;
+            printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+            res = mysql_use();
+            if (!(row = mysql_fetch_row(res))) continue;
+            support = getChanByName(row[0]);
+            public = (row[1] ? getChanByName(row[1]) : NULL);
+            if(target->chan == support || !((support && isUserOnChan(target->user, support)) || (public && isUserOnChan(target->user, public)))) {
+                if(helpnode->status == 1 && target->chan != support) {
+                    putsock(client, "MODE %s -i", support->name); //clear invite list
+                    if(isModeSet(support->modes, 'i'))
+                        putsock(client, "MODE %s +i", support->name);
+                    neonhelp_invite_active_requests(client, support, public, target->user);
+                }
+                //free the user's support request
+                if(prev_helpnode)
+                    prev_helpnode->next = helpnode->next;
+                else
+                    client->botclass_helpnode = helpnode->next;
+                neonhelp_destroy_support_request(client, helpnode, 1);
+            }
+        }
+    }
+}
+
+static void neonhelp_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(client, user, "MODCMD_CHAN_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(chan && isUserOnChan(bot->user, chan)) {
+            reply(client, user, "NS_INVITE_ON_CHAN", bot->user->nick, chan->name);
+        } else
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+void init_NeonHelp(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonhelp_bot_ready, module_id);
+    bind_privmsg(neonhelp_event_privmsg, module_id);
+    bind_chanmsg(neonhelp_event_chanmsg, module_id);
+    bind_part(neonhelp_event_part, module_id);
+    bind_kick(neonhelp_event_kick, module_id);
+       bind_invite(neonhelp_event_invite, module_id);
+    
+    set_trigger_callback(BOTID, module_id, neonhelp_trigger_callback);
+    
+    register_default_language_table(msgtab);
+}
+
+void free_NeonHelp(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonHelp.mod/bot_NeonHelp.h b/src/modules/NeonHelp.mod/bot_NeonHelp.h
new file mode 100644 (file)
index 0000000..d20238e
--- /dev/null
@@ -0,0 +1,48 @@
+/* bot_NeonHelp.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_NeonHelp_h
+#define _bot_NeonHelp_h
+
+#include "../../main.h"
+
+/* definition for ClientSocket struct */
+#define botclass_helpnode botclassvalue1 
+#define SOCKET_HAVE_HELPNODE SOCKET_HAVE_BOTCLASSVALUE1
+
+#define LOGBUFFERLINES 20
+
+struct UserNode;
+struct ChanNode;
+struct ClientSocket;
+
+struct NeonHelpNode {
+    struct UserNode *user;
+    int suppid;
+    char status:6;
+    char announce:2;
+    struct NeonHelpNode *next;
+    struct ChanNode *logchan;
+    char **log;
+};
+
+void init_NeonHelp(int type);
+void free_NeonHelp(int type);
+
+void neonhelp_destroy_support_request(struct ClientSocket *client, struct NeonHelpNode *helpnode, int do_reply);
+void neonhelp_invite_active_requests(struct ClientSocket *client, struct ChanNode *support, struct ChanNode *public, struct UserNode *ignore_user);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp.c b/src/modules/NeonHelp.mod/cmd_neonhelp.c
new file mode 100644 (file)
index 0000000..3c8be7d
--- /dev/null
@@ -0,0 +1,35 @@
+/* cmd_neonhelp.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_neonhelp.h"
+#include "../../modcmd.h"
+#include "../../ConfigParser.h"
+
+void register_commands() {
+    //NeonHelp Commands
+    register_command_alias(4, "NeonHelp");
+    
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,PRIVS,FLAGS) register_command(4, NAME, module_id, FUNCTION, PARAMCOUNT, PRIVS, 0, FLAGS)
+    //               NAME              FUNCTION        PARAMS     PRIVS                FLAGS
+    USER_COMMAND("next",         neonhelp_cmd_next,      0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("delete",       neonhelp_cmd_delete,    1, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("requests",     neonhelp_cmd_requests,  0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("stats",        neonhelp_cmd_stats,     0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("history",      neonhelp_cmd_history,   1, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    #undef USER_COMMAND
+    
+}
\ No newline at end of file
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp.h b/src/modules/NeonHelp.mod/cmd_neonhelp.h
new file mode 100644 (file)
index 0000000..42fe32d
--- /dev/null
@@ -0,0 +1,41 @@
+/* cmd_neonhelp.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_neonhelp_h
+#define _cmd_neonhelp_h
+#include "../module.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../mysqlConn.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../DBHelper.h"
+#include "../../IRCParser.h"
+#include "bot_NeonHelp.h"
+#include "../../lang.h"
+#include "../../tools.h"
+
+void register_commands();
+
+CMD_BIND(neonhelp_cmd_delete);
+CMD_BIND(neonhelp_cmd_history);
+CMD_BIND(neonhelp_cmd_next);
+CMD_BIND(neonhelp_cmd_requests);
+CMD_BIND(neonhelp_cmd_stats);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp_delete.c b/src/modules/NeonHelp.mod/cmd_neonhelp_delete.c
new file mode 100644 (file)
index 0000000..d9c9262
--- /dev/null
@@ -0,0 +1,96 @@
+/* cmd_neonhelp_delete.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonhelp.h"
+
+/*
+* argv[0]   suppid
+*/
+
+CMD_BIND(neonhelp_cmd_delete) {
+    //check permissions
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int caccess = 0;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        int userid;
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+    }
+    if(!caccess) {
+        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+        return;
+    }
+    if(!(client->flags & SOCKET_HAVE_HELPNODE) || client->botclass_helpnode == NULL) {
+        reply(textclient, user, "NH_NEXT_NONE");
+        return;
+    }
+    struct NeonHelpNode *helpnode, *prev_helpnode = NULL;
+    for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+        if(atoi(argv[0]) == helpnode->suppid)
+            break;
+        else
+            prev_helpnode = helpnode;
+    }
+    if(!helpnode) {
+        reply(textclient, user, "NH_NEXT_NOT_FOUND");
+        return;
+    }
+    if(helpnode->status == 1) {
+        struct ChanNode *support, *public;
+        support = getChanByName(row[0]);
+        public = (row[1] ? getChanByName(row[1]) : NULL);
+        if(isUserOnChan(helpnode->user, support)) {
+            if(public)
+                putsock(client, "KICK %s %s :your request has been closed", support->name, helpnode->user->nick);
+            else
+                putsock(client, "MODE %s -v %s", support->name, helpnode->user->nick);
+        } else {
+            putsock(client, "MODE %s -i", support->name); //clear invite list
+            if(isModeSet(support->modes, 'i'))
+                putsock(client, "MODE %s +i", support->name);
+            neonhelp_invite_active_requests(client, support, public, helpnode->user);
+        }
+    }
+    if(prev_helpnode)
+        prev_helpnode->next = helpnode->next;
+    else
+        client->botclass_helpnode = helpnode->next;
+    reply(textclient, user, "NH_DELETED_STAFF", helpnode->suppid, helpnode->user->nick);
+    neonhelp_destroy_support_request(client, helpnode, 1);
+}
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp_history.c b/src/modules/NeonHelp.mod/cmd_neonhelp_history.c
new file mode 100644 (file)
index 0000000..72d81f6
--- /dev/null
@@ -0,0 +1,136 @@
+/* cmd_neonhelp_history.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonhelp.h"
+
+/*
+* argv[0]   matching string
+*/
+
+CMD_BIND(neonhelp_cmd_history) {
+    //check permissions
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int caccess = 0;
+    int userid;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+    }
+    if(!caccess) {
+        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+        return;
+    }
+    struct Table *table;
+    char *match_str = escape_string(argv[0]);
+    printf_mysql_query("SELECT `id`, `nick`, `hand`, `host`, `time`, `text`, `status`, `supporter`, `users`.`user_user`, MD5(CONCAT(`id`, `host`, `time`, `supporter`)) FROM `helpserv_requests` LEFT JOIN `users` ON `supporter` = `user_id` WHERE `botid` = '%d' AND (`host` LIKE '%%%s%%' OR `hand` LIKE '%%%s%%' OR `nick` LIKE '%%%s%%') ORDER BY `time` ASC LIMIT 100", client->clientid, match_str, match_str, match_str);
+    res = mysql_use();
+    table = table_init(6, mysql_num_rows(res)*3 + 1, 0);
+    table->col_flags[1] |= TABLE_FLAG_COL_SKIP_NULL;
+    table->col_flags[2] |= TABLE_FLAG_COL_SKIP_NULL;
+    table->col_flags[3] |= TABLE_FLAG_COL_SKIP_NULL;
+    table->col_flags[4] |= TABLE_FLAG_COL_SKIP_NULL;
+    char *content[6];
+    content[0] = get_language_string(user, "NH_REQUESTS_HEADER_ID");
+    content[1] = get_language_string(user, "NH_REQUESTS_HEADER_NICK");
+    content[2] = get_language_string(user, "NH_REQUESTS_HEADER_AUTH");
+    content[3] = get_language_string(user, "NH_REQUESTS_HEADER_MASK");
+    content[4] = get_language_string(user, "NH_REQUESTS_HEADER_TIME");
+    content[5] = get_language_string(user, "NH_REQUESTS_HEADER_STATUS");
+    table_add(table, content);
+    char timestr[MAXLEN];
+    time_t rawtime;
+    struct tm *timeinfo;
+    char statestr[MAXLEN];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        content[0] = row[0];
+        content[1] = row[1];
+        content[2] = row[2];
+        content[3] = row[3];
+        rawtime = atoi(row[4]);
+        timeinfo = localtime(&rawtime);
+        strftime(timestr, MAXLEN, "%d.%m.%Y %H:%M:%S", timeinfo);
+        content[4] = timestr;
+        switch(atoi(row[6])) {
+        case 0:
+            content[5] = get_language_string(user, "NH_REQUESTS_STATE_PENDING");
+            break;
+        case 1:
+            sprintf(statestr, "%s (%s)", get_language_string(user, "NH_REQUESTS_STATE_ACTIVE"), row[8]);
+            content[5] = statestr;
+            break;
+        case 2:
+            sprintf(statestr, "%s (%s)", get_language_string(user, "NH_REQUESTS_STATE_CLOSED"), (strcmp(row[7], "0") ? row[8] : "*"));
+            content[5] = statestr;
+            break;
+        }
+        table_add(table, content);
+        char *p;
+        if((p = strstr(row[5], "\n")))
+            *p = '\0';
+        content[0] = " ";
+        content[1] = NULL;
+        content[2] = NULL;
+        content[3] = NULL;
+        content[4] = NULL;
+        sprintf(statestr, "\00314\002\002%s\003", row[5]);
+        content[5] = statestr;
+        table_add(table, content);
+        char setting[128];
+        sprintf(setting, "modules.%s.log_link", get_module_name(module_id));
+        if((p = get_string_field(setting))) {
+            statestr[0] = '\003';
+            statestr[1] = '1';
+            statestr[2] = '4';
+            int pos = sprintf(statestr+3, p, row[0], row[9]);
+            statestr[pos+3] = '\003';
+            statestr[pos+4] = '\0';
+            content[5] = statestr;
+            table_add(table, content);
+        }
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(table->entrys == 1)
+        reply(textclient, user, "NS_TABLE_NONE");
+    table_free(table);
+}
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp_next.c b/src/modules/NeonHelp.mod/cmd_neonhelp_next.c
new file mode 100644 (file)
index 0000000..033772f
--- /dev/null
@@ -0,0 +1,108 @@
+/* cmd_neonhelp_next.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonhelp.h"
+
+/*
+* argv[0]   (optional) suppid
+*/
+
+CMD_BIND(neonhelp_cmd_next) {
+    //check permissions
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int caccess = 0;
+    int userid;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+    }
+    if(!caccess) {
+        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+        return;
+    }
+    if(!(client->flags & SOCKET_HAVE_HELPNODE) || client->botclass_helpnode == NULL) {
+        reply(textclient, user, "NH_NEXT_NONE");
+        return;
+    }
+    struct NeonHelpNode *helpnode, *next_helpnode = NULL;
+    int lowest_id = -1;
+    for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+        if(helpnode->status == 0) {
+            if(argc) {
+                if(atoi(argv[0]) == helpnode->suppid) {
+                    next_helpnode = helpnode;
+                    break;
+                }
+            } else {
+                if(helpnode->suppid < lowest_id || lowest_id == -1) {
+                    lowest_id = helpnode->suppid;
+                    next_helpnode = helpnode;
+                }
+            }
+        }
+    }
+    if(!next_helpnode) {
+        reply(textclient, user, "NH_NEXT_NOT_FOUND");
+        return;
+    }
+    printf_mysql_query("SELECT `text` FROM `helpserv_requests` WHERE `id` = '%d'", next_helpnode->suppid);
+    res = mysql_use();
+    if (!(row2 = mysql_fetch_row(res))) return;
+    reply(textclient, user, "NH_NEXT_HEADER", next_helpnode->suppid, next_helpnode->user->nick);
+    char *a, *b = row2[0];
+    do {
+        a = strstr(b, "\n");
+        if(a) *a = '\0';
+        reply(textclient, user, "  %s", b);
+        if(a) {
+            *a = '\n';
+            b = a+1;
+        }
+    } while(a);
+    if(row[1])
+        putsock(client, "INVITE %s %s", next_helpnode->user->nick, row[0]);
+    else
+        putsock(client, "MODE %s +v %s", row[0], next_helpnode->user->nick);
+    char sendbuf1[MAXLEN];
+    char sendbuf2[MAXLEN];
+    char *join_now = (row[1] ? build_language_string(user, sendbuf2, "NH_NEXT_JOIN", row[0]) : "");
+    putsock(client, "PRIVMSG %s :%s %s", next_helpnode->user->nick, build_language_string(user, sendbuf1, "NH_NEXT_HELPER", next_helpnode->suppid, user->auth, user->nick), join_now);
+    next_helpnode->status = 1;
+    printf_mysql_query("UPDATE `helpserv_requests` SET `status` = '1', `supporter` = '%d' WHERE `id` = '%d'", userid, next_helpnode->suppid);
+}
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp_requests.c b/src/modules/NeonHelp.mod/cmd_neonhelp_requests.c
new file mode 100644 (file)
index 0000000..8906a91
--- /dev/null
@@ -0,0 +1,108 @@
+/* cmd_neonhelp_requests.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonhelp.h"
+
+/*
+* no args
+*/
+
+CMD_BIND(neonhelp_cmd_requests) {
+    //check permissions
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int caccess = 0;
+    int userid;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+    }
+    if(!caccess) {
+        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+        return;
+    }
+    struct Table *table;
+    printf_mysql_query("SELECT `id`, `time`, `text` FROM `helpserv_requests` WHERE `botid` = '%d' AND `status` != 2 ORDER BY `time` DESC", client->clientid);
+    res = mysql_use();
+    table = table_init(5, mysql_num_rows(res) + 1, 0);
+    char *content[5];
+    content[0] = get_language_string(user, "NH_REQUESTS_HEADER_ID");
+    content[1] = get_language_string(user, "NH_REQUESTS_HEADER_STATUS");
+    content[2] = get_language_string(user, "NH_REQUESTS_HEADER_NICK");
+    content[3] = get_language_string(user, "NH_REQUESTS_HEADER_TIME");
+    content[4] = get_language_string(user, "NH_REQUESTS_HEADER_REQUEST");
+    table_add(table, content);
+    char timestr[MAXLEN];
+    struct NeonHelpNode *helpnode;
+    int suppid;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        content[0] = row[0];
+        suppid = atoi(row[0]);
+        if(client->flags & SOCKET_HAVE_HELPNODE) {
+            for(helpnode = client->botclass_helpnode; helpnode; helpnode = helpnode->next) {
+                if(helpnode->suppid == suppid) {
+                    if(helpnode->status)
+                        content[1] = get_language_string(user, "NH_REQUESTS_STATE_ACTIVE");
+                    else
+                        content[1] = get_language_string(user, "NH_REQUESTS_STATE_PENDING");
+                    content[2] = helpnode->user->nick;
+                    break;
+                }
+            }
+        } else {
+            content[1] = get_language_string(user, "NH_REQUESTS_STATE_ERROR");
+            content[2] = NULL;
+        }
+        timeToStr(user, (time(0) - atoi(row[1])), 2, timestr);
+        content[3] = timestr;
+        char *p;
+        if((p = strstr(row[2], "\n")))
+            *p = '\0';
+        content[4] = row[2];
+        table_add(table, content);
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(table->entrys == 1)
+        reply(textclient, user, "NS_TABLE_NONE");
+    table_free(table);
+}
diff --git a/src/modules/NeonHelp.mod/cmd_neonhelp_stats.c b/src/modules/NeonHelp.mod/cmd_neonhelp_stats.c
new file mode 100644 (file)
index 0000000..1628b2b
--- /dev/null
@@ -0,0 +1,88 @@
+/* cmd_neonhelp_stats.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonhelp.h"
+
+/*
+* argv[0]   user
+*/
+
+CMD_BIND(neonhelp_cmd_stats) {
+    //check permissions
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int caccess = 0;
+    printf_mysql_query("SELECT `helpserv_support`, `helpserv_public`, `helpserv_intern` FROM `helpserv_settings` WHERE `helpserv_botid` = '%d'", client->clientid);
+    res = mysql_use();
+    if (!(row = mysql_fetch_row(res))) return;
+    //check if the user is a supporter (access in the support channel)
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        int userid;
+        if(user->flags & USERFLAG_HAS_USERID)
+            userid = user->user_id;
+        else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row2 = mysql_fetch_row(res)) != NULL) {
+                userid = atoi(row2[0]);
+                user->user_id = userid;
+                user->flags |= USERFLAG_HAS_USERID;
+            } else
+                userid = 0;
+        }
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d' AND `channel_name` = '%s'", userid, escape_string(row[0]));
+        res = mysql_use();
+        if ((row2 = mysql_fetch_row(res)) != NULL) {
+            int cflags = atoi(row2[1]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED))
+                caccess = atoi(row2[0]);
+        }
+    }
+    if(!caccess) {
+        reply(textclient, user, "MODCMD_ACCESS_DENIED");
+        return;
+    }
+    if(argc > 0) {
+        //following
+    } else {
+        struct Table *table;
+        printf_mysql_query("SELECT DISTINCT a.`supporter`, `user_user`, (SELECT COUNT(*) FROM `helpserv_requests` AS b WHERE b.`supporter` = a.`supporter` AND b.`time` > (UNIX_TIMESTAMP()-(3600*24))), (SELECT COUNT(*) FROM `helpserv_requests` AS b WHERE b.`supporter` = a.`supporter` AND b.`time` > (UNIX_TIMESTAMP()-(3600*24*7))), (SELECT COUNT(*) FROM `helpserv_requests` AS b WHERE b.`supporter` = a.`supporter` AND b.`time` > (UNIX_TIMESTAMP()-(3600*24*7*4))) FROM `helpserv_requests` AS a LEFT JOIN `users` ON a.`supporter` = `user_id` WHERE a.`time` > (UNIX_TIMESTAMP()-(3600*24*7*4))  AND `botid` = '%d' ORDER BY `user_user` ASC", client->clientid);
+        res = mysql_use();
+        table = table_init(4, mysql_num_rows(res) + 1, 0);
+        char *content[4];
+        content[0] = get_language_string(user, "NH_STATS_HEADER_USER");
+        content[1] = get_language_string(user, "NH_STATS_HEADER_LAST_24H");
+        content[2] = get_language_string(user, "NH_STATS_HEADER_LAST_7DAY");
+        content[3] = get_language_string(user, "NH_STATS_HEADER_LAST_30DAY");
+        table_add(table, content);
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            content[0] = (strcmp(row[0], "-1") ? row[1] : "-");
+            content[1] = row[2];
+            content[2] = row[3];
+            content[3] = row[4];
+            table_add(table, content);
+        }
+        char **table_lines = table_end(table);
+        int i;
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        if(table->entrys == 1)
+            reply(textclient, user, "NS_TABLE_NONE");
+        table_free(table);
+    }
+}
diff --git a/src/modules/NeonHelp.mod/module.c b/src/modules/NeonHelp.mod/module.c
new file mode 100644 (file)
index 0000000..53cea0b
--- /dev/null
@@ -0,0 +1,34 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonHelp.h"
+#include "cmd_neonhelp.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonHelp(type);
+}
+
+static void module_stop(int type) {
+    free_NeonHelp(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonKick.mod/bot_NeonKick.c b/src/modules/NeonKick.mod/bot_NeonKick.c
new file mode 100644 (file)
index 0000000..a3b7e93
--- /dev/null
@@ -0,0 +1,216 @@
+/* bot_NeonKick.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_NeonKick.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+
+#define BOTID NEONKICK_BOTID
+#define BOTALIAS "NeonKick"
+
+static void neonkick_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonkick_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    //this bot doesn't have a trigger
+    strcpy(trigger, "");
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_SILENT;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+static void neonkick_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(!(chan && isUserOnChan(bot->user, chan)))
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+static char* getSetting(struct UserNode *user, struct ChanNode *chan, const char *setting) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        return row[0];
+    } else
+        return NULL;
+}
+
+static void setSetting(struct UserNode *user, struct ChanNode *chan, const char *setting, const char *value) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `id`, `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(strcmp(row[1], value))
+            printf_mysql_query("UPDATE `fundata` SET `value` = '%s' WHERE `id` = '%s'", escape_string(value), row[0]);
+    } else
+        printf_mysql_query("INSERT INTO `fundata` (`user`, `cid`, `name`, `value`) VALUES ('%s', '%d', '%s', '%s')", escape_string(uname), cid, escape_string(setting), escape_string(value));
+}
+
+#include "event_neonkick_join.c"
+
+void init_NeonKick(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonkick_bot_ready, module_id);
+       bind_invite(neonkick_event_invite, module_id);
+    bind_join(neonkick_event_join, module_id);
+    
+    set_trigger_callback(BOTID, module_id, neonkick_trigger_callback);
+}
+
+void free_NeonKick(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonKick.mod/bot_NeonKick.h b/src/modules/NeonKick.mod/bot_NeonKick.h
new file mode 100644 (file)
index 0000000..bc3d8e7
--- /dev/null
@@ -0,0 +1,25 @@
+/* bot_NeonKick.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_NeonKick_h
+#define _bot_NeonKick_h
+
+#include "../../main.h"
+
+void init_NeonKick(int type);
+void free_NeonKick(int type);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonKick.mod/event_neonkick_join.c b/src/modules/NeonKick.mod/event_neonkick_join.c
new file mode 100644 (file)
index 0000000..6dc1224
--- /dev/null
@@ -0,0 +1,98 @@
+/* event_neonkick_join.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonkick_event_join_cache {
+    struct ClientSocket *client;
+    struct ChanUser *chanuser;
+};
+
+static USERAUTH_CALLBACK(neonkick_event_join_nick_lookup);
+static void neonkick_event_join_async1(struct ClientSocket *client, struct ChanUser *chanuser);
+
+static void neonkick_event_join(struct ChanUser *chanuser) {
+    struct UserNode *user = chanuser->user;
+    struct ClientSocket *client = getChannelBot(chanuser->chan, BOTID);
+    if(!client) return; //we can't "see" this event
+    if(chanuser->user == client->user) {
+        requestOp(client->user, chanuser->chan);
+        module_neonbackup_recover_chan(chanuser->chan);
+        return;
+    }
+    if(chanuser->user->flags & USERFLAG_ISBOT) return;
+    loadChannelSettings(chanuser->chan);
+    if(!(chanuser->chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        struct neonkick_event_join_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonkick", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->chanuser = chanuser;
+        get_userauth(user, module_id, neonkick_event_join_nick_lookup, cache);
+    } else
+        neonkick_event_join_async1(client, chanuser);
+}
+
+static USERAUTH_CALLBACK(neonkick_event_join_nick_lookup) {
+    struct neonkick_event_join_cache *cache = data;
+    if(user)
+        neonkick_event_join_async1(cache->client, cache->chanuser);
+    free(cache);
+}
+
+static void neonkick_event_join_async1(struct ClientSocket *client, struct ChanUser *chanuser) {
+    struct ChanNode *chan = chanuser->chan;
+    struct UserNode *user = chanuser->user;
+    MYSQL_RES *res;
+    MYSQL_ROW chanuserrow;
+    
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `chanuser_infoline`, `chanuser_seen`, `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        chanuserrow = mysql_fetch_row(res);
+    } else
+        chanuserrow = NULL;
+    int userflags = (chanuserrow ? atoi(chanuserrow[1]) : 0);
+    int uaccess = ((chanuserrow && !(userflags & DB_CHANUSER_SUSPENDED)) ? atoi(chanuserrow[0]) : 0);
+    if(uaccess > 0)
+        return; //no kick :D
+    char *tmp;
+    int global_kick_count = ((tmp = getSetting(NULL, chan, "kicks")) ? atoi(tmp) : 0);
+    int kick_count = ((tmp = getSetting(user, chan, "kicks")) ? atoi(tmp) : 0);
+    global_kick_count++;
+    kick_count++;
+    char buf[MAXLEN];
+    char reason[MAXLEN];
+    sprintf(buf, "%d", global_kick_count);
+    setSetting(NULL, chan, "kicks", buf);
+    sprintf(buf, "%d", kick_count);
+    setSetting(user, chan, "kicks", buf);
+    char *banmask = generate_banmask(chanuser->user, buf);
+    sprintf(reason, "Kick #%d (#%d for you)", global_kick_count, kick_count);
+    putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason);
+    putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
+    { // add timed ban
+        printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chanuser->chan->channel_id, escape_string(banmask), (unsigned long) (time(0) + 10), 0, escape_string(reason));
+        int banid = (int) mysql_insert_id(get_mysql_conn());
+        char nameBuf[MAXLEN];
+        char banidBuf[20];
+        sprintf(nameBuf, "ban_%d", banid);
+        sprintf(banidBuf, "%d", banid);
+        timeq_add_name(nameBuf, 10, module_id, channel_ban_timeout, strdup(banidBuf));
+    }
+}
diff --git a/src/modules/NeonKick.mod/module.c b/src/modules/NeonKick.mod/module.c
new file mode 100644 (file)
index 0000000..e372bf9
--- /dev/null
@@ -0,0 +1,32 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonKick.h"
+
+static int module_initialize() {
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonKick(type);
+}
+
+static void module_stop(int type) {
+    free_NeonKick(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonServ.mod/bot_NeonServ.c b/src/modules/NeonServ.mod/bot_NeonServ.c
new file mode 100644 (file)
index 0000000..0ea4cbb
--- /dev/null
@@ -0,0 +1,544 @@
+/* bot_NeonServ.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+#include "bot_NeonServ.h"
+#include "../../modcmd.h"
+#include "../../IRCEvents.h"
+#include "../../IRCParser.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../BanNode.h"
+#include "../../ModeNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../EventLogger.h"
+#include "cmd_neonserv.h"
+#include "../../ConfigParser.h"
+
+#define BOTID NEONSERV_BOTID
+#define BOTALIAS "NeonServ"
+
+static const struct default_language_entry msgtab[] = {
+    {"NS_USER_UNKNOWN", "User with nick $b%s$b does not exist."}, /* {ARGS: "TestUser"} */
+    {"NS_AUTH_UNKNOWN", "Account $b%s$b has not been registered."}, /* {ARGS: "TestAuth"} */
+    {"NS_USER_NEED_AUTH", "%s must first authenticate with $bAuthServ$b."}, /* {ARGS: "TestUser"} */
+    {"NS_YOU_NEED_AUTH", "You must first authenticate with $bAuthServ$b."},
+    {"NS_INVALID_ACCESS", "$b%d$b is an invalid access level."}, /* {ARGS: 1337} */
+    {"NS_ADDUSER_ALREADY_ADDED", "%s is already on the $b%s$b user list (with access %d)."}, /* {ARGS: "TestUser", "#TestChan", 123} */
+    {"NS_ADDUSER_DONE", "Added %s to the %s user list with access %d."}, /* {ARGS: "TestUser", "#TestChan", 123} */
+    {"NS_NOT_ON_USERLIST", "%s lacks access to $b%s$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_NOT_ON_USERLIST_YOU", "You lack access to $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_NOT_ON_CHANNEL", "%s isn't currently in $b%s$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_NOT_ON_CHANNEL_YOU", "You aren't currently in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DELUSER_DONE", "Deleted %s (with access %d) from the %s user list."}, /* {ARGS: "TestUser", 123, "#TestChan"} */
+    {"NS_ACCESS_OUTRANKED", "You cannot give users access greater than or equal to your own."},
+    {"NS_USER_OUTRANKED", "$b%s$b outranks you (command has no effect)."}, /* {ARGS: "TestUser"} */
+    {"NS_ACCESS_DENIED", "Access denied."},
+    {"NS_NO_ACCESS", "You lack sufficient access to use this command."},
+    {"NS_USER_PROTECTED", "Sorry, $b%s$b is protected."}, /* {ARGS: "TestUser"} */
+    {"NS_SERVICE_IMMUNE", "$b%s$b may not be kicked, killed, banned, or deopped."}, /* {ARGS: "TestUser"} */
+       {"NS_YOU_PROTECTED", "You may not kick or ban yourself."},
+    {"NS_TABLE_NONE", "   None"},
+    {"NS_TABLE_COUNT", "Found $b%d$b matches."}, /* {ARGS: 5} */
+    {"NS_BAN_ALREADY_ADDED", "$b%s$b is already banned in %s."}, /* {ARGS: "*!*@moeeep.*", "#TestChan"} */
+    {"NS_INVALID_ACCESS_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)."}, /* {ARGS: 450, 400} */
+    {"NS_CLVL_DONE", "%s now has access $b%d$b in %s."}, /* {ARGS: "TestUser", 123, "#TestChan"} */
+    {"NS_A_LACKS_ACCESS_BUT_GOD_NICK", "%s lacks access to %s but has $bsecurity override$b enabled."}, /* {ARGS: "TestAuth", "#TestChan"} */
+    {"NS_A_LACKS_ACCESS_BUT_GOD_AUTH", "%s (%s) lacks access to %s but has $bsecurity override$b enabled."}, /* {ARGS: "TestAuth", "TestUser", "#TestChan"} */
+    {"NS_A_ACCESS_NICK", "%s has access $b%d$b in %s."}, /* {ARGS: "TestAuth", 123, "#TestChan"} */
+    {"NS_A_ACCESS_AUTH", "%s (%s) has access $b%d$b in %s."}, /* {ARGS: "TestAuth", "TestUser", 123, "#TestChan"} */
+    {"NS_A_ACCESS_NICK_GOD", "%s has access $b%d$b in %s and has $bsecurity override$b enabled."}, /* {ARGS: "TestAuth", 123, "#TestChan"} */
+    {"NS_A_ACCESS_AUTH_GOD", "%s (%s) has access $b%d$b in %s and has $bsecurity override$b enabled."}, /* {ARGS: "TestAuth", "TestUser", 123, "#TestChan"} */
+    {"NS_A_SUSPENDED", "$b%s$b's access to %s has been suspended."}, /* {ARGS: "TestAuth", "#TestChan"} */
+    {"NS_A_IS_IRCOP", "%s is an $bIRC operator$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_USERS_HEADER", "%s users from level %d to %d:"}, /* {ARGS: "#TestChan", 1, 500} */
+    {"NS_USERS_HEADER_MATCH", "%s users from level %d to %d matching %s:"}, /* {ARGS: "#TestChan", 1, 500, "Test*"} */
+    {"NS_USERS_HEADER_ACCESS", "Access"},
+    {"NS_USERS_HEADER_ACCOUNT", "Account"},
+    {"NS_USERS_HEADER_SEEN", "Last Seen"},
+    {"NS_USERS_HEADER_STATE", "Status"},
+    {"NS_USERS_COUNT", "There are $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_USERS_COUNT_1", "There is $b%d$b user in %s."}, /* {ARGS: 1, "#TestChan"} */
+    {"NS_USERS_COUNT_MATCH", "There are $b%d$b users in %s. ($b%d$b matching your request)"}, /* {ARGS: 20, "#TestChan", 5} */
+    {"NS_USERS_COUNT_MATCH_1", "There is $b%d$b user in %s. ($b%d$b matching your request)"}, /* {ARGS: 1, "#TestChan", 1} */
+    {"NS_USERS_SEEN_HERE", "Here"},
+    {"NS_USERS_SEEN_INVISIBLE", "Here (invisible)"},
+    {"NS_USERS_SEEN_NEVER", "Never"},
+    {"NS_USERS_STATE_SUSPENDED", "Suspended"},
+    {"NS_USERS_STATE_NORMAL", "Normal"},
+    {"NS_SUSPEND_ALREADY", "$b%s$b is already suspended." }, /* {ARGS: "TestUser"} */
+    {"NS_SUSPEND_NOT", "$b%s$b is not suspended." }, /* {ARGS: "TestUser"} */
+    {"NS_SUSPEND_DONE", "$b%s$b's access to $b%s$b has been suspended." }, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_SUSPEND_RESTORED", "$b%s$b's access to $b%s$b has been restored." }, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_DELME_OWNER", "You cannot delete your owner access in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DELME_KEY", "To really remove yourself, you must use 'deleteme %s'."}, /* {ARGS: "abc123"} */
+    {"NS_DELME_DONE", "Your $b%d$b access has been deleted from $b%s$b."}, /* {ARGS: 123, "#TestChan"} */
+    {"NS_MYACCESS_SELF_ONLY", "You may only see the list of infolines for yourself (by using $bmyaccess$b with no arguments)."},
+    {"NS_MYACCESS_HEADER", "Showing all channel entries for account $b%s$b:"}, /* {ARGS: "TestAuth"} */
+    {"NS_MYACCESS_HEADER_MATCH", "Showing all channel entries for account $b%s$b matching %s:"}, /* {ARGS: "TestAuth", "#Test*"} */
+    {"NS_MYACCESS_HEADER_NAME", "Name"},
+    {"NS_MYACCESS_HEADER_ACCESS", "Access"},
+    {"NS_MYACCESS_HEADER_FLAGS", "Rights"},
+    {"NS_MYACCESS_HEADER_INFO", "Info"},
+    {"NS_MYACCESS_COUNT", "%s has access in $b%d$b channel(s) and is owner of $b%d$b channel(s)."}, /* {ARGS: "TestUser", 15, 5} */
+    {"NS_MYACCESS_COUNT_MATCH", "%s has access in $b%d$b channel(s) and is owner of $b%d$b channel(s) ($b%d$b channels matching your request)."}, /* {ARGS: "TestUser", 15, 5, 7} */
+    {"NS_UP_ALREADY_OP", "You are already opped in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_UP_ALREADY_VOICE", "You are already voiced in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DOWN_ALREADY", "You are not opped or voiced in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_MDELUSER_DONE", "Deleted $b%d$b account(s) matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list."}, /* {ARGS: 10, "Test*", 1, 200, "#TestChan"} */
+    {"NS_TRIM_DURATION_TOO_SHORT", "You must include a minimum inactivity duration of at least %d seconds to trim."},
+    {"NS_TRIM_DONE", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s."}, /* {ARGS: 10, 1, 100, "#TestChan", "10 days"} */
+    {"NS_TRIM_BAN_DONE", "Trimmed $b%d bans$b from the %s ban list who were banned for at least %s."}, /* {ARGS: 5, "#TestChan", "1 day"} */
+    {"NS_GIVEOWNER_SELF", "You cannot give ownership to your own account."},
+    {"NS_GIVEOWNER_TIMEOUT", "You must wait %s before you can give ownership of $b%s$b to someone else."}, /* {ARGS: "5 hours", "#TestChan"} */
+    {"NS_GIVEOWNER_CONFIRM", "To really give ownership to $b%1$s$b, you must use 'giveownership *%1$s %2$s'."}, /* {ARGS: "TestUser", "abc123"} */
+    {"NS_GIVEOWNER_DONE", "Ownership of $b%s$b has been transferred to account $b%s$b."}, /* {ARGS: "#TestChan", "TestUser"} */
+    {"NS_OP_FAIL", "$b%s$b could not op some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_OP_DONE", "Opped users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_HALFOP_FAIL", "$b%s$b could not halfop some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_HALFOP_DONE", "Half-Opped users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_VOICE_FAIL", "$b%s$b could not voice some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_VOICE_DONE", "Voiced users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DEOP_FAIL", "$b%s$b could not deop some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_DEOP_DONE", "Deopped users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DEHALFOP_FAIL", "$b%s$b could not dehalfop some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_DEHALFOP_DONE", "Dehalfopped users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_DEVOICE_FAIL", "$b%s$b could not devoice some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_DEVOICE_DONE", "Devoiced users in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_OPALL_SECURITY", "$bWARNING$b: Opping all users on a channel is very insecure! If you still want do op all users on %s use: '$bopall FORCE$b [nick mask]'"},
+    {"NS_OPALL_DONE", "Opped $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_HALFOPALL_DONE", "Half-Opped $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_VOICEALL_DONE", "Voiced $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_DEOPALL_DONE", "Deopped $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_DEHALFOPALL_DONE", "Dehalfopped $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_DEVOICEALL_DONE", "Devoiced $b%d$b users in %s."}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_KICK_DONE", "Kicked $b%d$b users from %s"}, /* {ARGS: 20, "#TestChan"} */
+    {"NS_KICK_FAIL", "$b%s$b could not kick some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_KICKBAN_DONE", "KickBanned $b%d$b users from %s"}, /* {ARGS: 10, "#TestChan"} */
+    {"NS_KICKBAN_FAIL", "$b%s$b could not kickban some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_BAN_DONE", "$b%d$b masks added to the %s ban list. (matching %d users)"}, /* {ARGS: 5, "#TestChan", 15} */
+    {"NS_BAN_FAIL", "$b%s$b could not ban some of the nicks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_LAME_MASK", "$b%s$b is a little too general. Try making it more specific."}, /* {ARGS: "*!*@*"} */
+    {"NS_LAME_MASK_WARNING", "$k4WARNING$k: $b%s$b is very general. (matches %d users)"},
+    {"NS_SET_HEADER", "Channel Settings for %s:"}, /* {ARGS: "#TestChan"} */
+    {"NS_SET_ON", "on"},
+    {"NS_SET_OFF", "off"},
+    {"NS_SET_UNKNOWN_SETTING", "$b%s$b is an unknown channel setting."}, /* {ARGS: "TestSetting"} */
+    {"NS_SET_CANNOT_SET", "That setting is above your current level, so you cannot change it."},
+    {"NS_SET_BADLEVEL", "You cannot change any setting to above your level."},
+    {"NS_SET_INVALID_OPTION", "$b%d$b is not a valid choice.  Choose one:"}, /* {ARGS: 5} */
+    {"NS_SET_INVALID_BOOLEAN", "$b%s$b is an invalid binary value."}, /* {ARGS: "3"} */
+    {"NS_SET_DEFAULTS_OWNER", "You must have access 500 in %s to reset it to the default options."}, /* {ARGS: "#TestChan"} */
+    {"NS_SET_DEFAULTS_CODE", "To reset %s's settings to the defaults, you must use 'set defaults %s'."}, /* {ARGS: "#TestChan", "abc123"} */
+    {"NS_SET_DEFAULTS_DONE", "All settings for %s have been reset to default values."}, /* {ARGS: "#TestChan"} */
+    {"NS_SET_TRIGGER_OWNER", "You must have access 500 in %s to change the channel trigger."}, /* {ARGS: "#TestChan"} */
+    {"NS_SET_HELP_USERINFO","(access to set the userinfo)"},
+    {"NS_SET_HELP_WIPEINFO","(access to clear the userinfo of other users)"},
+    {"NS_SET_HELP_INVITEME","(access to get invited by the bot)"},
+    {"NS_SET_HELP_ENFVOICE","(access to give voice to other users)"},
+    {"NS_SET_HELP_ENFOPS","(access to give op to their users)"},
+    {"NS_SET_HELP_GIVEOPS","(access to get op by the bot)"},
+    {"NS_SET_HELP_GIVEVOICE","(access to get voice by the bot)"},
+    {"NS_SET_HELP_KICK","(access to kick other users from the channel)"},
+    {"NS_SET_HELP_BAN","(access to ban other users from the channel)"},
+    {"NS_SET_HELP_STATICBAN","(access to add static bans to the channel banlist  e.g. +addban)"},
+    {"NS_SET_HELP_PUBCMD","(access to do public commands in the channel  e.g. +users)"},
+    {"NS_SET_HELP_ENFMODES","(access to override the modelock)"},
+    {"NS_SET_HELP_ENFTOPIC","(access to override the topicmask)"},
+    {"NS_SET_HELP_TOPICSNARF","(access to set the default topic by changing the topic with /TOPIC)"},
+    {"NS_SET_HELP_CHANGETOPIC","(access to change the topic)"},
+    {"NS_SET_HELP_SETTERS","(access to change this settings)"},
+    {"NS_SET_HELP_ADDUSER","(access to add an user to the userlist)"},
+    {"NS_SET_HELP_DELUSER","(access to delete an user from the userlist)"},
+    {"NS_SET_HELP_CLVL","(access to change the access of an user in the userlist)"},
+    {"NS_SET_HELP_RESYNC","(access to synchronize the channelrights (@,+) with the userlist)"},
+    {"NS_SET_HELP_SUSPEND","(access to suspend an user on the userlist)"},
+    {"NS_SET_OPTION_CTCPREACTION_0","Kick on disallowed CTCPs"},
+    {"NS_SET_OPTION_CTCPREACTION_1","Kickban on disallowed CTCPs"},
+    {"NS_SET_OPTION_CTCPREACTION_2","Short timed ban on disallowed CTCPs"},
+    {"NS_SET_OPTION_CTCPREACTION_3","Long timed ban on disallowed CTCPs"},
+    {"NS_SET_OPTION_NOTICEREACTION_0","Kick on disallowed NOTICEs"},
+    {"NS_SET_OPTION_NOTICEREACTION_1","Kickban on disallowed NOTICEs"},
+    {"NS_SET_OPTION_NOTICEREACTION_2","Short timed ban on disallowed NOTICEs"},
+    {"NS_SET_OPTION_NOTICEREACTION_3","Long timed ban on disallowed NOTICEs"},
+    {"NS_SET_OPTION_PROTECT_0","All users will be protected from users with equal or lower access."},
+    {"NS_SET_OPTION_PROTECT_1","All users with access will be protected from users with equal or lower access."},
+    {"NS_SET_OPTION_PROTECT_2","All users with access will be protected from user with lower access."},
+    {"NS_SET_OPTION_PROTECT_3","Nobody will be protected."},
+    {"NS_SET_OPTION_TOYS_0","Funcommands can't be used."},
+    {"NS_SET_OPTION_TOYS_1","Funcommands are possible but the reply will be sent as a notice."},
+    {"NS_SET_OPTION_TOYS_2","Funcommands are possible and the reply will be sent to the channel."},
+    {"NS_SET_OPTION_DYNLIMIT_0","off"},
+    {"NS_SET_OPTION_NODELETE_0","off  (only bot masters)"},
+    {"NS_SET_OPTION_NODELETE_1","on  (only bot masters)"},
+    {"NS_WIPEINFO_DONE", "Removed $b%s$b's infoline in $b%s$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_TRACE_HEADER", "The following users were found:"},
+    {"NS_ADDBAN_DONE", "$b%s$b permantly added to the %s ban list. (matching %d users)"}, /* {ARGS: "*!*@Test.*", "#TestChan", 4} */
+    {"NS_BANS_HEADER_MASK", "Mask"},
+    {"NS_BANS_HEADER_SETBY", "Set By"},
+    {"NS_BANS_HEADER_TRIGGERED", "Triggered"},
+    {"NS_BANS_HEADER_EXPIRES", "Expires"},
+    {"NS_BANS_HEADER_REASON", "Reason"},
+    {"NS_DELBAN_BANNED_BY", "%s is banned by %s."}, /* {ARGS: "*!*@bla*", "*!*@b*"} */
+    {"NS_DELBAN_FAIL", "Sorry, no ban found for $b%s$b."}, /* {ARGS: "*!*@bla*"} */
+    {"NS_DELBAN_DONE", "Removed $b%s$b from the %s ban list."}, /* {ARGS: "*!*@bla.*", "#TestChan"} */
+    {"NS_NETINFO_HEADER", "$bNetwork information$b"},
+    {"NS_NETINFO_BOTS", "Bots:"},
+    {"NS_NETINFO_UPTIME", "Uptime:"},
+    {"NS_NETINFO_TRAFFIC", "Traffic:"},
+    {"NS_NETINFO_CACHE", "Cache:"},
+    {"NS_NETINFO_DATABASE", "Database:"},
+    {"NS_NETINFO_CHANNEL", "  Channel:"},
+    {"NS_NETINFO_CHANNEL_BAN", "    Bans:"},
+    {"NS_NETINFO_USER", "  User:"},
+    {"NS_NETINFO_CHANUSER", "  Channel-User:"},
+    {"NS_NETINFO_OTHER", "  Other:"},
+    {"NS_NETINFO_THREADS", "Threads:"},
+    {"NS_NETINFO_VERSION", "Version:"},
+    {"NS_NETINFO_CODE", "Code:"},
+    {"NS_NETINFO_CODE_VALUE", "%s lines c code (view it at http://dev.pk910.de/NeonServ)"}, /* {ARGS: 20} */
+    {"NS_NETINFO_COMPILER", "Compiler:"},
+    {"NS_NETINFO_COMPILER_VALUE", "%s  (%s)"}, /* {ARGS: "GCC 4.4.5", "Sun Sep 18 2011 at 05:21:33 CEST"} */
+    {"NS_EXTTOPIC_INVALID_ID", "ADVANCEDTOPIC is enabled and $b%s$b is an invalid TOPIC ID. Valid topic id's are: 1-9"}, /* {ARGS: 10} */
+    {"NS_EXTTOPIC_TOPICID", "Topic %d: %s"}, /* {ARGS: 5, "topic"} */
+    {"NS_TOPIC_DONE", "Topic is now '%s'."}, /* {ARGS: "i like you :D"} */
+    {"NS_CHANSERVSYNC_UNSUPPORTED", "\0034WARNING\003: the user list style of %s is not known. %s can try to synchronize the userlist, but there is no guarantee that it is successful!"}, /* {ARGS: "CowBot"} */
+    {"NS_CHANSERVSYNC_KEY", "If you really want to synchronize the %s userlist with %s use: chanservsync %s %s"}, /* {ARGS: "#TestChan", "CowBot", "CowBot", "abc123"} */
+    {"NS_CHANSERVSYNC_INUSE", "$bchanservsync$b is already in use by someone else. Please try again in a few seconds..."},
+    {"NS_CHANSERVSYNC_SYNCHRONIZING", "Synchronizing userlist in %s with $b%s$b..."}, /* {ARGS: "#TestChan", "CowBot"} */
+    {"NS_CHANSERVSYNC_SYNCHRONIZED", "Synchronized user $b%s$b: access $b%d$b"}, /* {ARGS: "TestUser", 123} */
+    {"NS_REGISTER_ALREADY", "%s is already registered with %s."}, /* {ARGS: "#TestChan", "NeonServ"} */
+    {"NS_INVALID_CHANNEL_NAME", "%s is not a valid channel name."}, /* {ARGS: "#invalid"} */
+    {"NS_REGISTER_FULL", "the bot can not join more channels."},
+    {"NS_REGISTER_DISCONNECTED", "%s has been registered with a Bot, that is currently NOT connected. The Bot should join the channel, when it reconnects to the IRC-Network."}, /* {ARGS: "#TestChan"} */
+    {"NS_REGISTER_DONE", "$b%s$b is now registered to $b%s$b."}, /* {ARGS: "#TestChan", "TestUser"} */
+    {"NS_REGISTER_DONE_NOAUTH", "$b%s$b is now registered."}, /* {ARGS: "#TestChan"} */
+    {"NS_UNREGISTER_NOT_REGISTERED", "$b%s$b is not registered with %s."}, /* {ARGS: "#TestChan", "NeonServ"} */
+    {"NS_UNREGISTER_NODELETE", "$b%s$b is protected from being unregistered (nodelete)."}, /* {ARGS: "#TestChan"} */
+    {"NS_UNREGISTER_DONE", "$b%s$b unregistered."}, /* {ARGS: "#TestChan"} */
+    {"NS_RECOVER_DONE", "$b%s$b has been recovered."}, /* {ARGS: "#TestChan"} */
+    {"NS_RESYNC_DONE", "Synchronized users in $b%s$b with the userlist."}, /* {ARGS: "#TestChan"} */
+    {"NS_TIMEBAN_DURATION_TOO_SHORT", "You must specify a ban duration of at least %d seconds."}, /* {ARGS: 30} */
+    {"NS_TIMEBAN_DONE", "Banned $b%s$b from %s for %s. (matching %d users)"}, /* {ARGS: "*!*@bla*", "#TestChan", "2 hours", 5} */
+    {"NS_MODE_INVALID", "$b%s$b is an invalid set of channel modes."}, /* {ARGS: "+xyz"} */
+    {"NS_MODE_LOCKED", "Modes conflicting with $b%s$b are not allowed in %s."}, /* {ARGS: "+xyz", "#TestChan"} */
+    {"NS_MODE_DONE", "Channel modes are now $b%s$b."}, /* {ARGS: "+xyz"} */
+    {"NS_MODE_ENFOPS", "You may not op or deop users on $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_MODE_ENFVOICE", "You may not voice or devoice users on $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_MODE_CANBAN", "You may not ban or unban users on $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_GOD_ON", "Security override has been enabled."},
+    {"NS_GOD_OFF", "Security override has been disabled."},
+    {"NS_PEEK_HEADER", "$b%s$b Status:"}, /* {ARGS: "#TestChan"} */
+    {"NS_PEEK_TOPIC", "Topic:       %s"}, /* {ARGS: "TOPIC"} */
+    {"NS_PEEK_MODES", "Modes:       %s"}, /* {ARGS: "+xyz"} */
+    {"NS_PEEK_USERS", "Total Users: %d (%d ops, %d voices, %d regulars, %d invisible)"}, /* {ARGS: 20, 4, 6, 8, 2} */
+    {"NS_PEEK_USERS_HALFOP", "Total Users: %d (%d ops, %d halfops, %d voices, %d regulars, %d invisible)"}, /* {ARGS: 25, 5, 4, 6, 8, 2} */
+    {"NS_PEEK_OPS", "Ops:"},
+    {"NS_USET_GLOBAL", "$b--- Global ---$b"},
+    {"NS_USET_CHANNEL", "$b--- User options (channel) ---$b"},
+    {"NS_USET_NO_ACCESS", "no access"},
+    {"NS_USET_UNKNOWN_SETTING", "$b%s$b is an unknown uset setting."}, /* {ARGS: "TestSetting"} */
+    {"NS_RELOADLANG_UNKNOWN", "$b%s$b is an unknown language tag."}, /* {ARGS: "de"} */
+    {"NS_RELOADLANG_DONE", "$b%s$b (%s) reloaded."}, /* {ARGS: "Deutsch", "de"} */
+    {"NS_UNBAN_DONE", "$b%d$b masks removed from the %s ban list."}, /* {ARGS: 5, "#TestChan"} */
+    {"NS_UNBAN_FAIL", "$b%s$b could not unban some of the masks you provided."}, /* {ARGS: "NeonServ"} */
+    {"NS_UNBANALL_DONE", "all $b%d$b masks removed from the %s ban list."}, /* {ARGS: 5, "#TestChan"} */
+    {"NS_UNBANALL_FAIL", "$b%s$b could not find any bans in %s."}, /* {ARGS: "NeonServ", "#TestChan"} */
+    {"NS_UNBANME_DONE", "removed $b%d$b masks from the %s ban list."}, /* {ARGS: 5, "#TestChan"} */
+    {"NS_UNBANME_FAIL", "$b%s$b could not find any bans matching %s."}, /* {ARGS: "NeonServ", "TestUser!TestIdent@TestUser.user.WebGamesNet"} */
+    {"NS_INVITE_GLOBALLY_BLOCKED", "$b%s$b doesn't want to be invited at all."}, /* {ARGS: "TestUser"} */
+    {"NS_INVITE_RESTRICTION", "%s doesn't want to be invited to %s."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_INVITE_TIMEOUT", "%s has already been invited to $b%s$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_INVITE_ON_CHAN", "%s is already in $b%s$b."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_INVITE_DONE_USER", "You have been invited to join $b%s$b by %s. (Do $b/msg %s %1$s uset noinvite 1$b if you don't want to be invited to %1$s anymore.)"}, /* {ARGS: "#TestChan", "TestUser", "NeonServ"} */
+    {"NS_INVITE_DONE", "Invited $b%s$b to join %s."}, /* {ARGS: "TestUser", "#TestChan"} */
+    {"NS_INVITEME_ON_CHAN", "You are already in $b%s$b."}, /* {ARGS: "#TestChan"} */
+    {"NS_INVITEME_DONE", "You have been invited to join %s."}, /* {ARGS: "#TestChan"} */
+    {"NS_HELP_TOPIC", "No help on that topic."},
+    {"NS_CSUSPEND_ALREADY", "$b%s$b is already suspended."}, /* {ARGS: "#TestChan"} */
+    {"NS_CSUSPEND_DONE", "Channel $b%s$b has been temporarily suspended."}, /* {ARGS: "#TestChan"} */
+    {"NS_CUNSUSPEND_NOT", "$b%s$b is not suspended."}, /* {ARGS: "#TestChan"} */
+    {"NS_CUNSUSPEND_DONE", "Channel $b%s$b has been restored."}, /* {ARGS: "#TestChan"} */
+    {"NS_MOVE_SUSPENDED", "Moving cannot be performed if the source channel is suspended."},
+    {"NS_MOVE_SELF", "Moving cannot be performed if the source and target channels are the same."},
+    {"NS_MOVE_DONE", "Channel $b%s$b has been moved to $b%s$b."}, /* {ARGS: "#TestChan", "#NewTestChan"} */
+    {"NS_BIND_ALREADY", "$b%s$b is already bound to %s."}, /* {ARGS: "TestCommand", "TestFunction"} */
+    {"NS_BIND_UNKNOWN", "$b%s$b is an undefined function."}, /* {ARGS: "TestFunction"} */
+    {"NS_BIND_DONE", "New command $b%s$b bound to %s."}, /* {ARGS: "TestCommand", "TestFunction"} */
+    {"NS_UNBIND_NOT_FOUND", "There is no command called $b%s$b bound."}, /* {ARGS: "TestCommand"} */
+    {"NS_UNBIND_DONE", "Unbound command $b%s$b."}, /* {ARGS: "TestCommand"} */
+    {"NS_EVENTS_HEADER", "The following channel events were found:"},
+    {"NS_OPLOG_HEADER", "The following oper events were found:"},
+    {"NS_SEARCH_ZOMBIE_SCAN_IN_PROGRESS", "Another Zombie Scan is already in progress."},
+    {"NS_SEARCH_HEADER", "The following channels were found:"},
+    {"NS_COMMAND_BINDING", "$b%s$b is a binding of %s %s"}, /* {ARGS: "TestCommand", "TestFunction", "TestParameters"} */
+    {"NS_COMMAND_ACCESS", "You need at least %d channel access and %d oper access to execute this command."}, /* {ARGS: 500, 100} */
+    {"NS_TOPIC_ACCESS", "You lack sufficient access in %s to change the topic."}, /* {ARGS: "#TestChan"} */
+    {"NS_BOTWAR_DETECTED", "$b$k4BOTWAR DETECTED!$k Please check the channel configuration!$b"},
+    {"NS_BOTWAR_REPORTED", "A supporter has been informed to help you preventing botwars in the future."},
+    {"NS_BOTWAR_ALERT", "$b$k4BOTWAR ALERT:$k$b Botwar in $b%s$b detected. (opponent: $b%s$b) Please join and help them preventing Botwars."}, /* {ARGS: "#TestChan", "OtherBot"} */
+    {"NS_INVITE_FAIL", "$b%s$b is not registered with %s or suspended."}, /* {ARGS: "#TestChan", "NeonServ"} */
+    {"NS_SETACCESS_DONE", "$b%s$b has now %d global access."}, /* {ARGS: "TestUser", 1000} */
+    {"NS_ADDRANK_EXISTS", "Another support ranking called '$b%s$b' already exists."},  /* {ARGS: "Supporter"} */
+    {"NS_ADDRANK_DONE", "Support ranking '$b%s$b' created."},  /* {ARGS: "Supporter"} */
+    {"NS_DELRANK_NOT_FOUND", "There is no support ranking called '$b%s$b'."},  /* {ARGS: "Supporter"} */
+    {"NS_DELRANK_DELETED", "Support ranking called '$b%s$b' removed."},  /* {ARGS: "Supporter"} */
+    {"NS_LISTRANK_ID", "Id"},
+    {"NS_LISTRANK_NAME", "Name"},
+    {"NS_LISTRANK_ASSIGNED", "Assigned to"},
+    {"NS_LISTRANK_UNRANKED", "There are also %d unranked users with global access."},  /* {ARGS: 10} */
+    {"NS_SETRANK_NOT_FOUND", "There is no support ranking with ID '$b%s$b'."},  /* {ARGS: 42} */
+    {"NS_SETRANK_HEAD", "Support ranking settings for Id %s:"},  /* {ARGS: 42} */
+    {"NS_SETRANK_UNKNOWN_SETTING", "$b%s$b is an unknown support rank setting."},  /* {ARGS: "moep"} */
+    {"NS_SETRANK_ORDER_INVALID", "%d is an invalid numeric value. (valid: 1-99)"},  /* {ARGS: 100} */
+    {"NS_ASSIGNRANK_DONE", "$b%s$b is now ranked as '$b%s$b'."},  /* {ARGS: "TestUser", "Supporter"} */
+    {"NS_INFO_HEADER", "$b%s$b Information:"}, /* {ARGS: "#TestChan"} */
+    {"NS_INFO_DEFAULTTOPIC", "Default Topic:"},
+    {"NS_INFO_MODELOCK", "Mode Lock:"},
+    {"NS_INFO_RECORD", "Record Visitors:"},
+    {"NS_INFO_OWNER", "Owner:"},
+    {"NS_INFO_USERS", "Total User Count:"},
+    {"NS_INFO_BANS", "Ban Count:"},
+    {"NS_INFO_VISITED", "Visited:"},
+    {"NS_INFO_REGISTERED", "Registered:"},
+    {"NS_INFO_REGISTRAR", "Registered by:"},
+    {"NS_INFO_OWNERLOG", "Ownership transfer history for $b%s$b:"}, /* {ARGS: "#TestChan"} */
+    {"NS_INFO_OWNERCHANGE", " from %s to %s on %s"},
+    {"NS_RENAME_DONE", "Renamed $b%s$b to $b%s$b."}, /* {ARGS: "TestUser", "TestUser2"} */
+    {"NS_RENAME_FAIL", "Failed renaming $b%s$b."}, /* {ARGS: "TestUser"} */
+    {"NS_FUN_DISABLED", "Fun commands are disabled in %s."}, /* {ARGS: "#TestChan"} */
+    {"NS_UNBIND_REQUIRED", "%1$s is a required function and there is no other command bound to %1$s. Bind another command to %1$s first."}, /* {ARGS: "bind"} */
+    {"NS_COMMANDS_NAME", "Name"},
+    {"NS_COMMANDS_ACCESS", "Access"},
+    {"NS_COMMANDS_GACCESS", "GodAccess"},
+    {"NS_COMMANDS_TRIGGERED", "Triggered"},
+    {"NS_COMMANDS_FUNCTION", "Function"},
+    {"NS_DNR_SET", "$b%s$b is do-not-register (by $b%s$b): %s" }, /* {ARGS: "#TestChan", "TestUser", "because of it is like it is"} */
+    {"NS_DNR_SET_EXPIRES", "$b%s$b is do-not-register (by $b%s$b; expires %s): %s" }, /* {ARGS: "#TestChan", "TestUser", "1 day", "because of it is like it is"} */
+    {"NS_DNR_SET_ANONYM", "$b%s$b is do-not-register. Please contact the support to get more information."}, /* {ARGS: "TestUser"} */
+    {"NS_NOREGISTER_INVALID_DURATION", "$b%s$b is not a valid duration."}, /* {ARGS: "möp"} */
+    {"NS_NOREGISTER_REGISTERED", "$b%s$b is currently registered and can't be added to the do-not-register list."}, /* {ARGS: "#TestChan"} */
+    {"NS_NOREGISTER_DONE", "added $b%s$b to the do-not-register list."}, /* {ARGS: "#TestChan"} */
+    {"NS_NOREGISTER_HEAD", "The following do-not-registers were found:"},
+    {"NS_DNR_TARGET", "Target"},
+    {"NS_DNR_USER", "Issuer"},
+    {"NS_DNR_EXPIRES", "Expires"},
+    {"NS_DNR_REASON", "Reason"},
+    {"NS_STAFF_LOGGEDIN", "Logged in as"},
+    {"NS_BOTS_ID", "Id"},
+    {"NS_BOTS_NICK", "Nick"},
+    {"NS_BOTS_SERVER", "Server:Port(:Pass)"},
+    {"NS_BOTS_CLASS", "Bot Class"},
+    {"NS_BOTS_FLAGS", "Flags"},
+    {"NS_BOTS_CHANNELS", "Channels"},
+    {"NS_BOTS_TRIGGER", "Trigger"},
+    {"NS_NICKLIST_NICK", "Nick"},
+    {"NS_NICKLIST_STATE", "State"},
+    {"NS_NICKLIST_ACCESS", "Access"},
+    {"NS_NICKLIST_SYNC", "use `nicklist sync` to fix all red and orange entrys in the list above (add opped users with %d and voiced with %d access)"},
+    {"NS_NICKLIST_ACCESS_BOT", "Bot"},
+    {"NS_NICKLIST_ACCESS_OPER", "Operator"},
+    {"NS_SETBOT_UNKNOWN", "`%d` is an unknown botid."}, /* {ARGS: 50} */
+    {"NS_SETBOT_HEADER", "$bSettings for botid `%d`:$b"}, /* {ARGS: 50} */
+    {"NS_SETBOT_SETTING", "$b%s$b is an unknown bot setting."}, /* {ARGS: "strangeSetting"} */
+    {"NS_SETBOT_NICK_INVALID", "`%s` is an invalid botnick."}, /* {ARGS: "-SuperMagicBananaBotWithManyFunctions"} */
+    {"NS_SETBOT_NEED_RESTART", "You need to reconnect the bot to apply this setting."},
+    {"NS_SETBOT_PORT_INVALID", "`%s` is an invalid port number."}, /* {ARGS: "-1"} */
+    {"NS_SETBOT_INVALID_CLASS", "`%s` is an invalid botclass."}, /* {ARGS: "MistColaLeer"} */
+    {"NS_SETBOT_MAXCHAN_INVALID", "`%s` is an invalid maxchan value."}, /* {ARGS: "-1"} */
+    {"NS_SETBOT_PRIORITY_INVALID", "`%s` is an invalid priority value."}, /* {ARGS: "-1"} */
+    {"NS_SETBOT_TRIGGER_INVALID", "`%s` is an invalid bot trigger."}, /* {ARGS: "tooLongTrigger"} */
+    {"NS_SETBOT_TRIGGER_NOTE", "Please note: This Setting will only affect new channels."},
+    {"NS_ADDBOT_EXISTING", "A bot with nick %s does already exist."}, /* {ARGS: "NeonServ"} */
+    {"NS_ADDBOT_DONE", "Added %s with BotID $b%d$b."}, /* {ARGS: "NeonServ", 2} */
+    {"NS_DELBOT_NOT_FOUND", "Bot with BotID / nick $b%s$b not found."}, /* {ARGS: "NeonServ"} */
+    {"NS_DELBOT_DONE", "Bot deleted."},
+    {"NS_RECONNECT_DONE", "Reconnected bot."},
+    {"NS_MODCMD_SETTING", "$b%s$b is an unknown modcmd setting."}, /* {ARGS: "strangeSetting"} */
+    {"NS_MODCMD_HEADER", "$bSettings for command %s:$b"}, /* {ARGS: "access"} */
+    {"NS_MODCMD_OUTRANKED", "$b%s$b outranks you. (required access: %d)"}, /* {ARGS: "die", 1000} */
+    {"NS_MODCMD_STATIC_FLAG", "This Flag is added statically. It can't be modified manually."},
+    {"NS_MEMINFO_DISABLED", "Memory Debugger is disabled!"},
+    {"NS_MEMINFO_NAME", "Name"},
+    {"NS_MEMINFO_COUNT", "Count"},
+    {"NS_MEMINFO_SIZE", "Size"},
+    {"NS_MEMINFO_LINE", "Line"},
+    {"NS_MEMINFO_TOTAL", "Total"},
+    {NULL, NULL}
+};
+
+/* TODO:
+cmd_neonserv_open.c
+set modelock
+cmd_neonserv_modcmd.c
+cmd_neonserv_allowregister.c
+cmd_neonserv_noregister.c
+cmd_neonserv_expire.c
+cmd_neonserv_unvisited.c
+cmd_neonserv_merge.c
+cmd_neonserv_dnrsearch.c
+cmd_neonserv_iplocate.c
+cmd_neonserv_calc.c
+*/
+//EVENTS
+#include "event_neonserv_join.c"
+#include "event_neonserv_part.c"
+#include "event_neonserv_kick.c"
+#include "event_neonserv_mode.c"
+#include "event_neonserv_ctcp.c"
+#include "event_neonserv_notice.c"
+#include "event_neonserv_invite.c"
+#include "event_neonserv_topic.c"
+
+
+struct ClientSocket *getBotForChannel(struct ChanNode *chan) {
+    return getChannelBot(chan, BOTID);
+}
+
+static void neonserv_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonserv_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "+");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "+");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "~"));
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_REQUEST_INVITE | SOCKET_FLAG_REQUEST_OP;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+void init_NeonServ(int type) {
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonserv_bot_ready, module_id);
+    bind_join(neonserv_event_join, module_id);
+    bind_part(neonserv_event_part, module_id);
+    bind_chanctcp(neonserv_event_chanctcp, module_id);
+    bind_privctcp(general_event_privctcp, module_id);
+    bind_channotice(neonserv_event_channotice, module_id);
+    bind_topic(neonserv_event_topic, module_id);
+    bind_invite(neonserv_event_invite, module_id);
+    bind_mode(neonserv_event_mode, module_id);
+    bind_kick(neonserv_event_kick, module_id);
+    
+    set_trigger_callback(BOTID, module_id, neonserv_trigger_callback);
+    
+    register_default_language_table(msgtab);
+}
+
+void free_NeonServ(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonServ.mod/bot_NeonServ.h b/src/modules/NeonServ.mod/bot_NeonServ.h
new file mode 100644 (file)
index 0000000..1f3f2cc
--- /dev/null
@@ -0,0 +1,29 @@
+/* bot_NeonServ.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _bot_NeonServ_h
+#define _bot_NeonServ_h
+
+#include "../../main.h"
+
+struct ChanNode;
+
+void init_NeonServ(int type);
+void free_NeonServ(int type);
+
+struct ClientSocket *getBotForChannel(struct ChanNode *chan);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonServ.mod/cmd_neonserv.c b/src/modules/NeonServ.mod/cmd_neonserv.c
new file mode 100644 (file)
index 0000000..62750a4
--- /dev/null
@@ -0,0 +1,104 @@
+/* cmd_neonserv.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_neonserv.h"
+#include "../../modcmd.h"
+#include "../../ConfigParser.h"
+
+void register_commands() {
+    //NeonServ Commands
+    register_command_alias(1, "NeonServ");
+    
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,PRIVS,FLAGS) register_command(1, NAME, module_id, FUNCTION, PARAMCOUNT, PRIVS, 0, FLAGS)
+    //               NAME              FUNCTION        PARAMS     PRIVS                FLAGS
+    USER_COMMAND("adduser",      neonserv_cmd_adduser,   2, "#channel_canadd",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("deluser",      neonserv_cmd_deluser,   1, "#channel_candel",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("clvl",         neonserv_cmd_clvl,      2, "#channel_canclvl",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("access",       neonserv_cmd_access,    0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_LOG);
+    USER_COMMAND("users",        neonserv_cmd_users,     0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_NO_CROSSCHAN);
+    USER_COMMAND("suspend",      neonserv_cmd_suspend,   1, "#channel_cansuspend",  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("unsuspend",    neonserv_cmd_unsuspend, 1, "#channel_cansuspend",  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("delme",        neonserv_cmd_delme,     0,  "1",                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("myaccess",     neonserv_cmd_myaccess,  0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("up",           neonserv_cmd_up,        0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("down",         neonserv_cmd_down,      0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_LOG);
+    USER_COMMAND("upall",        neonserv_cmd_upall,     0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("downall",      neonserv_cmd_downall,   0, NULL,                   CMDFLAG_LOG);
+    USER_COMMAND("mdeluser",     neonserv_cmd_mdeluser,  2, "#channel_candel",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("trim",         neonserv_cmd_trim,      2, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("giveowner",    neonserv_cmd_giveowner, 1, "500",                  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("op",           neonserv_cmd_op,        1, "@#channel_getop,#channel_canop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("deop",         neonserv_cmd_deop,      1, "@#channel_getop,#channel_canop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("voice",        neonserv_cmd_voice,     1, "+#channel_getvoice,#channel_canvoice", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("devoice",      neonserv_cmd_devoice,   1, "+#channel_getvoice,#channel_canvoice", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("opall",        neonserv_cmd_opall,     0, "@#channel_getop,#channel_canop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("deopall",      neonserv_cmd_deopall,   0, "@#channel_getop,#channel_canop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("voiceall",     neonserv_cmd_voiceall,  0, "+#channel_getvoice,#channel_canvoice", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("devoiceall",   neonserv_cmd_devoiceall,0, "+#channel_getvoice,#channel_canvoice", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    if(get_int_field("General.have_halfop")) {
+    USER_COMMAND("halfop",       neonserv_cmd_halfop,    1, "%#channel_gethalfop,#channel_canhalfop", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("dehalfop",     neonserv_cmd_dehalfop,  1, "%#channel_gethalfop,#channel_canhalfop", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("halfopall",    neonserv_cmd_halfopall, 0, "%#channel_gethalfop,#channel_canhalfop", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("dehalfopall",  neonserv_cmd_dehalfopall,0,"%#channel_gethalfop,#channel_canhalfop", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    }
+    USER_COMMAND("set",          neonserv_cmd_set,       0, "#channel_setters",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("kick",         neonserv_cmd_kick,      1, "#channel_cankick",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("kickban",      neonserv_cmd_kickban,   1, "#channel_cankick,#channel_canban", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("ban",          neonserv_cmd_ban,       1, "#channel_canban",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("wipeinfo",     neonserv_cmd_wipeinfo,  1, "#channel_wipeinfo",    CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("addban",       neonserv_cmd_addban,    1, "#channel_staticban",   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("bans",         neonserv_cmd_bans,      0, "1",                    CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("delban",       neonserv_cmd_delban,    1, "#channel_staticban",   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("topic",        neonserv_cmd_topic,     0, "#channel_changetopic", CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("chanservsync", neonserv_cmd_chanservsync, 0,"500",                CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("resync",       neonserv_cmd_resync,    0, "#channel_canresync",   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("addtimeban",   neonserv_cmd_addtimeban,2, "#channel_staticban",   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("mode",         neonserv_cmd_mode,      1, "#channel_getop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("peek",         neonserv_cmd_peek,      0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_NO_CROSSCHAN);
+    USER_COMMAND("uset",         neonserv_cmd_uset,      0, NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_NO_CROSSCHAN);
+    USER_COMMAND("unban",        neonserv_cmd_unban,     1, "#channel_canban",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("unbanall",     neonserv_cmd_unbanall,  0, "#channel_canban",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("unbanme",      neonserv_cmd_unbanme,   0, "#channel_canban",      CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("invite",       neonserv_cmd_invite,    1, "#channel_canop",       CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("inviteme",     neonserv_cmd_inviteme,  0, "#channel_getinvite",   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("invitemeall",  neonserv_cmd_invitemeall,0,NULL,                   CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("help",         neonserv_cmd_help,      0, NULL,                   0);
+    USER_COMMAND("events",       neonserv_cmd_events,    0, "1",                    CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    USER_COMMAND("info",         neonserv_cmd_info,      0, NULL,                   CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_NO_CROSSCHAN);
+    USER_COMMAND("nicklist",     neonserv_cmd_nicklist,  0, "1",                    CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    #undef USER_COMMAND
+    
+    #define OPER_COMMAND(NAME,FUNCTION,PARAMCOUNT,GACCESS,FLAGS) register_command(1, NAME, module_id, FUNCTION, PARAMCOUNT, NULL, GACCESS, FLAGS)
+    //            NAME            FUNCTION              PARAMS  ACCS  FLAGS
+    OPER_COMMAND("trace",        neonserv_cmd_trace,     1,     400,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    OPER_COMMAND("recover",      neonserv_cmd_recover,   1,     200,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("csuspend",     neonserv_cmd_csuspend,  1,     100,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("cunsuspend",   neonserv_cmd_cunsuspend,1,     100,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("oplog",        neonserv_cmd_oplog,     0,     1,    CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("search",       neonserv_cmd_search,    1,     400,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    OPER_COMMAND("addrank",      neonserv_cmd_addrank,   1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("delrank",      neonserv_cmd_delrank,   1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("setrank",      neonserv_cmd_setrank,   1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("assignrank",   neonserv_cmd_assignrank,2,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("listrank",     neonserv_cmd_listrank,  0,     1,    CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("rename",       neonserv_cmd_rename,    2,     300,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("unvisited",    neonserv_cmd_unvisited, 0,     400,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("noregister",   neonserv_cmd_noregister,0,     300,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    #undef OPER_COMMAND
+    
+    neonserv_cmd_unvisited_init();
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv.h b/src/modules/NeonServ.mod/cmd_neonserv.h
new file mode 100644 (file)
index 0000000..da09147
--- /dev/null
@@ -0,0 +1,117 @@
+/* cmd_neonserv.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_neonserv_h
+#define _cmd_neonserv_h
+#include "../module.h"
+#include "../botid.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+#include "bot_NeonServ.h"
+#include "../../ConfigParser.h"
+
+void register_commands();
+
+/* cmd_neonserv_unvisited.c */
+void neonserv_cmd_unvisited_init();
+
+CMD_BIND(neonserv_cmd_access);
+CMD_BIND(neonserv_cmd_addban);
+CMD_BIND(neonserv_cmd_addrank);
+CMD_BIND(neonserv_cmd_addtimeban);
+CMD_BIND(neonserv_cmd_adduser);
+CMD_BIND(neonserv_cmd_assignrank);
+CMD_BIND(neonserv_cmd_ban);
+CMD_BIND(neonserv_cmd_bans);
+CMD_BIND(neonserv_cmd_chanservsync);
+CMD_BIND(neonserv_cmd_clvl);
+CMD_BIND(neonserv_cmd_csuspend);
+CMD_BIND(neonserv_cmd_cunsuspend);
+CMD_BIND(neonserv_cmd_dehalfop);
+CMD_BIND(neonserv_cmd_dehalfopall);
+CMD_BIND(neonserv_cmd_delban);
+CMD_BIND(neonserv_cmd_delme);
+CMD_BIND(neonserv_cmd_delrank);
+CMD_BIND(neonserv_cmd_deluser);
+CMD_BIND(neonserv_cmd_deop);
+CMD_BIND(neonserv_cmd_deopall);
+CMD_BIND(neonserv_cmd_devoice);
+CMD_BIND(neonserv_cmd_devoiceall);
+CMD_BIND(neonserv_cmd_down);
+CMD_BIND(neonserv_cmd_downall);
+CMD_BIND(neonserv_cmd_events);
+CMD_BIND(neonserv_cmd_giveowner);
+CMD_BIND(neonserv_cmd_halfop);
+CMD_BIND(neonserv_cmd_halfopall);
+CMD_BIND(neonserv_cmd_help);
+CMD_BIND(neonserv_cmd_info);
+CMD_BIND(neonserv_cmd_invite);
+CMD_BIND(neonserv_cmd_inviteme);
+CMD_BIND(neonserv_cmd_invitemeall);
+CMD_BIND(neonserv_cmd_kick);
+CMD_BIND(neonserv_cmd_kickban);
+CMD_BIND(neonserv_cmd_listrank);
+CMD_BIND(neonserv_cmd_mdeluser);
+CMD_BIND(neonserv_cmd_mode);
+CMD_BIND(neonserv_cmd_myaccess);
+CMD_BIND(neonserv_cmd_nicklist);
+CMD_BIND(neonserv_cmd_noregister);
+CMD_BIND(neonserv_cmd_op);
+CMD_BIND(neonserv_cmd_opall);
+CMD_BIND(neonserv_cmd_oplog);
+CMD_BIND(neonserv_cmd_peek);
+CMD_BIND(neonserv_cmd_recover);
+CMD_BIND(neonserv_cmd_rename);
+CMD_BIND(neonserv_cmd_resync);
+CMD_BIND(neonserv_cmd_search);
+CMD_BIND(neonserv_cmd_set);
+CMD_BIND(neonserv_cmd_setrank);
+CMD_BIND(neonserv_cmd_suspend);
+CMD_BIND(neonserv_cmd_topic);
+CMD_BIND(neonserv_cmd_trace);
+CMD_BIND(neonserv_cmd_trim);
+CMD_BIND(neonserv_cmd_unban);
+CMD_BIND(neonserv_cmd_unbanall);
+CMD_BIND(neonserv_cmd_unbanme);
+CMD_BIND(neonserv_cmd_unsuspend);
+CMD_BIND(neonserv_cmd_unvisited);
+CMD_BIND(neonserv_cmd_up);
+CMD_BIND(neonserv_cmd_upall);
+CMD_BIND(neonserv_cmd_users);
+CMD_BIND(neonserv_cmd_uset);
+CMD_BIND(neonserv_cmd_voice);
+CMD_BIND(neonserv_cmd_voiceall);
+CMD_BIND(neonserv_cmd_wipeinfo);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_access.c b/src/modules/NeonServ.mod/cmd_neonserv_access.c
new file mode 100644 (file)
index 0000000..ce84cd4
--- /dev/null
@@ -0,0 +1,173 @@
+/* cmd_neonserv_access.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_access_nick_lookup);
+static void neonserv_cmd_access_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *nick, char *auth, struct UserNode *target);
+static void neonserv_cmd_access_filter_ranking_info(char *info, char *buffer, char *nick, char *auth, char *rank_name);
+
+struct neonserv_cmd_access_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_access) {
+    if(argc == 0) {
+        if(!(user->flags & USERFLAG_ISAUTHED)) {
+            struct neonserv_cmd_access_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->nick = strdup(user->nick);
+            get_userauth(user, module_id, neonserv_cmd_access_nick_lookup, cache);
+        } else
+            neonserv_cmd_access_async1(client, textclient, user, chan, user->nick, user->auth, user);
+    }
+    else if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_access_async1(client, textclient, user, chan, NULL, argv[0], NULL);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+            if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_access_async1(client, textclient, user, chan, argv[0], cuser->auth, cuser);
+        } else {
+            struct neonserv_cmd_access_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_access_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_access_nick_lookup) {
+    struct neonserv_cmd_access_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        if(!strcmp(cache->nick, cache->user->nick))
+            reply(cache->textclient, cache->user, "NS_YOU_NEED_AUTH");
+        else
+            reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_access_async1(cache->client, cache->textclient, cache->user, cache->chan, user->nick, user->auth, user);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_access_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *nick, char *auth, struct UserNode *target) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW user_row, chanuser_row;
+    int userid;
+    printf_mysql_query("SELECT `user_id`, `user_access`, `user_god`, `user_rank`, `rank_info`, `rank_name` FROM `users` LEFT JOIN `support_ranks` ON `rank_id` = `user_rank` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((user_row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(user_row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `chanuser_infoline` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if(strcmp(user_row[3], "0") && strcmp(user_row[4], "")) {
+            char rank_info[MAXLEN];
+            neonserv_cmd_access_filter_ranking_info(user_row[4], rank_info, nick, auth, user_row[5]);
+            reply(textclient, user, "%s", rank_info);
+        }
+        if ((chanuser_row = mysql_fetch_row(res)) != NULL) {
+            //access output
+            if(nick)
+                reply(textclient, user, (strcmp(user_row[2], "1") ? "NS_A_ACCESS_AUTH" : "NS_A_ACCESS_AUTH_GOD"), nick, auth, atoi(chanuser_row[0]), chan->name);
+            else
+                reply(textclient, user, (strcmp(user_row[2], "1") ? "NS_A_ACCESS_NICK" : "NS_A_ACCESS_NICK_GOD"), auth, atoi(chanuser_row[0]), chan->name);
+            int cflags = atoi(chanuser_row[1]);
+            if(cflags & DB_CHANUSER_SUSPENDED)
+                reply(textclient, user, "NS_A_SUSPENDED", (nick ? nick : auth), chan->name);
+            if(chanuser_row[2] && strcmp(chanuser_row[2], ""))
+                reply(textclient, user, "[%s] %s", (nick ? nick : auth), chanuser_row[2]);
+        } else if(!strcmp(user_row[2], "1")) {
+            if(nick)
+                reply(textclient, user, "NS_A_LACKS_ACCESS_BUT_GOD_AUTH", nick, auth, chan->name);
+            else
+                reply(textclient, user, "NS_A_LACKS_ACCESS_BUT_GOD_NICK", auth, chan->name);
+        } else
+            reply(textclient, user, "NS_NOT_ON_USERLIST", (nick ? nick : auth), chan->name);
+    } else
+        reply(textclient, user, "NS_NOT_ON_USERLIST", (nick ? nick : auth), chan->name);
+    if(target && (target->flags & USERFLAG_ISIRCOP))
+        reply(textclient, user, "NS_A_IS_IRCOP", nick);
+}
+
+static void neonserv_cmd_access_filter_ranking_info(char *info, char *buffer, char *nick, char *auth, char *rank_name) {
+    int bufferPos = 0;
+    char *a, *b = info;
+    do {
+        if(!b) break;
+        a = strstr(b, "$");
+        if(a) *a = '\0';
+        bufferPos += sprintf(buffer + bufferPos, "%s", b);
+        if(!a) break;
+        switch(a[1]) {
+            case '\0':
+                a = NULL;
+                break;
+            case 'U':
+                bufferPos += sprintf(buffer + bufferPos, "%s", auth);
+                break;
+            case 'N':
+                bufferPos += sprintf(buffer + bufferPos, "%s", (nick ? nick : auth));
+                break;
+            case 'R':
+                bufferPos += sprintf(buffer + bufferPos, "%s", rank_name);
+                break;
+            default:
+                buffer[bufferPos++] = '$';
+                buffer[bufferPos++] = a[1];
+                break;
+        }
+        if(a)
+            b = a+2;
+    } while(a);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_addban.c b/src/modules/NeonServ.mod/cmd_neonserv_addban.c
new file mode 100644 (file)
index 0000000..63e8224
--- /dev/null
@@ -0,0 +1,128 @@
+/* cmd_neonserv_addban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    nick|*auth|*!*@mask
+* argv[1-*]  reason
+*/
+static USERLIST_CALLBACK(neonserv_cmd_addban_userlist_lookup);
+static void neonserv_cmd_addban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mas, char *reason);
+
+struct neonserv_cmd_addban_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *mask;
+    char *reason;
+};
+
+CMD_BIND(neonserv_cmd_addban) {
+    struct neonserv_cmd_addban_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->mask = strdup(argv[0]);
+    if(argc > 1) {
+        cache->reason = strdup(merge_argv(argv, 1, argc));
+    } else
+        cache->reason = NULL;
+    get_userlist(chan, module_id, neonserv_cmd_addban_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_addban_userlist_lookup) {
+    struct neonserv_cmd_addban_cache *cache = data;
+    neonserv_cmd_addban_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->mask, (cache->reason ? cache->reason : "Bye."));
+    free(cache->mask);
+    if(cache->reason)
+        free(cache->reason);
+    free(cache);
+}
+
+static void neonserv_cmd_addban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mask, char *reason) {
+    int match_count = 0;
+    char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    mask = make_banmask(mask, hostmask_buffer);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        cuser = chanuser->user;
+        sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+        if(!match(mask, usermask)) {
+            if(isNetworkService(chanuser->user)) {
+                reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                return;
+            }
+            if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                reply(textclient, user, "NS_YOU_PROTECTED");
+                return;
+            }
+            if(isUserProtected(chan, cuser, user)) {
+                reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                return;
+            }
+            match_count++;
+            if(match_count > 4 && (match_count * 3) > chan->usercount && !isGodMode(user)) {
+                reply(textclient, user, "NS_LAME_MASK", mask);
+                return;
+            }
+        }
+    }
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    //check if the provided mask is already banned by another ban
+    char *ban = getBanAffectingMask(chan, mask);
+    if(ban != NULL) {
+        reply(textclient, user, "NS_BAN_ALREADY_ADDED", mask, chan->name);
+        return;
+    }
+    //check if the provided mask affects any existing bans
+    printf_mysql_query("SELECT `ban_mask`, `ban_id` FROM `bans` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!match(mask, row[0])) {
+            //remove the ban
+            printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[1]);
+        }
+    }
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    int userid;
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL)
+        userid = atoi(row[0]);
+    else
+        return;
+    //add the ban
+    printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%d', '%s')", chan->channel_id, escape_string(mask), userid, escape_string(reason));
+    putsock(client, "MODE %s +b %s", chan->name, mask);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        cuser = chanuser->user;
+        sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+        if(!match(mask, usermask)) {
+            putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+        }
+    }
+    reply(textclient, user, "NS_ADDBAN_DONE", mask, chan->name, match_count);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_addrank.c b/src/modules/NeonServ.mod/cmd_neonserv_addrank.c
new file mode 100644 (file)
index 0000000..a049bbd
--- /dev/null
@@ -0,0 +1,36 @@
+/* cmd_neonserv_addrank.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]   rank name
+*/
+
+CMD_BIND(neonserv_cmd_addrank) {
+    char *name = merge_argv(argv, 0, argc);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `rank_name` FROM `support_ranks` WHERE `rank_name` = '%s'", escape_string(name));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        reply(textclient, user, "NS_ADDRANK_EXISTS", row[0]);
+        return;
+    }
+    printf_mysql_query("INSERT INTO `support_ranks` (`rank_name`) VALUES ('%s')", escape_string(name));
+    reply(textclient, user, "NS_ADDRANK_DONE", name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_addtimeban.c b/src/modules/NeonServ.mod/cmd_neonserv_addtimeban.c
new file mode 100644 (file)
index 0000000..2e096f9
--- /dev/null
@@ -0,0 +1,140 @@
+/* cmd_neonserv_addtimeban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    nick|*auth|*!*@mask
+* argv[1]    time
+* argv[2-*]  reason
+*/
+static USERLIST_CALLBACK(neonserv_cmd_addtimeban_userlist_lookup);
+static void neonserv_cmd_addtimeban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mask, int duration, char *reason);
+
+struct neonserv_cmd_addtimeban_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *mask;
+    int duration;
+    char *reason;
+};
+
+CMD_BIND(neonserv_cmd_addtimeban) {
+    int duration = strToTime(user, argv[1]);
+    if(duration < 5) {
+        reply(textclient, user, "NS_TIMEBAN_DURATION_TOO_SHORT", 5);
+        return;
+    }
+    struct neonserv_cmd_addtimeban_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->mask = strdup(argv[0]);
+    cache->duration = duration;
+    if(argc > 2) {
+        cache->reason = strdup(merge_argv(argv, 2, argc));
+    } else
+        cache->reason = NULL;
+    get_userlist(chan, module_id, neonserv_cmd_addtimeban_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_addtimeban_userlist_lookup) {
+    struct neonserv_cmd_addtimeban_cache *cache = data;
+    neonserv_cmd_addtimeban_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->mask, cache->duration, (cache->reason ? cache->reason : "Bye."));
+    free(cache->mask);
+    if(cache->reason)
+        free(cache->reason);
+    free(cache);
+}
+
+static void neonserv_cmd_addtimeban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mask, int duration, char *reason) {
+    int match_count = 0;
+    char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    mask = make_banmask(mask, hostmask_buffer);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        cuser = chanuser->user;
+        sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+        if(!match(mask, usermask)) {
+            if(isNetworkService(chanuser->user)) {
+                reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                return;
+            }
+            if(isUserProtected(chan, cuser, user)) {
+                reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                return;
+            }
+            match_count++;
+            if(match_count > 4 && (match_count * 3) > chan->usercount && !isGodMode(user)) {
+                reply(textclient, user, "NS_LAME_MASK", mask);
+                return;
+            }
+        }
+    }
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    //check if the provided mask is already banned by another ban
+    char *ban = getBanAffectingMask(chan, mask);
+    if(ban != NULL) {
+        reply(textclient, user, "NS_BAN_ALREADY_ADDED", mask, chan->name);
+        return;
+    }
+    //check if the provided mask affects any existing bans
+    printf_mysql_query("SELECT `ban_mask`, `ban_id` FROM `bans` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!match(mask, row[0])) {
+            //remove the ban
+            printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[1]);
+        }
+    }
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    int userid;
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL)
+        userid = atoi(row[0]);
+    else
+        return;
+    //add the ban
+    printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chan->channel_id, escape_string(mask), (unsigned long) (time(0) + duration), userid, escape_string(reason));
+    int banid = (int) mysql_insert_id(get_mysql_conn());
+    putsock(client, "MODE %s +b %s", chan->name, mask);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        cuser = chanuser->user;
+        sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+        if(!match(mask, usermask)) {
+            putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+        }
+    }
+    char nameBuf[MAXLEN];
+    if(duration < 86400*7) {
+        char banidBuf[20];
+        sprintf(nameBuf, "ban_%d", banid);
+        sprintf(banidBuf, "%d", banid);
+        timeq_add_name(nameBuf, duration, module_id, channel_ban_timeout, strdup(banidBuf));
+    }
+    reply(textclient, user, "NS_TIMEBAN_DONE", mask, chan->name, timeToStr(user, duration, 2, nameBuf), match_count);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_adduser.c b/src/modules/NeonServ.mod/cmd_neonserv_adduser.c
new file mode 100644 (file)
index 0000000..e8b14bd
--- /dev/null
@@ -0,0 +1,159 @@
+/* cmd_neonserv_adduser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+* argv[1] - chan access
+*/
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_adduser_auth_lookup);
+static USERAUTH_CALLBACK(neonserv_cmd_adduser_nick_lookup);
+static void neonserv_cmd_adduser_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, int access);
+
+struct neonserv_cmd_adduser_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    int access;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_adduser) {
+    int caccess;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    caccess = atoi(argv[1]);
+    if(caccess <= 0 || caccess > 500) {
+        reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+        return;
+    }
+    if(caccess >= getChannelAccess(user, chan)) {
+        if(isGodMode(user)) {
+            event->flags |= CMDFLAG_OPLOG;
+        } else {
+            reply(textclient, user, "NS_ACCESS_OUTRANKED");
+            return;
+        }
+    }
+    //check own access
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_user` = '%s'", escape_string(argv[0]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            neonserv_cmd_adduser_async1(client, textclient, user, chan, event, argv[0], row[0], caccess);
+        } else {
+            //we need to create a new user...
+            //but first lookup the auth to check if it really exists
+            struct neonserv_cmd_adduser_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->access = caccess;
+            cache->nick = strdup(argv[0]);
+            lookup_authname(argv[0], module_id, neonserv_cmd_adduser_auth_lookup, cache);
+        }
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_adduser_async1(client, textclient, user, chan, event, argv[0], cuser->auth, caccess);
+        } else {
+            struct neonserv_cmd_adduser_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->access = caccess;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_adduser_nick_lookup, cache);
+        }
+    }
+}
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_adduser_auth_lookup) {
+    struct neonserv_cmd_adduser_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->nick);
+    } else
+        neonserv_cmd_adduser_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, cache->nick, auth, cache->access);
+    free(cache->nick);
+    free(cache);
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_adduser_nick_lookup) {
+    struct neonserv_cmd_adduser_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_adduser_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth, cache->access);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_adduser_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, int caccess) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            reply(textclient, user, "NS_ADDUSER_ALREADY_ADDED", nick, chan->name, atoi(row[0]));
+            return;
+        }
+    } else {
+        printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(auth));
+        userid = (int) mysql_insert_id(get_mysql_conn());
+    }
+    printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`) VALUES ('%d', '%d', '%d')", chan->channel_id, userid, caccess);
+    reply(textclient, user, "NS_ADDUSER_DONE", nick, chan->name, caccess);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_assignrank.c b/src/modules/NeonServ.mod/cmd_neonserv_assignrank.c
new file mode 100644 (file)
index 0000000..19a906a
--- /dev/null
@@ -0,0 +1,151 @@
+/* cmd_neonserv_assignrank.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+* argv[1-*] - rank name
+*/
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_assignrank_auth_lookup);
+static USERAUTH_CALLBACK(neonserv_cmd_assignrank_nick_lookup);
+static void neonserv_cmd_assignrank_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *nick, char *auth, int rank_id);
+
+struct neonserv_cmd_assignrank_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    int rank_id;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_assignrank) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *name = merge_argv(argv, 1, argc);
+    int rank_id = 0;
+    if(stricmp(name, "*") && stricmp(name, "user") && stricmp(name, "none")) {
+        printf_mysql_query("SELECT `rank_id`, `rank_name` FROM `support_ranks` WHERE `rank_name` = '%s'", escape_string(name));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) == NULL) {
+            reply(textclient, user, "NS_DELRANK_NOT_FOUND", name);
+            return;
+        }
+        rank_id = atoi(row[0]);
+    }
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_user` = '%s'", escape_string(argv[0]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            neonserv_cmd_assignrank_async1(client, textclient, user, event, argv[0], row[0], rank_id);
+        } else {
+            //we need to create a new user...
+            //but first lookup the auth to check if it really exists
+            struct neonserv_cmd_assignrank_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->event = event;
+            cache->rank_id = rank_id;
+            cache->nick = strdup(argv[0]);
+            lookup_authname(argv[0], module_id, neonserv_cmd_assignrank_auth_lookup, cache);
+        }
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_assignrank_async1(client, textclient, user, event, argv[0], cuser->auth, rank_id);
+        } else {
+            struct neonserv_cmd_assignrank_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->event = event;
+            cache->rank_id = rank_id;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_assignrank_nick_lookup, cache);
+        }
+    }
+}
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_assignrank_auth_lookup) {
+    struct neonserv_cmd_assignrank_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->nick);
+    } else
+        neonserv_cmd_assignrank_async1(cache->client, cache->textclient, cache->user, cache->event, cache->nick, auth, cache->rank_id);
+    free(cache->nick);
+    free(cache);
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_assignrank_nick_lookup) {
+    struct neonserv_cmd_assignrank_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_assignrank_async1(cache->client, cache->textclient, cache->user, cache->event, user->nick, user->auth, cache->rank_id);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_assignrank_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *nick, char *auth, int rank_id) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row, rank = NULL;
+    int caccess = 0;
+    if(rank_id) {
+        printf_mysql_query("SELECT `rank_access`, `rank_name` FROM `support_ranks` WHERE `rank_id` = '%d'", rank_id);
+        res = mysql_use();
+        rank = mysql_fetch_row(res);
+        caccess = atoi(rank[0]);
+    }
+    printf_mysql_query("SELECT `user_id`, `user_rank` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(atoi(row[1]) != rank_id) {
+            printf_mysql_query("UPDATE `users` SET `user_access` = '%d', `user_rank` = '%d' WHERE `user_id` = '%s'", caccess, rank_id, row[0]);
+        }
+    } else if(rank_id) {
+        printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`, `user_rank`) VALUES ('%s', '%d', '%d')", escape_string(auth), caccess, rank_id);
+    }
+    reply(textclient, user, "NS_ASSIGNRANK_DONE", auth, (rank_id ? rank[1] : "*"));
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_ban.c b/src/modules/NeonServ.mod/cmd_neonserv_ban.c
new file mode 100644 (file)
index 0000000..2a419eb
--- /dev/null
@@ -0,0 +1,122 @@
+/* cmd_neonserv_ban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nick[,*auth[,*!*@mask[...]]]
+*/
+static USERLIST_CALLBACK(neonserv_cmd_ban_userlist_lookup);
+static void neonserv_cmd_ban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *masks);
+
+struct neonserv_cmd_ban_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *masks;
+};
+
+CMD_BIND(neonserv_cmd_ban) {
+    struct neonserv_cmd_ban_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->masks = strdup(merge_argv_char(argv, 0, argc, ','));
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_ban_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_ban_userlist_lookup) {
+    struct neonserv_cmd_ban_cache *cache = data;
+    neonserv_cmd_ban_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->masks);
+    free(cache->masks);
+    free(cache);
+}
+
+static void neonserv_cmd_ban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *masks) {
+    int done_masks = 0, provided_masks = 0, skip, match_count, total_match;
+    char *mask, *nextmask;
+    char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    nextmask = masks;
+    while((mask = nextmask)) {
+        nextmask = strstr(mask, ",");
+        if(nextmask) {
+            *nextmask = '\0';
+            nextmask++;
+        }
+        provided_masks++;
+        skip = 0;
+        match_count = 0;
+        mask = make_banmask(mask, hostmask_buffer);
+        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+            cuser = chanuser->user;
+            sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+            if(!match(mask, usermask)) {
+                cuser->flags |= USERFLAG_SCRIPTFLAG1; //we mark the user as 'matching'
+                if(isNetworkService(chanuser->user)) {
+                    reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                    skip = 1;
+                    break;
+                }
+                if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                    reply(textclient, user, "NS_YOU_PROTECTED");
+                    skip = 1;
+                    break;
+                }
+                if(isUserProtected(chan, cuser, user)) {
+                    reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                    skip = 1;
+                    break;
+                }
+                match_count++;
+                if(match_count > 4 && (match_count * 3) > chan->usercount && !isGodMode(user)) {
+                    skip = 1;
+                    reply(textclient, user, "NS_LAME_MASK", mask);
+                    break;
+                }
+            }
+        }
+        if(!skip) {
+            done_masks++;
+            modeBufferBan(modeBuf, mask);
+        }
+    }
+    total_match = 0; // count all users marked as 'matching'
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        cuser = chanuser->user;
+        if(cuser->flags & USERFLAG_SCRIPTFLAG1) {
+            cuser->flags &= ~USERFLAG_SCRIPTFLAG1;
+            total_match++;
+        }
+    }
+    freeModeBuffer(modeBuf);
+    if(done_masks == provided_masks)
+        reply(textclient, user, "NS_BAN_DONE", done_masks, chan->name, total_match);
+    else
+        reply(textclient, user, "NS_BAN_FAIL", client->user->nick);
+    if(done_masks)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_bans.c b/src/modules/NeonServ.mod/cmd_neonserv_bans.c
new file mode 100644 (file)
index 0000000..12f8464
--- /dev/null
@@ -0,0 +1,75 @@
+/* cmd_neonserv_bans.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    (optional) mask
+*/
+CMD_BIND(neonserv_cmd_bans) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    //ban list
+    int i, with_expire = 0, cindex = 0;
+    char triggered_str[MAXLEN], expires_str[MAXLEN];
+    struct Table *table;
+    printf_mysql_query("SELECT `ban_mask`, `user_user`, `ban_triggered`, `ban_timeout`, `ban_reason` FROM `bans` LEFT JOIN `users` ON `ban_owner` = `user_id` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    table = table_init(5, mysql_num_rows(res) + 1, 0);
+    char *content[5];
+    //add a NULL row (we add values later)
+    content[0] = NULL;
+    content[1] = NULL;
+    content[2] = NULL;
+    content[3] = NULL;
+    content[4] = NULL;
+    table_add(table, content);
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(argc > 0 && match(argv[0], row[0])) continue;
+        content[0] = row[0];
+        content[1] = row[1];
+        content[2] = (strcmp(row[2], "0") ? timeToStr(user, (time(0) - atoi(row[2])), 2, triggered_str) : get_language_string(user, "NS_USERS_SEEN_NEVER"));
+        if(strcmp(row[3], "0")) {
+            if(!with_expire) {
+                //we're using expire times now...
+                for(i = 0; i < cindex; i++)
+                    table_change_field(table, i+1, 3, get_language_string(user, "NS_USERS_SEEN_NEVER"));
+                with_expire = 1;
+            }
+            content[3] = timeToStr(user, (atoi(row[3]) - time(0)), 2, expires_str);
+        } else
+            content[3] = (with_expire ? get_language_string(user, "NS_USERS_SEEN_NEVER") : NULL);
+        content[4] = row[4];
+        cindex++;
+        table_add(table, content);
+    }
+    //now we add the table header
+    content[0] = get_language_string(user, "NS_BANS_HEADER_MASK");
+    content[1] = get_language_string(user, "NS_BANS_HEADER_SETBY");
+    content[2] = get_language_string(user, "NS_BANS_HEADER_TRIGGERED");
+    content[3] = (with_expire ? get_language_string(user, "NS_BANS_HEADER_EXPIRES") : NULL);
+    content[4] = get_language_string(user, "NS_BANS_HEADER_REASON");
+    table_change(table, 0, content);
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(!cindex)
+        reply(textclient, user, "NS_TABLE_NONE");
+    reply(textclient, user, "NS_TABLE_COUNT", cindex);
+    table_free(table);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_chanservsync.c b/src/modules/NeonServ.mod/cmd_neonserv_chanservsync.c
new file mode 100644 (file)
index 0000000..e4e0096
--- /dev/null
@@ -0,0 +1,270 @@
+/* cmd_neonserv_chanservsync.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - botnick
+* argv[1] - key
+*/
+#define CHANSERVSYNC_END_TIMEOUT 5
+
+static void neonserv_cmd_chanservsync_notice_listener(struct UserNode *user, struct UserNode *target, char *message);
+static void neonserv_cmd_chanservsync_free_cache();
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_chanservsync_auth_lookup);
+static void neonserv_cmd_chanservsync_synchronize_user(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *username, int userid, int caccess, time_t seen, int flags, int new);
+
+struct neonserv_cmd_chanservsync_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char *botnick;
+    time_t last_response;
+};
+
+struct neonserv_cmd_chanservsync_auth_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    int caccess;
+    time_t seen;
+    int flags;
+};
+
+struct neonserv_cmd_chanservsync_cache *neonserv_cmd_chanservsync_used = NULL;
+const char* neonserv_cmd_chanservsync_supported[] = {"ChanServ", NULL};
+
+CMD_BIND(neonserv_cmd_chanservsync) {
+    if(neonserv_cmd_chanservsync_used && time(0) - neonserv_cmd_chanservsync_used->last_response < CHANSERVSYNC_END_TIMEOUT) {
+        reply(textclient, user, "NS_CHANSERVSYNC_INUSE");
+        return;
+    }
+    if(neonserv_cmd_chanservsync_used) {
+        neonserv_cmd_chanservsync_free_cache();
+    }
+    char *botnick = "ChanServ";
+    char *key = "";
+    if(argc) {
+        if(argv[0][0] == '!') {
+            key = argv[0];
+        } else {
+            botnick = argv[0];
+            if(argc > 1)
+                key = argv[1];
+        }
+    }
+    int seed = 0;
+    char *tmp;
+    char synckey[18];
+    for(tmp = user->auth; *tmp; tmp++)
+        seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+    for(tmp = chan->name; *tmp; tmp++)
+        seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+    for(tmp = botnick; *tmp; tmp++)
+        seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+    sprintf(synckey, "!%08x!", seed);
+    if(strcmp(synckey, key)) {
+        int f = 0;
+        const char **supp = neonserv_cmd_chanservsync_supported;
+        while(*supp) {
+            if(!stricmp(*supp, botnick)) {
+                f = 1;
+                break;
+            }
+            supp++;
+        }
+        if(!f) {
+            reply(textclient, user, "NS_CHANSERVSYNC_UNSUPPORTED", botnick, client->user->nick);
+        }
+        reply(textclient, user, "NS_CHANSERVSYNC_KEY", client->user->nick, botnick, botnick, synckey);
+        return;
+    }
+    struct neonserv_cmd_chanservsync_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->chan = chan;
+    cache->botnick = strdup(botnick);
+    cache->last_response = time(0);
+    neonserv_cmd_chanservsync_used = cache;
+    putsock(client, "PRIVMSG %s :users %s", botnick, chan->name);
+    bind_privnotice(neonserv_cmd_chanservsync_notice_listener, module_id);
+    reply(textclient, user, "NS_CHANSERVSYNC_SYNCHRONIZING", chan->name, botnick);
+    logEvent(event);
+}
+
+static void neonserv_cmd_chanservsync_notice_listener(struct UserNode *user, struct UserNode *target, char *message) {
+    if(neonserv_cmd_chanservsync_used && neonserv_cmd_chanservsync_used->client->user == target && !stricmp(user->nick, neonserv_cmd_chanservsync_used->botnick)) {
+        //we've got a notice from our bot...
+        //let's try parsing it....
+        char *p = message;
+        char *tokens[MAXLEN];
+        int tokensPos = 0;
+        while(*p == ' ') //skip leading spaces (airb0t)
+            p++;
+        message = p;
+        char *q = p;
+        while(*q) {
+            if(*q < 32) *q = ' ';
+            q++;
+        }
+        while((q = strstr(p, " "))) {
+            *q = '\0';
+            do {
+                q++;
+            } while(*q == ' ' || *q == '-');
+            if(*p) {
+                tokens[tokensPos++] = p;
+            }
+            p = q;
+        }
+        if(*p) {
+            tokens[tokensPos++] = p;
+        }
+        int caccess;
+        char *username;
+        if(tokensPos == 1) {
+            //maybe a chip-like userlist
+            if(tokens[0][0] == '@') {
+                caccess = 200;
+                username = &tokens[0][1];
+            } else if(tokens[0][0] == '+') {
+                caccess = 100;
+                username = &tokens[0][1];
+            } else
+                return;
+        } else if(tokensPos >= 2) {
+            if(atoi(tokens[0]) > 0) {
+                caccess = atoi(tokens[0]);
+                username = tokens[1];
+            } else {
+                caccess = atoi(tokens[1]);
+                username = tokens[0];
+            }
+        } else
+            return;
+        if(caccess < 1 || caccess > 500) return;
+        int flags = 0;
+        time_t now = time(0);
+        time_t seen_time = now; //now - now = 0 (never)
+        neonserv_cmd_chanservsync_used->last_response = now;
+        if(strlen(username) < 3) return;
+        //ok we have access and username... maybe there is something else we can parse???
+        char *seen = NULL;
+        char *status = NULL;
+        if(tokensPos > 2) {
+            if(!stricmp("normal", tokens[2]) || !stricmp("suspended", tokens[2]) || !stricmp("bot", tokens[2])) {
+                status = tokens[2];
+                if (tokensPos > 3) {
+                    seen = merge_argv(tokens, 3, tokensPos);
+                }
+            } else if (tokensPos > 3) {
+                if(!stricmp("normal", tokens[tokensPos-1]) || !stricmp("suspended", tokens[tokensPos-1]) || !stricmp("bot", tokens[tokensPos-1])) {
+                    status = tokens[tokensPos-1];
+                    seen = merge_argv(tokens, 2, tokensPos-1);
+                } else {
+                    seen = merge_argv(tokens, 2, tokensPos);
+                }
+            } else {
+                seen = merge_argv(tokens, 2, tokensPos);
+            }
+        }
+        if(status && !stricmp(status, "suspended")) {
+            flags |= DB_CHANUSER_SUSPENDED;
+        }
+        if(seen) {
+            if(!stricmp(seen, "here"))
+                seen_time = 0;
+            else if(stricmp(seen, "never"))
+                seen_time = strToTime(user, seen);
+        }
+        seen_time = now - seen_time;
+        //we've collected all information now. synchronize the user (use the higher access if the user is already added)
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        int userid;
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(username));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            userid = atoi(row[0]);
+            neonserv_cmd_chanservsync_synchronize_user(neonserv_cmd_chanservsync_used->client, neonserv_cmd_chanservsync_used->textclient, neonserv_cmd_chanservsync_used->user, neonserv_cmd_chanservsync_used->chan, username, userid, caccess, seen_time, flags, 0);
+        } else if(!stricmp(user->nick, "chanserv")) {
+            printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(username));
+            userid = (int) mysql_insert_id(get_mysql_conn());
+            neonserv_cmd_chanservsync_synchronize_user(neonserv_cmd_chanservsync_used->client, neonserv_cmd_chanservsync_used->textclient, neonserv_cmd_chanservsync_used->user, neonserv_cmd_chanservsync_used->chan, username, userid, caccess, seen_time, flags, 1);
+        } else {
+            //lookup auth
+            struct neonserv_cmd_chanservsync_auth_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = neonserv_cmd_chanservsync_used->client;
+            cache->textclient = neonserv_cmd_chanservsync_used->textclient;
+            cache->user = neonserv_cmd_chanservsync_used->user;
+            cache->chan = neonserv_cmd_chanservsync_used->chan;
+            cache->caccess = caccess;
+            cache->seen = seen_time;
+            cache->flags = flags;
+            lookup_authname(username, module_id, neonserv_cmd_chanservsync_auth_lookup, cache);
+        }
+    }
+}
+
+static void neonserv_cmd_chanservsync_free_cache() {
+    free(neonserv_cmd_chanservsync_used->botnick);
+    free(neonserv_cmd_chanservsync_used);
+    unbind_privnotice(neonserv_cmd_chanservsync_notice_listener);
+    neonserv_cmd_chanservsync_used = NULL;
+}
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_chanservsync_auth_lookup) {
+    struct neonserv_cmd_chanservsync_auth_cache *cache = data;
+    if(exists) {
+        printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(auth));
+        int userid = (int) mysql_insert_id(get_mysql_conn());
+        neonserv_cmd_chanservsync_synchronize_user(cache->client, cache->textclient, cache->user, cache->chan, auth, userid, cache->caccess, cache->seen, cache->flags, 1);
+    }
+    free(cache);
+}
+
+static void neonserv_cmd_chanservsync_synchronize_user(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *username, int userid, int caccess, time_t seen, int flags, int new) {
+    //just sync the user with the given userid with the providet information
+    if(caccess == 500) caccess = 499;
+    if(new) {
+        //just add
+        printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`, `chanuser_seen`, `chanuser_flags`) VALUES ('%d', '%d', '%d', '%lu', '%d')", chan->channel_id, userid, caccess, (unsigned long) seen, flags);
+    } else {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        //check if already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id`, `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            //clvl
+            if(atoi(row[0]) >= caccess) return;
+            if(atol(row[2]) > seen) seen = atol(row[2]);
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '%d', `chanuser_seen` = '%lu' WHERE `chanuser_id` = '%s'", caccess, (unsigned long) seen, row[1]);
+        } else 
+            printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`, `chanuser_seen`, `chanuser_flags`) VALUES ('%d', '%d', '%d', '%lu', '%d')", chan->channel_id, userid, caccess, (unsigned long) seen, flags);
+    }
+    reply(textclient, user, "NS_CHANSERVSYNC_SYNCHRONIZED", username, caccess);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_clvl.c b/src/modules/NeonServ.mod/cmd_neonserv_clvl.c
new file mode 100644 (file)
index 0000000..159d540
--- /dev/null
@@ -0,0 +1,130 @@
+/* cmd_neonserv_clvl.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+* argv[1] - access
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_clvl_nick_lookup);
+static void neonserv_cmd_clvl_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, int caccess);
+
+struct neonserv_cmd_clvl_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+    int access;
+};
+
+CMD_BIND(neonserv_cmd_clvl) {
+    int caccess;
+    caccess = atoi(argv[1]);
+    if(caccess <= 0 || caccess > 500) {
+        reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+        return;
+    }
+    if(caccess >= getChannelAccess(user, chan)) {
+        if(isGodMode(user)) {
+            event->flags |= CMDFLAG_OPLOG;
+        } else {
+            reply(textclient, user, "NS_ACCESS_OUTRANKED");
+            return;
+        }
+    }
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_clvl_async1(client, textclient, user, chan, event, argv[0], argv[0], caccess);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_clvl_async1(client, textclient, user, chan, event, argv[0], cuser->auth, caccess);
+        } else {
+            struct neonserv_cmd_clvl_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            cache->access = caccess;
+            get_userauth(cuser, module_id, neonserv_cmd_clvl_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_clvl_nick_lookup) {
+    struct neonserv_cmd_clvl_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_clvl_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth, cache->access);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_clvl_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, int caccess) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            //clvl
+            if(atoi(row[0]) >= getChannelAccess(user, chan)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_USER_OUTRANKED", nick);
+                    return;
+                }
+            }
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '%d' WHERE `chanuser_id` = '%s'", caccess, row[1]);
+            reply(textclient, user, "NS_CLVL_DONE", nick, caccess, chan->name);
+            logEvent(event);
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_csuspend.c b/src/modules/NeonServ.mod/cmd_neonserv_csuspend.c
new file mode 100644 (file)
index 0000000..929fa4e
--- /dev/null
@@ -0,0 +1,68 @@
+/* cmd_neonserv_csuspend.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - channel
+*/
+CMD_BIND(neonserv_cmd_csuspend) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *channel = argv[0];
+    if(!is_valid_chan(channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", argv[0]);
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(textclient, user, "NS_CSUSPEND_ALREADY", channel);
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        putsock(bot, "PART %s :Channel suspended.", channel);
+    }
+    if(client->botid == NEONSERV_BOTID) {
+        char setting[128];
+        sprintf(setting, "modules.%s.auto_backup_unregister", get_module_name(module_id));
+        if(get_int_field(setting))
+            module_global_cmd_unregister_neonbackup(channel);
+    }
+    printf_mysql_query("UPDATE `bot_channels` SET `suspended` = '1' WHERE `id` = '%s'", row[1]);
+    reply(textclient, user, "NS_CSUSPEND_DONE", channel);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_cunsuspend.c b/src/modules/NeonServ.mod/cmd_neonserv_cunsuspend.c
new file mode 100644 (file)
index 0000000..e2fa17f
--- /dev/null
@@ -0,0 +1,68 @@
+/* cmd_neonserv_cunsuspend.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - channel
+*/
+CMD_BIND(neonserv_cmd_cunsuspend) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *channel = argv[0];
+    if(!is_valid_chan(channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", argv[0]);
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "0")) {
+        reply(textclient, user, "NS_CUNSUSPEND_NOT", channel);
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        putsock(bot, "JOIN %s", channel);
+    }
+    if(client->botid == NEONSERV_BOTID) {
+        char setting[128];
+        sprintf(setting, "modules.%s.auto_backup_register", get_module_name(module_id));
+        if(get_int_field(setting))
+            module_global_cmd_register_neonbackup(channel);
+    }
+    printf_mysql_query("UPDATE `bot_channels` SET `suspended` = '0' WHERE `id` = '%s'", row[1]);
+    reply(textclient, user, "NS_CUNSUSPEND_DONE", channel);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_dehalfop.c b/src/modules/NeonServ.mod/cmd_neonserv_dehalfop.c
new file mode 100644 (file)
index 0000000..2882180
--- /dev/null
@@ -0,0 +1,94 @@
+/* cmd_neonserv_dehalfop.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+static USERLIST_CALLBACK(neonserv_cmd_dehalfop_userlist_lookup);
+static void neonserv_cmd_dehalfop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc);
+
+struct neonserv_cmd_dehalfop_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char **argv;
+    int argc;
+};
+
+CMD_BIND(neonserv_cmd_dehalfop) {
+    struct neonserv_cmd_dehalfop_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->argv = calloc(argc, sizeof(char*));
+    int i;
+    for(i = 0; i < argc; i++) {
+        cache->argv[i] = strdup(argv[i]);
+    }
+    cache->argc = argc;
+    get_userlist(chan, module_id, neonserv_cmd_dehalfop_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_dehalfop_userlist_lookup) {
+    struct neonserv_cmd_dehalfop_cache *cache = data;
+    neonserv_cmd_dehalfop_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->argv, cache->argc);
+    int i;
+    for(i = 0; i < cache->argc; i++) {
+        free(cache->argv[i]);
+    }
+    free(cache->argv);
+    free(cache);
+}
+
+static void neonserv_cmd_dehalfop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc) {
+    int i, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(i = 0; i < argc; i++) {
+        cuser = searchUserByNick(argv[i]);
+        if(!cuser) continue;
+        chanuser = getChanUser(cuser, chan);
+        if(!chanuser) continue;
+        if(isNetworkService(cuser)) {
+            reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+            continue;
+        }
+        if(isUserProtected(chan, cuser, user)) {
+            reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+            continue;
+        }
+        done_users++;
+        if(!(chanuser->flags & CHANUSERFLAG_OPPED)) continue;
+        modeBufferDehalfop(modeBuf, argv[i]);
+    }
+    freeModeBuffer(modeBuf);
+    if(done_users == argc)
+        reply(textclient, user, "NS_DEHALFOP_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_DEHALFOP_FAIL", client->user->nick);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_dehalfopall.c b/src/modules/NeonServ.mod/cmd_neonserv_dehalfopall.c
new file mode 100644 (file)
index 0000000..ce948ac
--- /dev/null
@@ -0,0 +1,85 @@
+/* cmd_neonserv_dehalfopall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_dehalfopall_userlist_lookup);
+static void neonserv_cmd_dehalfopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc);
+
+struct neonserv_cmd_dehalfopall_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char **argv;
+    int argc;
+};
+
+CMD_BIND(neonserv_cmd_dehalfopall) {
+    struct neonserv_cmd_dehalfopall_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->argv = calloc(argc, sizeof(char*));
+    int i;
+    for(i = 0; i < argc; i++) {
+        cache->argv[i] = strdup(argv[i]);
+    }
+    cache->argc = argc;
+    get_userlist(chan, module_id, neonserv_cmd_dehalfopall_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_dehalfopall_userlist_lookup) {
+    struct neonserv_cmd_dehalfopall_cache *cache = data;
+    neonserv_cmd_dehalfopall_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->argv, cache->argc);
+    int i;
+    for(i = 0; i < cache->argc; i++) {
+        free(cache->argv[i]);
+    }
+    free(cache->argv);
+    free(cache);
+}
+
+static void neonserv_cmd_dehalfopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc) {
+    int issuer_access, victim_access, done_users = 0;
+    char *nickmask = NULL;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    if(argc > 0)
+        nickmask = argv[0];
+    modeBuf = initModeBuffer(client, chan);
+    issuer_access = getChannelAccess(user, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        victim_access = getChannelAccess(chanuser->user, chan);
+        if(victim_access >= issuer_access || isNetworkService(chanuser->user)) continue;
+        if(!(chanuser->flags & CHANUSERFLAG_OPPED)) continue;
+        modeBufferDehalfop(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_DEHALFOPALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_delban.c b/src/modules/NeonServ.mod/cmd_neonserv_delban.c
new file mode 100644 (file)
index 0000000..0086b9c
--- /dev/null
@@ -0,0 +1,51 @@
+/* cmd_neonserv_delban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    nick|*auth|*!*@mask
+*/
+
+CMD_BIND(neonserv_cmd_delban) {
+    char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+    char *mask = make_banmask(argv[0], hostmask_buffer);
+    int matching_bans = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    //check if the provided mask affects any existing bans
+    char nameBuf[20];
+    printf_mysql_query("SELECT `ban_mask`, `ban_id`, `ban_timeout` FROM `bans` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!match(mask, row[0])) {
+            //remove the ban
+            if(strcmp(row[2], "0")) {
+                sprintf(nameBuf, "ban_%s", row[1]);
+                timeq_del_name(nameBuf);
+            }
+            printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[1]);
+            matching_bans++;
+        }
+    }
+    if(matching_bans) {
+        putsock(client, "MODE %s -b %s", chan->name, mask);
+        reply(textclient, user, "NS_DELBAN_DONE", mask, chan->name);
+        logEvent(event);
+    } else
+        reply(textclient, user, "NS_DELBAN_FAIL", mask);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_delme.c b/src/modules/NeonServ.mod/cmd_neonserv_delme.c
new file mode 100644 (file)
index 0000000..29f4582
--- /dev/null
@@ -0,0 +1,62 @@
+/* cmd_neonserv_delme.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - key
+*/
+
+CMD_BIND(neonserv_cmd_delme) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            if(atoi(row[0]) == 500) {
+                reply(textclient, user, "NS_DELME_OWNER", chan->name);
+                return;
+            }
+            //check key
+            int seed = 0;
+            char *tmp;
+            static char unregkey[16];
+            for(tmp = user->auth; *tmp; tmp++)
+                seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+            for(tmp = chan->name; *tmp; tmp++)
+                seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+            sprintf(unregkey, "%08x", seed);
+            if(argc < 1 || strcmp(argv[0], unregkey)) {
+                reply(textclient, user, "NS_DELME_KEY", unregkey);
+                return;
+            } else {
+                //delete
+                printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_id` = '%s'", row[1]);
+                reply(textclient, user, "NS_DELME_DONE", atoi(row[0]), chan->name);
+                logEvent(event);
+                return;
+            }
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST_YOU", chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_delrank.c b/src/modules/NeonServ.mod/cmd_neonserv_delrank.c
new file mode 100644 (file)
index 0000000..cf1d3ec
--- /dev/null
@@ -0,0 +1,37 @@
+/* cmd_neonserv_delrank.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]   rank name
+*/
+
+CMD_BIND(neonserv_cmd_delrank) {
+    char *name = merge_argv(argv, 0, argc);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `rank_id`, `rank_name` FROM `support_ranks` WHERE `rank_name` = '%s'", escape_string(name));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_DELRANK_NOT_FOUND", name);
+        return;
+    }
+    printf_mysql_query("UPDATE `users` SET `user_rank` = '0', `user_access` = '0' WHERE `user_rank` = '%s'", row[0]);
+    printf_mysql_query("DELETE FROM `support_ranks` WHERE `rank_id` = '%s'", row[0]);
+    reply(textclient, user, "NS_DELRANK_DELETED", row[1]);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_deluser.c b/src/modules/NeonServ.mod/cmd_neonserv_deluser.c
new file mode 100644 (file)
index 0000000..e283f18
--- /dev/null
@@ -0,0 +1,113 @@
+/* cmd_neonserv_deluser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_deluser_nick_lookup);
+static void neonserv_cmd_deluser_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth);
+
+struct neonserv_cmd_deluser_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_deluser) {
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_deluser_async1(client, textclient, user, chan, event, argv[0], argv[0]);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_deluser_async1(client, textclient, user, chan, event, argv[0], cuser->auth);
+        } else {
+            struct neonserv_cmd_deluser_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_deluser_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_deluser_nick_lookup) {
+    struct neonserv_cmd_deluser_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_deluser_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_deluser_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            if(atoi(row[0]) >= getChannelAccess(user, chan)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_USER_OUTRANKED", nick);
+                    return;
+                }
+            }
+            //delete
+            printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_id` = '%s'", row[1]);
+            reply(textclient, user, "NS_DELUSER_DONE", nick, atoi(row[0]), chan->name);
+            logEvent(event);
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_deop.c b/src/modules/NeonServ.mod/cmd_neonserv_deop.c
new file mode 100644 (file)
index 0000000..d030b8e
--- /dev/null
@@ -0,0 +1,94 @@
+/* cmd_neonserv_deop.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+static USERLIST_CALLBACK(neonserv_cmd_deop_userlist_lookup);
+static void neonserv_cmd_deop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc);
+
+struct neonserv_cmd_deop_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char **argv;
+    int argc;
+};
+
+CMD_BIND(neonserv_cmd_deop) {
+    struct neonserv_cmd_deop_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->argv = calloc(argc, sizeof(char*));
+    int i;
+    for(i = 0; i < argc; i++) {
+        cache->argv[i] = strdup(argv[i]);
+    }
+    cache->argc = argc;
+    get_userlist(chan, module_id, neonserv_cmd_deop_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_deop_userlist_lookup) {
+    struct neonserv_cmd_deop_cache *cache = data;
+    neonserv_cmd_deop_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->argv, cache->argc);
+    int i;
+    for(i = 0; i < cache->argc; i++) {
+        free(cache->argv[i]);
+    }
+    free(cache->argv);
+    free(cache);
+}
+
+static void neonserv_cmd_deop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc) {
+    int i, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(i = 0; i < argc; i++) {
+        cuser = searchUserByNick(argv[i]);
+        if(!cuser) continue;
+        chanuser = getChanUser(cuser, chan);
+        if(!chanuser) continue;
+        if(isNetworkService(cuser)) {
+            reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+            continue;
+        }
+        if(isUserProtected(chan, cuser, user)) {
+            reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+            continue;
+        }
+        done_users++;
+        if(!(chanuser->flags & CHANUSERFLAG_OPPED)) continue;
+        modeBufferDeop(modeBuf, argv[i]);
+    }
+    freeModeBuffer(modeBuf);
+    if(done_users == argc)
+        reply(textclient, user, "NS_DEOP_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_DEOP_FAIL", client->user->nick);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_deopall.c b/src/modules/NeonServ.mod/cmd_neonserv_deopall.c
new file mode 100644 (file)
index 0000000..0299a8b
--- /dev/null
@@ -0,0 +1,85 @@
+/* cmd_neonserv_deopall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_deopall_userlist_lookup);
+static void neonserv_cmd_deopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc);
+
+struct neonserv_cmd_deopall_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char **argv;
+    int argc;
+};
+
+CMD_BIND(neonserv_cmd_deopall) {
+    struct neonserv_cmd_deopall_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->argv = calloc(argc, sizeof(char*));
+    int i;
+    for(i = 0; i < argc; i++) {
+        cache->argv[i] = strdup(argv[i]);
+    }
+    cache->argc = argc;
+    get_userlist(chan, module_id, neonserv_cmd_deopall_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_deopall_userlist_lookup) {
+    struct neonserv_cmd_deopall_cache *cache = data;
+    neonserv_cmd_deopall_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->argv, cache->argc);
+    int i;
+    for(i = 0; i < cache->argc; i++) {
+        free(cache->argv[i]);
+    }
+    free(cache->argv);
+    free(cache);
+}
+
+static void neonserv_cmd_deopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc) {
+    int issuer_access, victim_access, done_users = 0;
+    char *nickmask = NULL;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    if(argc > 0)
+        nickmask = argv[0];
+    modeBuf = initModeBuffer(client, chan);
+    issuer_access = getChannelAccess(user, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        victim_access = getChannelAccess(chanuser->user, chan);
+        if(victim_access >= issuer_access || isNetworkService(chanuser->user)) continue;
+        if(!(chanuser->flags & CHANUSERFLAG_OPPED)) continue;
+        modeBufferDeop(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_DEOPALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_devoice.c b/src/modules/NeonServ.mod/cmd_neonserv_devoice.c
new file mode 100644 (file)
index 0000000..1c25fa5
--- /dev/null
@@ -0,0 +1,50 @@
+/* cmd_neonserv_devoice.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+
+CMD_BIND(neonserv_cmd_devoice) {
+    int i, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(i = 0; i < argc; i++) {
+        cuser = searchUserByNick(argv[i]);
+        if(!cuser) continue;
+        chanuser = getChanUser(cuser, chan);
+        if(!chanuser) continue;
+        if(isUserProtected(chan, cuser, user)) {
+            reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+            continue;
+        }
+        done_users++;
+        if(!(chanuser->flags & CHANUSERFLAG_VOICED)) continue;
+        modeBufferDevoice(modeBuf, argv[i]);
+    }
+    freeModeBuffer(modeBuf);
+    if(done_users == argc)
+        reply(textclient, user, "NS_DEVOICE_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_DEVOICE_FAIL", client->user->nick);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_devoiceall.c b/src/modules/NeonServ.mod/cmd_neonserv_devoiceall.c
new file mode 100644 (file)
index 0000000..cc96cc3
--- /dev/null
@@ -0,0 +1,45 @@
+/* cmd_neonserv_devoiceall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    (optional) nick mask
+*/
+
+CMD_BIND(neonserv_cmd_devoiceall) {
+    int issuer_access, victim_access, done_users = 0;
+    char *nickmask = NULL;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    if(argc > 0)
+        nickmask = argv[0];
+    modeBuf = initModeBuffer(client, chan);
+    issuer_access = getChannelAccess(user, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        victim_access = getChannelAccess(chanuser->user, chan);
+        if(victim_access >= issuer_access) continue;
+        if(!(chanuser->flags & CHANUSERFLAG_VOICED)) continue;
+        modeBufferDevoice(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_DEVOICEALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_down.c b/src/modules/NeonServ.mod/cmd_neonserv_down.c
new file mode 100644 (file)
index 0000000..76897be
--- /dev/null
@@ -0,0 +1,38 @@
+/* cmd_neonserv_down.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_down) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) {
+        reply(textclient, user, "NS_NOT_ON_CHANNEL_YOU", chan->name);
+        return;
+    }
+    if((chanuser->flags & CHANUSERFLAG_OPPED)) {
+        putsock(client, "MODE %s -ov %s %s", chan->name, user->nick, user->nick);
+        logEvent(event);
+    } else if((chanuser->flags & CHANUSERFLAG_VOICED)) {
+        putsock(client, "MODE %s -v %s", chan->name, user->nick);
+        logEvent(event);
+    } else
+        reply(textclient, user, "NS_DOWN_ALREADY", chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_downall.c b/src/modules/NeonServ.mod/cmd_neonserv_downall.c
new file mode 100644 (file)
index 0000000..200eb29
--- /dev/null
@@ -0,0 +1,50 @@
+/* cmd_neonserv_downall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_downall) {
+    struct ChanUser *chanuser;
+    int botid = client->botid;
+    for(chanuser = getUserChannels(user, NULL); chanuser; chanuser = getUserChannels(user, chanuser)) {
+        chan = chanuser->chan;
+        loadChannelSettings(chan);
+        if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) continue;
+        printf_mysql_query("SELECT `botid` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, client->botid);
+        if (mysql_fetch_row(mysql_use()) == NULL) continue;
+        int done = 0;
+        client = getChannelBot(chan, botid);
+        if(!client) continue;
+        if((chanuser->flags & CHANUSERFLAG_OPPED)) {
+            putsock(client, "MODE %s -o %s", chan->name, user->nick);
+            done = 1;
+        }
+        if((chanuser->flags & CHANUSERFLAG_VOICED)) {
+            putsock(client, "MODE %s -v %s", chan->name, user->nick);
+            done = 1;
+        }
+        if(done) {
+            //event hack
+            event->chan = chan;
+            logEvent(event);
+        }
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_events.c b/src/modules/NeonServ.mod/cmd_neonserv_events.c
new file mode 100644 (file)
index 0000000..84312a9
--- /dev/null
@@ -0,0 +1,57 @@
+/* cmd_neonserv_events.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]     time
+* argv[1-*]   match
+*/
+
+CMD_BIND(neonserv_cmd_events) {
+    char *str_match;
+    int duration = (argc ? strToTime(user, argv[0]) : 0);
+    if(argc > (duration ? 1 : 0))
+        str_match = merge_argv(argv, (duration ? 1 : 0), argc);
+    else
+        str_match = "";
+    if(!duration) duration = (60*60*24);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `time`, `auth`, `nick`, `command` FROM `events` WHERE `cid` = '%d' AND `time` > '%lu' ORDER BY `time` ASC", chan->channel_id, ((unsigned long) time(0) - duration));
+    res = mysql_use();
+    int skip = mysql_num_rows(res) - 100;
+    int count = 0;
+    char timeBuf[50];
+    struct tm *timeinfo;
+    time_t event_time;
+    if(skip < 0) skip = 0;
+    reply(textclient, user, "NS_EVENTS_HEADER");
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(skip) {
+            skip--;
+            continue;
+        }
+        if(*str_match && match(str_match, row[3])) continue;
+        count++;
+        event_time = (time_t) atol(row[0]);
+        timeinfo = localtime(&event_time);
+        strftime(timeBuf, 80, "%X %x", timeinfo);
+        reply(textclient, user, "[%s] [%s:%s]: %s", timeBuf, row[2], row[1], row[3]);
+    }
+    reply(textclient, user, "NS_TABLE_COUNT", count);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_giveowner.c b/src/modules/NeonServ.mod/cmd_neonserv_giveowner.c
new file mode 100644 (file)
index 0000000..67b1463
--- /dev/null
@@ -0,0 +1,157 @@
+/* cmd_neonserv_giveowner.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+* argv[1] - key
+*/
+
+#define GIVEOWNER_TIMEOUT 86400 /* 60*60*24 = 86400 */
+
+static USERAUTH_CALLBACK(neonserv_cmd_giveowner_nick_lookup);
+static void neonserv_cmd_giveowner_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, char *key);
+
+struct neonserv_cmd_giveowner_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+    char *key;
+};
+
+CMD_BIND(neonserv_cmd_giveowner) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_lastgiveowner` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(strcmp(row[0], "0") && (atoi(row[0]) + GIVEOWNER_TIMEOUT) > time(0)) {
+        char timeBuf[MAXLEN];
+        reply(textclient, user, "NS_GIVEOWNER_TIMEOUT", timeToStr(user, (GIVEOWNER_TIMEOUT - (time(0) - atoi(row[0]))), 2, timeBuf), chan->name);
+        return;
+    }
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_giveowner_async1(client, textclient, user, chan, event, argv[0], argv[0], (argc != 1 ? argv[1] : NULL));
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_giveowner_async1(client, textclient, user, chan, event, argv[0], cuser->auth, (argc != 1 ? argv[1] : NULL));
+        } else {
+            struct neonserv_cmd_giveowner_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            cache->key = (argc != 1 ? strdup(argv[1]) : NULL);
+            get_userauth(cuser, module_id, neonserv_cmd_giveowner_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_giveowner_nick_lookup) {
+    struct neonserv_cmd_giveowner_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_giveowner_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth, cache->key);
+    free(cache->nick);
+    if(cache->key)
+        free(cache->key);
+    free(cache);
+}
+
+static void neonserv_cmd_giveowner_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, char *key) {
+    //we've got a valid auth now...
+    if(!stricmp(user->auth, auth)) {
+        reply(textclient, user, "NS_GIVEOWNER_SELF");
+        return;
+    }
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_user`, `dnr_timeout`, `dnr_reason`, `dnr_id` FROM `donotregister` LEFT JOIN `users` ON `dnr_user` = `user_id` WHERE `dnr_target` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if((row = mysql_fetch_row(res)) != NULL) {
+        int expire_time = atoi(row[1]);
+        if(expire_time) {
+            if(expire_time - time(0) <= 0) {
+                printf_mysql_query("DELETE FROM `donotregister` WHERE `dnr_id` = '%s'", row[3]);
+            } else {
+                reply(textclient, user, "NS_DNR_SET_ANONYM", auth);
+                return;
+            }
+        } else {
+            reply(textclient, user, "NS_DNR_SET_ANONYM", auth);
+            return;
+        }
+    }
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            int seed = 0;
+            char *tmp;
+            char giveownerkey[16];
+            for(tmp = user->auth; *tmp; tmp++)
+                seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+            for(tmp = chan->name; *tmp; tmp++)
+                seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+            sprintf(giveownerkey, "%08x", seed);
+            if(key && !stricmp(giveownerkey, key)) {
+                //give ownership
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '500' WHERE `chanuser_id` = '%s'", row[1]);
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '499' WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = (SELECT `user_id` FROM `users` WHERE `user_user` = '%s')", chan->channel_id, escape_string(user->auth));
+                printf_mysql_query("INSERT INTO `owner_history` (`owner_history_cid`, `owner_history_from_uid`, `owner_history_to_uid`, `owner_history_time`) VALUE ('%d', (SELECT `user_id` FROM `users` WHERE `user_user` = '%s'), '%d', UNIX_TIMESTAMP())", chan->channel_id, escape_string(user->auth), userid);
+                reply(textclient, user, "NS_GIVEOWNER_DONE", chan->name, auth);
+                logEvent(event);
+            } else {
+                reply(textclient, user, "NS_GIVEOWNER_CONFIRM", auth, giveownerkey);
+            }
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_halfop.c b/src/modules/NeonServ.mod/cmd_neonserv_halfop.c
new file mode 100644 (file)
index 0000000..8c83389
--- /dev/null
@@ -0,0 +1,93 @@
+/* cmd_neonserv_halfop.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+static USERLIST_CALLBACK(neonserv_cmd_halfop_userlist_lookup);
+static void neonserv_cmd_halfop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks);
+
+struct neonserv_cmd_halfop_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nicks;
+};
+
+CMD_BIND(neonserv_cmd_halfop) {
+    struct neonserv_cmd_halfop_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->nicks = strdup(merge_argv(argv, 0, argc));
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_halfop_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_halfop_userlist_lookup) {
+    struct neonserv_cmd_halfop_cache *cache = data;
+    neonserv_cmd_halfop_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nicks);
+    free(cache->nicks);
+    free(cache);
+}
+
+static void neonserv_cmd_halfop_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks) {
+    int total_users = 0, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    char *a, *b = nicks;
+    do {
+        a = strstr(b, " ");
+        if(a) *a = '\0';
+        total_users++;
+        cuser = searchUserByNick(b);
+        if(!cuser) {
+            //check for an invisible user
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if(!stricmp(chanuser->user->nick, b)) {
+                    cuser = chanuser->user;
+                    break;
+                }
+            }
+            if(!cuser) continue;
+        } else {
+            chanuser = getChanUser(cuser, chan);
+            if(!chanuser) continue;
+        }
+        done_users++;
+        if(chanuser->flags & CHANUSERFLAG_OPPED) continue;
+        modeBufferHalfop(modeBuf, b);
+        if(a) {
+            b = a+1;
+        }
+    } while(a);
+    freeModeBuffer(modeBuf);
+    if(done_users == total_users)
+        reply(textclient, user, "NS_HALFOP_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_HALFOP_FAIL", client->user->nick);
+    if(done_users) 
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_halfopall.c b/src/modules/NeonServ.mod/cmd_neonserv_halfopall.c
new file mode 100644 (file)
index 0000000..5ce22e8
--- /dev/null
@@ -0,0 +1,74 @@
+/* cmd_neonserv_halfopall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    "force"
+* argv[1]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_halfopall_userlist_lookup);
+static void neonserv_cmd_halfopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask);
+
+struct neonserv_cmd_halfopall_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nickmask;
+};
+
+CMD_BIND(neonserv_cmd_halfopall) {
+    struct neonserv_cmd_halfopall_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    if(argc) {
+        cache->nickmask = strdup(argv[0]);
+    } else
+        cache->nickmask = NULL;
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_halfopall_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_halfopall_userlist_lookup) {
+    struct neonserv_cmd_halfopall_cache *cache = data;
+    neonserv_cmd_halfopall_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nickmask);
+    if(cache->nickmask)
+        free(cache->nickmask);
+    free(cache);
+}
+
+static void neonserv_cmd_halfopall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask) {
+    int done_users = 0;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        if(chanuser->flags & CHANUSERFLAG_OPPED) continue;
+        modeBufferHalfop(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_HALFOPALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_help.c b/src/modules/NeonServ.mod/cmd_neonserv_help.c
new file mode 100644 (file)
index 0000000..1d6ce16
--- /dev/null
@@ -0,0 +1,97 @@
+/* cmd_neonserv_help.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]     index
+*/
+
+CMD_BIND(neonserv_cmd_help) {
+    char *ident;
+    if(argc)
+        ident = merge_argv(argv, 0, argc);
+    else
+        ident = "0";
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_lang` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    char *lang;
+    if ((row = mysql_fetch_row(res)) != NULL)
+        lang = row[0];
+    else
+        lang = "en";
+    printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = '%s' AND `ident` = '%s'", escape_string(lang), escape_string(ident));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        if(stricmp(lang, "en")) {
+            printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = 'en' AND `ident` = '%s'", escape_string(ident));
+            res = mysql_use();
+        }
+        if ((row = mysql_fetch_row(res)) == NULL) {
+            reply(textclient, user, "NS_HELP_TOPIC");
+            return;
+        }
+    }
+    char sendBuf[MAXLEN];
+    int sendBufPos = 0;
+    int i;
+    for(i = 0; i < strlen(row[0]); i++) {
+        switch(row[0][i]) {
+            case '\n':
+                if(sendBufPos) {
+                    sendBuf[sendBufPos] = '\0';
+                    reply(textclient, user, "%s", sendBuf);
+                    sendBufPos = 0;
+                }
+                break;
+            case '$':
+                switch(row[0][i+1]) {
+                    case 'b':
+                        sendBuf[sendBufPos++] = 2;
+                        i++;
+                        break;
+                    case 'k':
+                        sendBuf[sendBufPos++] = 3;
+                        i++;
+                        break;
+                    case 'u':
+                        sendBuf[sendBufPos++] = 31;
+                        i++;
+                        break;
+                    case 'C':
+                    case 'S':
+                        sendBufPos += sprintf(sendBuf + sendBufPos, "%s", client->user->nick);
+                        i++;
+                        break;
+                    default:
+                        sendBuf[sendBufPos++] = '$';
+                        break;
+                }
+                break;
+            default:
+                sendBuf[sendBufPos++] = row[0][i];
+                break;
+        }
+    }
+    if(sendBufPos) {
+        sendBuf[sendBufPos] = '\0';
+        reply(textclient, user, "%s", sendBuf);
+        sendBufPos = 0;
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_info.c b/src/modules/NeonServ.mod/cmd_neonserv_info.c
new file mode 100644 (file)
index 0000000..c562a39
--- /dev/null
@@ -0,0 +1,105 @@
+/* cmd_neonserv_info.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no parameters
+*/
+
+CMD_BIND(neonserv_cmd_info) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    struct Table *table;
+    printf_mysql_query("SELECT `channel_defaulttopic`, `channel_modes`, `channel_maxusers`, `channel_lastvisit`, `channel_registered`, `user_user` FROM `channels` LEFT JOIN `users` ON `channel_registrator` = `user_id` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    table = table_init(2, 9, 0);
+    table_set_bold(table, 0, 1);
+    char *content[2];
+    reply(textclient, user, "NS_INFO_HEADER", chan->name);
+    content[0] = get_language_string(user, "NS_INFO_DEFAULTTOPIC");
+    content[1] = row[0];
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_INFO_MODELOCK");
+    content[1] = row[1];
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_INFO_RECORD");
+    content[1] = row[2];
+    table_add(table, content);
+    printf_mysql_query("SELECT `user_user`, `chanuser_access` FROM `chanusers` LEFT JOIN `users` ON `user_id` = `chanuser_uid` WHERE `chanuser_cid` = '%d'", chan->channel_id);
+    res = mysql_use();
+    char ownerstr[MAXLEN];
+    int ownerpos = 0, usercount = 0;
+    int userisowner = 0;
+    while((row2 = mysql_fetch_row(res))) {
+        if(!strcmp(row2[1], "500")) {
+            ownerpos += sprintf(ownerstr + ownerpos, (ownerpos ? ", %s" : "%s"), row2[0]);
+            if((user->flags & USERFLAG_ISAUTHED) && !stricmp(row2[0], user->auth))
+                userisowner = 1;
+        }
+        usercount++;
+    }
+    content[0] = get_language_string(user, "NS_INFO_OWNER");
+    content[1] = ownerstr;
+    table_add(table, content);
+    sprintf(ownerstr, "%d", usercount);
+    content[0] = get_language_string(user, "NS_INFO_USERS");
+    content[1] = ownerstr;
+    table_add(table, content);
+    printf_mysql_query("SELECT COUNT(*) FROM `bans` WHERE `ban_channel` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row2 = mysql_fetch_row(res);
+    content[0] = get_language_string(user, "NS_INFO_BANS");
+    content[1] = row2[0];
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_INFO_VISITED");
+    content[1] = timeToStr(user, time(0) - atoi(row[3]), 2, ownerstr);
+    table_add(table, content);
+    if(strcmp(row[4], "0")) {
+        content[0] = get_language_string(user, "NS_INFO_REGISTERED");
+        content[1] = timeToStr(user, time(0) - atoi(row[4]), 2, ownerstr);
+        table_add(table, content);
+    }
+    if(row[5] && isGodMode(user)) {
+        content[0] = get_language_string(user, "NS_INFO_REGISTRAR");
+        content[1] = row[5];
+        table_add(table, content);
+    }
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    table_free(table);
+    if(userisowner || isGodMode(user)) {
+        printf_mysql_query("SELECT `owner_history_time`, a.`user_user`, b.`user_user` FROM `owner_history` LEFT JOIN `users` a ON `owner_history_from_uid` = a.`user_id` LEFT JOIN `users` b ON `owner_history_to_uid` = b.`user_id` WHERE `owner_history_cid` = '%d'", chan->channel_id);
+        res = mysql_use();
+        if(mysql_num_rows(res)) {
+            reply(textclient, user, "NS_INFO_OWNERLOG", chan->name);
+            time_t rawtime;
+            struct tm *timeinfo;
+            char timeBuf[80];
+            while((row = mysql_fetch_row(res))) {
+                rawtime = (time_t) atol(row[0]);
+                timeinfo = localtime(&rawtime);
+                strftime(timeBuf, 80, "%c", timeinfo);
+                reply(textclient, user, "NS_INFO_OWNERCHANGE", row[1], row[2], timeBuf);
+            }
+        }
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_invite.c b/src/modules/NeonServ.mod/cmd_neonserv_invite.c
new file mode 100644 (file)
index 0000000..b9bce28
--- /dev/null
@@ -0,0 +1,185 @@
+/* cmd_neonserv_invite.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_invite_nick_lookup);
+static void neonserv_cmd_invite_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth);
+static TIMEQ_CALLBACK(neonserv_cmd_invite_timeout_timeout);
+static struct neonserv_cmd_invite_timeout* neonserv_cmd_invite_add_timeout(char *nick, char *chan);
+static int neonserv_cmd_invite_is_timeout(char *nick, char *chan);
+static void neonserv_cmd_invite_del_timeout(struct neonserv_cmd_invite_timeout *timeout);
+
+
+struct neonserv_cmd_invite_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+};
+
+struct neonserv_cmd_invite_timeout {
+    char *nick;
+    char *chan;
+    
+    struct neonserv_cmd_invite_timeout *next;
+};
+
+static struct neonserv_cmd_invite_timeout *first_timeout = NULL, *last_timeout = NULL;
+
+CMD_BIND(neonserv_cmd_invite) {
+    if(neonserv_cmd_invite_is_timeout(argv[0], chan->name)) {
+        reply(textclient, user, "NS_INVITE_TIMEOUT", argv[0], chan->name);
+        return;
+    }
+    struct UserNode *cuser = getUserByNick(argv[0]);
+    if(!cuser) {
+        cuser = createTempUser(argv[0]);
+               if(!cuser) {
+            reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+            return;
+        }
+        cuser->flags |= USERFLAG_ISTMPUSER;
+    } else if(getChanUser(cuser, chan)) {
+        reply(textclient, user, "NS_INVITE_ON_CHAN", cuser->nick, chan->name);
+        /* BUG
+         This check does not work if the user is invisible (CHMODE +D/+d)
+         to fix this we'd need to request the full userlist...
+         this is really senseless to invite a simple user so we simply mark this bug as unsolvable.
+        */
+        return;
+    }
+    if(cuser->flags & USERFLAG_ISAUTHED) {
+        neonserv_cmd_invite_async1(client, textclient, user, chan, event, argv[0], cuser->auth);
+    } else {
+        struct neonserv_cmd_invite_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->textclient = textclient;
+        cache->user = user;
+        cache->chan = chan;
+        cache->event = event;
+        cache->nick = strdup(argv[0]);
+        get_userauth(cuser, module_id, neonserv_cmd_invite_nick_lookup, cache);
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_invite_nick_lookup) {
+    struct neonserv_cmd_invite_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    } else
+        neonserv_cmd_invite_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, ((user->flags & USERFLAG_ISAUTHED) ? user->auth : NULL));
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_invite_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth) {
+    if(auth && (!(user->flags & USERFLAG_ISAUTHED) || stricmp(auth, user->auth))) {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        printf_mysql_query("SELECT `user_id`, `user_block_invites` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            //check if the user has blocked invites globally
+            if(!strcmp(row[1], "1")) {
+                reply(textclient, user, "NS_INVITE_GLOBALLY_BLOCKED", nick);
+                return;
+            }
+            //check if the user has set noinvite
+            printf_mysql_query("SELECT `id` FROM `noinvite` WHERE `uid` = '%s' AND `cid` = '%d'", row[0], chan->channel_id);
+            res = mysql_use();
+            if ((row = mysql_fetch_row(res)) != NULL) {
+                reply(textclient, user, "NS_INVITE_RESTRICTION", nick, chan->name);
+                return;
+            }
+        }
+    }
+    struct neonserv_cmd_invite_timeout *timeout = neonserv_cmd_invite_add_timeout(nick, chan->name);
+    timeq_add(INVITE_TIMEOUT, module_id, neonserv_cmd_invite_timeout_timeout, timeout);
+    putsock(client, "INVITE %s %s", nick, chan->name);
+    struct UserNode *tmpu = getUserByNick(nick);
+    if(!tmpu) {
+        tmpu = createTempUser(nick);
+        tmpu->flags |= USERFLAG_ISTMPUSER | (auth ? USERFLAG_ISAUTHED : 0);
+        if(auth)
+            strcpy(tmpu->auth, auth);
+    }
+    reply(textclient, tmpu, "NS_INVITE_DONE_USER", chan->name, user->nick, client->user->nick);
+    reply(textclient, user, "NS_INVITE_DONE", nick, chan->name);
+}
+
+static TIMEQ_CALLBACK(neonserv_cmd_invite_timeout_timeout) {
+    struct neonserv_cmd_invite_timeout *entry = data;
+    neonserv_cmd_invite_del_timeout(entry);
+}
+
+static struct neonserv_cmd_invite_timeout* neonserv_cmd_invite_add_timeout(char *nick, char *chan) {
+    struct neonserv_cmd_invite_timeout *entry = malloc(sizeof(*entry));
+    if (!entry) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    entry->next = NULL;
+    entry->nick = strdup(nick);
+    entry->chan = strdup(chan);
+    if(last_timeout) {
+        last_timeout->next = entry;
+        last_timeout = entry;
+    } else {
+        last_timeout = entry;
+        first_timeout = entry;
+    }
+    return entry;
+}
+
+static int neonserv_cmd_invite_is_timeout(char *nick, char *chan) {
+    if(!first_timeout) return 0;
+    struct neonserv_cmd_invite_timeout *entry;
+    for(entry = first_timeout; entry; entry = entry->next) {
+        if(!stricmp(entry->nick, nick) && !stricmp(entry->chan, chan))
+            return 1;
+    }
+    return 0;
+}
+
+static void neonserv_cmd_invite_del_timeout(struct neonserv_cmd_invite_timeout *timeout) {
+    struct neonserv_cmd_invite_timeout *entry, *prev = NULL;
+    for(entry = first_timeout; entry; entry = entry->next) {
+        if(entry == timeout) {
+            if(prev)
+                prev->next = entry->next;
+            else
+                first_timeout = entry->next;
+            break;
+        } else
+            prev = entry;
+    }
+    if(last_timeout == timeout)
+        last_timeout = prev;
+    free(timeout->nick);
+    free(timeout->chan);
+    free(timeout);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_inviteme.c b/src/modules/NeonServ.mod/cmd_neonserv_inviteme.c
new file mode 100644 (file)
index 0000000..bbe7a30
--- /dev/null
@@ -0,0 +1,36 @@
+/* cmd_neonserv_inviteme.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_inviteme) {
+    if(getChanUser(user, chan)) {
+        reply(textclient, user, "NS_INVITEME_ON_CHAN", chan->name);
+        /* BUG
+         This check does not work if the user is invisible (CHMODE +D/+d)
+         to fix this we'd need to request the full userlist...
+         this is really senseless to invite a simple user so we simply mark this bug as unsolvable.
+        */
+        return;
+    }
+    putsock(client, "INVITE %s %s", user->nick, chan->name);
+    reply(textclient, user, "NS_INVITEME_DONE", chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_invitemeall.c b/src/modules/NeonServ.mod/cmd_neonserv_invitemeall.c
new file mode 100644 (file)
index 0000000..c7d3340
--- /dev/null
@@ -0,0 +1,52 @@
+/* cmd_neonserv_invitemeall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_invitemeall) {
+    //invite to all channels where autoinvite is enabled
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW chanuserrow, defaultrow = NULL;
+    printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `channel_name`, `channel_getinvite` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `user_user` = '%s' AND `chanuser_flags` >= '%d'", escape_string(user->auth), DB_CHANUSER_AUTOINVITE);
+    res = mysql_use();
+    struct ChanUser *bchanuser;
+    struct ClientSocket *bot;
+    while((chanuserrow = mysql_fetch_row(res)) != NULL) {
+        int userflags = atoi(chanuserrow[1]);
+        if(!(argc && !stricmp(argv[0], "all")) && !(userflags & DB_CHANUSER_AUTOINVITE)) continue;
+        if(!(chan = getChanByName(chanuserrow[2]))) continue; //no bot is in the channel
+        if((bchanuser = getChanUser(client->user, chan)) && (bchanuser->flags & CHANUSERFLAG_OPPED))
+            bot = client;
+        else {
+            bot = getBotForChannel(chan);
+        }
+        if(getChanUser(user, chan)) continue; //user is already in the channel
+        if(chanuserrow[3] == NULL && defaultrow == NULL) {
+            printf_mysql_query("SELECT `channel_getinvite` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res2 = mysql_use();
+            defaultrow = mysql_fetch_row(res2);
+        }
+        if(atoi(chanuserrow[0]) >= atoi((chanuserrow[3] ? chanuserrow[3] : defaultrow[0]))) {
+            putsock(bot, "INVITE %s %s", user->nick, chan->name);
+            reply(textclient, user, "NS_INVITEME_DONE", chan->name);
+        }
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_kick.c b/src/modules/NeonServ.mod/cmd_neonserv_kick.c
new file mode 100644 (file)
index 0000000..a4d3481
--- /dev/null
@@ -0,0 +1,167 @@
+/* cmd_neonserv_kick.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    nick[,*auth[,*!*@mask[...]]]
+* argv[1-*]  reason
+*/
+static USERLIST_CALLBACK(neonserv_cmd_kick_userlist_lookup);
+static void neonserv_cmd_kick_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks, char *reason);
+
+struct neonserv_cmd_kick_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nicks;
+    char *reason;
+};
+
+CMD_BIND(neonserv_cmd_kick) {
+    struct neonserv_cmd_kick_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->nicks = strdup(argv[0]);
+    if(argc > 1) {
+        cache->reason = strdup(merge_argv(argv, 1, argc));
+    } else
+        cache->reason = NULL;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_kick_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_kick_userlist_lookup) {
+    struct neonserv_cmd_kick_cache *cache = data;
+    neonserv_cmd_kick_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nicks, (cache->reason ? cache->reason : "Bye."));
+    free(cache->nicks);
+    if(cache->reason)
+        free(cache->reason);
+    free(cache);
+}
+
+static void neonserv_cmd_kick_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks, char *reason) {
+    int i, kicked_users = 0, provided_nicks = 0;
+    char *nick, *nextnick;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    nextnick = nicks;
+    while((nick = nextnick)) {
+        nextnick = strstr(nick, ",");
+        if(nextnick) {
+            *nextnick = '\0';
+            nextnick++;
+        }
+        if(!*nick) continue;
+        if(is_ircmask(nick)) {
+            //KICK HOSTMASK
+            char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+            struct ChanUser *kick_chanuser[chan->usercount];
+            int kick_chanuser_pos = 0;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                cuser = chanuser->user;
+                sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+                if(!match(nick, usermask)) {
+                    provided_nicks++;
+                    if(isNetworkService(chanuser->user)) {
+                        reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                        continue;
+                    }
+                    if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                        reply(textclient, user, "NS_YOU_PROTECTED");
+                        continue;
+                    }
+                    if(isUserProtected(chan, cuser, user)) {
+                        reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                        continue;
+                    }
+                    kick_chanuser[kick_chanuser_pos++] = chanuser;
+                    if(kick_chanuser_pos > 4 && (kick_chanuser_pos * 3) > chan->usercount && !isGodMode(user)) {
+                        kick_chanuser_pos = 0;
+                        reply(textclient, user, "NS_LAME_MASK", nick);
+                        break;
+                    }
+                }
+            }
+            for(i = 0; i < kick_chanuser_pos; i++) {
+                kicked_users++;
+                putsock(client, "KICK %s %s :(%s) %s", chan->name, kick_chanuser[i]->user->nick, user->nick, reason);
+            }
+        } else if(*nick == '*') {
+            //KICK AUTH
+            nick++;
+            cuser = NULL;
+            if(!stricmp(user->auth, nick)) {
+                reply(textclient, user, "NS_YOU_PROTECTED");
+                continue;
+            }
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if((chanuser->user->flags & USERFLAG_ISAUTHED) && !stricmp(chanuser->user->auth, nick)) {
+                    provided_nicks++;
+                    if(isNetworkService(chanuser->user)) {
+                        reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                        continue;
+                    }
+                    if(!cuser) {
+                        //check if the user is protected
+                        if(isUserProtected(chan, chanuser->user, user)) {
+                            reply(textclient, user, "NS_USER_PROTECTED", chanuser->user->nick);
+                            break; //all other users are also protected...
+                        }
+                        cuser = chanuser->user;
+                    }
+                    kicked_users++;
+                    putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+                }
+            }
+        } else {
+            provided_nicks++;
+            cuser = NULL;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if(!stricmp(chanuser->user->nick, nick)) {
+                    cuser = chanuser->user;
+                }
+            }
+            if(!cuser) continue;
+            if(isNetworkService(cuser)) {
+                reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+                continue;
+            }
+            if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                reply(textclient, user, "NS_YOU_PROTECTED");
+                continue;
+            }
+            if(isUserProtected(chan, cuser, user)) {
+                reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                continue;
+            }
+            kicked_users++;
+            putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+        }
+    }
+    if(kicked_users == provided_nicks)
+        reply(textclient, user, "NS_KICK_DONE", kicked_users, chan->name);
+    else
+        reply(textclient, user, "NS_KICK_FAIL", client->user->nick);
+    if(kicked_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_kickban.c b/src/modules/NeonServ.mod/cmd_neonserv_kickban.c
new file mode 100644 (file)
index 0000000..a580764
--- /dev/null
@@ -0,0 +1,169 @@
+/* cmd_neonserv_kickban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    nick[,*auth[,*!*@mask[...]]]
+* argv[1-*]  reason
+*/
+static USERLIST_CALLBACK(neonserv_cmd_kickban_userlist_lookup);
+static void neonserv_cmd_kickban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks, char *reason);
+
+struct neonserv_cmd_kickban_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nicks;
+    char *reason;
+};
+
+CMD_BIND(neonserv_cmd_kickban) {
+    struct neonserv_cmd_kickban_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->nicks = strdup(argv[0]);
+    if(argc > 1) {
+        cache->reason = strdup(merge_argv(argv, 1, argc));
+    } else
+        cache->reason = NULL;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_kickban_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_kickban_userlist_lookup) {
+    struct neonserv_cmd_kickban_cache *cache = data;
+    neonserv_cmd_kickban_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nicks, (cache->reason ? cache->reason : "Bye."));
+    free(cache->nicks);
+    if(cache->reason)
+        free(cache->reason);
+    free(cache);
+}
+
+static void neonserv_cmd_kickban_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks, char *reason) {
+    int i, kicked_users = 0, provided_nicks = 0;
+    char *nick, *nextnick;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    nextnick = nicks;
+    while((nick = nextnick)) {
+        nextnick = strstr(nick, ",");
+        if(nextnick) {
+            *nextnick = '\0';
+            nextnick++;
+        }
+        if(!*nick) continue;
+        if(is_ircmask(nick)) {
+            //KICK HOSTMASK
+            struct ChanUser *kickban_chanuser[chan->usercount];
+            int kick_chanuser_pos = 0;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                cuser = chanuser->user;
+                sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+                if(!match(nick, usermask)) {
+                    provided_nicks++;
+                    if(isNetworkService(chanuser->user)) {
+                        reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                        continue;
+                    }
+                    if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                        reply(textclient, user, "NS_YOU_PROTECTED");
+                        continue;
+                    }
+                    if(isUserProtected(chan, cuser, user)) {
+                        reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                        continue;
+                    }
+                    kickban_chanuser[kick_chanuser_pos++] = chanuser;
+                    if(kick_chanuser_pos > 4 && (kick_chanuser_pos * 3) > chan->usercount && !isGodMode(user)) {
+                        kick_chanuser_pos = 0;
+                        reply(textclient, user, "NS_LAME_MASK", nick);
+                        break;
+                    }
+                }
+            }
+            for(i = 0; i < kick_chanuser_pos; i++) {
+                if(i == 0) {
+                    putsock(client, "MODE %s +b %s", chan->name, nick);
+                }
+                kicked_users++;
+                putsock(client, "KICK %s %s :(%s) %s", chan->name, kickban_chanuser[i]->user->nick, user->nick, reason);
+            }
+        } else if(*nick == '*') {
+            //KICK AUTH
+            nick++;
+            cuser = NULL;
+            if(!stricmp(user->auth, nick)) {
+                reply(textclient, user, "NS_YOU_PROTECTED");
+                continue;
+            }
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if((chanuser->user->flags & USERFLAG_ISAUTHED) && !stricmp(chanuser->user->auth, nick)) {
+                    provided_nicks++;
+                    if(isNetworkService(chanuser->user)) {
+                        reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                        continue;
+                    }
+                    if(!cuser) {
+                        //check if the user is protected
+                        if(isUserProtected(chan, chanuser->user, user)) {
+                            reply(textclient, user, "NS_USER_PROTECTED", chanuser->user->nick);
+                            break; //all other users are also protected...
+                        }
+                        cuser = chanuser->user;
+                    }
+                    kicked_users++;
+                    putsock(client, "MODE %s +b %s", chan->name, generate_banmask(cuser, usermask));
+                    putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+                }
+            }
+        } else {
+            provided_nicks++;
+            cuser = searchUserByNick(nick);
+            if(!cuser) continue;
+            chanuser = getChanUser(cuser, chan);
+            if(!chanuser) continue;
+            if(isNetworkService(cuser)) {
+                reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+                continue;
+            }
+            if(cuser == user || ((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))) {
+                reply(textclient, user, "NS_YOU_PROTECTED");
+                continue;
+            }
+            if(isUserProtected(chan, cuser, user)) {
+                reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                continue;
+            }
+            kicked_users++;
+            putsock(client, "MODE %s +b %s", chan->name, generate_banmask(cuser, usermask));
+            putsock(client, "KICK %s %s :(%s) %s", chan->name, cuser->nick, user->nick, reason);
+        }
+    }
+    if(kicked_users == provided_nicks)
+        reply(textclient, user, "NS_KICKBAN_DONE", kicked_users, chan->name);
+    else
+        reply(textclient, user, "NS_KICKBAN_FAIL", client->user->nick);
+    if(kicked_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_listrank.c b/src/modules/NeonServ.mod/cmd_neonserv_listrank.c
new file mode 100644 (file)
index 0000000..162a6ca
--- /dev/null
@@ -0,0 +1,77 @@
+/* cmd_neonserv_listrank.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_listrank) {
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    struct Table *table;
+    int ranks = 0;
+    printf_mysql_query("SELECT `rank_id`, `rank_name` FROM `support_ranks` ORDER BY `rank_order` ASC");
+    res = mysql_use();
+    table = table_init(3, mysql_num_rows(res) + 1, 0);
+    char *content[3];
+    content[0] = get_language_string(user, "NS_LISTRANK_ID");
+    content[1] = get_language_string(user, "NS_LISTRANK_NAME");
+    content[2] = get_language_string(user, "NS_LISTRANK_ASSIGNED");
+    table_add(table, content);
+    char usersBuf[MAXLEN];
+    int usersPos, userCount;
+    char assignedBuf[MAXLEN];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        ranks++;
+        content[0] = row[0];
+        content[1] = row[1];
+        printf_mysql_query("SELECT `user_user`, `user_god` FROM `users` WHERE `user_rank` = '%s'", row[0]);
+        res2 = mysql_use();
+        usersPos = 0;
+        userCount = 0;
+        while((row2 = mysql_fetch_row(res2)) != NULL) {
+            usersPos += sprintf(usersBuf+usersPos, (usersPos ? ", %s%s" : "%s%s"), (strcmp(row2[1], "0") ? "@" : ""), row2[0]);
+            userCount++;
+        }
+        sprintf(assignedBuf, (userCount ? "%d (%s)" : "%d"), userCount, usersBuf);
+        content[2] = assignedBuf;
+        table_add(table, content);
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(!ranks)
+        reply(textclient, user, "NS_TABLE_NONE");
+    table_free(table);
+    printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_rank` = '0' AND `user_access` > 0");
+    res2 = mysql_use();
+    usersPos = 0;
+    userCount = 0;
+    while((row2 = mysql_fetch_row(res2)) != NULL) {
+        usersPos += sprintf(usersBuf+usersPos, (usersPos ? ", %s%s" : "%s%s"), (strcmp(row2[1], "0") ? "@" : ""), row2[0]);
+        userCount++;
+    }
+    reply(textclient, user, "NS_LISTRANK_UNRANKED", userCount);
+    if(userCount) {
+        reply(textclient, user, "  %s", usersBuf);
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_mdeluser.c b/src/modules/NeonServ.mod/cmd_neonserv_mdeluser.c
new file mode 100644 (file)
index 0000000..b511366
--- /dev/null
@@ -0,0 +1,64 @@
+/* cmd_neonserv_mdeluser.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]  access (format: minaccess-maxaccess)
+* argv[1]  pattern
+*/
+
+CMD_BIND(neonserv_cmd_mdeluser) {
+    int min_access, max_access;
+    char *seperator = strstr(argv[0], "-");
+    if(seperator) {
+        *seperator = '\0';
+        seperator++;
+        min_access = atoi(argv[0]);
+        max_access = atoi(seperator);
+        if(max_access < min_access) {
+            reply(textclient, user, "NS_INVALID_ACCESS_RANGE", min_access, max_access);
+            return;
+        }
+    } else {
+        min_access = atoi(argv[0]);
+        max_access = min_access;
+    }
+    if(max_access >= getChannelAccess(user, chan)) {
+        if(isGodMode(user)) {
+            event->flags |= CMDFLAG_OPLOG;
+        } else {
+            reply(textclient, user, "NS_NO_ACCESS");
+            return;
+        }
+    }
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int del_count = 0;
+    printf_mysql_query("SELECT `user_user`, `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `chanuser_access` >= '%d' AND `chanuser_access` <= '%d'", chan->channel_id, min_access, max_access);
+    res = mysql_use();
+    while((row = mysql_fetch_row(res)) != NULL) {
+        if(!match(argv[1], row[0])) {
+            del_count++;
+            printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_id` = '%s'", row[1]);
+        }
+    }
+    reply(textclient, user, "NS_MDELUSER_DONE", del_count, argv[1], min_access, max_access, chan->name);
+    if(del_count)
+        logEvent(event);
+}
+
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_mode.c b/src/modules/NeonServ.mod/cmd_neonserv_mode.c
new file mode 100644 (file)
index 0000000..41e0455
--- /dev/null
@@ -0,0 +1,297 @@
+/* cmd_neonserv_mode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - modes
+* argv[1-*] - parameters
+*/
+static USERLIST_CALLBACK(neonserv_cmd_mode_userlist_lookup);
+static void neonserv_cmd_mode_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mode);
+
+struct neonserv_cmd_mode_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *mode;
+};
+
+CMD_BIND(neonserv_cmd_mode) {
+    struct neonserv_cmd_mode_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->mode = strdup(merge_argv(argv, 0, argc));
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_mode_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_mode_userlist_lookup) {
+    struct neonserv_cmd_mode_cache *cache = data;
+    neonserv_cmd_mode_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->mode);
+    free(cache->mode);
+    free(cache);
+}
+
+static void neonserv_cmd_mode_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *mode) {
+    MYSQL_ROW row, defaults = NULL;
+    int i, arg, add = 1, skip = 0;
+    unsigned int modetype;
+    int db_canop, db_canvoice, db_canban, db_enfmodes;
+    struct ModeNode *modelock = createModeNode(NULL), *changemodes = createModeNode(NULL);
+    struct ModeBuffer *modeBuf;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    modeBuf = initModeBuffer(client, chan);
+    printf_mysql_query("SELECT `channel_canop`, `channel_canvoice`, `channel_canban`, `channel_enfmodes`, `channel_modes` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    row = mysql_fetch_row(mysql_use());
+    if(row[0] == NULL || row[1] == NULL || row[2] == NULL || row[3] == NULL || row[4] == NULL) {
+        printf_mysql_query("SELECT `channel_canop`, `channel_canvoice`, `channel_canban`, `channel_enfmodes`, `channel_modes` FROM `channels` WHERE `channel_name` = 'defaults'");
+        defaults = mysql_fetch_row(mysql_use());
+    }
+    db_canop = atoi((row[0] ? row[0] : defaults[0]));
+    db_canvoice = atoi((row[1] ? row[1] : defaults[1]));
+    db_canban = atoi((row[2] ? row[2] : defaults[2]));
+    db_enfmodes = atoi((row[3] ? row[3] : defaults[3]));
+    if(row[4])
+        parseModeString(modelock, row[4]);
+    else if(defaults[4])
+        parseModeString(modelock, defaults[4]);
+    int uaccess = getChannelAccess(user, chan);
+    char *a, *b = mode;
+    char *argv[MAXNUMPARAMS];
+    char *carg;
+    char tmp[MAXLEN];
+    int argc = 0;
+    do {
+        a = strstr(b, " ");
+        if(a) *a = '\0';
+        argv[argc++] = b;
+        if(argc == MAXNUMPARAMS) break;
+        if(a) b = a+1;
+    } while(a);
+    arg = 0;
+    while(arg < argc) {
+        char *modeStr = argv[arg++];
+        for(i = 0; i < strlen(modeStr); i++) {
+            switch(modeStr[i]) {
+                case '+':
+                    add = 1;
+                    break;
+                case '-':
+                    add = 0;
+                    break;
+                case 'o':
+                case 'v':
+                    if(arg == argc) {
+                        reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                        return;
+                    }
+                    carg = argv[arg++];
+                    if(modeStr[i] == 'o') {
+                        if(uaccess < db_canop) {
+                            reply(textclient, user, "NS_MODE_ENFOPS", chan->name);
+                            db_canop = -1;
+                            break;
+                        }
+                        if(db_canop == -1) break;
+                    } else {
+                        if(uaccess < db_canvoice) {
+                            reply(textclient, user, "NS_MODE_ENFVOICE", chan->name);
+                            db_canvoice = -1;
+                            break;
+                        }
+                        if(db_canvoice == -1) break;
+                    }
+                    cuser = searchUserByNick(carg);
+                    if(!cuser) {
+                        //check for an invisible user
+                        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                            if(!stricmp(chanuser->user->nick, carg)) {
+                                cuser = chanuser->user;
+                                break;
+                            }
+                        }
+                        if(!cuser) break;
+                    } else {
+                        chanuser = getChanUser(cuser, chan);
+                        if(!chanuser) break;
+                    }
+                    if(!(add ^ (chanuser->flags & (modeStr[i] == 'o' ? CHANUSERFLAG_OPPED : CHANUSERFLAG_VOICED)))) break;
+                    if(!add) {
+                        //check protection
+                        if(modeStr[i] == 'o' && isNetworkService(cuser)) {
+                            reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+                            break;
+                        }
+                        if(isUserProtected(chan, cuser, user)) {
+                            reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                            break;
+                        }
+                    }
+                    modeBufferSet(modeBuf, add, modeStr[i], carg);
+                    break;
+                case 'b':
+                    if(arg == argc) {
+                        reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                        return;
+                    }
+                    carg = argv[arg++];
+                    if(uaccess < db_canban) {
+                        reply(textclient, user, "NS_MODE_CANBAN", chan->name);
+                        db_canban = -1;
+                        break;
+                    }
+                    if(db_canban == -1) break;
+                    char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+                    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+                    struct BanNode *ban;
+                    int match_count = 0;
+                    carg = make_banmask(carg, hostmask_buffer);
+                    if(add) {
+                        for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                            cuser = chanuser->user;
+                            sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+                            if(!match(carg, usermask)) {
+                                if(isNetworkService(chanuser->user)) {
+                                    reply(textclient, user, "NS_SERVICE_IMMUNE", chanuser->user->nick);
+                                    skip = 1;
+                                    break;
+                                }
+                                if(isUserProtected(chan, cuser, user)) {
+                                    reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                                    skip = 1;
+                                    break;
+                                }
+                                match_count++;
+                                if(match_count > 4 && (match_count * 3) > chan->usercount && !isGodMode(user)) {
+                                    skip = 1;
+                                    reply(textclient, user, "NS_LAME_MASK", carg);
+                                    break;
+                                }
+                            }
+                        }
+                    } else {
+                        skip = 1;
+                        for(ban = chan->bans; ban; ban = ban->next) {
+                            if(!match(carg, ban->mask)) {
+                                skip = 0;
+                                break;
+                            }
+                        }
+                    }
+                    if(!skip) {
+                        modeBufferSet(modeBuf, add, 'b', carg);
+                    }
+                    break;
+                default:
+                    modetype = getModeType(modelock, modeStr[i]);
+                    if(modetype == 0) {
+                        reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                        return;
+                    }
+                    if(isModeAffected(modelock, modeStr[i]) && add == !isModeSet(modelock, modeStr[i]) && uaccess < db_enfmodes) {
+                        if(isGodMode(user))
+                            event->flags |= CMDFLAG_OPLOG;
+                        else {
+                            getFullModeString(modelock, tmp);
+                            reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                            return;
+                        }
+                    }
+                    if(add && (modetype & CHANNEL_MODE_TYPE) != CHANNEL_MODE_TYPE_D) {
+                        if(arg == argc) {
+                            reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                            return;
+                        }
+                        carg = argv[arg++];
+                        if((modetype & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING && isModeSet(modelock, modeStr[i])) {
+                            char *modelock_val = getModeValue(modelock, modeStr[i]);
+                            if(stricmp(carg, modelock_val)) {
+                                if(isGodMode(user))
+                                    event->flags |= CMDFLAG_OPLOG;
+                                else {
+                                    getFullModeString(modelock, tmp);
+                                    reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                                    return;
+                                }
+                            }
+                        }
+                        if((modetype & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING && isModeSet(modelock, modeStr[i])) {
+                            int *modelock_val = getModeValue(modelock, modeStr[i]);
+                            if(atoi(carg) != *modelock_val) {
+                                if(isGodMode(user))
+                                    event->flags |= CMDFLAG_OPLOG;
+                                else {
+                                    getFullModeString(modelock, tmp);
+                                    reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                                    return;
+                                }
+                            }
+                        }
+                    } else if(!add && (modetype & CHANNEL_MODE_TYPE) == CHANNEL_MODE_TYPE_B) {
+                        if(arg == argc && !(modetype & CHANNEL_MODE_KEY)) {
+                            reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                            return;
+                        }
+                        carg = (arg == argc ? NULL : argv[arg++]);
+                    } else
+                        carg = NULL;
+                    if((modetype & CHANNEL_MODE_TYPE) == CHANNEL_MODE_TYPE_D && isModeSet(chan->modes, modeStr[i]) == add)
+                        break;
+                    if(!isModeAffected(changemodes, modeStr[i])) {
+                        if(!add && (modetype & CHANNEL_MODE_KEY)) {
+                            if(isModeSet(chan->modes, modeStr[i])) {
+                                char *current_val = getModeValue(chan->modes, modeStr[i]);
+                                carg = current_val;
+                            }
+                        }
+                        if(parseMode(changemodes, add, modeStr[i], carg)) {
+                            if(carg) {
+                                if(add && (modetype & CHANNEL_MODE_KEY) && isModeSet(chan->modes, modeStr[i])) {
+                                    char *current_val = getModeValue(chan->modes, modeStr[i]);
+                                    modeBufferSet(modeBuf, 0, modeStr[i], current_val);
+                                    flushModeBuffer(modeBuf);
+                                }
+                                if(!add && !isModeSet(chan->modes, modeStr[i])) break;
+                                modeBufferSet(modeBuf, add, modeStr[i], carg);
+                            } else {
+                                modeBufferSimpleMode(modeBuf, add, modeStr[i]);
+                            }
+                        } else {
+                            reply(textclient, user, "NS_MODE_INVALID", modeStr[i]);
+                            return;
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+    getFullModeString(changemodes, tmp);
+    freeModeBuffer(modeBuf);
+    if(strcmp(tmp, "+"))
+        reply(textclient, user, "NS_MODE_DONE", tmp);
+    
+    logEvent(event);
+    freeModeNode(modelock);
+    freeModeNode(changemodes);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_myaccess.c b/src/modules/NeonServ.mod/cmd_neonserv_myaccess.c
new file mode 100644 (file)
index 0000000..29f8cfd
--- /dev/null
@@ -0,0 +1,197 @@
+/* cmd_neonserv_myaccess.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_myaccess_nick_lookup);
+static void neonserv_cmd_myaccess_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, char *chanmatch);
+
+struct neonserv_cmd_myaccess_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+    char *chanmatch;
+};
+
+CMD_BIND(neonserv_cmd_myaccess) {
+    char *chanmatch = NULL;
+    if(argc == 0 || argv[0][0] == '#') {
+        if(argc != 0) {
+            chanmatch = argv[0];
+        }
+        if(!(user->flags & USERFLAG_ISAUTHED)) {
+            struct neonserv_cmd_myaccess_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->nick = strdup(argv[0]);
+            cache->chanmatch = (chanmatch ? strdup(chanmatch) : NULL);
+            get_userauth(user, module_id, neonserv_cmd_myaccess_nick_lookup, cache);
+        } else
+            neonserv_cmd_myaccess_async1(client, textclient, user, chan, event, user->nick, user->auth, chanmatch);
+    }
+    else if(argv[0][0] == '*') {
+        //we've got an auth
+        if(argc > 1 && argv[1][0] == '#') {
+            chanmatch = argv[1];
+        }
+        argv[0]++;
+        neonserv_cmd_myaccess_async1(client, textclient, user, chan, event, NULL, argv[0], chanmatch);
+    } else {
+        if(argc > 1 && argv[1][0] == '#') {
+            chanmatch = argv[1];
+        }
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_myaccess_async1(client, textclient, user, chan, event, argv[0], cuser->auth, chanmatch);
+        } else {
+            struct neonserv_cmd_myaccess_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            cache->chanmatch = (chanmatch ? strdup(chanmatch) : NULL);
+            get_userauth(cuser, module_id, neonserv_cmd_myaccess_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_myaccess_nick_lookup) {
+    struct neonserv_cmd_myaccess_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        if(!strcmp(cache->nick, cache->user->nick))
+            reply(cache->textclient, cache->user, "NS_YOU_NEED_AUTH");
+        else
+            reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_myaccess_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth, cache->chanmatch);
+    if(cache->chanmatch)
+        free(cache->chanmatch);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_myaccess_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth, char *chanmatch) {
+    //we've got a valid auth now...
+    if(stricmp(user->auth, auth)) {
+        if(!isGodMode(user)) {
+            reply(textclient, user, "NS_MYACCESS_SELF_ONLY");
+            return;
+        } else
+            event->flags |= CMDFLAG_OPLOG;
+    }
+    MYSQL_RES *res, *default_res;
+    MYSQL_ROW user_row, chanuser_row, default_chan = NULL;
+    char flagBuf[5];
+    int userid, cflags, caccess, flagPos;
+    int i, total_count = 0, match_count = 0, owner_count = 0;
+    struct Table *table = NULL;
+    printf_mysql_query("SELECT `user_id`, `user_access`, `user_god` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    char *content[4];
+    content[0] = get_language_string(user, "NS_MYACCESS_HEADER_NAME");
+    content[1] = get_language_string(user, "NS_MYACCESS_HEADER_ACCESS");
+    content[2] = get_language_string(user, "NS_MYACCESS_HEADER_FLAGS");
+    content[3] = get_language_string(user, "NS_MYACCESS_HEADER_INFO");
+    if(chanmatch)
+        reply(textclient, user, "NS_MYACCESS_HEADER_MATCH", auth, chanmatch);
+    else
+        reply(textclient, user, "NS_MYACCESS_HEADER", auth);
+    if ((user_row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(user_row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `chanuser_infoline`, `channel_name`, `channel_getop`, `channel_getvoice`, `botid` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` LEFT JOIN `bot_channels` ON `chanuser_cid` = `chanid` LEFT JOIN `bots` ON `bots`.`id` = `botid` WHERE `chanuser_uid` = '%d' AND `botclass` = '%d' AND `active` = '1' ORDER BY `chanuser_access` DESC, `channel_name` ASC", userid, client->botid);
+        res = mysql_use();
+        total_count = mysql_num_rows(res);
+        table = table_init(4, total_count + 1, 0);
+        table_add(table, content);
+        while ((chanuser_row = mysql_fetch_row(res)) != NULL) {
+            if(!strcmp(chanuser_row[0], "500")) owner_count++;
+            if(chanmatch && match(chanmatch, chanuser_row[0])) continue;
+            match_count++;
+            if((!chanuser_row[4] || !chanuser_row[5]) && !default_chan) {
+                printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
+                default_res = mysql_use();
+                default_chan = mysql_fetch_row(default_res);
+            }
+            flagPos = 0;
+            content[0] = chanuser_row[3];
+            content[1] = chanuser_row[0];
+            cflags = atoi(chanuser_row[1]);
+            caccess = atoi(chanuser_row[0]);
+            if((cflags & DB_CHANUSER_SUSPENDED))
+                flagPos += sprintf(flagBuf + flagPos, "s");
+            if(caccess >= (chanuser_row[4] ? atoi(chanuser_row[4]) : atoi(default_chan[0])))
+                flagPos += sprintf(flagBuf + flagPos, "o");
+            if(caccess >= (chanuser_row[5] ? atoi(chanuser_row[5]) : atoi(default_chan[1])))
+                flagPos += sprintf(flagBuf + flagPos, "v");
+            if((cflags & DB_CHANUSER_AUTOINVITE))
+                flagPos += sprintf(flagBuf + flagPos, "i");
+            content[2] = (flagPos ? flagBuf : "");
+            content[3] = chanuser_row[2];
+            table_add(table, content);
+        }
+    } else {
+        table = table_init(4, 1, 0);
+        table_add(table, content);
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(!match_count)
+        reply(textclient, user, "NS_TABLE_NONE");
+    if(chanmatch) {
+        reply(textclient, user, "NS_MYACCESS_COUNT_MATCH", auth, total_count, owner_count, match_count, chanmatch);
+    } else {
+        reply(textclient, user, "NS_MYACCESS_COUNT", auth, total_count, owner_count);
+    }
+    table_free(table);
+    if(event->flags & CMDFLAG_OPLOG)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_nicklist.c b/src/modules/NeonServ.mod/cmd_neonserv_nicklist.c
new file mode 100644 (file)
index 0000000..0fad5a8
--- /dev/null
@@ -0,0 +1,329 @@
+/* cmd_neonserv_nicklist.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    "force"
+* argv[1]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_nicklist_userlist_lookup);
+static void neonserv_cmd_nicklist_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask, int flags);
+static void neonserv_cmd_nicklist_synchronize_user(struct ChanNode *chan, struct UserNode *user, int access);
+
+#define NEONSERV_CMD_NICKLIST_FLAG_SYNCUSERS  0x01
+#define NEONSERV_CMD_NICKLIST_FLAG_NOWHO      0x02
+#define NEONSERV_CMD_NICKLIST_FLAG_VISCOUNT   0x04
+
+struct neonserv_cmd_nicklist_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nickmask;
+    int flags;
+};
+
+CMD_BIND(neonserv_cmd_nicklist) {
+    int flags = 0;
+    while(argc) {
+        if(!stricmp(argv[0], "sync")) {
+            if(!checkChannelAccess(user, chan, "channel_canadd", 0)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_ACCESS_DENIED");
+                    return;
+                }
+            }
+            flags |= NEONSERV_CMD_NICKLIST_FLAG_SYNCUSERS;
+            event->flags |= CMDFLAG_LOG;
+        } else if(argc && !stricmp(argv[0], "nowho") && isGodMode(user)) {
+            flags |= NEONSERV_CMD_NICKLIST_FLAG_NOWHO;
+        } else if(argc && !stricmp(argv[0], "viscount") && isGodMode(user)) {
+            flags |= NEONSERV_CMD_NICKLIST_FLAG_VISCOUNT;
+        } else
+            break;
+        argv++;
+        argc--;
+    }
+    if(flags & NEONSERV_CMD_NICKLIST_FLAG_NOWHO) {
+        neonserv_cmd_nicklist_async1(client, textclient, user, chan, event, (argc ? argv[0] : NULL), flags);
+        return;
+    }
+    struct neonserv_cmd_nicklist_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    if(argc) {
+        cache->nickmask = strdup(argv[0]);
+    } else
+        cache->nickmask = NULL;
+    cache->flags = flags;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_nicklist_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_nicklist_userlist_lookup) {
+    struct neonserv_cmd_nicklist_cache *cache = data;
+    neonserv_cmd_nicklist_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nickmask, cache->flags);
+    if(cache->nickmask)
+        free(cache->nickmask);
+    free(cache);
+}
+
+static int neonserv_cmd_nicklist_sort_flags[] = { 
+    CHANUSERFLAG_OPPED | CHANUSERFLAG_HALFOPPED | CHANUSERFLAG_VOICED,
+    CHANUSERFLAG_OPPED | CHANUSERFLAG_HALFOPPED,
+    CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED,
+    CHANUSERFLAG_OPPED,
+    CHANUSERFLAG_HALFOPPED | CHANUSERFLAG_VOICED,
+    CHANUSERFLAG_HALFOPPED,
+    CHANUSERFLAG_VOICED,
+    CHANUSERFLAG_INVISIBLE,
+    0
+};
+
+static int neonserv_cmd_nicklist_sort(const void *a, const void *b) {
+    const struct ChanUser *chanuser_a = *((struct ChanUser * const *) a);
+    const struct ChanUser *chanuser_b = *((struct ChanUser * const *) b); 
+    int i_a = 0, i_b = 0;
+    while((chanuser_a->flags & (CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED | CHANUSERFLAG_INVISIBLE)) != neonserv_cmd_nicklist_sort_flags[i_a] && neonserv_cmd_nicklist_sort_flags[i_a])
+        i_a++;
+    while((chanuser_b->flags & (CHANUSERFLAG_OPPED | CHANUSERFLAG_VOICED | CHANUSERFLAG_INVISIBLE)) != neonserv_cmd_nicklist_sort_flags[i_b] && neonserv_cmd_nicklist_sort_flags[i_b])
+        i_b++;
+    if(i_a == i_b) {
+        return stricmp(chanuser_a->user->nick, chanuser_b->user->nick);
+    } else
+        return i_a - i_b;
+}
+
+static void neonserv_cmd_nicklist_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask, int flags) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults = NULL;
+    struct Table *table;
+    char *content[4];
+    int userlistlen, i, j;
+    int db_enfops, db_enfvoice;
+    int caccess, synced_user, accessbufpos;
+    struct ChanUser *chanusers[chan->usercount];
+    struct ChanUser *chanuser;
+    struct ClientSocket *bot;
+    int chanuser_count;
+    char statebuf[5];
+    char accessbuf[50];
+    char viscountbuf[50];
+    int uaccess;
+    
+    i = 3;
+    if(flags & NEONSERV_CMD_NICKLIST_FLAG_VISCOUNT) {
+        i++;
+        content[3] = "VisCount";
+    }
+    table = table_init(i, chan->usercount + 1, 0);
+    content[0] = get_language_string(user, "NS_NICKLIST_NICK");
+    content[1] = get_language_string(user, "NS_NICKLIST_STATE");
+    content[2] = get_language_string(user, "NS_NICKLIST_ACCESS");
+    table_add(table, content);
+    
+    printf_mysql_query("SELECT `chanuser_access`, `user_user`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d'", chan->channel_id);
+    res = mysql_use();
+    userlistlen = mysql_num_rows(res);
+    i = 0;
+    MYSQL_ROW userlist[userlistlen];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        userlist[i++] = row;
+    }
+    
+    printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    row = mysql_fetch_row(mysql_use());
+    if(row[0] == NULL || row[1] == NULL) {
+        printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
+        defaults = mysql_fetch_row(mysql_use());
+    }
+    db_enfops = atoi((row[0] ? row[0] : defaults[0]));
+    db_enfvoice = atoi((row[1] ? row[1] : defaults[1]));
+    
+    chanuser_count = 0;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        chanusers[chanuser_count++] = chanuser;
+    }
+    qsort(chanusers, chanuser_count, sizeof(struct ChanUser *), neonserv_cmd_nicklist_sort);
+    
+    caccess = getChannelAccess(user, chan);
+    synced_user = 0;
+    for(i = 0; i < chanuser_count; i++) {
+        chanuser = chanusers[i];
+        
+        content[0] = chanuser->user->nick;
+        
+        j = 0;
+        if((chanuser->flags & CHANUSERFLAG_INVISIBLE)) statebuf[j++] = '<';
+        if((chanuser->flags & CHANUSERFLAG_OPPED)) statebuf[j++] = '@';
+        if((chanuser->flags & CHANUSERFLAG_HALFOPPED)) statebuf[j++] = '%';
+        if((chanuser->flags & CHANUSERFLAG_VOICED)) statebuf[j++] = '+';
+        statebuf[j++] = '\0';
+        content[1] = statebuf;
+        
+        uaccess = 0;
+        if(chanuser->user->flags & USERFLAG_ISAUTHED) {
+            for(j = 0; j < userlistlen; j++) {
+                if(!stricmp(chanuser->user->auth, userlist[j][1])) {
+                    uaccess = atoi(userlist[j][0]);
+                    if((((chanuser->flags & CHANUSERFLAG_OPPED) && uaccess < db_enfops) || ((chanuser->flags & CHANUSERFLAG_VOICED) && uaccess < db_enfvoice)) && !isNetworkService(chanuser->user)) {
+                        if(flags & NEONSERV_CMD_NICKLIST_FLAG_SYNCUSERS) {
+                            if((chanuser->flags & CHANUSERFLAG_OPPED) && (db_enfops < caccess || isGodMode(user))) {
+                                if(db_enfops >= caccess)
+                                    event->flags |= CMDFLAG_OPLOG;
+                                uaccess = db_enfops;
+                            } else if((chanuser->flags & CHANUSERFLAG_VOICED) && (caccess < db_enfvoice || isGodMode(user))) {
+                                if(db_enfvoice >= caccess)
+                                    event->flags |= CMDFLAG_OPLOG;
+                                uaccess = db_enfvoice;
+                            } else {
+                                //fail...
+                                accessbufpos = sprintf(accessbuf, "\00307%d\003", uaccess);
+                                break;
+                            }
+                            neonserv_cmd_nicklist_synchronize_user(chan, chanuser->user, uaccess);
+                            accessbufpos = sprintf(accessbuf, "\00309%d\003", uaccess);
+                            synced_user = 1;
+                        } else {
+                            synced_user = 1;
+                            accessbufpos = sprintf(accessbuf, "\00307%d\003", uaccess);
+                        }
+                    } else if((uaccess >= db_enfops && !(chanuser->flags & CHANUSERFLAG_OPPED)) || (uaccess >= db_enfvoice && !(chanuser->flags & CHANUSERFLAG_OPPED_OR_VOICED)))
+                        accessbufpos = sprintf(accessbuf, "\00303%d\003", uaccess);
+                    else
+                        accessbufpos = sprintf(accessbuf, "%d", uaccess);
+                    break;
+                }
+            }
+        }
+        if(!uaccess && (chanuser->flags & CHANUSERFLAG_OPPED_OR_VOICED) && !isNetworkService(chanuser->user)) {
+            if(flags & NEONSERV_CMD_NICKLIST_FLAG_SYNCUSERS) {
+                if((chanuser->flags & CHANUSERFLAG_OPPED) && (db_enfops < caccess || isGodMode(user))) {
+                    if(db_enfops >= caccess)
+                        event->flags |= CMDFLAG_OPLOG;
+                    uaccess = db_enfops;
+                } else if((chanuser->flags & CHANUSERFLAG_VOICED) && (db_enfvoice < caccess || isGodMode(user))) {
+                    if(db_enfvoice >= caccess)
+                        event->flags |= CMDFLAG_OPLOG;
+                    uaccess = db_enfvoice;
+                } else {
+                    uaccess = 0;
+                    accessbufpos = sprintf(accessbuf, "\003040\003");
+                }
+                if(uaccess && (chanuser->user->flags & USERFLAG_ISAUTHED)) {
+                    neonserv_cmd_nicklist_synchronize_user(chan, chanuser->user, uaccess);
+                    accessbufpos = sprintf(accessbuf, "\00309%d\003", uaccess);
+                    synced_user = 1;
+                } else if(uaccess) {
+                    accessbufpos = sprintf(accessbuf, "\003040\003");
+                }
+            } else {
+                synced_user = 1;
+                if(((chanuser->flags & CHANUSERFLAG_OPPED) && db_enfops > uaccess) || ((chanuser->flags & CHANUSERFLAG_VOICED) && db_enfvoice > uaccess))
+                    accessbufpos = sprintf(accessbuf, "\003040\003");
+                else
+                    accessbufpos = sprintf(accessbuf, "0");
+            }
+        } else if(!uaccess)
+            accessbufpos = sprintf(accessbuf, "0");
+        j = 0;
+        if(isBot(chanuser->user)) {
+            //check if bot is secret
+            for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                if(bot->user == chanuser->user)
+                    break;
+            }
+            if(bot && !(bot->flags & SOCKET_FLAG_SECRET_BOT)) {
+                if(j)
+                    accessbufpos += sprintf(accessbuf+accessbufpos, ", ");
+                else {
+                    accessbufpos += sprintf(accessbuf+accessbufpos, " (");
+                    j = 1;
+                }
+                accessbufpos += sprintf(accessbuf+accessbufpos, "%s", get_language_string(user, "NS_NICKLIST_ACCESS_BOT"));
+            }
+        }
+        if(chanuser->user->flags & USERFLAG_ISIRCOP) {
+            if(j)
+                accessbufpos += sprintf(accessbuf+accessbufpos, ", ");
+            else {
+                accessbufpos += sprintf(accessbuf+accessbufpos, " (");
+                j = 1;
+            }
+            accessbufpos += sprintf(accessbuf+accessbufpos, "%s", get_language_string(user, "NS_NICKLIST_ACCESS_OPER"));
+        }
+        if(j)
+            accessbufpos += sprintf(accessbuf+accessbufpos, ")");
+        content[2] = accessbuf;
+        if(flags & NEONSERV_CMD_NICKLIST_FLAG_VISCOUNT) {
+            if(chanuser->flags & CHANUSERFLAG_PARTING)
+                sprintf(viscountbuf, "%d (\003P\003 %d)", chanuser->visCount, chanuser->old_visCount);
+            else
+                sprintf(viscountbuf, "%d", chanuser->visCount);
+            content[3] = viscountbuf;
+        }
+        table_add(table, content);
+    }
+    
+    //send the table
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(table->length == 1)
+        reply(textclient, user, "NS_TABLE_NONE");
+    reply(textclient, user, "NS_TABLE_COUNT", table->length - 1);
+    table_free(table);
+    if(synced_user) {
+        if(!(flags & NEONSERV_CMD_NICKLIST_FLAG_SYNCUSERS))
+            reply(textclient, user, "NS_NICKLIST_SYNC", db_enfops, db_enfvoice);
+        else
+            logEvent(event);
+    }
+}
+
+static void neonserv_cmd_nicklist_synchronize_user(struct ChanNode *chan, struct UserNode *user, int caccess) {
+    if(!(user->flags & USERFLAG_ISAUTHED)) return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+    } else {
+        printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(user->auth));
+        userid = (int) mysql_insert_id(get_mysql_conn());
+    }
+    //check if already added
+    printf_mysql_query("SELECT `chanuser_access`, `chanuser_id`, `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        //clvl
+        if(atoi(row[0]) >= caccess) return;
+        printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = '%d' WHERE `chanuser_id` = '%s'", caccess, row[1]);
+    } else 
+        printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`) VALUES ('%d', '%d', '%d')", chan->channel_id, userid, caccess);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_noregister.c b/src/modules/NeonServ.mod/cmd_neonserv_noregister.c
new file mode 100644 (file)
index 0000000..2c15f49
--- /dev/null
@@ -0,0 +1,199 @@
+/* cmd_neonserv_noregister.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]   *auth/#channel
+* argv[1]   duration
+* argv[2-*] reason
+*/
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_noregister_auth_lookup);
+static USERAUTH_CALLBACK(neonserv_cmd_noregister_nick_lookup);
+static void neonserv_cmd_noregister_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event,char *auth, int duration, char *reason);
+static void neonserv_cmd_noregister_list(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc);
+
+struct neonserv_cmd_noregister_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    int duration;
+    char *reason, *nick;
+};
+
+CMD_BIND(neonserv_cmd_noregister) {
+    if(argc < 3) {
+        neonserv_cmd_noregister_list(client, textclient, user, chan, event, argv, argc);
+        return;
+    }
+    int duration = strToTime(user, argv[1]);
+    if(duration == 0 && strcmp(argv[1], "0")) {
+        reply(textclient, user, "NS_NOREGISTER_INVALID_DURATION", argv[1]);
+        return;
+    }
+    char *reason = merge_argv(argv, 2, argc);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(argv[0][0] == '#') {
+        printf_mysql_query("SELECT `botid` FROM `bot_channels` LEFT JOIN `channels` ON `bot_channels`.`chanid` = `channels`.`channel_id` WHERE `channel_name` = '%s'", escape_string(argv[0]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            reply(textclient, user, "NS_NOREGISTER_REGISTERED", argv[0]);
+            return;
+        }
+        neonserv_cmd_noregister_async1(client, textclient, user, chan, event, argv[0], duration, reason);
+    } else if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_user` = '%s'", escape_string(argv[0]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            neonserv_cmd_noregister_async1(client, textclient, user, chan, event, row[0], duration, reason);
+        } else {
+            //we need to create a new user...
+            //but first lookup the auth to check if it really exists
+            struct neonserv_cmd_noregister_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            cache->duration = duration;
+            cache->reason = strdup(reason);
+            lookup_authname(argv[0], module_id, neonserv_cmd_noregister_auth_lookup, cache);
+        }
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_noregister_async1(client, textclient, user, chan, event, cuser->auth, duration, reason);
+        } else {
+            struct neonserv_cmd_noregister_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(user->nick);
+            cache->duration = duration;
+            cache->reason = strdup(reason);
+            get_userauth(cuser, module_id, neonserv_cmd_noregister_nick_lookup, cache);
+        }
+    }
+}
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_noregister_auth_lookup) {
+    struct neonserv_cmd_noregister_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->nick);
+    } else
+        neonserv_cmd_noregister_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, auth, cache->duration, cache->reason);
+    free(cache->reason);
+    free(cache->nick);
+    free(cache);
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_noregister_nick_lookup) {
+    struct neonserv_cmd_noregister_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_noregister_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->auth, cache->duration, cache->reason);
+    free(cache->reason);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_noregister_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event,char *auth, int duration, char *reason) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `dnr_id` FROM `donotregister` WHERE `dnr_target` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if((row = mysql_fetch_row(res)) != NULL) {
+        printf_mysql_query("DELETE FROM `donotregister` WHERE `dnr_id` = '%s'", row[0]);
+    }
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL)
+        userid = atoi(row[0]);
+    else
+        userid = 0;
+    printf_mysql_query("INSERT INTO `donotregister` (`dnr_target`, `dnr_timeout`, `dnr_user`, `dnr_reason`) VALUES ('%s', '%lu', '%d', '%s')", escape_string(auth), (duration ? (time(0)+duration) : 0), userid, escape_string(reason));
+    reply(textclient, user, "NS_NOREGISTER_DONE", auth);
+}
+
+static void neonserv_cmd_noregister_list(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char **argv, int argc) {
+    struct Table *table;
+    int entrys = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `dnr_target`, `dnr_timeout`, `user_user`, `dnr_reason` FROM `donotregister` LEFT JOIN `users` ON `dnr_user` = `user_id` ORDER BY `dnr_target` ASC");
+    res = mysql_use();
+    table = table_init(4, mysql_num_rows(res) + 1, 0);
+    char *content[4];
+    content[0] = get_language_string(user, "NS_DNR_TARGET");
+    content[1] = get_language_string(user, "NS_DNR_USER");
+    content[2] = get_language_string(user, "NS_DNR_EXPIRES");
+    content[3] = get_language_string(user, "NS_DNR_REASON");
+    table_add(table, content);
+    int duration;
+    char expires_str[MAXLEN];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        entrys++;
+        content[0] = row[0];
+        content[1] = row[2];
+        duration = atoi(row[1]);
+        content[2] = (duration ? timeToStr(user, (duration - time(0)), 2, expires_str) : get_language_string(user, "NS_USERS_SEEN_NEVER"));
+        content[3] = row[3];
+        table_add(table, content);
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(!entrys)
+        reply(textclient, user, "NS_TABLE_NONE");
+    table_free(table);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_op.c b/src/modules/NeonServ.mod/cmd_neonserv_op.c
new file mode 100644 (file)
index 0000000..d6679bc
--- /dev/null
@@ -0,0 +1,93 @@
+/* cmd_neonserv_op.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+static USERLIST_CALLBACK(neonserv_cmd_op_userlist_lookup);
+static void neonserv_cmd_op_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks);
+
+struct neonserv_cmd_op_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nicks;
+};
+
+CMD_BIND(neonserv_cmd_op) {
+    struct neonserv_cmd_op_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->nicks = strdup(merge_argv(argv, 0, argc));
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_op_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_op_userlist_lookup) {
+    struct neonserv_cmd_op_cache *cache = data;
+    neonserv_cmd_op_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nicks);
+    free(cache->nicks);
+    free(cache);
+}
+
+static void neonserv_cmd_op_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks) {
+    int total_users = 0, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    char *a, *b = nicks;
+    do {
+        a = strstr(b, " ");
+        if(a) *a = '\0';
+        total_users++;
+        cuser = searchUserByNick(b);
+        if(!cuser) {
+            //check for an invisible user
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if(!stricmp(chanuser->user->nick, b)) {
+                    cuser = chanuser->user;
+                    break;
+                }
+            }
+            if(!cuser) continue;
+        } else {
+            chanuser = getChanUser(cuser, chan);
+            if(!chanuser) continue;
+        }
+        done_users++;
+        if(chanuser->flags & CHANUSERFLAG_OPPED) continue;
+        modeBufferOp(modeBuf, b);
+        if(a) {
+            b = a+1;
+        }
+    } while(a);
+    freeModeBuffer(modeBuf);
+    if(done_users == total_users)
+        reply(textclient, user, "NS_OP_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_OP_FAIL", client->user->nick);
+    if(done_users) 
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_opall.c b/src/modules/NeonServ.mod/cmd_neonserv_opall.c
new file mode 100644 (file)
index 0000000..3f2646a
--- /dev/null
@@ -0,0 +1,78 @@
+/* cmd_neonserv_opall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    "force"
+* argv[1]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_opall_userlist_lookup);
+static void neonserv_cmd_opall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask);
+
+struct neonserv_cmd_opall_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nickmask;
+};
+
+CMD_BIND(neonserv_cmd_opall) {
+    if(!argc || strcmp(argv[0], "FORCE")) {
+        reply(textclient, user, "NS_OPALL_SECURITY", chan->name);
+        return;
+    }
+    struct neonserv_cmd_opall_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    if(argc > 1) {
+        cache->nickmask = strdup(argv[1]);
+    } else
+        cache->nickmask = NULL;
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_opall_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_opall_userlist_lookup) {
+    struct neonserv_cmd_opall_cache *cache = data;
+    neonserv_cmd_opall_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nickmask);
+    if(cache->nickmask)
+        free(cache->nickmask);
+    free(cache);
+}
+
+static void neonserv_cmd_opall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask) {
+    int done_users = 0;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        if(chanuser->flags & CHANUSERFLAG_OPPED) continue;
+        modeBufferOp(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_OPALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_oplog.c b/src/modules/NeonServ.mod/cmd_neonserv_oplog.c
new file mode 100644 (file)
index 0000000..06e33e8
--- /dev/null
@@ -0,0 +1,57 @@
+/* cmd_neonserv_oplog.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]     time
+* argv[1-*]   match
+*/
+
+CMD_BIND(neonserv_cmd_oplog) {
+    char *str_match;
+    int duration = (argc ? strToTime(user, argv[0]) : 0);
+    if(argc > (duration ? 1 : 0))
+        str_match = merge_argv(argv, (duration ? 1 : 0), argc);
+    else
+        str_match = "";
+    if(!duration) duration = (60*60*24);
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `godlog_time`, `user_user`, `channel_name`, `godlog_cmd` FROM `godlog` LEFT JOIN `channels` ON `godlog_cid` = `channel_id` LEFT JOIN `users` ON `godlog_uid` = `user_id` WHERE `godlog_time` > '%lu' ORDER BY `godlog_time` ASC", ((unsigned long) time(0) - duration));
+    res = mysql_use();
+    int skip = mysql_num_rows(res) - 100;
+    int count = 0;
+    char timeBuf[50];
+    struct tm *timeinfo;
+    time_t event_time;
+    if(skip < 0) skip = 0;
+    reply(textclient, user, "NS_EVENTS_HEADER");
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(skip) {
+            skip--;
+            continue;
+        }
+        if(*str_match && match(str_match, row[3])) continue;
+        count++;
+        event_time = (time_t) atol(row[0]);
+        timeinfo = localtime(&event_time);
+        strftime(timeBuf, 80, "%X %x", timeinfo);
+        reply(textclient, user, "[%s] [%s%s%s]: %s", timeBuf, row[1], (row[2] ? ":" : ""), (row[2] ? row[2] : ""), row[3]);
+    }
+    reply(textclient, user, "NS_TABLE_COUNT", count);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_peek.c b/src/modules/NeonServ.mod/cmd_neonserv_peek.c
new file mode 100644 (file)
index 0000000..fe600cd
--- /dev/null
@@ -0,0 +1,89 @@
+/* cmd_neonserv_peek.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no parameters
+*/
+static USERLIST_CALLBACK(neonserv_cmd_peek_userlist_lookup);
+static void neonserv_cmd_peek_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan);
+
+struct neonserv_cmd_peek_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+};
+
+CMD_BIND(neonserv_cmd_peek) {
+    struct neonserv_cmd_peek_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_peek_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_peek_userlist_lookup) {
+    struct neonserv_cmd_peek_cache *cache = data;
+    neonserv_cmd_peek_async1(cache->client, cache->textclient, cache->user, chan);
+    free(cache);
+}
+
+static void neonserv_cmd_peek_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan) {
+    reply(textclient, user, "NS_PEEK_HEADER", chan->name);
+    reply(textclient, user, "NS_PEEK_TOPIC", chan->topic);
+    char tmpStr[MAXLEN];
+    getModeString(chan->modes, tmpStr);
+    reply(textclient, user, "NS_PEEK_MODES", tmpStr);
+    struct ChanUser *chanuser;
+    int with_halfops = get_int_field("General.have_halfop");
+    int op_count = 0, halfop_count = 0, voice_count = 0, normal_count = 0, invi_count = 0;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(chanuser->flags & CHANUSERFLAG_OPPED)
+            op_count++;
+        else if(with_halfops && (chanuser->flags & CHANUSERFLAG_HALFOPPED))
+            halfop_count++;
+        else if(chanuser->flags & CHANUSERFLAG_VOICED)
+            voice_count++;
+        else if(chanuser->flags & CHANUSERFLAG_INVISIBLE)
+            invi_count++;
+        else
+            normal_count++;
+    }
+    if(with_halfops)
+        reply(textclient, user, "NS_PEEK_USERS_HALFOP", op_count+halfop_count+voice_count+invi_count+normal_count, op_count, halfop_count, voice_count, normal_count, invi_count);
+    else
+        reply(textclient, user, "NS_PEEK_USERS", op_count+voice_count+invi_count+normal_count, op_count, voice_count, normal_count, invi_count);
+    int tmpStrPos = 0;
+    int headerlen = 10 + strlen(user->nick);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(chanuser->flags & CHANUSERFLAG_OPPED) {
+            if(tmpStrPos + headerlen + strlen(chanuser->user->nick) + 2 >= 512) {
+                //clear buffer
+                reply(textclient, user, "%s", tmpStr);
+                tmpStrPos = 0;
+            }
+            tmpStrPos += sprintf(tmpStr + tmpStrPos, (tmpStrPos ? ", %s" : "%s"), chanuser->user->nick);
+        }
+    }
+    if(tmpStrPos) {
+        reply(textclient, user, "%s", tmpStr);
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_recover.c b/src/modules/NeonServ.mod/cmd_neonserv_recover.c
new file mode 100644 (file)
index 0000000..814d9ab
--- /dev/null
@@ -0,0 +1,76 @@
+/* cmd_neonserv_recover.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - channel
+*/
+CMD_BIND(neonserv_cmd_recover) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    char *channel = argv[0];
+    if(!is_valid_chan(channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", argv[0]);
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        reply(textclient, user, "NS_REGISTER_ALREADY", argv[0], client->user->nick);
+        return;
+    }
+    printf_mysql_query("SELECT `id`, `max_channels`, `defaulttrigger` FROM `bots` WHERE `botclass` = '%d' ORDER BY `register_priority` DESC", client->botid);
+    res = mysql_use();
+    int botid = 0;
+    char *bottrigger;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        //check channel count
+        printf_mysql_query("SELECT COUNT(*) FROM `bot_channels` WHERE `botid` = '%s'", row[0]);
+        row2 = mysql_fetch_row(mysql_use());
+        if(atoi(row2[0]) < atoi(row[1])) {
+            botid = atoi(row[0]);
+            bottrigger = row[2];
+            break;
+        }
+    }
+    if(!botid) {
+        reply(textclient, user, "NS_REGISTER_FULL");
+        return;
+    }
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        putsock(bot, "JOIN %s", channel);
+    } else
+        reply(textclient, user, "NS_REGISTER_DISCONNECTED");
+    printf_mysql_query("INSERT INTO `bot_channels` (`botid`, `chanid`, `trigger`) VALUES ('%d', '%d', '%s')", botid, chanid, bottrigger);
+    reply(textclient, user, "NS_RECOVER_DONE", channel);
+    logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_rename.c b/src/modules/NeonServ.mod/cmd_neonserv_rename.c
new file mode 100644 (file)
index 0000000..34b72b0
--- /dev/null
@@ -0,0 +1,68 @@
+/* cmd_neonserv_rename.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]   old auth
+* argv[1]   new auth
+*/
+
+struct neonserv_cmd_rename_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *oldauth, *newauth;
+};
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_rename_auth_lookup);
+static void neonserv_cmd_rename_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *oldauth, char *newauth);
+
+CMD_BIND(neonserv_cmd_rename) {
+    struct neonserv_cmd_rename_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->oldauth = strdup(argv[0]);
+    cache->newauth = strdup(argv[1]);
+    lookup_authname(argv[1], module_id, neonserv_cmd_rename_auth_lookup, cache);
+}
+
+static AUTHLOOKUP_CALLBACK(neonserv_cmd_rename_auth_lookup) {
+    struct neonserv_cmd_rename_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->newauth);
+    } else
+        neonserv_cmd_rename_async1(cache->client, cache->textclient, cache->user, cache->event, cache->oldauth, auth);
+    free(cache->oldauth);
+    free(cache->newauth);
+    free(cache);
+}
+
+static void neonserv_cmd_rename_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *oldauth, char *newauth) {
+    if(renameAccount(oldauth, newauth)) {
+        reply(textclient, user, "NS_RENAME_DONE", oldauth, newauth);
+    } else {
+        reply(textclient, user, "NS_RENAME_FAIL", oldauth);
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_resync.c b/src/modules/NeonServ.mod/cmd_neonserv_resync.c
new file mode 100644 (file)
index 0000000..46a095e
--- /dev/null
@@ -0,0 +1,167 @@
+/* cmd_neonserv_resync.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - (optional) usermask
+* argv[1] - (optional) min access
+* argv[2] - (optional) max access
+* argv[1/3] - (optional) FORCE (override NoAutoOp)
+*/
+static USERLIST_CALLBACK(neonserv_cmd_resync_userlist_lookup);
+static void neonserv_cmd_resync_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *usermask, int min_access, int max_access, char override_noautoop);
+
+struct neonserv_cmd_resync_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    char *usermask;
+    int min_access;
+    int max_access;
+    char override_noautoop;
+};
+
+CMD_BIND(neonserv_cmd_resync) {
+    int min_access = 0, max_access = 500;
+    char *usermask = NULL;
+    char override_noautoop = 0;
+    if(argc > 0) {
+        usermask = argv[0];
+        if(argc > 2) {
+            min_access = atoi(argv[1]);
+            max_access = atoi(argv[2]);
+            if(argc > 3)
+                override_noautoop = (!stricmp(argv[3], "FORCE") ? 1 : 0);
+        } else if(argc > 1)
+            override_noautoop = (!stricmp(argv[1], "FORCE") ? 1 : 0);
+    }
+    struct neonserv_cmd_resync_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->usermask = (usermask ? strdup(usermask) : NULL);
+    cache->min_access = min_access;
+    cache->max_access = max_access;
+    cache->override_noautoop = override_noautoop;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_resync_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_resync_userlist_lookup) {
+    struct neonserv_cmd_resync_cache *cache = data;
+    neonserv_cmd_resync_async1(cache->client, cache->textclient, cache->user, chan, cache->usermask, cache->min_access, cache->max_access, cache->override_noautoop);
+    if(cache->usermask)
+        free(cache->usermask);
+    free(cache);
+}
+
+static void neonserv_cmd_resync_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *usermask, int min_access, int max_access, char override_noautoop) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults = NULL;
+    int i;
+    int resync_op = 1;
+    int with_halfop = get_int_field("General.have_halfop");
+    int resync_halfop = with_halfop;
+    int resync_voice = 1;
+    if(usermask && usermask[0] == '@') {
+        resync_voice = 0;
+        resync_halfop = 0;
+        usermask++;
+        if(!*usermask) usermask = NULL;
+    } else if(usermask && with_halfop && usermask[0] == 'h') {
+        resync_op = 0;
+        resync_voice = 0;
+        usermask++;
+        if(!*usermask) usermask = NULL;
+    } else if(usermask && usermask[0] == '+') {
+        resync_op = 0;
+        resync_halfop = 0;
+        usermask++;
+        if(!*usermask) usermask = NULL;
+    }
+    struct ChanUser *chanuser;
+    int db_enfops, db_enfhalfop, db_enfvoice;
+    printf_mysql_query("SELECT `channel_getop`, `channel_getvoice`, `channel_gethalfop` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    row = mysql_fetch_row(mysql_use());
+    if(row[0] == NULL || row[1] == NULL) {
+        printf_mysql_query("SELECT `channel_getop`, `channel_getvoice`, `channel_gethalfop` FROM `channels` WHERE `channel_name` = 'defaults'");
+        defaults = mysql_fetch_row(mysql_use());
+    }
+    db_enfops = atoi((row[0] ? row[0] : defaults[0]));
+    db_enfvoice = atoi((row[1] ? row[1] : defaults[1]));
+    db_enfhalfop = (with_halfop ? atoi((row[2] ? row[2] : defaults[2])) : 0);
+    printf_mysql_query("SELECT `chanuser_access`, `user_user`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' ORDER BY `chanuser_access` DESC, `user_user` ASC", chan->channel_id);
+    res = mysql_use();
+    char *db_users[mysql_num_rows(res)];
+    int db_access[mysql_num_rows(res)];
+    int db_flags[mysql_num_rows(res)];
+    int db_count = 0;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        db_users[db_count] = row[1];
+        db_access[db_count] = atoi(row[0]);
+        db_flags[db_count] = atoi(row[2]);
+        db_count++;
+    }
+    int caccess, cflags;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        caccess = 0;
+        cflags = 0;
+        if((chanuser->user->flags & USERFLAG_ISAUTHED)) {
+            for(i = 0; i < db_count; i++) {
+                if(!stricmp(db_users[i], chanuser->user->auth)) {
+                    caccess = db_access[i];
+                    cflags = db_flags[i];
+                    if(cflags & DB_CHANUSER_SUSPENDED)
+                        caccess = 0;
+                    break;
+                }
+            }
+        }
+        if((usermask && *usermask && match(usermask, chanuser->user->nick)) || caccess < min_access || caccess > max_access) continue;
+        if(caccess >= db_enfops) {
+            if(!(chanuser->flags & CHANUSERFLAG_OPPED) && resync_op && (override_noautoop || !(cflags & DB_CHANUSER_NOAUTOOP)))
+                modeBufferOp(modeBuf, chanuser->user->nick);
+        } else if(with_halfop && caccess >= db_enfhalfop) {
+            if((chanuser->flags & CHANUSERFLAG_OPPED) && resync_op && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDeop(modeBuf, chanuser->user->nick);
+            if(!(chanuser->flags & CHANUSERFLAG_HALFOPPED) && resync_halfop && (override_noautoop || !(cflags & DB_CHANUSER_NOAUTOOP)))
+                modeBufferHalfop(modeBuf, chanuser->user->nick);
+        } else if(caccess >= db_enfvoice) {
+            if((chanuser->flags & CHANUSERFLAG_OPPED) && resync_op && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDeop(modeBuf, chanuser->user->nick);
+            if((chanuser->flags & CHANUSERFLAG_HALFOPPED) && resync_halfop && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDehalfop(modeBuf, chanuser->user->nick);
+            if(!(chanuser->flags & CHANUSERFLAG_VOICED) && resync_voice && (override_noautoop || !(cflags & DB_CHANUSER_NOAUTOOP)))
+                modeBufferVoice(modeBuf, chanuser->user->nick);
+        } else {
+            if((chanuser->flags & CHANUSERFLAG_OPPED) && resync_op && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDeop(modeBuf, chanuser->user->nick);
+            if((chanuser->flags & CHANUSERFLAG_HALFOPPED) && resync_halfop && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDehalfop(modeBuf, chanuser->user->nick);
+            if((chanuser->flags & CHANUSERFLAG_VOICED) && resync_voice && !(chanuser->user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP)))
+                modeBufferDevoice(modeBuf, chanuser->user->nick);
+        }
+        
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_RESYNC_DONE", chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_search.c b/src/modules/NeonServ.mod/cmd_neonserv_search.c
new file mode 100644 (file)
index 0000000..f3ea659
--- /dev/null
@@ -0,0 +1,221 @@
+/* cmd_neonserv_search.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+#define CMD_SEARCH_FLAG_HAS_NODELETE 0x001
+#define CMD_SEARCH_FLAG_NOT_NODELETE 0x002
+#define CMD_SEARCH_FLAG_HAS_SUSPENDED 0x004
+#define CMD_SEARCH_FLAG_NOT_SUSPENDED 0x008
+#define CMD_SEARCH_FLAG_IS_JOINED  0x010 /* state */
+#define CMD_SEARCH_FLAG_NOT_JOINED 0x020 /* state */
+#define CMD_SEARCH_FLAG_IS_OPPED   0x040 /* state */
+#define CMD_SEARCH_FLAG_NOT_OPPED  0x080 /* state */
+#define CMD_SEARCH_FLAG_IS_VOICED  0x100 /* state */
+#define CMD_SEARCH_FLAG_NOT_VOICED 0x200 /* state */
+#define CMD_SEARCH_FLAG_IS_ZOMBIE  0x400
+#define CMD_SEARCH_FLAG_NOT_ZOMBIE 0x800
+
+#define CMD_SEARCH_FLAG_STATES     0x3f0
+
+struct neonserv_cmd_search_criteria {
+    char *name;
+    char *registrar;
+    char *onchan;
+    unsigned int flags : 16;
+    unsigned int unvisited;
+    unsigned int registered;
+    unsigned int limit : 16;
+};
+
+static char neonserv_cmd_search_zombie = 0;
+
+CMD_BIND(neonserv_cmd_search) {
+    //ok parse the criterias
+    struct neonserv_cmd_search_criteria criteria;
+    memset(&criteria, 0, sizeof(criteria));
+    criteria.limit = 50;
+    int i, show_chans = 0, positive;
+    if(!stricmp(argv[0], "print")) {
+        show_chans = 1;
+    }
+    for(i = 1; i < argc; i += 2) {
+        if(argc <= i+1) {
+            reply(textclient, user, "MODCMD_LESS_PARAM_COUNT");
+            return;
+        }
+        if(!stricmp(argv[i], "name")) criteria.name = argv[i+1];
+        else if(!stricmp(argv[i], "registrar")) criteria.registrar = argv[i+1];
+        else if(!stricmp(argv[i], "onchan")) criteria.onchan = argv[i+1];
+        else if(!stricmp(argv[i], "unvisited")) criteria.unvisited = strToTime(user, argv[i+1]);
+        else if(!stricmp(argv[i], "registered")) criteria.registered = strToTime(user, argv[i+1]);
+        else if(!stricmp(argv[i], "flags")) {
+            if(argv[i+1][0] == '+') {
+                positive = 1;
+                argv[i+1]++;
+            } else if(argv[i+1][0] == '-') {
+                positive = 0;
+                argv[i+1]++;
+            } else
+                positive = 1;
+            if(!stricmp(argv[i+1], "nodelete")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_HAS_NODELETE;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_NODELETE;
+            } else if(!stricmp(argv[i+1], "suspended")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_HAS_SUSPENDED;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_SUSPENDED;
+            } else if(!stricmp(argv[i+1], "zombie")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_IS_ZOMBIE;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_ZOMBIE;
+            }
+        }
+        else if(!stricmp(argv[i], "state")) {
+            if(argv[i+1][0] == '+') {
+                positive = 1;
+                argv[i+1]++;
+            } else if(argv[i+1][0] == '-') {
+                positive = 0;
+                argv[i+1]++;
+            } else
+                positive = 1;
+            if(!stricmp(argv[i+1], "joined")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_IS_JOINED;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_JOINED;
+            } else if(!stricmp(argv[i+1], "opped")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_IS_OPPED;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_OPPED;
+            } else if(!stricmp(argv[i+1], "voiced")) {
+                if(positive)
+                    criteria.flags |= CMD_SEARCH_FLAG_IS_VOICED;
+                else
+                    criteria.flags |= CMD_SEARCH_FLAG_NOT_VOICED;
+            }
+        }
+        else if(!stricmp(argv[i], "limit")) {
+            criteria.limit = atoi(argv[i+1]);
+        }
+    }
+    int matches = 0;
+    if((criteria.flags & (CMD_SEARCH_FLAG_IS_ZOMBIE | CMD_SEARCH_FLAG_NOT_ZOMBIE))) {
+        if(neonserv_cmd_search_zombie) {
+            reply(textclient, user, "NS_SEARCH_ZOMBIE_SCAN_IN_PROGRESS");
+            return;
+        }
+        neonserv_cmd_search_zombie = 1;
+    }
+    
+    reply(textclient, user, "NS_SEARCH_HEADER");
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT `channel_name`, `user_user`, `channel_registered`, `channel_nodelete`, `suspended`, `channel_id` FROM `bot_channels` LEFT JOIN `bots` ON `bots`.`id` = `botid` LEFT JOIN `channels` ON `chanid` = `channel_id` LEFT JOIN `users` ON `channel_registrator` = `user_id` WHERE `botclass` = '%d' AND `active` = '1'", client->botid);
+    res = mysql_use();
+    struct ChanNode *channel;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if((criteria.flags & (CMD_SEARCH_FLAG_IS_ZOMBIE | CMD_SEARCH_FLAG_NOT_ZOMBIE))) {
+            channel = getChanByName(row[0]);
+            if(channel)
+                channel->flags |= CHANFLAG_SCRIPTFLAG1;
+            if(show_chans && matches == criteria.limit) {
+                //too many
+                continue;
+            }
+        } else {
+            if(show_chans && matches == criteria.limit) {
+                //too many
+                break;
+            }
+        }
+        if((criteria.flags & CMD_SEARCH_FLAG_IS_ZOMBIE)) continue;
+        if(criteria.name && match(criteria.name, row[0])) continue;
+        if(criteria.registrar && row[1] && match(criteria.registrar, row[1])) continue;
+        if(criteria.unvisited) {
+            printf_mysql_query("SELECT `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%s' ORDER BY `chanuser_seen` DESC LIMIT 1", row[5]);
+            res2 = mysql_use();
+            row2 = mysql_fetch_row(res2);
+            if(!row2) continue;
+            if((time(0) - atoi(row2[0])) < criteria.unvisited) continue;
+        }
+        if(criteria.registered && (time(0) - atoi(row[2])) < criteria.registered) continue;
+        
+        if((criteria.flags & CMD_SEARCH_FLAG_HAS_NODELETE) && strcmp(row[3], "1")) continue;
+        if((criteria.flags & CMD_SEARCH_FLAG_NOT_NODELETE) && strcmp(row[3], "0")) continue;
+        if((criteria.flags & CMD_SEARCH_FLAG_HAS_SUSPENDED) && strcmp(row[4], "1")) continue;
+        if((criteria.flags & CMD_SEARCH_FLAG_NOT_SUSPENDED) && strcmp(row[4], "0")) continue;
+        if((criteria.flags & CMD_SEARCH_FLAG_STATES) || criteria.onchan) {
+            if(!(criteria.flags & (CMD_SEARCH_FLAG_IS_ZOMBIE | CMD_SEARCH_FLAG_NOT_ZOMBIE)))
+                channel = getChanByName(row[0]);
+            if(criteria.onchan) {
+                if(!channel) continue;
+                struct ChanUser *chanuser = NULL;
+                for(chanuser = getChannelUsers(channel, NULL); chanuser; chanuser = getChannelUsers(channel, chanuser)) {
+                    if(!match(criteria.onchan, chanuser->user->nick)) break;
+                }
+                if(!chanuser) continue;
+            }
+            if((criteria.flags & CMD_SEARCH_FLAG_IS_JOINED) && !channel) continue;
+            if((criteria.flags & CMD_SEARCH_FLAG_NOT_JOINED) && channel) continue;
+            if(channel && (criteria.flags & (CMD_SEARCH_FLAG_IS_OPPED | CMD_SEARCH_FLAG_NOT_OPPED | CMD_SEARCH_FLAG_IS_VOICED | CMD_SEARCH_FLAG_NOT_VOICED))) {
+                int flags = 0;
+                struct ClientSocket *bot;
+                for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                    if(bot->botid == client->botid) {
+                        struct ChanUser *chanuser = getChanUser(bot->user, channel);
+                        if(chanuser) {
+                            flags = chanuser->flags;
+                            break;
+                        }
+                    }
+                }
+                if((criteria.flags & CMD_SEARCH_FLAG_IS_OPPED) && !(flags & CHANUSERFLAG_OPPED)) continue;
+                if((criteria.flags & CMD_SEARCH_FLAG_NOT_OPPED) && (flags & CHANUSERFLAG_OPPED)) continue;
+                if((criteria.flags & CMD_SEARCH_FLAG_IS_VOICED) && !(flags & CHANUSERFLAG_VOICED)) continue;
+                if((criteria.flags & CMD_SEARCH_FLAG_NOT_VOICED) && (flags & CHANUSERFLAG_VOICED)) continue;
+            }
+        }
+        matches++;
+        //output
+        if(show_chans) {
+            reply(textclient, user, "%s", row[0]);
+        }
+    }
+    if((criteria.flags & (CMD_SEARCH_FLAG_IS_ZOMBIE | CMD_SEARCH_FLAG_NOT_ZOMBIE))) {
+        struct ChanUser *chanuser;
+        for(chanuser = getUserChannels(client->user, NULL); chanuser; chanuser = getUserChannels(client->user, chanuser)) {
+            channel = chanuser->chan;
+            if(channel->flags & CHANFLAG_SCRIPTFLAG1) {
+                channel->flags &= ~CHANFLAG_SCRIPTFLAG1;
+            } else if((criteria.flags & CMD_SEARCH_FLAG_IS_ZOMBIE)) {
+                matches++;
+                if(show_chans) {
+                    reply(textclient, user, "%s", channel->name);
+                }
+            }
+        }
+    }
+    neonserv_cmd_search_zombie = 0;
+    reply(textclient, user, "NS_TABLE_COUNT", matches);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_set.c b/src/modules/NeonServ.mod/cmd_neonserv_set.c
new file mode 100644 (file)
index 0000000..692823e
--- /dev/null
@@ -0,0 +1,536 @@
+/* cmd_neonserv_set.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+typedef char* neonserv_cmd_set_function(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+static void neonserv_cmd_set_setting(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int setting, char *argument);
+static char* neonserv_cmd_set_trigger(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+static char* neonserv_cmd_set_modes(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+static char* neonserv_cmd_set_dynlimit(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+static char* neonserv_cmd_set_nodelete(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+static char* neonserv_cmd_set_backupbot(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
+
+#define NS_VALID_FUNCTION 0x01
+#define NS_VALID_STRING   0x02
+#define NS_VALID_ACCESS   0x04
+#define NS_VALID_NO501    0x08
+#define NS_VALID_OPTIONS  0x10
+#define NS_VALID_NUMERIC  0x20
+#define NS_VALID_BOOLEAN  0x40
+#define NS_VALID_IF_HALFOP 0x80
+
+#define NS_HAS_OPT  0x100 /* options (SET_OPTION_{NAME}_{VALUE}) */
+#define NS_HAS_HELP 0x200 /* help    (SET_HELP_{NAME}) - only shown if help is requested */
+
+static const struct {
+    const char *setting;
+    const char *chanfield;
+    unsigned int valid;
+    void *parameter;
+} channel_settings[] = {
+    {"TRIGGER",         NULL,                   NS_VALID_FUNCTION,                  neonserv_cmd_set_trigger},
+    {"DEFAULTTOPIC",    "channel_defaulttopic", NS_VALID_STRING,                    NULL},
+    {"TOPICMASK",       "channel_topicmask",    NS_VALID_STRING,                    NULL},
+    {"ADVANCEDTOPIC",   "channel_exttopic",     NS_VALID_BOOLEAN | NS_HAS_OPT,      NULL},
+    {"GREETING",        "channel_greeting",     NS_VALID_STRING,                    NULL},
+    {"USERGREETING",    "channel_usergreeting", NS_VALID_STRING,                    NULL},
+    {"USERINFO",        "channel_userinfo",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"WIPEINFO",        "channel_wipeinfo",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"MODES",           "channel_modes",        NS_VALID_FUNCTION,                  neonserv_cmd_set_modes},
+    {"INVITEME",        "channel_getinvite",    NS_VALID_ACCESS,                    NULL},
+    {"GIVEOPS",         "channel_getop",        NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"GIVEHALFOPS",     "channel_gethalfop",    NS_VALID_ACCESS | NS_HAS_HELP | NS_VALID_IF_HALFOP, NULL},
+    {"GIVEVOICE",       "channel_getvoice",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"ENFOPS",          "channel_canop",        NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"ENFHALFOPS",      "channel_canhalfop",    NS_VALID_ACCESS | NS_HAS_HELP | NS_VALID_IF_HALFOP, NULL},
+    {"ENFVOICE",        "channel_canvoice",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"KICK",            "channel_cankick",      NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"BAN",             "channel_canban",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"STATICBAN",       "channel_staticban",    NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"PUBCMD",          "channel_pubcmd",       NS_VALID_ACCESS,                    NULL},
+    {"ENFMODES",        "channel_enfmodes",     NS_VALID_ACCESS,                    NULL},
+    {"ENFTOPIC",        "channel_enftopic",     NS_VALID_ACCESS,                    NULL},
+    {"TOPICSNARF",      "channel_topicsnarf",   NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"CHANGETOPIC",     "channel_changetopic",  NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"SETTERS",         "channel_setters",      NS_VALID_ACCESS | NS_VALID_NO501 | NS_HAS_HELP, NULL},
+    {"ADDUSER",         "channel_canadd",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"DELUSER",         "channel_candel",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"CLVL",            "channel_canclvl",      NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"RESYNC",          "channel_canresync",    NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"SUSPEND",         "channel_cansuspend",   NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
+    {"NOTICEUSERS",     "channel_notice",       NS_VALID_ACCESS,                    NULL},
+    {"NOTICEREACTION",  "channel_noticereaction", NS_VALID_OPTIONS | NS_HAS_OPT,    "4"},
+    {"CTCPUSERS",       "channel_ctcp",         NS_VALID_ACCESS,                    NULL},
+    {"CTCPREACTION",    "channel_ctcpreaction", NS_VALID_OPTIONS | NS_HAS_OPT,      "4"},
+    {"PROTECT",         "channel_protect",      NS_VALID_OPTIONS | NS_HAS_OPT,      "4"},
+    {"TOYS",            "channel_toys",         NS_VALID_OPTIONS | NS_HAS_OPT,      "3"},
+    {"DYNLIMIT",        "channel_dynlimit",     NS_VALID_NUMERIC | NS_VALID_FUNCTION | NS_HAS_OPT, neonserv_cmd_set_dynlimit},
+    {"NODELETE",        "channel_nodelete",     NS_VALID_BOOLEAN | NS_VALID_FUNCTION, neonserv_cmd_set_nodelete},
+    {"BACKUPBOT",       NULL,                   NS_VALID_BOOLEAN | NS_VALID_FUNCTION, neonserv_cmd_set_backupbot},
+    {NULL, NULL, 0, NULL}
+};
+
+#define MAX_QUERY_LEN 1024
+CMD_BIND(neonserv_cmd_set) {
+    int i, j;
+    if(argc && !strcmp(argv[0], "defaults")) {
+        //reset channel settings
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess < 500) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_DEFAULTS_OWNER", chan->name);
+                return;
+            }
+        }
+        int seed = 0;
+        char *tmp;
+        static char defaultskey[16];
+        for(tmp = user->auth; *tmp; tmp++)
+            seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+        for(tmp = chan->name; *tmp; tmp++)
+            seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+        sprintf(defaultskey, "%08x", seed);
+        if(argc > 1 && !strcmp(argv[1], defaultskey)) {
+            char query[MAX_QUERY_LEN];
+            int querypos = 0;
+            i = 0;
+            while(channel_settings[i].setting) {
+                if(channel_settings[i].chanfield)
+                    querypos += sprintf(query + querypos, "`%s` = NULL, ", channel_settings[i].chanfield);
+                i++;
+            }
+            if(querypos) {
+                query[querypos-2] = '\0';
+            }
+            printf_mysql_query("UPDATE `channels` SET %s WHERE `channel_id` = '%d'", query, chan->channel_id);
+            reply(textclient, user, "NS_SET_DEFAULTS_DONE", chan->name);
+            logEvent(event);
+        } else {
+            reply(textclient, user, "NS_SET_DEFAULTS_CODE", chan->name, defaultskey);
+        }
+    } else if(argc && strcmp(argv[0], "help")) {
+        //find the correct command
+        i = 0;
+        j = 0;
+        char *args = (argc > 1 ? merge_argv(argv, 1, argc) : NULL);
+        int with_halfops = get_int_field("General.have_halfop");
+        while(channel_settings[i].setting) {
+            if(!stricmp(channel_settings[i].setting, argv[0]) && (!(channel_settings[i].valid & NS_VALID_IF_HALFOP) || with_halfops)) {
+                //setting found
+                if(!stricmp(channel_settings[i].setting, "BACKUPBOT")) {
+                    char setting[128];
+                    sprintf(setting, "modules.%s.channel_backup_setting", get_module_name(module_id));
+                    if(!get_int_field(setting)) {
+                        i++;
+                        continue;
+                    }
+                }
+                if(channel_settings[i].valid & NS_VALID_FUNCTION) {
+                    neonserv_cmd_set_function *func = channel_settings[i].parameter;
+                    func(client, textclient, user, chan, event, channel_settings[i].setting, args);
+                } else {
+                    neonserv_cmd_set_setting(client, textclient, user, chan, event, i, args);
+                }
+                j = 1;
+                break;
+            }
+            i++;
+        }
+        if(j == 0) {
+            //unknown setting
+            reply(textclient, user, "NS_SET_UNKNOWN_SETTING", argv[0]);
+        }
+    } else {
+        char query[MAX_QUERY_LEN], *value, *org_value, *tmp, nameBuf[64];
+        int querypos = 0;
+        MYSQL_RES *res, *defaults_res;
+        MYSQL_ROW row, defaults;
+        struct Table *table;
+        char *content[2];
+        int with_halfops = get_int_field("General.have_halfop");
+        int channel_backup_setting;
+        i = 0;
+        j = 0;
+        while(channel_settings[i].setting) {
+            if((channel_settings[i].valid & NS_VALID_IF_HALFOP) && !with_halfops) {
+                j++;
+                i++;
+                continue;
+            }
+            if(!stricmp(channel_settings[i].setting, "BACKUPBOT")) {
+                char setting[128];
+                sprintf(setting, "modules.%s.channel_backup_setting", get_module_name(module_id));
+                channel_backup_setting = get_int_field(setting);
+                if(!channel_backup_setting) {
+                    i++;
+                    j++;
+                    continue;
+                }
+            }
+            if(channel_settings[i].chanfield)
+                querypos += sprintf(query + querypos, ", `%s`", channel_settings[i].chanfield);
+            i++;
+        }
+        table = table_init(2, i-j, 0);
+        table_set_bold(table, 0, 1);
+        printf_mysql_query("SELECT `channel_id` %s FROM `channels` WHERE `channel_name` = 'defaults'", query);
+        defaults_res = mysql_use();
+        defaults = mysql_fetch_row(defaults_res);
+        printf_mysql_query("SELECT `channel_name` %s FROM `channels` WHERE `channel_id` = '%d'", query, chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        i = 0;
+        j = 0;
+        reply(textclient, user, "NS_SET_HEADER", chan->name);
+        while(channel_settings[i].setting) {
+            if((channel_settings[i].valid & NS_VALID_IF_HALFOP) && !with_halfops) {
+                i++;
+                continue;
+            }
+            if(!stricmp(channel_settings[i].setting, "BACKUPBOT") && !channel_backup_setting) {
+                i++;
+                continue;
+            }
+            if(channel_settings[i].chanfield) {
+                j++;
+                org_value = (row[j] ? row[j] : defaults[j]);
+            } else if(channel_settings[i].valid & NS_VALID_FUNCTION) {
+                neonserv_cmd_set_function *func = channel_settings[i].parameter;
+                org_value = func(client, textclient, user, chan, event, NULL, NULL);
+            } else
+                org_value = "0";
+            value = org_value;
+            if(channel_settings[i].valid & NS_VALID_BOOLEAN) {
+                if(!strcmp(value, "0"))
+                    value = get_language_string(user, "NS_SET_OFF");
+                else
+                    value = get_language_string(user, "NS_SET_ON");
+            }
+            strcpy(query, value);
+            querypos = strlen(query);
+            if(channel_settings[i].valid & NS_HAS_OPT) {
+                sprintf(nameBuf, "NS_SET_OPTION_%s_%s", channel_settings[i].setting, org_value);
+                tmp = get_language_string(user, nameBuf);
+                if(tmp) {
+                    querypos += sprintf(query+querypos, " - %s", tmp);
+                }
+            }
+            if(argc && channel_settings[i].valid & NS_HAS_HELP) {
+                sprintf(nameBuf, "NS_SET_HELP_%s", channel_settings[i].setting);
+                tmp = get_language_string(user, nameBuf);
+                if(tmp) {
+                    querypos += sprintf(query+querypos, " - %s", tmp);
+                }
+            }
+            content[0] = (char*)channel_settings[i].setting;
+            content[1] = query;
+            table_add(table, content);
+            i++;
+        }
+        char **table_lines = table_end(table);
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        table_free(table);
+    }
+}
+
+static void neonserv_cmd_set_setting(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int setting, char *args) {
+    char *value;
+    char nameBuf[64];
+    //get current value
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", channel_settings[setting].chanfield, chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(row[0] == NULL) {
+        printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_name` = 'defaults'", channel_settings[setting].chanfield);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+    }
+    value = row[0];
+    if(args) {
+        //change the channel setting
+        //check the new argument
+        int valid = channel_settings[setting].valid;
+        if(valid & NS_VALID_STRING) {
+            if(!strcmp(args, "*")) {
+                args = "";
+            }
+        }
+        if(valid & NS_VALID_ACCESS) {
+            int caccess = atoi(args);
+            int max = ((valid & NS_VALID_NO501) ? 500 : 501);
+            if(caccess < 0 || caccess > max) {
+                reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+                return;
+            }
+            int uaccess = getChannelAccess(user, chan);
+            if(uaccess == 500) uaccess++;
+            if(atoi(value) > uaccess) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_SET_CANNOT_SET");
+                    return;
+                }
+            }
+            if(caccess > uaccess) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_SET_BADLEVEL");
+                    return;
+                }
+            }
+            sprintf(nameBuf, "%d", caccess);
+            args = nameBuf;
+        }
+        if(valid & NS_VALID_OPTIONS) {
+            int options = atoi((char *) channel_settings[setting].parameter);
+            int coption = atoi(args);
+            if(coption < 0 || coption >= options) {
+                reply(textclient, user, "NS_SET_INVALID_OPTION", coption);
+                int i;
+                int nameBufPos = 0;
+                if(valid & NS_HAS_OPT) {
+                    for(i = 0; i < options; i++) {
+                        sprintf(nameBuf, "NS_SET_OPTION_%s_%d", channel_settings[setting].setting, i);
+                        reply(textclient, user, "\002%d\002 - %s", i, get_language_string(user, nameBuf));
+                    }
+                } else {
+                    for(i = 0; i < options; i++) {
+                        nameBufPos += sprintf(nameBuf + nameBufPos, "\002%d\002, ", i);
+                    }
+                    if(nameBufPos) {
+                        nameBuf[nameBufPos-2] = '\0';
+                        reply(textclient, user, nameBuf);
+                    }
+                }
+                return;
+            }
+        }
+        if(valid & NS_VALID_NUMERIC) {
+            sprintf(nameBuf, "%d", atoi(args));
+            args = nameBuf;
+        }
+        if(valid & NS_VALID_BOOLEAN) {
+            if(!strcmp(args, "0") || !stricmp(args, "off") || !stricmp(args, get_language_string(user, "NS_SET_OFF"))) {
+                args = "0";
+            } else if(!strcmp(args, "1") || !stricmp(args, "on") || !stricmp(args, get_language_string(user, "NS_SET_ON"))) {
+                args = "1";
+            } else {
+                reply(textclient, user, "NS_SET_INVALID_BOOLEAN", args);
+                return;
+            }
+        }
+        //valid - set it
+        value = args;
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%s' WHERE `channel_id` = '%d'", channel_settings[setting].chanfield, escape_string(value), chan->channel_id);
+        logEvent(event);
+    }
+    if(channel_settings[setting].valid & NS_HAS_OPT) {
+        sprintf(nameBuf, "NS_SET_OPTION_%s_%s", channel_settings[setting].setting, value);
+        char *tmp = get_language_string(user, nameBuf);
+        if(tmp)
+            reply(textclient, user, "\002%s\002 %s - %s", channel_settings[setting].setting, value, tmp);
+        else
+            reply(textclient, user, "\002%s\002 %s", channel_settings[setting].setting, value);
+    } else
+        reply(textclient, user, "\002%s\002 %s", channel_settings[setting].setting, value);
+    if(channel_settings[setting].valid & NS_HAS_HELP) {
+         sprintf(nameBuf, "NS_SET_HELP_%s", channel_settings[setting].setting);
+         reply(textclient, user, "  %s", get_language_string(user, nameBuf));
+    }
+}
+
+static char* neonserv_cmd_set_trigger(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
+    char *trigger;
+    //get current trigger
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(client->botid)
+        printf_mysql_query("SELECT `trigger`, `defaulttrigger`, `bot_channels`.`id` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, client->botid);
+    else
+        printf_mysql_query("SELECT `trigger`, `defaulttrigger`, `bot_channels`.`id` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botid` = '%d'", chan->channel_id, client->clientid);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    trigger = (row[0] ? row[0] : row[1]);
+    if(argument) {
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess < 500) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_TRIGGER_OWNER", chan->name);
+                return NULL;
+            }
+        }
+        if(strlen(argument) > 15)
+            argument[15] = '\0';
+        printf_mysql_query("UPDATE `bot_channels` SET `trigger` = '%s' WHERE `id` = '%d'", escape_string(argument), row[2]);
+        trigger = argument;
+        if(client->botid)
+            changeChannelTrigger(client->botid, chan, trigger);
+        else
+            changeBotwiseChannelTrigger(client->botid, client->clientid, chan, trigger);
+        logEvent(event);
+    }
+    if(setting) {
+        reply(textclient, user, "\002%s\002 %s", setting, trigger);
+    }
+    return trigger;
+}
+
+static char* neonserv_cmd_set_modes(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
+    char *value;
+    char valueBuf[MAXLEN];
+    //get current value
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_modes` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(row[0] == NULL) {
+        printf_mysql_query("SELECT `channel_modes` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+    }
+    value = row[0];
+    if(argument) {
+        //change the channel setting
+        struct ModeNode *modenode = createModeNode(NULL);
+        parseModeString(modenode, argument);
+        getFullModeString(modenode, valueBuf);
+        value = valueBuf;
+        printf_mysql_query("UPDATE `channels` SET `channel_modes` = '%s' WHERE `channel_id` = '%d'", escape_string(value), chan->channel_id);
+        //TODO: set modelock
+        freeModeNode(modenode);
+    }
+    if(setting) {
+        reply(textclient, user, "\002%s\002 %s", setting, value);
+    }
+    return value;
+}
+
+static char* neonserv_cmd_set_dynlimit(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
+    char *value;
+    char tmp[64];
+    //get current value
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_dynlimit` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(row[0] == NULL) {
+        printf_mysql_query("SELECT `channel_dynlimit` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+    }
+    value = row[0];
+    if(argument) {
+        //change the channel setting
+        sprintf(tmp, "%d", atoi(argument));
+        argument = tmp;
+        printf_mysql_query("UPDATE `channels` SET `channel_dynlimit` = '%s' WHERE `channel_id` = '%d'", escape_string(argument), chan->channel_id);
+        if(strcmp(argument, "0"))
+            putsock(client, "MODE %s +l %d", chan->name, (chan->usercount + atoi(argument)));
+        else if(isModeSet(chan->modes, 'l'))
+            putsock(client, "MODE %s -l", chan->name);
+        value = argument;
+        logEvent(event);
+    }
+    if(setting) {
+        reply(textclient, user, "\002%s\002 %s", setting, value);
+    }
+    return value;
+}
+
+static char* neonserv_cmd_set_nodelete(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
+    char *value;
+    //get current value
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_nodelete` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(row[0] == NULL) {
+        printf_mysql_query("SELECT `channel_nodelete` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+    }
+    value = row[0];
+    if(argument && isGodMode(user)) {
+        //change the channel setting
+        if(!strcmp(argument, "0") || !strcmp(argument, "off") || !strcmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            argument = "0";
+        } else if(!strcmp(argument, "1") || !strcmp(argument, "on") || !strcmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            argument = "1";
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argument);
+            return NULL;
+        }
+        printf_mysql_query("UPDATE `channels` SET `channel_nodelete` = '%s' WHERE `channel_id` = '%d'", escape_string(argument), chan->channel_id);
+        event->flags |= CMDFLAG_OPLOG;
+        value = argument;
+        logEvent(event);
+    }
+    if(setting) {
+        reply(textclient, user, "\002%s\002 %s", setting, value);
+    }
+    return value;
+}
+
+static char* neonserv_cmd_set_backupbot(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
+    int backupbot;
+    //get current trigger
+    MYSQL_RES *res;
+    printf_mysql_query("SELECT `botid` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, NEONBACKUP_BOTID);
+    res = mysql_use();
+    if(mysql_fetch_row(res))
+        backupbot = 1;
+    else 
+        backupbot = 0;
+    if(argument) {
+        //change the channel setting
+        if(!strcmp(argument, "0") || !strcmp(argument, "off") || !strcmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            if(backupbot) {
+                module_global_cmd_unregister_neonbackup(chan->name);
+                backupbot = 0;
+            }
+        } else if(!strcmp(argument, "1") || !strcmp(argument, "on") || !strcmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            if(!backupbot) {
+                module_global_cmd_register_neonbackup(chan->name);
+                backupbot = 1;
+            }
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argument);
+            return NULL;
+        }
+        logEvent(event);
+    }
+    if(setting) {
+        reply(textclient, user, "\002%s\002 %s", setting, (backupbot ? "1" : "0"));
+    }
+    return (backupbot ? "1" : "0");
+}
+
+#undef MAX_QUERY_LEN
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_setrank.c b/src/modules/NeonServ.mod/cmd_neonserv_setrank.c
new file mode 100644 (file)
index 0000000..91a3588
--- /dev/null
@@ -0,0 +1,113 @@
+/* cmd_neonserv_setrank.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]   rank id
+* argv[1]   setting
+* argv[2-*] value
+*/
+
+static void neonserv_cmd_setrank_name(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value);
+static void neonserv_cmd_setrank_info(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value);
+static void neonserv_cmd_setrank_access(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value);
+static void neonserv_cmd_setrank_order(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value);
+
+CMD_BIND(neonserv_cmd_setrank) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `rank_id`, `rank_name`, `rank_info`, `rank_access`, `rank_order` FROM `support_ranks` WHERE `rank_id` = '%s'", escape_string(argv[0]));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_SETRANK_NOT_FOUND", argv[0]);
+        return;
+    }
+    if(argc < 2) {
+        reply(textclient, user, "NS_SETRANK_HEAD", row[0]);
+        reply(textclient, user, "\002NAME   \002 %s", row[1]);
+        reply(textclient, user, "\002INFO   \002 %s", row[2]);
+        reply(textclient, user, "\002ACCESS \002 %s", row[3]);
+        reply(textclient, user, "\002ORDER  \002 %s", row[4]);
+    } else {
+        char *value = NULL;
+        if(argc > 2) {
+            value = merge_argv(argv, 2, argc);
+        }
+        if(!stricmp(argv[1], "name"))
+            neonserv_cmd_setrank_name(textclient, user, atoi(row[0]), row[1], value);
+        else if(!stricmp(argv[1], "info"))
+            neonserv_cmd_setrank_info(textclient, user, atoi(row[0]), row[2], value);
+        else if(!stricmp(argv[1], "access"))
+            neonserv_cmd_setrank_access(textclient, user, atoi(row[0]), row[3], value);
+        else if(!stricmp(argv[1], "order"))
+            neonserv_cmd_setrank_order(textclient, user, atoi(row[0]), row[4], value);
+        else
+            reply(textclient, user, "NS_SETRANK_UNKNOWN_SETTING", row[1]);
+    }
+}
+
+static void neonserv_cmd_setrank_name(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value) {
+    if(value && stricmp(value, current)) {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        printf_mysql_query("SELECT `rank_id` FROM `support_ranks` WHERE `rank_name` = '%s'", escape_string(value));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            reply(textclient, user, "NS_ADDRANK_EXISTS", value);
+            return;
+        }
+        printf_mysql_query("UPDATE `support_ranks` SET `rank_name` = '%s' WHERE `rank_id` = '%d'", escape_string(value), rank_id);
+        current = value;
+    }
+    reply(textclient, user, "\002NAME\002 %s", current);
+}
+
+static void neonserv_cmd_setrank_info(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value) {
+    if(value && stricmp(value, current)) {
+        printf_mysql_query("UPDATE `support_ranks` SET `rank_info` = '%s' WHERE `rank_id` = '%d'", escape_string(value), rank_id);
+        current = value;
+    }
+    reply(textclient, user, "\002INFO\002 %s", current);
+}
+
+static void neonserv_cmd_setrank_access(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value) {
+    if(value && stricmp(value, current)) {
+        int new_access = atoi(value);
+        if(new_access <= 0 || new_access > 1000) {
+            reply(textclient, user, "NS_INVALID_ACCESS", new_access);
+            return;
+        }
+        printf_mysql_query("UPDATE `users` SET `user_access` = '%d' WHERE `user_rank` = '%d'", new_access, rank_id);
+        printf_mysql_query("UPDATE `support_ranks` SET `rank_access` = '%d' WHERE `rank_id` = '%d'", new_access, rank_id);
+        current = value;
+    }
+    reply(textclient, user, "\002ACCESS\002 %s", current);
+}
+
+static void neonserv_cmd_setrank_order(struct ClientSocket *textclient, struct UserNode *user, int rank_id, char *current, char *value) {
+    if(value && stricmp(value, current)) {
+        int new_order = atoi(value);
+        if(new_order <= 0 || new_order > 99) {
+            reply(textclient, user, "NS_SETRANK_ORDER_INVALID", new_order);
+            return;
+        }
+        printf_mysql_query("UPDATE `support_ranks` SET `rank_order` = '%d' WHERE `rank_id` = '%d'", new_order, rank_id);
+        current = value;
+    }
+    reply(textclient, user, "\002ORDER\002 %s", current);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_suspend.c b/src/modules/NeonServ.mod/cmd_neonserv_suspend.c
new file mode 100644 (file)
index 0000000..e4fc158
--- /dev/null
@@ -0,0 +1,119 @@
+/* cmd_neonserv_suspend.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_suspend_nick_lookup);
+static void neonserv_cmd_suspend_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth);
+
+struct neonserv_cmd_suspend_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_suspend) {
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_suspend_async1(client, textclient, user, chan, event, argv[0], argv[0]);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_suspend_async1(client, textclient, user, chan, event, argv[0], cuser->auth);
+        } else {
+            struct neonserv_cmd_suspend_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_suspend_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_suspend_nick_lookup) {
+    struct neonserv_cmd_suspend_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_suspend_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_suspend_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid, cflags;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            if(atoi(row[0]) >= getChannelAccess(user, chan)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_USER_OUTRANKED", nick);
+                    return;
+                }
+            }
+            //suspend
+            cflags = atoi(row[2]);
+            if(cflags & DB_CHANUSER_SUSPENDED) {
+                reply(textclient, user, "NS_SUSPEND_ALREADY", nick);
+                return;
+            }
+            cflags |= DB_CHANUSER_SUSPENDED;
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", cflags, row[1]);
+            reply(textclient, user, "NS_SUSPEND_DONE", nick, chan->name);
+            logEvent(event);
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_topic.c b/src/modules/NeonServ.mod/cmd_neonserv_topic.c
new file mode 100644 (file)
index 0000000..068c547
--- /dev/null
@@ -0,0 +1,157 @@
+/* cmd_neonserv_topic.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* ADVANCEDTOPIC enabled
+* argv[0]    topic id
+* argv[1-*]  topic
+*
+* ADVANCEDTOPIC disabled
+* argv[0-*]  topic
+*/
+
+#define ADVANCEDTOPIC_MAXID 9
+
+CMD_BIND(neonserv_cmd_topic) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, default_row = NULL;
+    int advanced_topic, i;
+    char *newtopic;
+    char *a,*b;
+    
+    printf_mysql_query("SELECT `channel_exttopic`, `channel_exttopic_topic`, `channel_topicmask`, `channel_enftopic`, `channel_topicsnarf`, `channel_defaulttopic` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(!row[0] || !row[3] || !row[4]) {
+        printf_mysql_query("SELECT `channel_exttopic`, `channel_enftopic`, `channel_topicsnarf` FROM `channels` WHERE `channel_name` = 'defaults'");
+        default_row = mysql_fetch_row(mysql_use());
+    }
+    
+    if(row[0] == NULL) {
+        advanced_topic = atoi(default_row[0]);
+    } else
+        advanced_topic = atoi(row[0]);
+    if(argc == 0) {
+        //default topic!
+        putsock(client, "TOPIC %s :%s", chan->name, row[5]);
+        reply(textclient, user, "NS_TOPIC_DONE", row[5]);
+        logEvent(event);
+        return;
+    }
+    int uaccess = getChannelAccess(user, chan);
+    if(!strcmp(row[2], "") || uaccess >= atoi((row[3] ? row[3] : default_row[1]))) {
+        //just set the topic
+        newtopic = merge_argv(argv, 0, argc);
+        if(uaccess >= atoi((row[4] ? row[4] : default_row[2]))) {
+            //set the default topic
+            printf_mysql_query("UPDATE `channels` SET `channel_defaulttopic` = '%s' WHERE `channel_id` = '%d'", escape_string(newtopic), chan->channel_id);
+        }
+        putsock(client, "TOPIC %s :%s", chan->name, newtopic);
+        reply(textclient, user, "NS_TOPIC_DONE", newtopic);
+        logEvent(event);
+        return;
+    }
+    if(advanced_topic) {
+        char *advtopics[ADVANCEDTOPIC_MAXID];
+        int topic_id = 0;
+        topic_id = atoi(argv[0]);
+        if(!topic_id || topic_id > ADVANCEDTOPIC_MAXID) {
+            reply(textclient, user, "NS_EXTTOPIC_INVALID_ID", argv[0]);
+            return;
+        }
+        //parse topics
+        i = 0;
+        b = row[1];
+        while((a = strstr(b, "\n")) != NULL) {
+            *a = '\0';
+            if(i == ADVANCEDTOPIC_MAXID-1) break;
+            advtopics[i++] = b;
+            b = a+1;
+        }
+        advtopics[i++] = b;
+        for(;i < ADVANCEDTOPIC_MAXID;i++)
+            advtopics[i] = "";
+        if(argc < 2) {
+            //just show the topic with this id
+            reply(textclient, user, "NS_EXTTOPIC_TOPICID", topic_id, advtopics[topic_id-1]);
+            return;
+        }
+        newtopic = merge_argv(argv, 1, argc);
+        if(!strcmp(newtopic, "*")) 
+            newtopic = "";
+        advtopics[topic_id-1] = newtopic;
+        char topiclist[MAXLEN*2];
+        topiclist[0] = '\0';
+        int topiclistpos = 0;
+        for(i = 0; i < ADVANCEDTOPIC_MAXID; i++) {
+            if(topiclistpos + strlen(advtopics[i]) + 2 >= MAXLEN) break;
+            topiclistpos += sprintf(topiclist+topiclistpos, (i ? "\n%s" : "%s"), advtopics[i]);
+        }
+        printf_mysql_query("UPDATE `channels` SET `channel_exttopic_topic` = '%s' WHERE `channel_id` = '%d'", escape_string(topiclist), chan->channel_id);
+        //now build the new topic and set it...
+        topiclistpos = 0;
+        b = row[2];
+        char *topicpart, *debugbb;
+        while((a = strstr(b, "%")) != NULL) {
+            *a = '\0';
+            if(isdigit(a[1]) && a[1] - 48 > 0) {
+                topicpart = advtopics[a[1] - 49];
+                if(isdigit(topicpart[0]) && isdigit(b[strlen(b)-1])) 
+                    debugbb = "\002\002"; //double bold to prevent following digits used as color code
+                else
+                    debugbb = "";
+                topiclistpos += sprintf(topiclist + topiclistpos, "%s%s%s", b, debugbb, topicpart);
+                b = a+2;
+            } else {
+                topiclistpos += sprintf(topiclist + topiclistpos, "%s%%", b);
+                b = a+1;
+            }
+        }
+        topiclistpos += sprintf(topiclist + topiclistpos, "%s", b);
+        if(topiclistpos > MAXLEN)
+            topiclist[MAXLEN] = '\0';
+        putsock(client, "TOPIC %s :%s", chan->name, topiclist);
+        reply(textclient, user, "NS_TOPIC_DONE", topiclist);
+        logEvent(event);
+    } else {
+        newtopic = merge_argv(argv, 0, argc);
+        char topiclist[MAXLEN*2];
+        topiclist[0] = '\0';
+        int topiclistpos = 0;
+        b = row[2];
+        char *debugbb;
+        while((a = strstr(b, "*")) != NULL) {
+            *a = '\0';
+            if(isdigit(newtopic[0]) && isdigit(b[strlen(b)-1])) 
+                debugbb = "\002\002"; //double bold to prevent following digits used as color code
+            else
+                debugbb = "";
+            topiclistpos += sprintf(topiclist + topiclistpos, "%s%s%s", b, debugbb, newtopic);
+            b = a+1;
+        }
+        topiclistpos += sprintf(topiclist + topiclistpos, "%s", b);
+        if(topiclistpos > MAXLEN)
+            topiclist[MAXLEN] = '\0';
+        putsock(client, "TOPIC %s :%s", chan->name, topiclist);
+        reply(textclient, user, "NS_TOPIC_DONE", topiclist);
+        logEvent(event);
+    }
+}
+
+#undef ADVANCEDTOPIC_MAXID
\ No newline at end of file
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_trace.c b/src/modules/NeonServ.mod/cmd_neonserv_trace.c
new file mode 100644 (file)
index 0000000..49dc741
--- /dev/null
@@ -0,0 +1,113 @@
+/* cmd_neonserv_trace.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+#define NS_TRACE_CRITERIA_AUTHED  0x01
+#define NS_TRACE_CRITERIA_NUMCHAN 0x02
+
+struct neonserv_cmd_trace_criteria {
+    char *mask;
+    char *nick;
+    char *ident;
+    char *host;
+    char *account;
+    unsigned int flags : 4;
+    unsigned int authed : 1;
+    unsigned int used_channel : 5; //32 max
+    char *channel[10];
+    unsigned int numchannels;
+    unsigned int limit : 16;
+};
+
+CMD_BIND(neonserv_cmd_trace) {
+    //ok parse the criterias
+    struct neonserv_cmd_trace_criteria *criteria = malloc(sizeof(*criteria));
+    if (!criteria) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    memset(criteria, 0, sizeof(*criteria));
+    criteria->limit = 50;
+    int i, show_user = 0;
+    if(!stricmp(argv[0], "print")) {
+        show_user = 1;
+    }
+    for(i = 1; i < argc; i += 2) {
+        if(argc <= i+1) {
+            reply(textclient, user, "MODCMD_LESS_PARAM_COUNT");
+            return;
+        }
+        if(!stricmp(argv[i], "mask")) criteria->mask = argv[i+1];
+        else if(!stricmp(argv[i], "nick")) criteria->nick = argv[i+1];
+        else if(!stricmp(argv[i], "ident")) criteria->ident = argv[i+1];
+        else if(!stricmp(argv[i], "host")) criteria->host = argv[i+1];
+        else if(!stricmp(argv[i], "account")) criteria->account = argv[i+1];
+        else if(!stricmp(argv[i], "authed")) {
+            if(!strcmp(argv[i+1], "0") || !strcmp(argv[i+1], "off") || !strcmp(argv[i+1], get_language_string(user, "NS_SET_OFF"))) {
+                criteria->authed = 1;
+            } else if(!strcmp(argv[i+1], "0") || !strcmp(argv[i+1], "off") || !strcmp(argv[i+1], get_language_string(user, "NS_SET_OFF"))) {
+                criteria->authed = 0;
+            } else {
+                reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argv[i+1]);
+                return;
+            }
+            criteria->flags |= NS_TRACE_CRITERIA_AUTHED;
+        }
+        else if(!stricmp(argv[i], "channel")) criteria->channel[criteria->used_channel++] = argv[i+1];
+        else if(!stricmp(argv[i], "numchannels")) {
+            criteria->numchannels = atoi(argv[i+1]);
+            criteria->flags |= NS_TRACE_CRITERIA_NUMCHAN;
+        }
+        else if(!stricmp(argv[i], "limit")) {
+            criteria->limit = atoi(argv[i+1]);
+        }
+    }
+    char tmp[MAXLEN];
+    int matches = 0;
+    struct UserNode *cuser;
+    reply(textclient, user, "NS_TRACE_HEADER");
+    for(cuser = getAllUsers(NULL); cuser; cuser = getAllUsers(cuser)) {
+        if(show_user && matches == criteria->limit) {
+            //too many
+            break;
+        }
+        if(criteria->mask) {
+            sprintf(tmp, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+            if(match(criteria->mask, tmp)) continue;
+        }
+        if(criteria->nick && match(criteria->nick, cuser->nick)) continue;
+        if(criteria->ident && match(criteria->ident, cuser->ident)) continue;
+        if(criteria->host && match(criteria->host, cuser->host)) continue;
+        if(criteria->account && (!(cuser->flags & USERFLAG_ISAUTHED) || match(criteria->account, cuser->auth))) continue;
+        if((criteria->flags & NS_TRACE_CRITERIA_AUTHED) && (criteria->authed ^ (cuser->flags & USERFLAG_ISAUTHED))) continue;
+        if((criteria->flags & NS_TRACE_CRITERIA_NUMCHAN)) {
+            int ccount = 0;
+            struct ChanUser *chanuser;
+            for(chanuser = getUserChannels(cuser, NULL); chanuser; chanuser = getUserChannels(cuser, chanuser))
+                ccount++;
+            if(ccount < criteria->numchannels)
+                continue;
+        }
+        matches++;
+        //output
+        if(show_user) {
+            reply(textclient, user, "%s!%s@%s %s", cuser->nick, cuser->ident, cuser->host, ((cuser->flags & USERFLAG_ISAUTHED) ? cuser->auth : "*"));
+        }
+    }
+    reply(textclient, user, "NS_TABLE_COUNT", matches);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_trim.c b/src/modules/NeonServ.mod/cmd_neonserv_trim.c
new file mode 100644 (file)
index 0000000..52478a9
--- /dev/null
@@ -0,0 +1,158 @@
+/* cmd_neonserv_trim.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]  target (format: minaccess-maxaccess/users/bans)
+* argv[1]  duration
+*/
+static USERLIST_CALLBACK(neonserv_cmd_trim_userlist_lookup);
+static void neonserv_cmd_trim_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int min_access, int max_access, int duration);
+
+struct neonserv_cmd_trim_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    int min_access;
+    int max_access;
+    int duration;
+};
+
+CMD_BIND(neonserv_cmd_trim) {
+    if(stricmp(argv[0], "bans") && !checkChannelAccess(user, chan, "channel_candel", 0)) {
+        if(isGodMode(user)) {
+            event->flags |= CMDFLAG_OPLOG;
+        } else {
+            reply(textclient, user, "NS_ACCESS_DENIED");
+            return;
+        }
+    }
+    int min_access, max_access;
+    int duration = strToTime(user, argv[1]);
+    if(duration < 30) {
+        reply(textclient, user, "NS_TRIM_DURATION_TOO_SHORT", 30);
+        return;
+    }
+    if(!stricmp(argv[0], "users")) {
+        min_access = 1;
+        max_access = getChannelAccess(user, chan) - 1;
+    } else if(!stricmp(argv[0], "bans")) {
+        if(!checkChannelAccess(user, chan, "channel_staticban", 0)) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_ACCESS_DENIED");
+                return;
+            }
+        }
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        char nameBuf[20];
+        printf_mysql_query("SELECT `ban_mask`, `ban_id`, `ban_timeout` FROM `bans` WHERE `ban_channel` = '%d' AND `ban_triggered` < %d", chan->channel_id, (int) (time(0) - duration));
+        res = mysql_use();
+        int bancount = mysql_num_rows(res);
+        struct ModeBuffer *modenode = initModeBuffer(client, chan);
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            if(strcmp(row[2], "0")) {
+                sprintf(nameBuf, "ban_%s", row[1]);
+                timeq_del_name(nameBuf);
+            }
+            printf_mysql_query("DELETE FROM `bans` WHERE `ban_id` = '%s'", row[1]);
+            modeBufferUnban(modenode, row[0]);
+        }
+        freeModeBuffer(modenode);
+        char timeBuf[MAXLEN];
+        reply(textclient, user, "NS_TRIM_BAN_DONE", bancount, chan->name, timeToStr(user, duration, 3, timeBuf));
+        if(bancount)
+            logEvent(event);
+        return;
+    } else {
+        char *seperator = strstr(argv[0], "-");
+        if(seperator) {
+            *seperator = '\0';
+            seperator++;
+            min_access = atoi(argv[0]);
+            max_access = atoi(seperator);
+            if(max_access < min_access) {
+                reply(textclient, user, "NS_INVALID_ACCESS_RANGE", min_access, max_access);
+                return;
+            }
+        } else {
+            min_access = atoi(argv[0]);
+            max_access = min_access;
+        }
+        if(max_access >= getChannelAccess(user, chan)) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_NO_ACCESS");
+                return;
+            }
+        }
+    }
+    struct neonserv_cmd_trim_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->min_access = min_access;
+    cache->max_access = max_access;
+    cache->duration = duration;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_trim_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_trim_userlist_lookup) {
+    struct neonserv_cmd_trim_cache *cache = data;
+    //got userlist
+    neonserv_cmd_trim_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->min_access, cache->max_access, cache->duration);
+    free(cache);
+}
+
+static void neonserv_cmd_trim_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int min_access, int max_access, int duration) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int trim_count = 0, is_here;
+    struct ChanUser *chanuser;
+    printf_mysql_query("SELECT `chanuser_seen`, `user_user`, `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `chanuser_access` >= '%d' AND `chanuser_access` <= '%d'", chan->channel_id, min_access, max_access);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!strcmp(row[0], "0") || time(0) - atoi(row[0]) >= duration) {
+            //check if the user is currently in the channel
+            is_here = 0;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if((chanuser->user->flags & USERFLAG_ISAUTHED) && !strcmp(chanuser->user->auth, row[1])) {
+                    is_here = 1;
+                    break;
+                }
+            }
+            if(!is_here) {
+                //delete the user
+                trim_count++;
+                printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_id` = '%s'", row[2]);
+            }
+        }
+    }
+    char timeBuf[MAXLEN];
+    reply(textclient, user, "NS_TRIM_DONE", trim_count, min_access, max_access, chan->name, timeToStr(user, duration, 3, timeBuf));
+    if(trim_count)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_unban.c b/src/modules/NeonServ.mod/cmd_neonserv_unban.c
new file mode 100644 (file)
index 0000000..6361bb8
--- /dev/null
@@ -0,0 +1,145 @@
+/* cmd_neonserv_unban.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nick[,*auth[,*!*@mask[...]]]
+*/
+struct neonserv_cmd_unban_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    struct ModeBuffer *modeBuf;
+    int provided_masks, done_masks, pending_whos, unbanned_masks;
+};
+
+static USERAUTH_CALLBACK(neonserv_cmd_unban_userauth_lookup);
+static void neonserv_cmd_unban_nick(struct neonserv_cmd_unban_cache *cache, struct UserNode *user);
+static void neonserv_cmd_unban_mask(struct neonserv_cmd_unban_cache *cache, char *mask);
+static void neonserv_cmd_unban_finish(struct neonserv_cmd_unban_cache *cache);
+
+CMD_BIND(neonserv_cmd_unban) {
+    char *mask, *nextmask;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    nextmask = merge_argv_char(argv, 0, argc, ',');
+    struct neonserv_cmd_unban_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->chan = chan;
+    cache->event = event;
+    cache->modeBuf = modeBuf;
+    cache->done_masks = 0;
+    cache->provided_masks = 0;
+    cache->unbanned_masks = 0;
+    while((mask = nextmask)) {
+        nextmask = strstr(mask, ",");
+        if(nextmask) {
+            *nextmask = '\0';
+            nextmask++;
+        }
+        cache->provided_masks++;
+        if(is_valid_nick(mask)) {
+            struct UserNode *cuser = getUserByNick(mask);
+            if(!cuser) {
+                cuser = createTempUserMask(mask);
+                               if(!cuser) {
+                                       break; //internal bot error
+                               }
+                cuser->flags |= USERFLAG_ISTMPUSER;
+                get_userauth(cuser, module_id, neonserv_cmd_unban_userauth_lookup, cache);
+                cache->pending_whos++;
+            } else {
+                neonserv_cmd_unban_nick(cache, cuser);
+            }
+        } else {
+            neonserv_cmd_unban_mask(cache, mask);
+        }
+    }
+    if(!cache->pending_whos)
+        neonserv_cmd_unban_finish(cache);
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_unban_userauth_lookup) {
+    struct neonserv_cmd_unban_cache *cache = data;
+    cache->pending_whos--;
+    if(user)
+        neonserv_cmd_unban_nick(cache, user);
+    else
+        neonserv_cmd_unban_mask(cache, user_nick);
+    if(!cache->pending_whos)
+        neonserv_cmd_unban_finish(cache);
+}
+
+static void neonserv_cmd_unban_nick(struct neonserv_cmd_unban_cache *cache, struct UserNode *user) {
+    int matches = 0;
+    struct BanNode *ban;
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    sprintf(usermask, "%s!%s@%s", user->nick, user->ident, user->host);
+    for(ban = cache->chan->bans; ban; ban = ban->next) {
+        if(!match(ban->mask, usermask)) {
+            modeBufferUnban(cache->modeBuf, ban->mask);
+            cache->unbanned_masks++;
+            matches++;
+        }
+    }
+    if(matches)
+        cache->done_masks++;
+}
+
+static void neonserv_cmd_unban_mask(struct neonserv_cmd_unban_cache *cache, char *mask) {
+    char banmask[NICKLEN+USERLEN+HOSTLEN+3];
+    int matches = 0;
+    struct BanNode *ban;
+    mask = make_banmask(mask, banmask);
+    for(ban = cache->chan->bans; ban; ban = ban->next) {
+        if(!match(mask, ban->mask)) {
+            modeBufferUnban(cache->modeBuf, ban->mask);
+            cache->unbanned_masks++;
+            matches++;
+        }
+    }
+    if(matches)
+        cache->done_masks++;
+    else {
+        for(ban = cache->chan->bans; ban; ban = ban->next) {
+            if(!match(ban->mask, mask)) {
+                reply(cache->textclient, cache->user, "NS_DELBAN_BANNED_BY", mask, ban->mask);
+                break;
+            }
+        }
+    }
+}
+
+static void neonserv_cmd_unban_finish(struct neonserv_cmd_unban_cache *cache) {
+    freeModeBuffer(cache->modeBuf);
+    if(cache->done_masks == cache->provided_masks)
+        reply(cache->textclient, cache->user, "NS_UNBAN_DONE", cache->unbanned_masks, cache->chan->name);
+    else
+        reply(cache->textclient, cache->user, "NS_UNBAN_FAIL", cache->client->user->nick);
+    if(cache->done_masks)
+        logEvent(cache->event);
+    free(cache);
+}
+
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_unbanall.c b/src/modules/NeonServ.mod/cmd_neonserv_unbanall.c
new file mode 100644 (file)
index 0000000..edb1150
--- /dev/null
@@ -0,0 +1,39 @@
+/* cmd_neonserv_unbanall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nothing
+*/
+
+CMD_BIND(neonserv_cmd_unbanall) {
+    struct ModeBuffer *modeBuf;
+    int bans = 0;
+    struct BanNode *ban;
+    modeBuf = initModeBuffer(client, chan);
+    for(ban = chan->bans; ban; ban = ban->next) {
+        modeBufferUnban(modeBuf, ban->mask);
+        bans++;
+    }
+    freeModeBuffer(modeBuf);
+    if(bans) {
+        reply(textclient, user, "NS_UNBANALL_DONE", bans, chan->name);
+        logEvent(event);
+    } else
+        reply(textclient, user, "NS_UNBANALL_FAIL", client->user->nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_unbanme.c b/src/modules/NeonServ.mod/cmd_neonserv_unbanme.c
new file mode 100644 (file)
index 0000000..3cec26d
--- /dev/null
@@ -0,0 +1,43 @@
+/* cmd_neonserv_unbanme.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nothing
+*/
+
+CMD_BIND(neonserv_cmd_unbanme) {
+    struct ModeBuffer *modeBuf;
+    int bans = 0;
+    struct BanNode *ban;
+    modeBuf = initModeBuffer(client, chan);
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    sprintf(usermask, "%s!%s@%s", user->nick, user->ident, user->host);
+    for(ban = chan->bans; ban; ban = ban->next) {
+        if(!match(ban->mask, usermask)) {
+            modeBufferUnban(modeBuf, ban->mask);
+            bans++;
+        }
+    }
+    freeModeBuffer(modeBuf);
+    if(bans) {
+        reply(textclient, user, "NS_UNBANME_DONE", bans, chan->name);
+        logEvent(event);
+    } else
+        reply(textclient, user, "NS_UNBANME_FAIL", client->user->nick, usermask);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_unsuspend.c b/src/modules/NeonServ.mod/cmd_neonserv_unsuspend.c
new file mode 100644 (file)
index 0000000..7c88303
--- /dev/null
@@ -0,0 +1,119 @@
+/* cmd_neonserv_unsuspend.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_unsuspend_nick_lookup);
+static void neonserv_cmd_unsuspend_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth);
+
+struct neonserv_cmd_unsuspend_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_unsuspend) {
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_unsuspend_async1(client, textclient, user, chan, event, argv[0], argv[0]);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_unsuspend_async1(client, textclient, user, chan, event, argv[0], cuser->auth);
+        } else {
+            struct neonserv_cmd_unsuspend_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_unsuspend_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_unsuspend_nick_lookup) {
+    struct neonserv_cmd_unsuspend_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_unsuspend_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_unsuspend_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid, cflags;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id`, `chanuser_flags` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            if(atoi(row[0]) >= getChannelAccess(user, chan)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_USER_OUTRANKED", nick);
+                    return;
+                }
+            }
+            //unsuspend
+            cflags = atoi(row[2]);
+            if(!(cflags & DB_CHANUSER_SUSPENDED)) {
+                reply(textclient, user, "NS_SUSPEND_NOT", nick);
+                return;
+            }
+            cflags &= ~DB_CHANUSER_SUSPENDED;
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", cflags, row[1]);
+            reply(textclient, user, "NS_SUSPEND_RESTORED", nick, chan->name);
+            logEvent(event);
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_unvisited.c b/src/modules/NeonServ.mod/cmd_neonserv_unvisited.c
new file mode 100644 (file)
index 0000000..5f1572e
--- /dev/null
@@ -0,0 +1,212 @@
+/* cmd_neonserv_unvisited.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+#include "../botid.h"
+
+struct neonserv_cmd_unvisited_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    int duration, unregister_matches;
+    int who_count, matches;
+};
+
+static USERLIST_CALLBACK(neonserv_cmd_unvisited_userlist_lookup);
+static void neonserv_check_unvisited(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, int duration, int unregister_matches);
+static int neonserv_cmd_unvisited_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, int duration, int unregister_matches);
+static void neonserv_cmd_unvisited_async2(struct neonserv_cmd_unvisited_cache *cache);
+static void neonserv_cmd_unvisited_unreg(struct ClientSocket *client, char *channel);
+static TIMEQ_CALLBACK(neonserv_check_unvisited_timer);
+
+CMD_BIND(neonserv_cmd_unvisited) {
+    int duration = (argc ? strToTime(user, argv[0]) : 60*60*24*7*3);
+    reply(textclient, user, "NS_SEARCH_HEADER");
+    int unreg = 0;
+    if(argc > 1 && !stricmp(argv[1], "unregister")) 
+        unreg = 1;
+    neonserv_check_unvisited(client, textclient, user, duration, unreg);
+}
+
+void neonserv_cmd_unvisited_init() {
+    if(!timeq_name_exists("neonserv_unvisited"))
+        timeq_add_name("neonserv_unvisited", 1200, module_id, neonserv_check_unvisited_timer, NULL);
+}
+
+static TIMEQ_CALLBACK(neonserv_check_unvisited_timer) {
+    char tmp[200];
+    char *modname = get_module_name(module_id);
+    sprintf(tmp, "modules.%s.chan_expire_freq", modname);
+    char *check_freq_str = get_string_field(tmp);
+    int check_freq;
+    if(!check_freq_str || (check_freq = strToTime(NULL, check_freq_str)) < (60*60)) {
+        timeq_add_name("neonserv_unvisited", 1800, module_id, neonserv_check_unvisited_timer, NULL);
+        return;
+    }
+    sprintf(tmp, "modules.%s.chan_expire_delay", modname);
+    char *check_expire_str = get_string_field(tmp);
+    int duration;
+    if(!check_expire_str || (duration = strToTime(NULL, check_expire_str)) < 60*60*24*7) return;
+    neonserv_check_unvisited(NULL, NULL, NULL, duration, 1);
+}
+
+static void neonserv_check_unvisited(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, int duration, int unregister_matches) {
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    struct ChanNode *channel;
+    struct neonserv_cmd_unvisited_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->duration = duration;
+    cache->unregister_matches = unregister_matches;
+    cache->who_count = 1; /* small fake to prevent the cache to be freed too early */
+    cache->matches = 0;
+    int botid;
+    if(client)
+        botid = client->botid;
+    else
+        botid = NEONSERV_BOTID;
+    printf_mysql_query("SELECT `channel_id`, `channel_name`, `channel_nodelete` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` LEFT JOIN `users` ON `channel_registrator` = `user_id` WHERE `botid` = '%d'", botid);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!strcmp(row[2], "1")) continue;
+        printf_mysql_query("SELECT `chanuser_seen` FROM `chanusers` WHERE `chanuser_cid` = '%s' AND `chanuser_access` >= 300 ORDER BY `chanuser_seen` DESC LIMIT 1", row[0]);
+        res2 = mysql_use();
+        row2 = mysql_fetch_row(res2);
+        if(row2 && atol(row2[0]) > (time(0) - duration)) continue;
+        channel = getChanByName(row[1]);
+        if(channel) {
+            cache->who_count++;
+            channel->channel_id = atoi(row[0]);
+            get_userlist_with_invisible(channel, module_id, neonserv_cmd_unvisited_userlist_lookup, cache);
+        } else {
+            if(textclient)
+                reply(textclient, user, "%s", row[1]);
+            if(unregister_matches)
+                neonserv_cmd_unvisited_unreg(client, row[1]);
+            cache->matches++;
+        }   
+    }
+    cache->who_count--; //see fix on line 78
+    if(cache->who_count == 0) {
+        neonserv_cmd_unvisited_async2(cache);
+    }
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_unvisited_userlist_lookup) {
+    struct neonserv_cmd_unvisited_cache *cache = data;
+    if(neonserv_cmd_unvisited_async1(cache->client, cache->textclient, cache->user, chan, cache->duration, cache->unregister_matches))
+        cache->matches++;
+    cache->who_count--;
+    if(cache->who_count == 0) {
+        neonserv_cmd_unvisited_async2(cache);
+    }
+}
+
+static int neonserv_cmd_unvisited_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, int duration, int unregister_matches) {
+    struct ChanUser *chanuser;
+    MYSQL_RES *res2;
+    MYSQL_ROW row2;
+    int active = 0;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(isBot(chanuser->user)) continue;
+        if((chanuser->user->flags & USERFLAG_ISAUTHED)) {
+            printf_mysql_query("SELECT `chanuser_id`, `chanuser_access` FROM `chanusers` LEFT JOIN `users` ON `user_id` = `chanuser_uid` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(chanuser->user->auth));
+            res2 = mysql_use();
+            row2 = mysql_fetch_row(res2);
+            if(row2 && atoi(row2[1]) >= 300) {
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_seen` = UNIX_TIMESTAMP() WHERE `chanuser_id` = '%s'", row2[0]);
+                active = 1;
+            }
+        }
+    }
+    if(!active) {
+        if(textclient)
+            reply(textclient, user, "%s", chan->name);
+        if(unregister_matches)
+            neonserv_cmd_unvisited_unreg(client, chan->name);
+    }
+    return !active;
+}
+
+static void neonserv_cmd_unvisited_async2(struct neonserv_cmd_unvisited_cache *cache) {
+    if(cache->textclient)
+        reply(cache->textclient, cache->user, "NS_TABLE_COUNT", cache->matches);
+    free(cache);
+}
+
+static void neonserv_cmd_unvisited_unreg(struct ClientSocket *client, char *channel) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int sync_neonspam_unreg = get_int_field("General.sync_neonspam_unreg");
+    int botid;
+    if(client)
+        botid = client->botid;
+    else
+        botid = NEONSERV_BOTID;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended`, `channels`.`channel_nodelete` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        return;
+    }
+    int clientid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == clientid)
+            break;
+    }
+    if(!strcmp(row[3], "1")) return;
+    printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
+    if(bot && strcmp(row[2], "1")) {
+        putsock(bot, "PART %s :Channel unregistered.", channel);
+    }
+    if(botid == NEONSERV_BOTID && sync_neonspam_unreg) {
+        botid = NEONSPAM_BOTID;
+        printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), botid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) == NULL) {
+            return;
+        }
+        clientid = atoi(row[0]);
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->clientid == clientid)
+                break;
+        }
+        printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
+        if(bot && strcmp(row[2], "1")) {
+            putsock(bot, "PART %s :Channel unregistered.", channel);
+        }
+    }
+    if(botid == NEONSERV_BOTID) {
+        char setting[128];
+        sprintf(setting, "modules.%s.auto_backup_unregister", get_module_name(module_id));
+        if(get_int_field(setting))
+            module_global_cmd_unregister_neonbackup(channel);
+    }
+    char *alertchan = get_string_field("General.alertchan");
+    if(alertchan) {
+        struct ChanNode *alertchan_chan = getChanByName(alertchan);
+        struct ClientSocket *alertclient;
+        if(alertchan_chan && (alertclient = getChannelBot(alertchan_chan, 0)) != NULL) {
+            putsock(alertclient, "PRIVMSG %s :Unregistered %s (unvisited)", alertchan_chan->name, channel);
+        }
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_up.c b/src/modules/NeonServ.mod/cmd_neonserv_up.c
new file mode 100644 (file)
index 0000000..a5d4d85
--- /dev/null
@@ -0,0 +1,95 @@
+/* cmd_neonserv_up.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+struct neonserv_cmd_up_cache {
+    struct ClientSocket *client;
+    struct ClientSocket *textclient;
+    struct UserNode *user;
+    struct Event *event;
+};
+
+static void neonserv_cmd_up_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event);
+static USERLIST_CALLBACK(neonserv_cmd_up_userlist_lookup);
+
+CMD_BIND(neonserv_cmd_up) {
+    if(isModeSet(chan->modes, 'd') || isModeSet(chan->modes, 'D')) {
+        struct neonserv_cmd_up_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->textclient = textclient;
+        cache->user = user;
+        cache->event = event;
+        get_userlist_if_invisible(chan, module_id, neonserv_cmd_up_userlist_lookup, cache);
+    } else {
+        neonserv_cmd_up_async1(client, textclient, user, chan, event);
+    }
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_up_userlist_lookup) {
+    struct neonserv_cmd_up_cache *cache = data;
+    neonserv_cmd_up_async1(cache->client, cache->textclient, cache->user, chan, cache->event);
+    free(cache);
+}
+
+static void neonserv_cmd_up_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) {
+        reply(textclient, user, "NS_NOT_ON_CHANNEL_YOU", chan->name);
+        return;
+    }
+    loadChannelSettings(chan);
+    MYSQL_RES *res, *default_res;
+    MYSQL_ROW row, default_row;
+    int chan_getop, chan_getvoice, caccess;
+    printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(!row[0] || !row[1]) {
+        printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
+        default_res = mysql_use();
+        if ((default_row = mysql_fetch_row(default_res)) == NULL) return;
+        chan_getop = (row[0] ? atoi(row[0]) : atoi(default_row[0]));
+        chan_getvoice = (row[1] ? atoi(row[1]) : atoi(default_row[1]));
+    } else {
+        chan_getop = atoi(row[0]);
+        chan_getvoice = atoi(row[1]);
+    }
+    caccess = getChannelAccess(user, chan);
+    if(caccess >= chan_getop) {
+        if(!(chanuser->flags & CHANUSERFLAG_OPPED)) {
+            putsock(client, "MODE %s +o %s", chan->name, user->nick);
+            logEvent(event);
+        } else
+            reply(textclient, user, "NS_UP_ALREADY_OP", chan->name);
+    } else if(caccess >= chan_getvoice) {
+        if(!(chanuser->flags & CHANUSERFLAG_VOICED)) {
+            putsock(client, "MODE %s +v %s", chan->name, user->nick);
+            logEvent(event);
+        } else
+            reply(textclient, user, "NS_UP_ALREADY_VOICE", chan->name);
+    } else
+        reply(textclient, user, "NS_NOT_ON_USERLIST_YOU", chan->name);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_upall.c b/src/modules/NeonServ.mod/cmd_neonserv_upall.c
new file mode 100644 (file)
index 0000000..738ccf6
--- /dev/null
@@ -0,0 +1,73 @@
+/* cmd_neonserv_upall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(neonserv_cmd_upall) {
+    MYSQL_RES *res, *default_res;
+    MYSQL_ROW row, default_row;
+    struct ChanUser *chanuser;
+    int userid, chan_getop, chan_getvoice, caccess;
+    int botid = client->botid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", user->auth);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL)
+        return;
+    userid = atoi(row[0]);
+    printf_mysql_query("SELECT `chanuser_access`, `channel_getop`, `channel_getvoice`, `channel_name`, `channel_id` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` WHERE `chanuser_uid` = '%d'", userid);
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        chan = getChanByName(row[3]);
+        if(!chan) continue;
+        printf_mysql_query("SELECT `botid` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%s' AND `botclass` = '%d'", row[4], client->botid);
+        if (mysql_fetch_row(mysql_use()) == NULL) continue;
+        if(!(chanuser = getChanUser(user, chan))) continue;
+        if(!row[1] || !row[2]) {
+            printf_mysql_query("SELECT `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
+            default_res = mysql_use();
+            if ((default_row = mysql_fetch_row(default_res)) == NULL) return;
+            chan_getop = (row[1] ? atoi(row[1]) : atoi(default_row[0]));
+            chan_getvoice = (row[2] ? atoi(row[2]) : atoi(default_row[1]));
+        } else {
+            chan_getop = atoi(row[1]);
+            chan_getvoice = atoi(row[2]);
+        }
+        caccess = atoi(row[0]);
+        int done = 0;
+        client = getChannelBot(chan, botid);
+        if(!client) continue;
+        if(caccess >= chan_getop) {
+            if(!(chanuser->flags & CHANUSERFLAG_OPPED)) {
+                putsock(client, "MODE %s +o %s", chan->name, user->nick);
+                done = 1;
+            }
+        } else if(caccess >= chan_getvoice) {
+            if(!(chanuser->flags & CHANUSERFLAG_VOICED)) {
+                putsock(client, "MODE %s +v %s", chan->name, user->nick);
+                done = 1;
+            }
+        }
+        if(done) {
+            event->chan = chan;
+            logEvent(event);
+        }
+    }
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_users.c b/src/modules/NeonServ.mod/cmd_neonserv_users.c
new file mode 100644 (file)
index 0000000..4da41b5
--- /dev/null
@@ -0,0 +1,132 @@
+/* cmd_neonserv_users.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - usermask
+* argv[1] - min access
+* argv[2] - max access
+*/
+static USERLIST_CALLBACK(neonserv_cmd_users_userlist_lookup);
+static void neonserv_cmd_users_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *usermask, int min_access, int max_access);
+
+struct neonserv_cmd_users_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    char *usermask;
+    int min_access;
+    int max_access;
+};
+
+CMD_BIND(neonserv_cmd_users) {
+    int min_access = 1, max_access = 500;
+    char *usermask = NULL;
+    if(argc > 0)
+        usermask = argv[0];
+    if(argc > 2) {
+        min_access = atoi(argv[1]);
+        max_access = atoi(argv[2]);
+    }
+    struct neonserv_cmd_users_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->usermask = (usermask ? strdup(usermask) : NULL);
+    cache->min_access = min_access;
+    cache->max_access = max_access;
+    get_userlist_with_invisible(chan, module_id, neonserv_cmd_users_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_users_userlist_lookup) {
+    struct neonserv_cmd_users_cache *cache = data;
+    neonserv_cmd_users_async1(cache->client, cache->textclient, cache->user, chan, cache->usermask, cache->min_access, cache->max_access);
+    if(cache->usermask)
+        free(cache->usermask);
+    free(cache);
+}
+
+static void neonserv_cmd_users_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, char *usermask, int min_access, int max_access) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int content_count = 0, cflags, is_here, caccess, i;
+    char seenstr[MAXLEN];
+    struct Table *table;
+    struct ChanUser *chanuser;
+    printf_mysql_query("SELECT `chanuser_access`, `user_user`, `chanuser_seen`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' ORDER BY `chanuser_access` DESC, `user_user` ASC", chan->channel_id);
+    res = mysql_use();
+    table = table_init(4, mysql_num_rows(res) + 1, 0);
+    if(usermask)
+        reply(textclient, user, "NS_USERS_HEADER_MATCH", chan->name, min_access, max_access, usermask);
+    else
+        reply(textclient, user, "NS_USERS_HEADER", chan->name, min_access, max_access);
+    char *content[4];
+    content[0] = get_language_string(user, "NS_USERS_HEADER_ACCESS");
+    content[1] = get_language_string(user, "NS_USERS_HEADER_ACCOUNT");
+    content[2] = get_language_string(user, "NS_USERS_HEADER_SEEN");
+    content[3] = get_language_string(user, "NS_USERS_HEADER_STATE");
+    table_add(table, content);
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        caccess = atoi(row[0]);
+        if((!usermask || !match(usermask, row[1])) && (min_access == 1 || caccess >= min_access) && (max_access == 500 || caccess <= max_access)) {
+            content[0] = row[0];
+            content[1] = row[1];
+            is_here = 0;
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if((chanuser->user->flags & USERFLAG_ISAUTHED) && !stricmp(chanuser->user->auth, row[1])) {
+                    if((chanuser->flags & CHANUSERFLAG_INVISIBLE))
+                        is_here = 2;
+                    else {
+                        is_here = 1;
+                        break;
+                    }
+                }
+            }
+            if(is_here) {
+                content[2] = get_language_string(user, (is_here == 2 ? "NS_USERS_SEEN_INVISIBLE" : "NS_USERS_SEEN_HERE"));
+            } else if(!strcmp(row[2], "0")) {
+                content[2] = get_language_string(user, "NS_USERS_SEEN_NEVER");
+            } else {
+                timeToStr(user, (time(0) - atoi(row[2])), 2, seenstr);
+                content[2] = seenstr; //generate time
+            }
+            cflags = atoi(row[3]);
+            if(cflags & DB_CHANUSER_SUSPENDED)
+                content[3] = get_language_string(user, "NS_USERS_STATE_SUSPENDED");
+            else
+                content[3] = get_language_string(user, "NS_USERS_STATE_NORMAL");
+            content_count++;
+            table_add(table, content);
+        }
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    if(!content_count)
+        reply(textclient, user, "NS_TABLE_NONE");
+    if(usermask || min_access != 1 || max_access != 500)
+        reply(textclient, user, (table->length == 2 ? "NS_USERS_COUNT_MATCH_1" : "NS_USERS_COUNT_MATCH"), table->length - 1, chan->name, content_count);
+    else
+        reply(textclient, user, (table->length == 2 ? "NS_USERS_COUNT_1" : "NS_USERS_COUNT"), table->length - 1, chan->name);
+    table_free(table);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_uset.c b/src/modules/NeonServ.mod/cmd_neonserv_uset.c
new file mode 100644 (file)
index 0000000..e1b588f
--- /dev/null
@@ -0,0 +1,272 @@
+/* cmd_neonserv_uset.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+typedef void neonserv_cmd_uset_function(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_language(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_blockinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_noinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_autoinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_noautoop(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+static void neonserv_cmd_uset_info(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults);
+
+#define USET_FLAG_GLOBAL    0x01
+#define USET_FLAG_CHANNEL   0x02
+#define USET_FLAG_STAFF     0x04
+#define USET_FLAG_INVISIBLE 0x08
+#define USET_FLAG_DEFAULTS  0x10
+
+#define USET_DEFAULTS_QUERY \
+    printf_mysql_query("SELECT `chanuser_flags`, `chanuser_infoline`, `chanuser_access`, `channel_getinvite`, `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` LEFT JOIN `channels` ON `channel_id` = `chanuser_cid` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth))
+
+static const struct {
+    const char *setting;
+    neonserv_cmd_uset_function *function;
+    int flags;
+} uset_settings[] = {
+    {"Language",        neonserv_cmd_uset_language,    USET_FLAG_GLOBAL},
+    {"BlockInvites",    neonserv_cmd_uset_blockinvite, USET_FLAG_GLOBAL},
+    {"BlockInvite",     neonserv_cmd_uset_blockinvite, USET_FLAG_GLOBAL | USET_FLAG_INVISIBLE},
+    {"BlockAllInvites", neonserv_cmd_uset_blockinvite, USET_FLAG_GLOBAL | USET_FLAG_INVISIBLE},
+    {"NoInvite",        neonserv_cmd_uset_noinvite,    USET_FLAG_CHANNEL},
+    {"AutoInvite",      neonserv_cmd_uset_autoinvite,  USET_FLAG_CHANNEL | USET_FLAG_DEFAULTS},
+    {"NoAutoOp",        neonserv_cmd_uset_noautoop,    USET_FLAG_CHANNEL | USET_FLAG_DEFAULTS},
+    {"NoAutoVoice",     neonserv_cmd_uset_noautoop,    USET_FLAG_CHANNEL | USET_FLAG_DEFAULTS | USET_FLAG_INVISIBLE}, //alias of NoAutoOp
+    {"Info",            neonserv_cmd_uset_info,        USET_FLAG_CHANNEL | USET_FLAG_DEFAULTS},
+    {NULL, NULL, 0}
+};
+
+CMD_BIND(neonserv_cmd_uset) {
+    MYSQL_RES *res;
+    MYSQL_ROW row = NULL;
+    int i = 0, j = 0;
+    loadUserSettings(user);
+    if(argc > 0) {
+        char *args = (argc > 1 ? merge_argv(argv, 1, argc) : NULL);
+        while(uset_settings[i].setting) {
+            if(!stricmp(uset_settings[i].setting, argv[0])) {
+                //setting found
+                if(uset_settings[i].flags & USET_FLAG_CHANNEL) {
+                    if(!chan)
+                        goto neonserv_cmd_uset_err_chan_required;
+                    loadChannelSettings(chan);
+                    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+                        neonserv_cmd_uset_err_chan_required:
+                        reply(textclient, user, "MODCMD_CHAN_REQUIRED");
+                        return;
+                    }
+                    if((uset_settings[i].flags & USET_FLAG_DEFAULTS) && !row) {
+                        USET_DEFAULTS_QUERY;
+                        res = mysql_use();
+                        row = mysql_fetch_row(res);
+                        if(!row) {
+                            reply(textclient, user, "NS_NOT_ON_USERLIST_YOU", chan->name);
+                            return;
+                        }
+                    }
+                }
+                neonserv_cmd_uset_function *func = uset_settings[i].function;
+                func(client, textclient, user, chan, event, uset_settings[i].setting, args, row);
+                j = 1;
+                break;
+            }
+            i++;
+        }
+        if(j == 0) {
+            //unknown setting
+            reply(textclient, user, "NS_USET_UNKNOWN_SETTING", argv[0]);
+        }
+    } else {
+        //view all options
+        reply(textclient, user, "NS_USET_GLOBAL");
+        while(uset_settings[i].setting) {
+            if((uset_settings[i].flags & (USET_FLAG_INVISIBLE | USET_FLAG_GLOBAL)) == USET_FLAG_GLOBAL) {
+                neonserv_cmd_uset_function *func = uset_settings[i].function;
+                func(client, textclient, user, chan, event, uset_settings[i].setting, NULL, row);
+            }
+            i++;
+        }
+        if(!chan) return;
+        loadChannelSettings(chan);
+        if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+        reply(textclient, user, "NS_USET_CHANNEL");
+        i = 0;
+        while(uset_settings[i].setting) {
+            if((uset_settings[i].flags & (USET_FLAG_INVISIBLE | USET_FLAG_CHANNEL)) == USET_FLAG_CHANNEL) {
+                if((uset_settings[i].flags & USET_FLAG_DEFAULTS) && !row) {
+                    USET_DEFAULTS_QUERY;
+                    res = mysql_use();
+                    row = mysql_fetch_row(res);
+                    if(!row) {
+                        i++;
+                        continue;
+                    }
+                }
+                neonserv_cmd_uset_function *func = uset_settings[i].function;
+                func(client, textclient, user, chan, event, uset_settings[i].setting, NULL, row);
+            }
+            i++;
+        }
+    }
+}
+
+static void neonserv_cmd_uset_language(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct language* lang;
+    if(argument) {
+        if((lang = get_language_by_tag(argument)) == NULL && (lang = get_language_by_name(argument)) == NULL) {
+            lang = user->language;
+        } else {
+            printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+            res = mysql_use();
+            if ((row = mysql_fetch_row(res)) != NULL) {
+                printf_mysql_query("UPDATE `users` SET `user_lang` = '%s' WHERE `user_id` = '%s'", escape_string(lang->langtag), row[0]);
+            } else {
+                printf_mysql_query("INSERT INTO `users` (`user_user`, `user_lang`) VALUES ('%s', '%s')", escape_string(user->auth), escape_string(lang->langtag));
+            }
+            struct UserNode *cuser;
+            for(cuser = getAllUsers(NULL); cuser; cuser = getAllUsers(cuser)) {
+                if((cuser->flags & USERFLAG_ISAUTHED) && !stricmp(user->auth, cuser->auth))
+                    cuser->language = lang;
+            }
+        }
+    } else
+        lang = user->language;
+    reply(textclient, user, "\002Language    \002%s", lang->langname);
+    char tmp[MAXLEN];
+    int tmppos = 0;
+    lang = get_default_language();
+    tmppos = sprintf(tmp, "%s (%s)", lang->langname, lang->langtag);
+    printf_mysql_query("SELECT `lang`,`text` FROM `language` WHERE `ident` = 'name'");
+    res = mysql_use();
+    while((row = mysql_fetch_row(res)) != NULL) {
+        tmppos += sprintf(tmp + tmppos, ", %s (%s)", row[1], row[0]);
+    }
+    reply(textclient, user, "  %s", tmp);
+}
+
+static void neonserv_cmd_uset_blockinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_id`, `user_block_invites` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    int blockinvite = (row ? atoi(row[1]) : 0);
+    if(argument) {
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            blockinvite = 0;
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            blockinvite = 1;
+        }
+        if(blockinvite && !row) {
+            printf_mysql_query("INSERT INTO `users` (`user_user`, `user_block_invites`) VALUES ('%s', '1')", escape_string(user->auth));
+        } else if(blockinvite != atoi(row[1])) {
+            printf_mysql_query("UPDATE `users` SET `user_block_invites` = '%d' WHERE `user_id` = '%s'", blockinvite, row[0]);
+        }
+    }
+    reply(textclient, user, "\002BlockInvite \002%s", (blockinvite ? get_language_string(user, "NS_SET_ON") : get_language_string(user, "NS_SET_OFF")));
+}
+
+static void neonserv_cmd_uset_noinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `id` FROM `noinvite` LEFT JOIN `users` ON `uid` = `user_id` WHERE `cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    int noinvite = (row ? 1 : 0);
+    if(argument) {
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            if(noinvite) {
+                printf_mysql_query("DELETE FROM `noinvite` WHERE `id` = '%s'", row[0]);
+                noinvite = 0;
+            }
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            if(!noinvite) {
+                int userid;
+                printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+                res = mysql_use();
+                if ((row = mysql_fetch_row(res)) != NULL) {
+                    userid = atoi(row[0]);
+                } else {
+                    printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(user->auth));
+                    userid = (int) mysql_insert_id(get_mysql_conn());
+                }
+                printf_mysql_query("INSERT INTO `noinvite` (`uid`, `cid`) VALUES ('%d', '%d')", userid, chan->channel_id);
+                noinvite = 1;
+            }
+        }
+    }
+    reply(textclient, user, "\002NoInvite    \002%s", (noinvite ? get_language_string(user, "NS_SET_ON") : get_language_string(user, "NS_SET_OFF")));
+}
+
+static void neonserv_cmd_uset_autoinvite(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    int flags = atoi(defaults[0]);
+    int getInvite = 0;
+    if(!defaults[3] && atoi(defaults[2]) >= atoi(getChanDefault("channel_getinvite")))
+        getInvite = 1;
+    else if(defaults[3] && atoi(defaults[2]) >= atoi(defaults[3]))
+        getInvite = 1;
+    if(getInvite && argument) {
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            if(flags & DB_CHANUSER_AUTOINVITE) {
+                flags &= ~DB_CHANUSER_AUTOINVITE;
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", flags, defaults[4]);
+            }
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            if(!(flags & DB_CHANUSER_AUTOINVITE)) {
+                flags |= DB_CHANUSER_AUTOINVITE;
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", flags, defaults[4]);
+            }
+        }
+    }
+    if(getInvite)
+        reply(textclient, user, "\002AutoInvite  \002%s", ((flags & DB_CHANUSER_AUTOINVITE) ? get_language_string(user, "NS_SET_ON") : get_language_string(user, "NS_SET_OFF")));
+    else
+        reply(textclient, user, "\002AutoInvite  \002%s", get_language_string(user, "NS_USET_NO_ACCESS"));
+}
+
+static void neonserv_cmd_uset_noautoop(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    int flags = atoi(defaults[0]);
+    if(argument) {
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            if(flags & DB_CHANUSER_NOAUTOOP) {
+                flags &= ~DB_CHANUSER_NOAUTOOP;
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", flags, defaults[4]);
+            }
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            if(!(flags & DB_CHANUSER_NOAUTOOP)) {
+                flags |= DB_CHANUSER_NOAUTOOP;
+                printf_mysql_query("UPDATE `chanusers` SET `chanuser_flags` = '%d' WHERE `chanuser_id` = '%s'", flags, defaults[4]);
+            }
+        }
+    }
+    reply(textclient, user, "\002NoAutoOp    \002%s", ((flags & DB_CHANUSER_NOAUTOOP) ? get_language_string(user, "NS_SET_ON") : get_language_string(user, "NS_SET_OFF")));
+}
+
+static void neonserv_cmd_uset_info(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument, MYSQL_ROW defaults) {
+    char *infoline;
+    if(argument) {
+        infoline = argument;
+        if(!strcmp(infoline, "*"))
+            infoline = "";
+        printf_mysql_query("UPDATE `chanusers` SET `chanuser_infoline` = '%s' WHERE `chanuser_id` = '%s'", escape_string(infoline), defaults[4]);
+    } else
+        infoline = defaults[1];
+    reply(textclient, user, "\002Info        \002%s", infoline);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_voice.c b/src/modules/NeonServ.mod/cmd_neonserv_voice.c
new file mode 100644 (file)
index 0000000..ea3de40
--- /dev/null
@@ -0,0 +1,93 @@
+/* cmd_neonserv_voice.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0-*]    nicks
+*/
+static USERLIST_CALLBACK(neonserv_cmd_voice_userlist_lookup);
+static void neonserv_cmd_voice_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks);
+
+struct neonserv_cmd_voice_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nicks;
+};
+
+CMD_BIND(neonserv_cmd_voice) {
+    struct neonserv_cmd_voice_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    cache->nicks = strdup(merge_argv(argv, 0, argc));
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_voice_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_voice_userlist_lookup) {
+    struct neonserv_cmd_voice_cache *cache = data;
+    neonserv_cmd_voice_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nicks);
+    free(cache->nicks);
+    free(cache);
+}
+
+static void neonserv_cmd_voice_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nicks) {
+    int total_users = 0, done_users = 0;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    char *a, *b = nicks;
+    do {
+        a = strstr(b, " ");
+        if(a) *a = '\0';
+        total_users++;
+        cuser = searchUserByNick(b);
+        if(!cuser) {
+            //check for an invisible user
+            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                if(!stricmp(chanuser->user->nick, b)) {
+                    cuser = chanuser->user;
+                    break;
+                }
+            }
+            if(!cuser) continue;
+        } else {
+            chanuser = getChanUser(cuser, chan);
+            if(!chanuser) continue;
+        }
+        done_users++;
+        if(chanuser->flags & CHANUSERFLAG_VOICED) continue;
+        modeBufferVoice(modeBuf, b);
+        if(a) {
+            b = a+1;
+        }
+    } while(a);
+    freeModeBuffer(modeBuf);
+    if(done_users == total_users)
+        reply(textclient, user, "NS_VOICE_DONE", chan->name);
+    else
+        reply(textclient, user, "NS_VOICE_FAIL", client->user->nick);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_voiceall.c b/src/modules/NeonServ.mod/cmd_neonserv_voiceall.c
new file mode 100644 (file)
index 0000000..a1c2325
--- /dev/null
@@ -0,0 +1,73 @@
+/* cmd_neonserv_voiceall.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0]    (optional) nick mask
+*/
+static USERLIST_CALLBACK(neonserv_cmd_voiceall_userlist_lookup);
+static void neonserv_cmd_voiceall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask);
+
+struct neonserv_cmd_voiceall_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    char *nickmask;
+};
+
+CMD_BIND(neonserv_cmd_voiceall) {
+    struct neonserv_cmd_voiceall_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->user = user;
+    cache->event = event;
+    if(argc > 0) {
+        cache->nickmask = strdup(argv[0]);
+    } else
+        cache->nickmask = NULL;
+    get_userlist_if_invisible(chan, module_id, neonserv_cmd_voiceall_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_cmd_voiceall_userlist_lookup) {
+    struct neonserv_cmd_voiceall_cache *cache = data;
+    neonserv_cmd_voiceall_async1(cache->client, cache->textclient, cache->user, chan, cache->event, cache->nickmask);
+    if(cache->nickmask)
+        free(cache->nickmask);
+    free(cache);
+}
+
+static void neonserv_cmd_voiceall_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nickmask) {
+    int done_users = 0;
+    struct ChanUser *chanuser;
+    struct ModeBuffer *modeBuf;
+    modeBuf = initModeBuffer(client, chan);
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        if(nickmask && match(nickmask, chanuser->user->nick)) continue;
+        if(chanuser->flags & CHANUSERFLAG_VOICED) continue;
+        modeBufferVoice(modeBuf, chanuser->user->nick);
+        done_users++;
+    }
+    freeModeBuffer(modeBuf);
+    reply(textclient, user, "NS_VOICEALL_DONE", done_users, chan->name);
+    if(done_users)
+        logEvent(event);
+}
diff --git a/src/modules/NeonServ.mod/cmd_neonserv_wipeinfo.c b/src/modules/NeonServ.mod/cmd_neonserv_wipeinfo.c
new file mode 100644 (file)
index 0000000..bde1a24
--- /dev/null
@@ -0,0 +1,113 @@
+/* cmd_neonserv_wipeinfo.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonserv.h"
+
+/*
+* argv[0] - nick / *auth
+*/
+static USERAUTH_CALLBACK(neonserv_cmd_wipeinfo_nick_lookup);
+static void neonserv_cmd_wipeinfo_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth);
+
+struct neonserv_cmd_wipeinfo_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+};
+
+CMD_BIND(neonserv_cmd_wipeinfo) {
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        neonserv_cmd_wipeinfo_async1(client, textclient, user, chan, event, argv[0], argv[0]);
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            neonserv_cmd_wipeinfo_async1(client, textclient, user, chan, event, argv[0], cuser->auth);
+        } else {
+            struct neonserv_cmd_wipeinfo_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, neonserv_cmd_wipeinfo_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonserv_cmd_wipeinfo_nick_lookup) {
+    struct neonserv_cmd_wipeinfo_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        neonserv_cmd_wipeinfo_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, user->nick, user->auth);
+    free(cache->nick);
+    free(cache);
+}
+
+static void neonserv_cmd_wipeinfo_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *nick, char *auth) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int userid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        userid = atoi(row[0]);
+        //check if the user is already added
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_id` FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chan->channel_id, userid);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            if(atoi(row[0]) >= getChannelAccess(user, chan)) {
+                if(isGodMode(user)) {
+                    event->flags |= CMDFLAG_OPLOG;
+                } else {
+                    reply(textclient, user, "NS_USER_OUTRANKED", nick);
+                    return;
+                }
+            }
+            //delete
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_infoline` = '' WHERE `chanuser_id` = '%s'", row[1]);
+            reply(textclient, user, "NS_WIPEINFO_DONE", nick, chan->name);
+            logEvent(event);
+            return;
+        }
+    }
+    reply(textclient, user, "NS_NOT_ON_USERLIST", nick, chan->name);
+}
diff --git a/src/modules/NeonServ.mod/event_neonserv_ctcp.c b/src/modules/NeonServ.mod/event_neonserv_ctcp.c
new file mode 100644 (file)
index 0000000..200b80f
--- /dev/null
@@ -0,0 +1,112 @@
+/* event_neonserv_ctcp.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonserv_event_ctcp_cache {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char *command;
+    char *text;
+};
+
+static USERAUTH_CALLBACK(neonserv_event_ctcp_nick_lookup);
+static void neonserv_event_ctcp_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *command, char *text);
+
+static void neonserv_event_chanctcp(struct UserNode *user, struct ChanNode *chan, char *command, char *text) {
+    if(!stricmp(command, "ACTION")) return; //always allow CTCP ACTION (/me)
+    struct ClientSocket *client = getBotForChannel(chan);
+    if(!client) return; //we can't "see" this event
+    if(isNetworkService(user)) return; 
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        struct neonserv_event_ctcp_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->user = user;
+        cache->chan = chan;
+        cache->command = strdup(command);
+        cache->text = (text ? strdup(text) : NULL);
+        get_userauth(user, module_id, neonserv_event_ctcp_nick_lookup, cache);
+    } else
+        neonserv_event_ctcp_async1(client, user, chan, command, text);
+}
+
+static USERAUTH_CALLBACK(neonserv_event_ctcp_nick_lookup) {
+    struct neonserv_event_ctcp_cache *cache = data;
+    if(user) {
+        neonserv_event_ctcp_async1(cache->client, cache->user, cache->chan, cache->command, cache->text);
+    }
+    free(cache->command);
+    if(cache->text)
+        free(cache->text);
+    free(cache);
+}
+
+static void neonserv_event_ctcp_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *command, char *text) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaultrow = NULL, chanuser;
+    int uaccess = 0;
+    printf_mysql_query("SELECT `channel_ctcp`, `channel_ctcpreaction` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(!row[0] || !row[1]) {
+        printf_mysql_query("SELECT `channel_ctcp`, `channel_ctcpreaction` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaultrow = mysql_fetch_row(res);
+    }
+    int ctcpaccess = atoi((row[0] ? row[0] : defaultrow[0]));
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        chanuser = mysql_fetch_row(res);
+        if(chanuser)
+            uaccess = ((atoi(chanuser[1]) & DB_CHANUSER_SUSPENDED) ? 0 : atoi(chanuser[0]));
+    }
+    int duration = 0;
+    char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
+    char *banmask = NULL;
+    char *reason = "disallowed channel CTCP";
+    if(uaccess < ctcpaccess) {
+        switch(atoi((row[1] ? row[1] : defaultrow[1]))) {
+            case 2: //TIMEBAN: 3min
+                duration = 180;
+            case 3: //TIMEBAN: 1h
+                if(!duration)
+                    duration = 3600;
+                banmask = generate_banmask(user, banmaskBuf);
+                printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chan->channel_id, escape_string(banmask), (unsigned long) (time(0) + duration), 0, escape_string(reason));
+                int banid = (int) mysql_insert_id(get_mysql_conn());
+                char nameBuf[MAXLEN];
+                char banidBuf[20];
+                sprintf(nameBuf, "ban_%d", banid);
+                sprintf(banidBuf, "%d", banid);
+                timeq_add_name(nameBuf, duration, module_id, channel_ban_timeout, strdup(banidBuf));
+            case 1: //KICKBAN
+                if(!banmask)
+                    banmask = generate_banmask(user, banmaskBuf);
+                putsock(client, "MODE %s +b %s", chan->name, banmask);
+            case 0: //KICK
+                putsock(client, "KICK %s %s :%s", chan->name, user->nick, reason);
+                break;
+        }
+    }
+}
+
diff --git a/src/modules/NeonServ.mod/event_neonserv_invite.c b/src/modules/NeonServ.mod/event_neonserv_invite.c
new file mode 100644 (file)
index 0000000..b777ebc
--- /dev/null
@@ -0,0 +1,47 @@
+/* event_neonserv_invite.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static void neonserv_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(client, user, "MODCMD_CHAN_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(chan && isUserOnChan(bot->user, chan)) {
+            reply(client, user, "NS_INVITE_ON_CHAN", bot->user->nick, chan->name);
+        } else
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
diff --git a/src/modules/NeonServ.mod/event_neonserv_join.c b/src/modules/NeonServ.mod/event_neonserv_join.c
new file mode 100644 (file)
index 0000000..5f77f42
--- /dev/null
@@ -0,0 +1,251 @@
+/* event_neonserv_join.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonserv_event_join_cache {
+    struct ClientSocket *client;
+    struct ChanUser *chanuser;
+};
+
+static USERAUTH_CALLBACK(neonserv_event_join_nick_lookup);
+static void neonserv_event_join_async1(struct ClientSocket *client, struct ChanUser *chanuser);
+static TIMEQ_CALLBACK(neonserv_event_join_dynlimit);
+
+static void neonserv_event_join(struct ChanUser *chanuser) {
+    struct UserNode *user = chanuser->user;
+    struct ClientSocket *client = getBotForChannel(chanuser->chan);
+    if(!client) return; //we can't "see" this event
+    if(chanuser->user == client->user) {
+        requestOp(client->user, chanuser->chan);
+        module_neonbackup_recover_chan(chanuser->chan);
+        return;
+    }
+    if(chanuser->user->flags & USERFLAG_ISBOT) return;
+    loadChannelSettings(chanuser->chan);
+    if(!(chanuser->chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    char *ban;
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    sprintf(usermask, "%s!%s@%s", user->nick, user->ident, user->host);
+    if((ban = getBanAffectingMask(chanuser->chan, usermask)) != NULL && !(user->flags & (USERFLAG_ISBOT | USERFLAG_ISIRCOP))) {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        printf_mysql_query("SELECT `ban_reason`, `user_user` FROM `bans` LEFT JOIN `users` ON `ban_owner` = `user_id` WHERE `ban_channel` = '%d' AND `ban_mask` = '%s'", chanuser->chan->channel_id, escape_string(ban));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            putsock(client, "MODE %s +b %s", chanuser->chan->name, ban);
+            putsock(client, "KICK %s %s :(%s) %s", chanuser->chan->name, chanuser->user->nick, (row[1] ? row[1] : client->user->nick), row[0]);
+            return;
+        }
+    }
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        struct neonserv_event_join_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->chanuser = chanuser;
+        get_userauth(user, module_id, neonserv_event_join_nick_lookup, cache);
+    } else
+        neonserv_event_join_async1(client, chanuser);
+}
+
+static USERAUTH_CALLBACK(neonserv_event_join_nick_lookup) {
+    struct neonserv_event_join_cache *cache = data;
+    if(user)
+        neonserv_event_join_async1(cache->client, cache->chanuser);
+    free(cache);
+}
+
+static void neonserv_event_join_async1(struct ClientSocket *client, struct ChanUser *chanuser) {
+    struct ClientSocket *textclient = ((client->flags & SOCKET_FLAG_PREFERRED) ? client : get_prefered_bot(client->botid));
+    struct ChanNode *chan = chanuser->chan;
+    struct UserNode *user = chanuser->user;
+    struct ModeBuffer *modeBuf;
+    int with_halfops = get_int_field("General.have_halfop");
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, chanuserrow, defaultrow = NULL;
+    printf_mysql_query("SELECT `channel_maxusers`, `channel_greeting`, `channel_usergreeting`, `channel_getop`, `channel_getvoice`, `channel_userinfo`, `channel_dynlimit`, `channel_gethalfop` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(!row[3] || !row[4] || !row[5] || (!row[7] && with_halfops)) {
+        printf_mysql_query("SELECT `channel_getop`, `channel_getvoice`, `channel_userinfo`, `channel_gethalfop` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaultrow = mysql_fetch_row(res);
+    }
+    if(chan->usercount > atoi(row[0])) {
+        //update maxusers
+        printf_mysql_query("UPDATE `channels` SET `channel_maxusers` = '%d' WHERE `channel_id` = '%d'", chan->usercount, chan->channel_id);
+    }
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `chanuser_infoline`, `chanuser_seen`, `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        chanuserrow = mysql_fetch_row(res);
+    } else
+        chanuserrow = NULL;
+    if(!chanuserrow)
+        printf_mysql_query("UPDATE `channels` SET `channel_lastvisit` = UNIX_TIMESTAMP() WHERE `channel_id` = '%d'", chan->channel_id);
+    int userflags = (chanuserrow ? atoi(chanuserrow[1]) : 0);
+    int uaccess = ((chanuserrow && !(userflags & DB_CHANUSER_SUSPENDED)) ? atoi(chanuserrow[0]) : 0);
+    //GREETING
+    char greeting[MAXLEN];
+    int greetingPos = 0;
+    char *a, *b = (chanuserrow && *row[2] ? row[2] : row[1]);
+    do {
+        if(!b) break;
+        a = strstr(b, "$");
+        if(a) *a = '\0';
+        greetingPos += sprintf(greeting + greetingPos, "%s", b);
+        if(!a) break;
+        switch(a[1]) {
+            case '\0':
+                a = NULL;
+                break;
+            case 'A':
+                greetingPos += sprintf(greeting + greetingPos, "%d", uaccess);
+                break;
+            case 'B':
+                greetingPos += sprintf(greeting + greetingPos, "%s", client->user->nick);
+                break;
+            case 'N':
+                greetingPos += sprintf(greeting + greetingPos, "%s", user->nick);
+                break;
+            case 'H':
+                greetingPos += sprintf(greeting + greetingPos, "%s@%s", user->ident, user->host);
+                break;
+            case 'U':
+                greetingPos += sprintf(greeting + greetingPos, "%s", ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*"));
+                break;
+            default:
+                greeting[greetingPos++] = '$';
+                greeting[greetingPos++] = a[1];
+                break;
+        }
+        if(a)
+            b = a+2;
+    } while(a);
+    if(greetingPos && *row[2])
+        reply(textclient, user, "[%s] %s", chan->name, greeting);
+    //USER RIGHTS
+    if(!(userflags & DB_CHANUSER_NOAUTOOP)) {
+        int getop = atoi((row[3] ? row[3] : defaultrow[0]));
+        int gethalfop = (with_halfops ? atoi((row[7] ? row[7] : defaultrow[3])) : 0);
+        int getvoice = atoi((row[4] ? row[4] : defaultrow[1]));
+        modeBuf = initModeBuffer(client, chan);
+        if(uaccess >= getop && uaccess != 0) { //we disallow auto op for all users
+            modeBufferOp(modeBuf, user->nick);
+        } else if(with_halfops && uaccess >= gethalfop) {
+            modeBufferHalfop(modeBuf, user->nick);
+        } else if(uaccess >= getvoice) {
+            modeBufferVoice(modeBuf, user->nick);
+        }
+        freeModeBuffer(modeBuf);
+    }
+    //INFOLINE
+    int userinfoaccess = atoi((row[5] ? row[5] : defaultrow[2]));
+    if(chanuserrow && strcmp(chanuserrow[2], "") && uaccess > userinfoaccess) {
+        if(!strcmp(chanuserrow[3], "0") || time(0) - atol(chanuserrow[3]) >= 30) {
+            putsock(client, "PRIVMSG %s :[%s] %s", chan->name, user->nick, chanuserrow[2]);
+        }
+    }
+    //SEEN
+    if(chanuserrow) {
+        printf_mysql_query("UPDATE `chanusers` SET `chanuser_seen` = UNIX_TIMESTAMP() WHERE `chanuser_id` = '%s'", chanuserrow[4]);
+    }
+    //DYNLIMIT
+    if(row[6] && strcmp(row[6], "0")) {
+        char nameBuf[CHANNELLEN + 10];
+        sprintf(nameBuf, "dynlimit_%s", chan->name);
+        if(!timeq_name_exists(nameBuf)) {
+            //neonserv_event_join_dynlimit
+            timeq_add_name(nameBuf, 30, module_id, neonserv_event_join_dynlimit, strdup(chan->name));
+        }
+    }
+    //AUTOINVITE
+    if((user->flags & USERFLAG_ISAUTHED) && (!chanuserrow || !strcmp(chanuserrow[3], "0") || time(0) - atol(chanuserrow[3]) >= 30)) {
+        //check if it's the first channel, the user is seen by the bot (IMPORTANT: ignore other bot's channel!)
+        char first_chan = 1;
+        char bot_in_chan;
+        struct ChanUser *cchanuser, *bchanuser;
+        struct ClientSocket *bot;
+        for(cchanuser = getUserChannels(user, NULL); cchanuser; cchanuser = getUserChannels(user, cchanuser)) {
+            if(chanuser == cchanuser) continue; //ignore this one ;)
+            //check if this bot is in the specific channel
+            bot_in_chan = 0;
+            for(bchanuser = getChannelUsers(cchanuser->chan, NULL); bchanuser; bchanuser = getChannelUsers(cchanuser->chan, bchanuser)) {
+                if(bchanuser->user->flags & USERFLAG_ISBOT) {
+                    //get the botid
+                    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                        if(bot->user == bchanuser->user) {
+                            if(bot->botid == client->botid)
+                                bot_in_chan = 1;
+                            break;
+                        }
+                    }
+                    if(bot_in_chan) break;
+                }
+            }
+            if(!bot_in_chan) continue;
+            first_chan = 0;
+            break;
+        }
+        if(first_chan) {
+            //autoinvite :)
+            defaultrow = NULL;
+            printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags`, `channel_name`, `channel_getinvite` FROM `chanusers` LEFT JOIN `channels` ON `chanuser_cid` = `channel_id` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `user_user` = '%s' AND `chanuser_flags` >= '%d'", escape_string(user->auth), DB_CHANUSER_AUTOINVITE);
+            res = mysql_use();
+            int getinvite;
+            while((chanuserrow = mysql_fetch_row(res)) != NULL) {
+                userflags = atoi(chanuserrow[1]);
+                if(!(userflags & DB_CHANUSER_AUTOINVITE)) continue;
+                if(!(chan = getChanByName(chanuserrow[2]))) continue; //no bot is in the channel
+                if((bchanuser = getChanUser(client->user, chan)) && (bchanuser->flags & CHANUSERFLAG_OPPED))
+                    bot = client;
+                else {
+                    bot = getBotForChannel(chan);
+                }
+                if(chanuserrow[3] == NULL && defaultrow == NULL) {
+                    printf_mysql_query("SELECT `channel_getinvite` FROM `channels` WHERE `channel_name` = 'defaults'");
+                    res2 = mysql_use();
+                    defaultrow = mysql_fetch_row(res2);
+                }
+                getinvite = atoi((chanuserrow[3] ? chanuserrow[3] : defaultrow[0]));
+                if(atoi(chanuserrow[0]) >= getinvite) {
+                    putsock(bot, "INVITE %s %s", user->nick, chan->name);
+                }
+            }
+        }
+    }
+}
+
+static TIMEQ_CALLBACK(neonserv_event_join_dynlimit) {
+    char *chanName = data;
+    struct ChanNode *chan = getChanByName(chanName);
+    free(chanName);
+    if(!chan) return;
+    struct ClientSocket *client = getBotForChannel(chan);
+    if(!client) return;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `channel_dynlimit` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(row[0] && strcmp(row[0], "0")) {
+        putsock(client, "MODE %s +l %d", chan->name, chan->usercount + atoi(row[0]));
+    }
+}
diff --git a/src/modules/NeonServ.mod/event_neonserv_kick.c b/src/modules/NeonServ.mod/event_neonserv_kick.c
new file mode 100644 (file)
index 0000000..2daf63a
--- /dev/null
@@ -0,0 +1,99 @@
+/* event_neonserv_kick.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonserv_event_kick_cache {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct UserNode *target;
+    struct ChanNode *chan;
+    int userauth_pending;
+};
+
+static USERAUTH_CALLBACK(neonserv_event_kick_nick_lookup);
+static void neonserv_event_kick_async1(struct neonserv_event_kick_cache *cache);
+static void neonserv_event_kick_async2(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, struct ChanNode *chan);
+
+static void neonserv_event_kick(struct UserNode *user, struct ChanUser *target, char *reason) {
+    struct ChanNode *chan = target->chan;
+    struct ClientSocket *client;
+    if(isBot(target->user)) {
+        client = getChannelBot(chan, 0);
+        struct ClientSocket *bot = client;
+        for(client = getBots(SOCKET_FLAG_READY, NULL); client; client = getBots(SOCKET_FLAG_READY, client)) {
+            if(client->user == target->user) {
+                break;
+            }
+        }
+        if(!client) return;
+        if(bot && bot != client && (isModeSet(chan->modes, 'i') || isModeSet(chan->modes, 'a') || isModeSet(chan->modes, 'l'))) {
+            struct ChanUser *chanuser = getChanUser(bot->user, chan);
+            if(chanuser && chanuser->flags & CHANUSERFLAG_OPPED)
+                putsock(bot, "INVITE %s %s", target->user->nick, chan->name);
+        }
+        char *key = "";
+        if(isModeSet(chan->modes, 'k')) {
+            key = getModeValue(chan->modes, 'k');
+        }
+        putsock(client, "JOIN %s %s", chan->name, key);
+        return;
+    }
+    client = getBotForChannel(chan);
+    if(!client) return; //we can't "see" this event
+    if(isNetworkService(user)) return; 
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    struct neonserv_event_kick_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->user = user;
+    cache->target = target->user;
+    cache->chan = target->chan;
+    cache->userauth_pending = 0;
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        get_userauth(user, module_id, neonserv_event_kick_nick_lookup, cache);
+        cache->userauth_pending++;
+    }
+    if(!(target->user->flags & USERFLAG_ISAUTHED)) {
+        get_userauth(target->user, module_id, neonserv_event_kick_nick_lookup, cache);
+        cache->userauth_pending++;
+    }
+    neonserv_event_kick_async1(cache);
+}
+
+static USERAUTH_CALLBACK(neonserv_event_kick_nick_lookup) {
+    struct neonserv_event_kick_cache *cache = data;
+    cache->userauth_pending--;
+    neonserv_event_kick_async1(cache);
+}
+
+static void neonserv_event_kick_async1(struct neonserv_event_kick_cache *cache) {
+    if(cache->userauth_pending == 0) {
+        neonserv_event_kick_async2(cache->client, cache->user, cache->target, cache->chan);
+        free(cache);
+    }
+}
+
+static void neonserv_event_kick_async2(struct ClientSocket *client, struct UserNode *user, struct UserNode *target, struct ChanNode *chan) {
+    if(isUserProtected(chan, target, user)) {
+        char buf[MAXLEN];
+        putsock(client, "KICK %s %s :%s", chan->name, user->nick, build_language_string(user, buf, "NS_USER_PROTECTED", target->nick));
+        putsock(client, "INVITE %s %s", target->nick, chan->name);
+    }
+}
diff --git a/src/modules/NeonServ.mod/event_neonserv_mode.c b/src/modules/NeonServ.mod/event_neonserv_mode.c
new file mode 100644 (file)
index 0000000..b6d0652
--- /dev/null
@@ -0,0 +1,355 @@
+/* event_neonserv_mode.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static USERLIST_CALLBACK(neonserv_event_mode_userlist_lookup);
+static void neonserv_event_mode_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *modes, char **argv, int argc);
+static int neonserv_cmd_mode_botwar_detect(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, int *botwar_detect_executed);
+
+struct neonserv_event_mode_cache {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    char *modes;
+    char **argv;
+    int argc;
+};
+
+static void neonserv_event_mode(struct UserNode *user, struct ChanNode *chan, char *modes, char **argv, int argc) {
+    struct ClientSocket *client = getBotForChannel(chan);
+    if(!client) return; //we can't "see" this event
+    if(isNetworkService(user)) return;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    struct neonserv_event_mode_cache *cache = malloc(sizeof(*cache));
+    char **temp_argv = NULL;
+    if(argc) 
+        temp_argv = malloc(argc*sizeof(*temp_argv));
+    if (!cache || (argc && !temp_argv)) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    if(argc) {
+        int i;
+        for(i = 0; i < argc; i++) {
+            temp_argv[i] = strdup(argv[i]);
+        }
+    }
+    cache->client = client;
+    cache->user = user;
+    cache->modes = strdup(modes);
+    cache->argv = temp_argv;
+    cache->argc = argc;
+    get_userlist_with_invisible(chan, module_id, neonserv_event_mode_userlist_lookup, cache);
+}
+
+static USERLIST_CALLBACK(neonserv_event_mode_userlist_lookup) {
+    struct neonserv_event_mode_cache *cache = data;
+    neonserv_event_mode_async1(cache->client, cache->user, chan, cache->modes, cache->argv, cache->argc);
+    if(cache->argc) {
+        int i;
+        for(i = 0; i < cache->argc; i++) {
+            free(cache->argv[i]);
+        }
+        free(cache->argv);
+    }
+    free(cache->modes);
+    free(cache);
+}
+
+static void neonserv_event_mode_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *modes, char **argv, int argc) {
+    struct ClientSocket *textclient = ((client->flags & SOCKET_FLAG_PREFERRED) ? client : get_prefered_bot(client->botid));
+    int botwar_detect_executed = 0;
+    MYSQL_ROW row, defaults = NULL;
+    int i, arg, add = 1, skip = 0;
+    unsigned int modetype;
+    int db_canop, db_canhalfop, db_canvoice, db_canban, db_enfmodes, db_getop, db_gethalfop, db_getvoice;
+    struct ModeNode *modelock = createModeNode(NULL);
+    struct ModeBuffer *modeBuf;
+    struct UserNode *cuser;
+    struct ChanUser *chanuser;
+    int with_halfops = get_int_field("General.have_halfop");
+    modeBuf = initModeBuffer(client, chan);
+    printf_mysql_query("SELECT `channel_canop`, `channel_canvoice`, `channel_canban`, `channel_enfmodes`, `channel_modes`, `channel_getop`, `channel_getvoice`, `channel_gethalfop`, `channel_canhalfop` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    row = mysql_fetch_row(mysql_use());
+    if(row[0] == NULL || row[1] == NULL || row[2] == NULL || row[3] == NULL || row[4] == NULL || row[5] == NULL || row[6] == NULL || (row[7] == NULL && with_halfops) || (row[8] == NULL && with_halfops)) {
+        printf_mysql_query("SELECT `channel_canop`, `channel_canvoice`, `channel_canban`, `channel_enfmodes`, `channel_modes`, `channel_getop`, `channel_getvoice`, `channel_gethalfop`, `channel_canhalfop` FROM `channels` WHERE `channel_name` = 'defaults'");
+        defaults = mysql_fetch_row(mysql_use());
+    }
+    db_canop = atoi((row[0] ? row[0] : defaults[0]));
+    db_canvoice = atoi((row[1] ? row[1] : defaults[1]));
+    db_canban = atoi((row[2] ? row[2] : defaults[2]));
+    db_enfmodes = atoi((row[3] ? row[3] : defaults[3]));
+    db_getop = atoi((row[5] ? row[5] : defaults[5]));
+    db_getvoice = atoi((row[6] ? row[6] : defaults[6]));
+    db_gethalfop = (with_halfops ? atoi((row[7] ? row[7] : defaults[7])) : 0);
+    db_canhalfop = (with_halfops ? atoi((row[8] ? row[8] : defaults[8])) : 0);
+    if(row[4])
+        parseModeString(modelock, row[4]);
+    else if(defaults[4])
+        parseModeString(modelock, defaults[4]);
+    int uaccess = getChannelAccess(user, chan);
+    int caccess;
+    char *carg;
+    int sent_modes_locked = 0;
+    char deop_user = 0;
+    char tmp[MAXLEN];
+    arg = 0;
+    for(i = 0; i < strlen(modes); i++) {
+        switch(modes[i]) {
+            case '+':
+                add = 1;
+                break;
+            case '-':
+                add = 0;
+                break;
+            case 'o':
+            case 'h':
+            case 'v':
+                if(arg == argc) {
+                    break;
+                }
+                carg = argv[arg++];
+                cuser = searchUserByNick(carg);
+                if(!cuser) {
+                    break; //internal Bot error - this should never happen
+                }
+                caccess = getChannelAccess(cuser, chan);
+                if(modes[i] == 'o' && !add && cuser == client->user) {
+                    //someone deopped the bot???
+                    if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                                               requestOp(client->user, chan);
+                } else if(modes[i] == 'o' && add && isBot(cuser)) {
+                    //someone opped a bot
+                    if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                                               module_neonbackup_recover_chan(chan);
+                }
+                if((modes[i] == 'o' || (modes[i] == 'h' && !with_halfops)) && !(add && isBot(cuser))) {
+                    if(uaccess < db_canop) {
+                        reply(textclient, user, "NS_MODE_ENFOPS", chan->name);
+                        db_canop = -1;
+                        if(uaccess < db_getop)
+                            deop_user = 1;
+                    }
+                    if(db_canop == -1 && caccess < db_getop) {
+                        if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                            modeBufferSet(modeBuf, !add, modes[i], carg);
+                        break;
+                    }
+                } else if(modes[i] == 'h' && with_halfops && !(add && isBot(cuser))) {
+                    if(uaccess < db_canhalfop) {
+                        reply(textclient, user, "NS_MODE_ENFOPS", chan->name);
+                        db_canhalfop = -1;
+                        if(uaccess < db_gethalfop)
+                            deop_user = 1;
+                    }
+                    if(db_canhalfop == -1 && caccess < db_gethalfop) {
+                        if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                            modeBufferSet(modeBuf, !add, modes[i], carg);
+                        break;
+                    }
+                } else if(modes[i] == 'v' && !(add && isBot(cuser))) {
+                    if(uaccess < db_canvoice) {
+                        reply(textclient, user, "NS_MODE_ENFVOICE", chan->name);
+                        db_canvoice = -1;
+                        if(uaccess < db_getop)
+                            deop_user = 1;
+                    }
+                    if(db_canvoice == -1 && caccess < db_getvoice) {
+                        if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                            modeBufferSet(modeBuf, !add, modes[i], carg);
+                        break;
+                    }
+                }
+                if(!add) {
+                    //check protection
+                    if(isBot(cuser)) {
+                        //don't send this - just try to reop
+                        //reply(textclient, user, "NS_SERVICE_IMMUNE", cuser->nick);
+                        if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                            modeBufferSet(modeBuf, !add, modes[i], carg);
+                        break;
+                    }
+                    if(isUserProtected(chan, cuser, user)) {
+                        reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                        if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                            modeBufferSet(modeBuf, !add, modes[i], carg);
+                        if(uaccess < db_getop)
+                            deop_user = 1;
+                        break;
+                    }
+                }
+                break;
+            case 'b':
+                if(arg == argc) {
+                    break;
+                }
+                carg = argv[arg++];
+                if(uaccess < db_canban) {
+                    reply(textclient, user, "NS_MODE_CANBAN", chan->name);
+                    db_canban = -1;
+                }
+                char hostmask_buffer[NICKLEN+USERLEN+HOSTLEN+3];
+                char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+                int match_count = 0;
+                if(db_canban == -1) {
+                    if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed)) {
+                        if(!add) {
+                            //check if a user just removed a bot ban
+                            for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                                cuser = chanuser->user;
+                                sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+                                if(!match(carg, usermask) && isBot(cuser)) {
+                                    skip = 1;
+                                    break;
+                                }
+                            }
+                            if(skip)
+                                break;
+                        }
+                        modeBufferSet(modeBuf, !add, modes[i], carg);
+                    }
+                    if(uaccess < db_getop)
+                        deop_user = 1;
+                    break;
+                }
+                carg = make_banmask(carg, hostmask_buffer);
+                if(add) {
+                    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+                        cuser = chanuser->user;
+                        sprintf(usermask, "%s!%s@%s", cuser->nick, cuser->ident, cuser->host);
+                        if(!match(carg, usermask)) {
+                            if(isUserProtected(chan, cuser, user)) {
+                                reply(textclient, user, "NS_USER_PROTECTED", cuser->nick);
+                                skip = 1;
+                                break;
+                            }
+                            match_count++;
+                        }
+                    }
+                    if(match_count > 4 && (match_count * 3) > chan->usercount) {
+                        reply(textclient, user, "NS_LAME_MASK_WARNING", carg, match_count);
+                    }
+                }
+                if(skip) {
+                    if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                        modeBufferSet(modeBuf, !add, modes[i], carg);
+                    if(uaccess < db_getop)
+                        deop_user = 1;
+                }
+                break;
+            default:
+                modetype = getModeType(modelock, modes[i]);
+                if(modetype == 0) {
+                    break; //unknown mode
+                }
+                
+                if(add && (modetype & CHANNEL_MODE_TYPE) != CHANNEL_MODE_TYPE_D) {
+                    if(arg == argc) {
+                        break;
+                    }
+                    carg = argv[arg++];
+                    if((modetype & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING && isModeSet(modelock, modes[i])) {
+                        char *modelock_val = getModeValue(modelock, modes[i]);
+                        if(stricmp(carg, modelock_val) && uaccess < db_enfmodes && !isGodMode(user)) {
+                            if(!sent_modes_locked) {
+                                getFullModeString(modelock, tmp);
+                                reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                                sent_modes_locked = 1;
+                                if(uaccess < db_getop)
+                                    deop_user = 1;
+                            }
+                            if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                                modeBufferSet(modeBuf, add, modes[i], modelock_val);
+                            break;
+                        }
+                    }
+                    if((modetype & CHANNEL_MODE_VALUE) == CHANNEL_MODE_VALUE_STRING && isModeSet(modelock, modes[i])) {
+                        int *modelock_val = getModeValue(modelock, modes[i]);
+                        if(atoi(carg) != *modelock_val && uaccess < db_enfmodes && !isGodMode(user)) {
+                            if(!sent_modes_locked) {
+                                getFullModeString(modelock, tmp);
+                                reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                                sent_modes_locked = 1;
+                                if(uaccess < db_getop)
+                                    deop_user = 1;
+                            }
+                            sprintf(tmp, "%d", *modelock_val);
+                            if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                                modeBufferSet(modeBuf, add, modes[i], tmp);
+                            break;
+                        }
+                    }
+                } else if(!add && (modetype & CHANNEL_MODE_TYPE) == CHANNEL_MODE_TYPE_B) {
+                    if(arg == argc) {
+                        break;
+                    }
+                    carg = argv[arg++];
+                } else
+                    carg = NULL;
+                if(isModeAffected(modelock, modes[i]) && add == !isModeSet(modelock, modes[i]) && uaccess < db_enfmodes && !isGodMode(user)) {
+                    if(!sent_modes_locked) {
+                        getFullModeString(modelock, tmp);
+                        reply(textclient, user, "NS_MODE_LOCKED", tmp, chan->name);
+                        sent_modes_locked = 1;
+                        if(uaccess < db_getop)
+                            deop_user = 1;
+                    }
+                    if(!neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed))
+                        modeBufferSet(modeBuf, !add, modes[i], carg);
+                    break;
+                }
+                break;
+        }
+    }
+    if(deop_user && !neonserv_cmd_mode_botwar_detect(client, user, chan, &botwar_detect_executed)) {
+        modeBufferDeop(modeBuf, user->nick);
+    }
+    freeModeBuffer(modeBuf);
+    freeModeNode(modelock);
+}
+
+static int neonserv_cmd_mode_botwar_detect(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, int *botwar_detect_executed) {
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!chanuser) return 0; //flying super cow?
+    if(time(0) - chanuser->changeTime > BOTWAR_DETECTION_TIME) {
+        chanuser->changeTime = time(0);
+        chanuser->chageEvents = 1;
+    } else {
+        if(!*botwar_detect_executed)
+            chanuser->chageEvents++;
+        *botwar_detect_executed = 1;
+        if(chanuser->chageEvents >= BOTWAR_DETECTION_EVENTS || chanuser->chageEvents < 0) {
+            //BOTWAR!
+            chanuser->changeTime = time(0);
+            if(chanuser->chageEvents > 0) {
+                char *alertchan = get_string_field("General.alertchan");
+                putsock(client, "NOTICE @%s :%s %s", chan->name, get_language_string(user, "NS_BOTWAR_DETECTED"), (alertchan ? get_language_string(user, "NS_BOTWAR_REPORTED") : ""));
+                if(alertchan) {
+                    struct ChanNode *alertchan_chan = getChanByName(alertchan);
+                    struct ClientSocket *alertclient;
+                    if(alertchan_chan && (alertclient = getBotForChannel(chan)) != NULL) {
+                        char msgBuf[MAXLEN];
+                        putsock(alertclient, "PRIVMSG %s :%s", alertchan_chan->name, build_language_string(NULL, msgBuf, "NS_BOTWAR_ALERT", chan->name, user->nick));
+                    }
+                }
+            }
+            chanuser->chageEvents = -2;
+            return 1;
+        }
+    }
+    return 0;
+}
+
diff --git a/src/modules/NeonServ.mod/event_neonserv_notice.c b/src/modules/NeonServ.mod/event_neonserv_notice.c
new file mode 100644 (file)
index 0000000..c15089a
--- /dev/null
@@ -0,0 +1,107 @@
+/* event_neonserv_notice.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonserv_event_notice_cache {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char *message;
+};
+
+static USERAUTH_CALLBACK(neonserv_event_notice_nick_lookup);
+static void neonserv_event_notice_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message);
+
+static void neonserv_event_channotice(struct UserNode *user, struct ChanNode *chan, char *message) {
+    struct ClientSocket *client = getBotForChannel(chan);
+    if(!client) return; //we can't "see" this event
+    if(isNetworkService(user)) return; 
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        struct neonserv_event_notice_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->user = user;
+        cache->chan = chan;
+        cache->message = strdup(message);
+        get_userauth(user, module_id, neonserv_event_notice_nick_lookup, cache);
+    } else
+        neonserv_event_notice_async1(client, user, chan, message);
+}
+
+static USERAUTH_CALLBACK(neonserv_event_notice_nick_lookup) {
+    struct neonserv_event_notice_cache *cache = data;
+    if(user) {
+        neonserv_event_notice_async1(cache->client, cache->user, cache->chan, cache->message);
+    }
+    free(cache->message);
+    free(cache);
+}
+
+static void neonserv_event_notice_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaultrow = NULL, chanuser;
+    int uaccess = 0;
+    printf_mysql_query("SELECT `channel_ctcp`, `channel_ctcpreaction` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(!row[0] || !row[1]) {
+        printf_mysql_query("SELECT `channel_ctcp`, `channel_ctcpreaction` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaultrow = mysql_fetch_row(res);
+    }
+    int noticeaccess = atoi((row[0] ? row[0] : defaultrow[0]));
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        chanuser = mysql_fetch_row(res);
+        if(chanuser)
+            uaccess = ((atoi(chanuser[1]) & DB_CHANUSER_SUSPENDED) ? 0 : atoi(chanuser[0]));
+    }
+    int duration = 0;
+    char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
+    char *banmask = NULL;
+    char *reason = "disallowed channel NOTICE";
+    if(uaccess < noticeaccess) {
+        switch(atoi((row[1] ? row[1] : defaultrow[1]))) {
+            case 2: //TIMEBAN: 3min
+                duration = 180;
+            case 3: //TIMEBAN: 1h
+                if(!duration)
+                    duration = 3600;
+                banmask = generate_banmask(user, banmaskBuf);
+                printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chan->channel_id, escape_string(banmask), (unsigned long) (time(0) + duration), 0, escape_string(reason));
+                int banid = (int) mysql_insert_id(get_mysql_conn());
+                char nameBuf[MAXLEN];
+                char banidBuf[20];
+                sprintf(nameBuf, "ban_%d", banid);
+                sprintf(banidBuf, "%d", banid);
+                timeq_add_name(nameBuf, duration, module_id, channel_ban_timeout, strdup(banidBuf));
+            case 1: //KICKBAN
+                if(!banmask)
+                    banmask = generate_banmask(user, banmaskBuf);
+                putsock(client, "MODE %s +b %s", chan->name, banmask);
+            case 0: //KICK
+                putsock(client, "KICK %s %s :%s", chan->name, user->nick, reason);
+                break;
+        }
+    }
+}
+
diff --git a/src/modules/NeonServ.mod/event_neonserv_part.c b/src/modules/NeonServ.mod/event_neonserv_part.c
new file mode 100644 (file)
index 0000000..70a6683
--- /dev/null
@@ -0,0 +1,31 @@
+/* event_neonserv_part.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static void neonserv_event_part(struct ChanUser *chanuser, int quit, char *reason) {
+    struct ChanNode *chan = chanuser->chan;
+    struct UserNode *user = chanuser->user;
+    MYSQL_RES *res;
+    MYSQL_ROW chanuserrow;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_id` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        if((chanuserrow = mysql_fetch_row(res)) != NULL)
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_seen` = UNIX_TIMESTAMP() WHERE `chanuser_id` = '%s'", chanuserrow[0]);
+    }
+}
diff --git a/src/modules/NeonServ.mod/event_neonserv_topic.c b/src/modules/NeonServ.mod/event_neonserv_topic.c
new file mode 100644 (file)
index 0000000..3958f83
--- /dev/null
@@ -0,0 +1,114 @@
+/* event_neonserv_topic.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+struct neonserv_event_topic_cache {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char *new_topic;
+};
+
+static USERAUTH_CALLBACK(neonserv_event_topic_nick_lookup);
+static void neonserv_event_topic_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, const char *new_topic);
+
+static void neonserv_event_topic(struct UserNode *user, struct ChanNode *chan, const char *new_topic) {
+    struct ClientSocket *client = getBotForChannel(chan);
+    if(!client) return; //we can't "see" this event
+    if(isNetworkService(user)) return; 
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) return;
+    if(!(user->flags & USERFLAG_ISAUTHED)) {
+        struct neonserv_event_topic_cache *cache = malloc(sizeof(*cache));
+        if (!cache) {
+            printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+            return;
+        }
+        cache->client = client;
+        cache->user = user;
+        cache->chan = chan;
+        cache->new_topic = strdup(new_topic);
+        get_userauth(user, module_id, neonserv_event_topic_nick_lookup, cache);
+    } else
+        neonserv_event_topic_async1(client, user, chan, new_topic);
+}
+
+static USERAUTH_CALLBACK(neonserv_event_topic_nick_lookup) {
+    struct neonserv_event_topic_cache *cache = data;
+    if(user) {
+        neonserv_event_topic_async1(cache->client, cache->user, cache->chan, cache->new_topic);
+    }
+    free(cache->new_topic);
+    free(cache);
+}
+
+static void neonserv_event_topic_async1(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, const char *new_topic) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaultrow = NULL, chanuserrow;
+    int uaccess = 0;
+    printf_mysql_query("SELECT `channel_changetopic`, `channel_topicsnarf` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) return;
+    if(!row[0] || !row[1]) {
+        printf_mysql_query("SELECT `channel_changetopic`, `channel_topicsnarf` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaultrow = mysql_fetch_row(res);
+    }
+    int changetopic = atoi((row[0] ? row[0] : defaultrow[0]));
+    int topicsnarf = atoi((row[1] ? row[1] : defaultrow[1]));
+    if((user->flags & USERFLAG_ISAUTHED)) {
+        printf_mysql_query("SELECT `chanuser_access`, `chanuser_flags` FROM `chanusers` LEFT JOIN `users` ON `chanuser_uid` = `user_id` WHERE `chanuser_cid` = '%d' AND `user_user` = '%s'", chan->channel_id, escape_string(user->auth));
+        res = mysql_use();
+        chanuserrow = mysql_fetch_row(res);
+        if(chanuserrow)
+            uaccess = ((atoi(chanuserrow[1]) & DB_CHANUSER_SUSPENDED) ? 0 : atoi(chanuserrow[0]));
+    }
+    if(uaccess < changetopic) {
+        //undo topic change
+        struct ClientSocket *textclient = ((client->flags & SOCKET_FLAG_PREFERRED) ? client : get_prefered_bot(client->botid));
+        struct ChanUser *chanuser = getChanUser(user, chan);
+        if(!chanuser) return; //flying super cow?
+        if(time(0) - chanuser->changeTime > BOTWAR_DETECTION_TIME) {
+            chanuser->changeTime = time(0);
+            chanuser->chageEvents = 1;
+        } else {
+            chanuser->chageEvents++;
+            if(chanuser->chageEvents >= BOTWAR_DETECTION_EVENTS || chanuser->chageEvents < 0) {
+                //BOTWAR!
+                chanuser->changeTime = time(0);
+                if(chanuser->chageEvents > 0) {
+                    char *alertchan = get_string_field("General.alertchan");
+                    putsock(client, "NOTICE @%s :%s %s", chan->name, get_language_string(user, "NS_BOTWAR_DETECTED"), (alertchan ? get_language_string(user, "NS_BOTWAR_REPORTED") : ""));
+                    if(alertchan) {
+                        struct ChanNode *alertchan_chan = getChanByName(alertchan);
+                        struct ClientSocket *alertclient;
+                        if(alertchan_chan && (alertclient = getBotForChannel(chan)) != NULL) {
+                            char msgBuf[MAXLEN];
+                            putsock(alertclient, "PRIVMSG %s :%s", alertchan_chan->name, build_language_string(NULL, msgBuf, "NS_BOTWAR_ALERT", chan->name, user->nick));
+                        }
+                    }
+                }
+                chanuser->chageEvents = -2;
+                return;
+            }
+        }
+        reply(textclient, user, "NS_TOPIC_ACCESS", chan->name);
+        putsock(client, "TOPIC %s :%s", chan->name, chan->topic);
+    } else if(uaccess >= topicsnarf) {
+        printf_mysql_query("UPDATE `channels` SET `channel_defaulttopic` = '%s' WHERE `channel_id` = '%d'", escape_string(new_topic), chan->channel_id);
+    }
+}
+
diff --git a/src/modules/NeonServ.mod/module.c b/src/modules/NeonServ.mod/module.c
new file mode 100644 (file)
index 0000000..f24d537
--- /dev/null
@@ -0,0 +1,34 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonServ.h"
+#include "cmd_neonserv.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonServ(type);
+}
+
+static void module_stop(int type) {
+    free_NeonServ(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/NeonSpam.mod/bot_NeonSpam.c b/src/modules/NeonSpam.mod/bot_NeonSpam.c
new file mode 100644 (file)
index 0000000..dd2e4d1
--- /dev/null
@@ -0,0 +1,421 @@
+/* bot_NeonSpam.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../botid.h"
+
+#include "bot_NeonSpam.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+#include "cmd_neonspam.h"
+
+#define BOTID NEONSPAM_BOTID
+#define BOTALIAS "NeonSpam"
+
+static const struct default_language_entry msgtab[] = {
+    {"SS_SET_PERCENT", "%u is an invalid percent value (valid: 0-100)"}, /* {ARGS: 120} */
+    {"SS_SET_SENSIBILITY", "%s is an invalid sensibility format. (valid: amount:time   e.g. 5:10)"}, /* {ARGS: "möp"} */
+    {"SS_SET_SENSIBILITY_AMOUNT", "%d is an invalid amount value. (valid: %d-%d)"}, /* {ARGS: 120, 1, 5} */
+    {"SS_SET_SENSIBILITY_TIME", "%d is an invalid time value. (valid: %d-%d)"}, /* {ARGS: 300, 15, 180} */
+    {"SS_SET_SPAMLIMIT", "%d is an invalid spamlimit value. (valid: %d-%d)"}, /* {ARGS: 120, 2, 8} */
+    {"SS_SET_OPTION_SpamReaction_0", "Kick"},
+    {"SS_SET_OPTION_SpamReaction_1", "KickBan"},
+    {"SS_SET_OPTION_SpamReaction_2", "Timed Ban"},
+    {"SS_SET_OPTION_FloodReaction_0", "Kick"},
+    {"SS_SET_OPTION_FloodReaction_1", "KickBan"},
+    {"SS_SET_OPTION_FloodReaction_2", "Timed Ban"},
+    {"SS_SET_OPTION_JoinReaction_0", "Kick"},
+    {"SS_SET_OPTION_JoinReaction_1", "KickBan"},
+    {"SS_SET_OPTION_JoinReaction_2", "Timed Ban"},
+    {"SS_SET_OPTION_CapsReaction_0", "Kick"},
+    {"SS_SET_OPTION_CapsReaction_1", "KickBan"},
+    {"SS_SET_OPTION_CapsReaction_2", "Timed Ban"},
+    {"SS_SET_OPTION_DigitReaction_0", "Kick"},
+    {"SS_SET_OPTION_DigitReaction_1", "KickBan"},
+    {"SS_SET_OPTION_DigitReaction_2", "Timed Ban"},
+    {"SS_SET_OPTION_BadwordReaction_0", "Kick"},
+    {"SS_SET_OPTION_BadwordReaction_1", "KickBan"},
+    {"SS_SET_OPTION_BadwordReaction_2", "Timed Ban"},
+    {"SS_BADWORD_ADDED", "Badword '%s' added. (ID: %d)"}, /* {ARGS: "NeonServ is bad", 1} */
+    {"SS_BADWORD_ID_UNKNOWN", "Badword ID %d was not found in %s."}, /* {ARGS: 1, "#channel"} */
+    {"SS_BADWORD_DELETED", "Badword '%s' deleted. (ID: %d)"}, /* {ARGS: "NeonServ is bad", 1} */
+    {"SS_BADWORDS_REACTION_0", "Kick"},
+    {"SS_BADWORDS_REACTION_1", "KickBan"},
+    {"SS_BADWORDS_REACTION_2", "Timed Ban"},
+    {"SS_BADWORDS_ID", "Id"},
+    {"SS_BADWORDS_PATTERN", "Pattern"},
+    {"SS_BADWORDS_SCAN", "Scan"},
+    {"SS_BADWORDS_REACTION", "Reaction"},
+    {NULL, NULL}
+};
+
+static unsigned int convertNeonSpamSettingsToFlags(char *str);
+static void createSpamNode(struct ChanUser *chanuser);
+static void freeJoinNode(struct NeonSpamJoinNode *joinnode);
+static struct NeonSpamJoinNode *getNeonSpamJoinNode(struct ChanUser *chanuser);
+
+#define SPAMSERV_CHECK_IGNORE 0
+#define SPAMSERV_CHECK_WARN   1
+#define SPAMSERV_CHECK_PUNISH 2
+#define SPAMSERV_CHECK_DEAD   3 /* scanner has already killed the user */
+
+#define SPAMSERV_MSG_SPAM       "Spamming"
+#define SPAMSERV_MSG_FLOOD      "Flooding the channel/network"
+#define SPAMSERV_MSG_ADV        "Advertising"
+#define SPAMSERV_MSG_JOINFLOOD  "Join flooding the channel"
+#define SPAMSERV_MSG_WARNING    "%s is against the channel rules"
+#define SPAMSERV_MSG_BOTNET     "BotNet detected."
+#define SPAMSERV_MSG_CAPS       "Using too many chars in UPPER CASE"
+#define SPAMSERV_MSG_DIGIT      "Using too many numeric chars"
+#define SPAMSERV_MSG_BADWORD    "Your message contained a forbidden word."
+
+//EVENTS
+#include "event_neonspam_join.c"
+#include "event_neonspam_chanmsg.c"
+
+static void neonspam_bot_ready(struct ClientSocket *client) {
+    if(client->botid != BOTID)
+        return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    
+    printf_mysql_query("SELECT `automodes`, `oper_user`, `oper_pass` FROM `bots` WHERE `id` = '%d'", client->clientid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(row[1] && row[2]) {
+            putsock(client, "OPER %s %s", row[1], row[2]);
+        }
+        putsock(client, "MODE %s +%s", client->user->nick, row[0]);
+    }
+    
+    printf_mysql_query("SELECT `channel_name`, `channel_key` FROM `bot_channels` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `botid` = '%d' AND `suspended` = '0'", client->clientid);
+    res = mysql_use();
+    
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        putsock(client, "JOIN %s %s", row[0], row[1]);
+    }
+}
+
+static void neonspam_trigger_callback(int clientid, struct ChanNode *chan, char *trigger) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    if(!(chan->flags & CHANFLAG_CHAN_REGISTERED)) {
+        strcpy(trigger, "~");
+        return;
+    }
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chan->channel_id, BOTID);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        strcpy(trigger, "~");
+        return;
+    }
+    if(row[0] && *row[0])
+        strcpy(trigger, row[0]);
+    else
+        strcpy(trigger, ((row[1] && *row[1]) ? row[1] : "~"));
+}
+
+static void start_bots(int type) {
+    struct ClientSocket *client;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row;
+    
+    if(type == MODSTATE_STARTSTOP) {
+        printf_mysql_query("SELECT `nick`, `ident`, `realname`, `server`, `port`, `pass`, `textbot`, `id`, `queue`, `ssl`, `bind`, `secret` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1'", BOTID);
+        res = mysql_use();
+        
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            client = create_socket(row[3], atoi(row[4]), row[10], row[5], row[0], row[1], row[2]);
+            client->flags |= (strcmp(row[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+            client->flags |= (strcmp(row[8], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+            client->flags |= (strcmp(row[9], "0") ? SOCKET_FLAG_SSL : 0);
+            client->flags |= (strcmp(row[11], "0") ? SOCKET_FLAG_SECRET_BOT : 0);
+            client->flags |= SOCKET_FLAG_REQUEST_INVITE | SOCKET_FLAG_REQUEST_OP;
+            client->botid = BOTID;
+            client->clientid = atoi(row[7]);
+            connect_socket(client);
+        }
+    }
+    
+    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access`, `flags` FROM `bot_binds` WHERE `botclass` = '%d'", BOTID);
+    res2 = mysql_use();
+    while ((row = mysql_fetch_row(res2)) != NULL) {
+        if(bind_cmd_to_command(BOTID, row[0], row[1])) {
+            if(row[2] && strcmp(row[2], "")) {
+                bind_set_parameters(BOTID, row[0], row[2]);
+            }
+            if(row[3]) {
+                bind_set_global_access(BOTID, row[0], atoi(row[3]));
+            }
+            if(row[4]) {
+                bind_set_channel_access(BOTID, row[0], row[4]);
+            }
+            if(strcmp(row[5], "0"))
+                bind_set_bind_flags(BOTID, row[0], atoi(row[5]));
+        }
+    }
+    bind_unbound_required_functions(BOTID);
+}
+
+char* convertNeonSpamSettingsToString(unsigned int flags, char *buffer) {
+    int pos = 0;
+    unsigned int i;
+    int j = 0;
+    char *chars = SPAMSETTINGS_CHARS;
+    for(i = 1; i <= SPAMSETTINGS_FLAGS; i = i << 1) {
+        if(flags & i)
+            buffer[pos++] = chars[j];
+        j++;
+    }
+    buffer[pos] = '\0';
+    return buffer;
+}
+
+static unsigned int convertNeonSpamSettingsToFlags(char *str) {
+    unsigned int i = 1, flags = 0;
+    int j = 0;
+    char *chars = SPAMSETTINGS_CHARS;
+    while(*str) {
+        for(; i <= SPAMSETTINGS_FLAGS; i = i << 1) {
+            if(*str == chars[j]) {
+                flags |= i;
+                j++;
+                i = i << 1;
+                break;
+            }
+            j++;
+        }
+        str++;
+    }
+    return flags;
+}
+
+int loadNeonSpamSettings(struct ChanNode *chan) {
+    if(chan->spam_settings) return 0;
+    struct NeonSpamSettings *settings = malloc(sizeof(*settings));
+    if(!settings) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return 0;
+    }
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults = NULL;
+    loadChannelSettings(chan);
+    printf_mysql_query("SELECT `channel_scanner`, `channel_spam_limit`, `channel_spam_except`, `channel_flood_limit`, `channel_flood_time`, `channel_flood_except`, `channel_join_limit`, `channel_join_time`, `channel_join_except`, `channel_caps_percent`, `channel_caps_except`, `channel_digit_percent`, `channel_digit_except`, `channel_badword_except` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res)))
+        return 0;
+    if(!row[0] || !row[1] || !row[2] || !row[3] || !row[4] || !row[5] || !row[6] || !row[7] || !row[8] || !row[9] || !row[10] || !row[11] || !row[12]) {
+        printf_mysql_query("SELECT `channel_scanner`, `channel_spam_limit`, `channel_spam_except`, `channel_flood_limit`, `channel_flood_time`, `channel_flood_except`, `channel_join_limit`, `channel_join_time`, `channel_join_except`, `channel_caps_percent`, `channel_caps_except`, `channel_digit_percent`, `channel_digit_except`, `channel_badword_except` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaults = mysql_fetch_row(res);
+    }
+    settings->flags = convertNeonSpamSettingsToFlags(row[0] ? row[0] : defaults[0]);
+    settings->spam_amount = atoi(row[1] ? row[1] : defaults[1]);
+    settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] = atoi(row[2] ? row[2] : defaults[2]);
+    settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX] = atoi(row[3] ? row[3] : defaults[3]);
+    settings->sensibility_time[SPAMSETTINGS_FLOODSENINDEX] = atoi(row[4] ? row[4] : defaults[4]);
+    settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] = atoi(row[5] ? row[5] : defaults[5]);
+    settings->sensibility_amount[SPAMSETTINGS_JOINSENINDEX] = atoi(row[6] ? row[6] : defaults[6]);
+    settings->sensibility_time[SPAMSETTINGS_JOINSENINDEX] = atoi(row[7] ? row[7] : defaults[7]);
+    settings->exceptlevel[SPAMSETTINGS_JOINEXCINDEX] = atoi(row[8] ? row[8] : defaults[8]);
+    settings->percent[SPAMSETTINGS_CAPSPERCENTINDEX] = atoi(row[9] ? row[9] : defaults[9]);
+    settings->exceptlevel[SPAMSETTINGS_CAPSEXCINDEX] = atoi(row[10] ? row[10] : defaults[10]);
+    settings->percent[SPAMSETTINGS_DIGITPERCENTINDEX] = atoi(row[11] ? row[11] : defaults[11]);
+    settings->exceptlevel[SPAMSETTINGS_DIGITEXCINDEX] = atoi(row[12] ? row[12] : defaults[12]);
+    settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX] = atoi(row[13] ? row[13] : defaults[13]);
+    settings->join_nodes = NULL;
+    settings->lastmsg_time = 0;
+    int i;
+    for(i = 0; i < BOTNETSCAN_USERS; i++) 
+        settings->botnicks[i] = NULL;
+    settings->badwords = NULL;
+    printf_mysql_query("SELECT `badword_match`, `badword_reaction`, `badword_reaction_time`, `badword_id`, `badword_scan_ops`, `badword_scan_voice`, `badword_exceptlevel`, `badword_use_default`, `badword_use_default_reaction` FROM `spamserv_badwords` WHERE `badword_cid` = '%d'", chan->channel_id);
+    res = mysql_use();
+    while((row = mysql_fetch_row(res))) {
+        struct NeonSpamBadword *badword = malloc(sizeof(*badword));
+        badword->id = atoi(row[3]);
+        badword->badword = strdup(row[0]);
+        badword->use_defaults = atoi(row[7]);
+        badword->exceptlevel = atoi(row[6]);
+        badword->scan_ops = atoi(row[4]);
+        badword->scan_voice = atoi(row[5]);
+        badword->use_default_reaction = atoi(row[8]);
+        badword->reaction = atoi(row[1]);
+        badword->reaction_time = atoi(row[2]);
+        badword->next = settings->badwords;
+        settings->badwords = badword;
+    }
+    chan->spam_settings = settings;
+    return 1;
+}
+
+void freeNeonSpamSettings(struct NeonSpamSettings *settings) {
+    struct NeonSpamJoinNode *joinnode, *nextjoinnode;
+    for(joinnode = settings->join_nodes; joinnode; joinnode = nextjoinnode) {
+        nextjoinnode = joinnode->next;
+        freeJoinNode(joinnode);
+    }
+    struct NeonSpamBadword *badword, *nextbadword;
+    for(badword = settings->badwords; badword; badword = nextbadword) {
+        nextbadword = badword->next;
+        free(badword->badword);
+        free(badword);
+    }
+    free(settings);
+}
+
+static void freeJoinNode(struct NeonSpamJoinNode *joinnode) {
+    free(joinnode->ident);
+    free(joinnode->host);
+    free(joinnode);
+}
+
+static struct NeonSpamJoinNode *getNeonSpamJoinNode(struct ChanUser *chanuser) {
+    struct NeonSpamJoinNode *joinnode, *prevjoinnode = NULL, *nextjoinnode, *result = NULL;
+    for(joinnode = chanuser->chan->spam_settings->join_nodes; joinnode; joinnode = nextjoinnode) {
+        nextjoinnode = joinnode->next;
+        if(!stricmp(joinnode->ident, chanuser->user->ident) && !stricmp(joinnode->host, chanuser->user->host)) {
+            prevjoinnode = joinnode;
+            result = joinnode;
+        } else if(time(0) - joinnode->last_penalty_update > MAX_JOIN_TIME) {
+            freeJoinNode(joinnode);
+            if(prevjoinnode)
+                prevjoinnode->next = nextjoinnode;
+            else
+                chanuser->chan->spam_settings->join_nodes = nextjoinnode;
+        } else 
+            prevjoinnode = joinnode;
+    }
+    if(result)
+        return result;
+    joinnode = malloc(sizeof(*joinnode));
+    if(!joinnode) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    joinnode->ident = strdup(chanuser->user->ident);
+    joinnode->host = strdup(chanuser->user->host);
+    joinnode->last_penalty_update = time(0);
+    joinnode->joinpenalty = 0;
+    joinnode->next = chanuser->chan->spam_settings->join_nodes;
+    chanuser->chan->spam_settings->join_nodes = joinnode;
+    return joinnode;
+}
+
+static void createSpamNode(struct ChanUser *chanuser) {
+    struct NeonSpamNode *spamnode = malloc(sizeof(*spamnode));
+    if(!spamnode) {
+        printf_log("neonserv", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    spamnode->lastmsg = 0;
+    spamnode->spamcount = 0;
+    spamnode->floodpenalty = 0;
+    spamnode->last_penalty_update = time(0);
+    chanuser->spamnode = spamnode;
+}
+
+static int neonspam_event_freechan(struct ChanNode *chan) {
+    if(chan->spam_settings)
+        freeNeonSpamSettings(chan->spam_settings);
+    return 1;
+}
+
+static void neonspam_event_invite(struct ClientSocket *client, struct UserNode *user, char *channel) {
+       if(client->botid != BOTID)
+               return;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(client, user, "NS_INVITE_FAIL", channel, client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(client, user, "MODCMD_CHAN_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        struct ChanNode *chan = getChanByName(channel);
+        if(chan && isUserOnChan(bot->user, chan)) {
+            reply(client, user, "NS_INVITE_ON_CHAN", bot->user->nick, chan->name);
+        } else
+            putsock(bot, "JOIN %s", channel);
+    }
+}
+
+void init_NeonSpam(int type) {
+    
+    set_bot_alias(BOTID, BOTALIAS);
+    start_bots(type);
+    
+    if(type == MODSTATE_REBIND) return;
+    
+    //register events
+    bind_bot_ready(neonspam_bot_ready, module_id);
+    bind_join(neonspam_event_join, module_id);
+    bind_chanmsg(neonspam_event_chanmsg, module_id);
+    bind_privctcp(general_event_privctcp, module_id);
+    bind_freechan(neonspam_event_freechan, module_id);
+    bind_invite(neonspam_event_invite, module_id);
+       
+    set_trigger_callback(BOTID, module_id, neonspam_trigger_callback);
+    
+    register_default_language_table(msgtab);
+}
+
+void free_NeonSpam(int type) {
+    unbind_allcmd(BOTID);
+    if(type == MODSTATE_STARTSTOP) {
+        //disconnect all our bots
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->botid == BOTID) {
+                unbind_botwise_allcmd(0, client->clientid);
+                close_socket(client);
+                break;
+            }
+        }
+    }
+}
+
+#undef BOTID
+#undef BOTALIAS
diff --git a/src/modules/NeonSpam.mod/bot_NeonSpam.h b/src/modules/NeonSpam.mod/bot_NeonSpam.h
new file mode 100644 (file)
index 0000000..7024986
--- /dev/null
@@ -0,0 +1,170 @@
+/* bot_NeonSpam.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#ifndef _bot_NeonSpam_h
+#define _bot_NeonSpam_h
+
+#include "../../main.h"
+
+struct ChanNode;
+
+//SPAMSCAN
+#define SPAMSETTINGS_SPAMSCAN           0x000001
+#define SPAMSETTINGS_SPAMSCAN_OPS       0x000002
+#define SPAMSETTINGS_SPAMSCAN_VOICE     0x000004
+#define SPAMSETTINGS_SPAMCHARS          "abc"
+#define SPAMSETTINGS_SPAMEXCINDEX       0
+
+//FLOODSCAN
+#define SPAMSETTINGS_FLOODSCAN          0x000008
+#define SPAMSETTINGS_FLOODSCAN_OPS      0x000010
+#define SPAMSETTINGS_FLOODSCAN_VOICE    0x000020
+#define SPAMSETTINGS_FLOODCHARS         "def"
+#define SPAMSETTINGS_FLOODEXCINDEX      1
+#define SPAMSETTINGS_FLOODSENINDEX      0
+
+//JOINSCAN
+#define SPAMSETTINGS_JOINSCAN           0x000040
+#define SPAMSETTINGS_JOINSCAN_OPS       0x000080
+#define SPAMSETTINGS_JOINSCAN_VOICE     0x000100
+#define SPAMSETTINGS_JOINCHARS          "ghi"
+#define SPAMSETTINGS_JOINEXCINDEX       2
+#define SPAMSETTINGS_JOINSENINDEX       1
+
+//BOTNET SCAN
+#define SPAMSETTINGS_BOTNETSCAN         0x000200
+#define SPAMSETTINGS_BOTNETSCAN_OPS     0x000400
+#define SPAMSETTINGS_BOTNETSCAN_VOICE   0x000800
+#define SPAMSETTINGS_BOTNETSCAN_STRIPCC 0x001000
+#define SPAMSETTINGS_BOTNETCHARS        "jklm"
+#define SPAMSETTINGS_BOTNETEXCINDEX     3
+
+//CAPSSCAN
+#define SPAMSETTINGS_CAPSSCAN           0x002000
+#define SPAMSETTINGS_CAPSSCAN_OPS       0x004000
+#define SPAMSETTINGS_CAPSSCAN_VOICE     0x008000
+#define SPAMSETTINGS_CAPSCHARS          "nop"
+#define SPAMSETTINGS_CAPSEXCINDEX       4
+#define SPAMSETTINGS_CAPSPERCENTINDEX   0
+
+//DIGITSCAN
+#define SPAMSETTINGS_DIGITSCAN          0x010000
+#define SPAMSETTINGS_DIGITSCAN_OPS      0x020000
+#define SPAMSETTINGS_DIGITSCAN_VOICE    0x040000
+#define SPAMSETTINGS_DIGITCHARS         "qrs"
+#define SPAMSETTINGS_DIGITEXCINDEX      5
+#define SPAMSETTINGS_DIGITPERCENTINDEX  1
+
+//BADWORDSCAN
+#define SPAMSETTINGS_BADWORDSCAN        0x080000
+#define SPAMSETTINGS_BADWORDSCAN_OPS    0x100000
+#define SPAMSETTINGS_BADWORDSCAN_VOICE  0x200000
+#define SPAMSETTINGS_BADWORDCHARS       "tuv"
+#define SPAMSETTINGS_BADWORDEXCINDEX    6
+
+#define SPAMSETTINGS_CHARS     SPAMSETTINGS_SPAMCHARS SPAMSETTINGS_FLOODCHARS SPAMSETTINGS_JOINCHARS SPAMSETTINGS_BOTNETCHARS SPAMSETTINGS_CAPSCHARS SPAMSETTINGS_DIGITCHARS SPAMSETTINGS_BADWORDCHARS
+#define SPAMSETTINGS_FLAGS              0x7fffff /* all flags that can be stored in the database */
+#define SPAMSETTINGS_EXCEPTINDEXES      7
+#define SPAMSETTINGS_SENSIBILITYINDEXES 2
+#define SPAMSETTINGS_PERCENTINDEXES     2
+
+//SCRIPT FLAGS
+#define SPAMSETTINGS_KICKEDBOTQUEUE     0x0400000
+#define SPAMSETTINGS_ISTIMEBAN          0x0800000
+#define SPAMSETTINGS_SETTIMEBAN         0x1000000
+
+#define MAX_FLOOD_AMOUNT 300
+#define MIN_FLOOD_AMOUNT 2
+#define MAX_FLOOD_TIME   200
+
+#define MAX_JOIN_AMOUNT 300
+#define MIN_JOIN_AMOUNT 2
+#define MAX_JOIN_TIME   200
+
+#define BOTNETSCAN_USERS 4
+#define BOTNETSCAN_TIME 2
+
+struct NeonSpamSettings {
+    unsigned int flags;
+    unsigned char spam_amount;
+    unsigned char sensibility_amount[SPAMSETTINGS_SENSIBILITYINDEXES];
+    unsigned char sensibility_time[SPAMSETTINGS_SENSIBILITYINDEXES];
+    unsigned int exceptlevel[SPAMSETTINGS_EXCEPTINDEXES];
+    unsigned char percent[SPAMSETTINGS_PERCENTINDEXES];
+    
+    //joinflood
+    struct NeonSpamJoinNode *join_nodes;
+    
+    //botnet
+    unsigned long lastmsg; //crc32 hash
+    time_t lastmsg_time;
+    char *botnicks[BOTNETSCAN_USERS];
+    
+    //badword scan
+    struct NeonSpamBadword *badwords;
+};
+
+struct NeonSpamBadword {
+    int id;
+    char *badword;
+    unsigned char use_defaults : 1;
+    unsigned int exceptlevel : 9;
+    unsigned char scan_ops : 1;
+    unsigned char scan_voice : 1;
+    unsigned char use_default_reaction : 1;
+    unsigned int reaction;
+    unsigned int reaction_time;
+    struct NeonSpamBadword *next;
+};
+
+/* PENALTY SYSTEM
+* user gets MAX_FLOOD_TIME points per message
+* points get removed each loop
+* pounts to be removed each second:
+*  MAX_FLOOD_TIME/flood_time
+* 
+* the floodlimit is reached, if the penalty points 
+* are bigger than MAX_FLOOD_TIME * flood_amount
+*/
+
+#define NEONSPAMNODE_FLAG_CAPSSCAN_WARNED  0x01
+#define NEONSPAMNODE_FLAG_DIGITSCAN_WARNED 0x02
+
+struct NeonSpamNode {
+    unsigned long lastmsg; //crc32 hash
+    unsigned char spamcount;
+    int floodpenalty;
+    time_t last_penalty_update;
+    unsigned char flags;
+};
+
+struct NeonSpamJoinNode {
+    char *ident;
+    char *host;
+    int joinpenalty;
+    time_t last_penalty_update;
+    struct NeonSpamJoinNode *next;
+};
+
+void init_NeonSpam(int type);
+void free_NeonSpam(int type);
+
+void freeNeonSpamSettings(struct NeonSpamSettings *settings);
+char* convertNeonSpamSettingsToString(unsigned int flags, char *buffer);
+int loadNeonSpamSettings(struct ChanNode *chan);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam.c b/src/modules/NeonSpam.mod/cmd_neonspam.c
new file mode 100644 (file)
index 0000000..3ca339d
--- /dev/null
@@ -0,0 +1,36 @@
+/* cmd_neonspam.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+
+#include "cmd_neonspam.h"
+#include "../../modcmd.h"
+#include "../../ConfigParser.h"
+
+void register_commands() {
+    //NeonSpam Commands
+    register_command_alias(2, "NeonSpam");
+    
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,PRIVS,FLAGS) register_command(2, NAME, module_id, FUNCTION, PARAMCOUNT, PRIVS, 0, FLAGS)
+    //               NAME              FUNCTION        PARAMS     PRIVS                FLAGS
+    USER_COMMAND("set",          neonspam_cmd_set,       0, "#channel_setters",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("addbad",       neonspam_cmd_addbad,    1, "#channel_setters",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("delbad",       neonspam_cmd_delbad,    1, "#channel_setters",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    USER_COMMAND("badwords",     neonspam_cmd_badwords,  0, "#channel_setters",     CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_LOG);
+    
+    #undef USER_COMMAND
+    
+}
\ No newline at end of file
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam.h b/src/modules/NeonSpam.mod/cmd_neonspam.h
new file mode 100644 (file)
index 0000000..8023e30
--- /dev/null
@@ -0,0 +1,49 @@
+/* cmd_neonspam.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_neonspam_h
+#define _cmd_neonspam_h
+#include "../module.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+#include "bot_NeonSpam.h"
+
+void register_commands();
+
+CMD_BIND(neonspam_cmd_addbad);
+CMD_BIND(neonspam_cmd_badwords);
+CMD_BIND(neonspam_cmd_delbad);
+CMD_BIND(neonspam_cmd_set);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam_addbad.c b/src/modules/NeonSpam.mod/cmd_neonspam_addbad.c
new file mode 100644 (file)
index 0000000..49efe50
--- /dev/null
@@ -0,0 +1,57 @@
+/* cmd_neonspam_addbad.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonspam.h"
+
+CMD_BIND(neonspam_cmd_addbad) {
+    char *cbadword = merge_argv(argv, 0, argc);
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults;
+    loadChannelSettings(chan);
+    loadNeonSpamSettings(chan);
+    struct NeonSpamSettings *settings = chan->spam_settings;
+    if(!settings) return;
+    printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(!row[0] || !row[1]) {
+        printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaults = mysql_fetch_row(res);
+    }
+    int reaction = atoi((row[0] ? row[0] : defaults[0])) + 1;
+    int reaction_time = atoi((row[1] ? row[1] : defaults[1]));
+    if(strlen(cbadword) > 128)
+        cbadword[128] = 0;
+    int scan_ops = ((settings->flags & SPAMSETTINGS_BADWORDSCAN_OPS) ? 1 : 0);
+    int scan_voice = ((settings->flags & SPAMSETTINGS_BADWORDSCAN_VOICE) ? 1 : 0);
+    printf_mysql_query("INSERT INTO `spamserv_badwords` (`badword_cid`, `badword_match`, `badword_use_default`, `badword_exceptlevel`, `badword_scan_ops`, `badword_scan_voice`, `badword_use_default_reaction`, `badword_reaction`, `badword_reaction_time`) VALUES ('%d', '%s', '1', '%d', '%d', '%d', '1', '%d', '%d')", chan->channel_id, escape_string(cbadword), settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX], scan_ops, scan_voice, reaction, reaction_time);
+    int badword_id = (int) mysql_insert_id(get_mysql_conn());
+    struct NeonSpamBadword *badword = malloc(sizeof(*badword));
+    badword->id = badword_id;
+    badword->badword = strdup(cbadword);
+    badword->use_defaults = 1;
+    badword->exceptlevel = settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX];
+    badword->scan_ops = scan_ops;
+    badword->scan_voice = scan_voice;
+    badword->use_default_reaction = 1;
+    badword->reaction = reaction;
+    badword->reaction_time = reaction_time;
+    badword->next = settings->badwords;
+    settings->badwords = badword;
+    reply(textclient, user, "SS_BADWORD_ADDED", cbadword, badword_id);
+}
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam_badwords.c b/src/modules/NeonSpam.mod/cmd_neonspam_badwords.c
new file mode 100644 (file)
index 0000000..02d4d46
--- /dev/null
@@ -0,0 +1,106 @@
+/* cmd_neonspam_badwords.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonspam.h"
+
+CMD_BIND(neonspam_cmd_badwords) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults;
+    loadChannelSettings(chan);
+    loadNeonSpamSettings(chan);
+    struct NeonSpamSettings *settings = chan->spam_settings;
+    if(!settings) return;
+    if(argc > 0) {
+        int badword_id = atoi(argv[0]);
+        printf_mysql_query("SELECT `badword_match` FROM `spamserv_badwords` WHERE `badword_id` = '%d' AND `badword_cid` = '%d'", badword_id, chan->channel_id);
+        res = mysql_use();
+        if(!(row = mysql_fetch_row(res))) {
+            reply(textclient, user, "SS_BADWORD_ID_UNKNOWN", badword_id, chan->name);
+            return;
+        }
+        //to be continued...
+        
+    } else {
+        struct Table *table;
+        printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        int default_reaction = atoi((row[0] ? row[0] : defaults[0]));
+        int default_reaction_time = atoi((row[1] ? row[1] : defaults[1]));
+        printf_mysql_query("SELECT `badword_id`, `badword_match`, `badword_use_default`, `badword_exceptlevel`, `badword_scan_ops`, `badword_scan_voice`, `badword_use_default_reaction`, `badword_reaction`, `badword_reaction_time` FROM `spamserv_badwords` WHERE `badword_cid` = '%d' ORDER BY `badword_id` ASC", chan->channel_id);
+        res = mysql_use();
+        table = table_init(4, mysql_num_rows(res) + 1, 0);
+        char *content[4];
+        content[0] = get_language_string(user, "SS_BADWORDS_ID");
+        content[1] = get_language_string(user, "SS_BADWORDS_PATTERN");
+        content[2] = get_language_string(user, "SS_BADWORDS_SCAN");
+        content[3] = get_language_string(user, "SS_BADWORDS_REACTION");
+        table_add(table, content);
+        char default_exception[100];
+        char exception_buf[100], reaction_buf[100], reaction_time_buf[100];
+        int exception_pos = 0, reaction_pos;
+        int reaction, reaction_time;
+        if(settings->flags & SPAMSETTINGS_BADWORDSCAN_OPS)
+            exception_pos += sprintf(default_exception+exception_pos, (exception_pos ? " & @" : "@"));
+        if(settings->flags & SPAMSETTINGS_BADWORDSCAN_VOICE)
+            exception_pos += sprintf(default_exception+exception_pos, (exception_pos ? " & +" : "+"));
+        if(settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX])
+            exception_pos += sprintf(default_exception+exception_pos, (exception_pos ? " & <%d" : "<%d"), settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX]);
+        while ((row = mysql_fetch_row(res)) != NULL) {
+            content[0] = row[0];
+            content[1] = row[1];
+            exception_pos = 0;
+            if(strcmp(row[2], "0")) {
+                exception_pos += sprintf(exception_buf, "\00314%s\003", default_exception);
+            } else {
+                if(!strcmp(row[4], "1"))
+                    exception_pos += sprintf(exception_buf+exception_pos, (exception_pos ? " & @" : "@"));
+                if(!strcmp(row[5], "1"))
+                    exception_pos += sprintf(exception_buf+exception_pos, (exception_pos ? " & +" : "+"));
+                if(atoi(row[3]))
+                    exception_pos += sprintf(exception_buf+exception_pos, (exception_pos ? " & <%d" : "<%d"), atoi(row[3]));
+            }
+            content[2] = exception_buf;
+            reaction_pos = 0;
+            if(strcmp(row[6], "0")) {
+                reaction = default_reaction;
+                reaction_time = default_reaction_time;
+            } else {
+                reaction = atoi(row[7]);
+                reaction_time = atoi(row[8]);
+            }
+            sprintf(reaction_buf, "SS_BADWORDS_REACTION_%d", reaction);
+            reaction_pos += sprintf(reaction_buf, "%s", get_language_string(user, reaction_buf));
+            if(reaction == 2) {
+                reaction_pos += sprintf(reaction_buf+reaction_pos, " (%s)", timeToStr(user, reaction_time, 2, reaction_time_buf));
+            }
+            content[3] = reaction_buf;
+            table_add(table, content);
+        }
+        char **table_lines = table_end(table);
+        int i;
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        table_free(table);
+    }
+}
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam_delbad.c b/src/modules/NeonSpam.mod/cmd_neonspam_delbad.c
new file mode 100644 (file)
index 0000000..c0d1788
--- /dev/null
@@ -0,0 +1,49 @@
+/* cmd_neonspam_delbad.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonspam.h"
+
+CMD_BIND(neonspam_cmd_delbad) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    loadChannelSettings(chan);
+    loadNeonSpamSettings(chan);
+    struct NeonSpamSettings *settings = chan->spam_settings;
+    if(!settings) return;
+    int badword_id = atoi(argv[0]);
+    printf_mysql_query("SELECT `badword_match` FROM `spamserv_badwords` WHERE `badword_id` = '%d' AND `badword_cid` = '%d'", badword_id, chan->channel_id);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        reply(textclient, user, "SS_BADWORD_ID_UNKNOWN", badword_id, chan->name);
+        return;
+    }
+    struct NeonSpamBadword *badword, *prev = NULL;
+    for(badword = settings->badwords; badword; badword = badword->next) {
+        if(badword->id == badword_id) {
+            if(prev)
+                prev->next = badword->next;
+            else
+                settings->badwords = badword->next;
+            free(badword->badword);
+            free(badword);
+            break;
+        } else
+            prev = badword;
+    }
+    printf_mysql_query("DELETE FROM `spamserv_badwords` WHERE `badword_id` = '%d'", badword_id);
+    reply(textclient, user, "SS_BADWORD_DELETED", row[0], badword_id);
+}
diff --git a/src/modules/NeonSpam.mod/cmd_neonspam_set.c b/src/modules/NeonSpam.mod/cmd_neonspam_set.c
new file mode 100644 (file)
index 0000000..f976376
--- /dev/null
@@ -0,0 +1,665 @@
+/* cmd_neonspam_set.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_neonspam.h"
+
+typedef char* neonspam_cmd_set_function(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_set_trigger(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setflags(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setexcept(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_set_reaction(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_set_reaction_time(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setpercent(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setsensibility(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_set_spamlimit(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setscanops(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setscanvoice(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+static char* neonspam_cmd_setscanexcept(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf);
+
+static MYSQL_ROW neonspam_settings_row, neonspam_settings_defaults;
+#define SPAMSERV_SETTINGS_QUERY "`channel_spam_reaction`, `channel_spam_reaction_duration`, `channel_flood_reaction`, `channel_flood_reaction_duration`, `channel_join_reaction`, `channel_join_reaction_duration`, `channel_botnet_bantime`, `channel_caps_reaction`, `channel_caps_reaction_duration`, `channel_digit_reaction`, `channel_digit_reaction_duration`, `channel_badword_reaction`, `channel_badword_reaction_duration`"
+#define SPAMSERV_SETTINGS_RESET "\
+`channel_scanner` = NULL, \
+`channel_spam_limit` = NULL, \
+`channel_spam_reaction` = NULL, \
+`channel_spam_reaction_duration` = NULL, \
+`channel_spam_except` = NULL, \
+`channel_flood_limit` = NULL, \
+`channel_flood_time` = NULL, \
+`channel_flood_reaction` = NULL, \
+`channel_flood_reaction_duration` = NULL, \
+`channel_flood_except` = NULL, \
+`channel_join_limit` = NULL, \
+`channel_join_time` = NULL, \
+`channel_join_reaction` = NULL, \
+`channel_join_reaction_duration` = NULL, \
+`channel_join_except` = NULL, \
+`channel_botnet_bantime` = NULL, \
+`channel_botnet_except` = NULL, \
+`channel_caps_percent` = NULL, \
+`channel_caps_reaction` = NULL, \
+`channel_caps_reaction_duration` = NULL, \
+`channel_caps_except` = NULL,\
+`channel_digit_percent` = NULL, \
+`channel_digit_reaction` = NULL, \
+`channel_digit_reaction_duration` = NULL, \
+`channel_digit_except` = NULL, \
+`channel_badword_reaction` = NULL, \
+`channel_badword_reaction_duration` = NULL, \
+`channel_badword_except` = NULL "
+
+#define SET_HELP       0x0001
+#define SET_BOOL       0x0002
+#define SET_OPT        0x0004
+
+#define SET_SCANOPS    0x0010
+#define SET_SCANVOICE  0x0020
+#define SET_SCANEXCEPT 0x0040
+
+#define SET_OPT_MAX    0xff00
+#define SET_OPT_SHIFT  8
+
+static const struct {
+    unsigned int if_flag;
+    unsigned int indent;
+    const char *setting;
+    void *function;
+    int intparam;
+    char *charparam;
+    int flags;
+} neonspam_settings[] = {
+    {0,                                                0,  "Trigger",           neonspam_cmd_set_trigger,         0,                              NULL,                             SET_HELP},
+    
+    {0,                                                0,  "SpamScan",          neonspam_cmd_setflags,            SPAMSETTINGS_SPAMSCAN,          NULL,                             SET_BOOL},
+    {SPAMSETTINGS_SPAMSCAN,                            2,  "SpamLimit",         neonspam_cmd_set_spamlimit,       0,                              NULL,                             SET_HELP},
+    {SPAMSETTINGS_SPAMSCAN | SPAMSETTINGS_SETTIMEBAN,  2,  "SpamReaction",      neonspam_cmd_set_reaction,        0,                              "channel_spam_reaction",          SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_SPAMSCAN | SPAMSETTINGS_ISTIMEBAN,   4,  "SpamBanDuration",   neonspam_cmd_set_reaction_time,   1,                              "channel_spam_reaction_duration", 0},
+    {SPAMSETTINGS_SPAMSCAN,                            2,  "SpamScanChanOps",   neonspam_cmd_setflags,            SPAMSETTINGS_SPAMSCAN_OPS,      NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_SPAMSCAN,                            2,  "SpamScanVoiced",    neonspam_cmd_setflags,            SPAMSETTINGS_SPAMSCAN_VOICE,    NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_SPAMSCAN,                            2,  "SpamScanExcept",    neonspam_cmd_setexcept,           SPAMSETTINGS_SPAMEXCINDEX,      "channel_spam_except",            SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "FloodScan",         neonspam_cmd_setflags,            SPAMSETTINGS_FLOODSCAN,         NULL,                             SET_BOOL},
+    {SPAMSETTINGS_FLOODSCAN,                           2,  "FloodSensibility",  neonspam_cmd_setsensibility,      SPAMSETTINGS_FLOODSENINDEX,     "channel_flood_",                 SET_HELP},
+    {SPAMSETTINGS_FLOODSCAN | SPAMSETTINGS_SETTIMEBAN, 2,  "FloodReaction",     neonspam_cmd_set_reaction,        2,                              "channel_flood_reaction",         SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_FLOODSCAN | SPAMSETTINGS_ISTIMEBAN,  4,  "FloodBanDuration",  neonspam_cmd_set_reaction_time,   3,                              "channel_flood_reaction_duration",0},
+    {SPAMSETTINGS_FLOODSCAN,                           2,  "FloodScanChanOps",  neonspam_cmd_setflags,            SPAMSETTINGS_FLOODSCAN_OPS,     NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_FLOODSCAN,                           2,  "FloodScanVoiced",   neonspam_cmd_setflags,            SPAMSETTINGS_FLOODSCAN_VOICE,   NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_FLOODSCAN,                           2,  "FloodScanExcept",   neonspam_cmd_setexcept,           SPAMSETTINGS_FLOODEXCINDEX,     "channel_flood_except",           SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "JoinScan",          neonspam_cmd_setflags,            SPAMSETTINGS_JOINSCAN,          NULL,                             SET_BOOL},
+    {SPAMSETTINGS_JOINSCAN,                            2,  "JoinSensibility",   neonspam_cmd_setsensibility,      SPAMSETTINGS_JOINSENINDEX,      "channel_join_",                  SET_HELP},
+    {SPAMSETTINGS_JOINSCAN | SPAMSETTINGS_SETTIMEBAN,  2,  "JoinReaction",      neonspam_cmd_set_reaction,        4,                              "channel_join_reaction",          SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_JOINSCAN | SPAMSETTINGS_ISTIMEBAN,   4,  "JoinBanDuration",   neonspam_cmd_set_reaction_time,   5,                              "channel_join_reaction_duration", 0},
+    {SPAMSETTINGS_JOINSCAN,                            2,  "JoinScanChanOps",   neonspam_cmd_setflags,            SPAMSETTINGS_JOINSCAN_OPS,      NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_JOINSCAN,                            2,  "JoinScanVoiced",    neonspam_cmd_setflags,            SPAMSETTINGS_JOINSCAN_VOICE,    NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_JOINSCAN,                            2,  "JoinScanExcept",    neonspam_cmd_setexcept,           SPAMSETTINGS_JOINEXCINDEX,      "channel_join_except",            SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "BotNetScan",        neonspam_cmd_setflags,            SPAMSETTINGS_BOTNETSCAN,        NULL,                             SET_BOOL},
+    {SPAMSETTINGS_BOTNETSCAN,                          4,  "BotNetBanDuration", neonspam_cmd_set_reaction_time,   6,                              "channel_botnet_bantime",         0},
+    {SPAMSETTINGS_BOTNETSCAN,                          2,  "BotNetScanChanOps", neonspam_cmd_setflags,            SPAMSETTINGS_BOTNETSCAN_OPS,    NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_BOTNETSCAN,                          2,  "BotNetScanVoiced",  neonspam_cmd_setflags,            SPAMSETTINGS_BOTNETSCAN_VOICE,  NULL,                             SET_BOOL | SET_SCANVOICE},
+    //{SPAMSETTINGS_BOTNETSCAN,                          2,  "BotNetScanExcept",  neonspam_cmd_setexcept,           SPAMSETTINGS_BOTNETEXCINDEX,    "channel_botnet_except",          SET_HELP},
+    
+    {0,                                                0,  "CapsScan",          neonspam_cmd_setflags,            SPAMSETTINGS_CAPSSCAN,          NULL,                             SET_BOOL},
+    {SPAMSETTINGS_CAPSSCAN,                            2,  "CapsPercent",       neonspam_cmd_setpercent,          SPAMSETTINGS_CAPSPERCENTINDEX,  "channel_caps_percent",           SET_HELP},
+    {SPAMSETTINGS_CAPSSCAN | SPAMSETTINGS_SETTIMEBAN,  2,  "CapsReaction",      neonspam_cmd_set_reaction,        7,                              "channel_caps_reaction",          SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_CAPSSCAN | SPAMSETTINGS_ISTIMEBAN,   4,  "CapsBanDuration",   neonspam_cmd_set_reaction_time,   8,                              "channel_caps_reaction_duration", 0},
+    {SPAMSETTINGS_CAPSSCAN,                            2,  "CapsScanChanOps",   neonspam_cmd_setflags,            SPAMSETTINGS_CAPSSCAN_OPS,      NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_CAPSSCAN,                            2,  "CapsScanVoiced",    neonspam_cmd_setflags,            SPAMSETTINGS_CAPSSCAN_VOICE,    NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_CAPSSCAN,                            2,  "CapsScanExcept",    neonspam_cmd_setexcept,           SPAMSETTINGS_CAPSEXCINDEX,      "channel_caps_except",            SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "DigitScan",         neonspam_cmd_setflags,            SPAMSETTINGS_DIGITSCAN,         NULL,                             SET_BOOL},
+    {SPAMSETTINGS_DIGITSCAN,                           2,  "DigitPercent",      neonspam_cmd_setpercent,          SPAMSETTINGS_DIGITPERCENTINDEX, "channel_digit_percent",          SET_HELP},
+    {SPAMSETTINGS_DIGITSCAN | SPAMSETTINGS_SETTIMEBAN, 2,  "DigitReaction",     neonspam_cmd_set_reaction,        9,                              "channel_digit_reaction",         SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_DIGITSCAN | SPAMSETTINGS_ISTIMEBAN,  4,  "DigitBanDuration",  neonspam_cmd_set_reaction_time,   10,                             "channel_digit_reaction_duration", 0},
+    {SPAMSETTINGS_DIGITSCAN,                           2,  "DigitScanChanOps",  neonspam_cmd_setflags,            SPAMSETTINGS_DIGITSCAN_OPS,     NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_DIGITSCAN,                           2,  "DigitScanVoiced",   neonspam_cmd_setflags,            SPAMSETTINGS_DIGITSCAN_VOICE,   NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_DIGITSCAN,                           2,  "DigitScanExcept",   neonspam_cmd_setexcept,           SPAMSETTINGS_DIGITEXCINDEX,     "channel_digit_except",           SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "BadwordScan",       neonspam_cmd_setflags,            SPAMSETTINGS_BADWORDSCAN,       NULL,                             SET_BOOL},
+    {SPAMSETTINGS_BADWORDSCAN | SPAMSETTINGS_SETTIMEBAN,2, "BadwordReaction",   neonspam_cmd_set_reaction,        11,                             "channel_badword_reaction",       SET_OPT | (3 << SET_OPT_SHIFT)},
+    {SPAMSETTINGS_BADWORDSCAN | SPAMSETTINGS_ISTIMEBAN,4,  "BadwordBanDuration",neonspam_cmd_set_reaction_time,   12,                             "channel_badword_reaction_duration", 0},
+    {SPAMSETTINGS_BADWORDSCAN,                         2,  "BadwordScanChanOps",neonspam_cmd_setflags,            SPAMSETTINGS_BADWORDSCAN_OPS,   NULL,                             SET_BOOL | SET_SCANOPS},
+    {SPAMSETTINGS_BADWORDSCAN,                         2,  "BadwordScanVoiced", neonspam_cmd_setflags,            SPAMSETTINGS_BADWORDSCAN_VOICE, NULL,                             SET_BOOL | SET_SCANVOICE},
+    {SPAMSETTINGS_BADWORDSCAN,                         2,  "BadwordScanExcept", neonspam_cmd_setexcept,           SPAMSETTINGS_BADWORDEXCINDEX,   "channel_badword_except",         SET_HELP | SET_SCANEXCEPT},
+    
+    {0,                                                0,  "GlobalScanChanOps", neonspam_cmd_setscanops,          0,                              NULL,                             SET_BOOL},
+    {0,                                                0,  "GlobalScanVoice",   neonspam_cmd_setscanvoice,        0,                              NULL,                             SET_BOOL},
+    {0,                                                0,  "GlobalScanExcept",  neonspam_cmd_setscanexcept,       0,                              NULL,                             SET_HELP},
+    
+    {0, 0, NULL, NULL, 0, NULL, 0}
+};
+
+#define MAX_QUERY_LEN 1024
+CMD_BIND(neonspam_cmd_set) {
+    int i, j;
+    loadNeonSpamSettings(chan);
+    if(argc && !strcmp(argv[0], "defaults")) {
+        //reset channel settings
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess < 500) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_DEFAULTS_OWNER", chan->name);
+                return;
+            }
+        }
+        int seed = 0;
+        char *tmp;
+        static char defaultskey[16];
+        for(tmp = user->auth; *tmp; tmp++)
+            seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+        for(tmp = chan->name; *tmp; tmp++)
+            seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
+        sprintf(defaultskey, "%08x", seed);
+        if(argc > 1 && !strcmp(argv[1], defaultskey)) {
+            printf_mysql_query("UPDATE `channels` SET " SPAMSERV_SETTINGS_RESET " WHERE `channel_id` = '%d'", chan->channel_id);
+            reply(textclient, user, "NS_SET_DEFAULTS_DONE", chan->name);
+            logEvent(event);
+        } else {
+            reply(textclient, user, "NS_SET_DEFAULTS_CODE", chan->name, defaultskey);
+        }
+    } else if(argc && strcmp(argv[0], "help")) {
+        //find the correct command
+        i = 0;
+        j = 0;
+        char *args = (argc > 1 ? merge_argv(argv, 1, argc) : NULL);
+        neonspam_settings_row = NULL;
+        neonspam_settings_defaults = NULL;
+        while(neonspam_settings[i].setting) {
+            if(!stricmp(neonspam_settings[i].setting, argv[0])) {
+                //setting found
+                char valueBuf[MAXLEN], nameBuf[MAXLEN];
+                char *value, *optimized_value, *option_help;
+                neonspam_cmd_set_function *func = neonspam_settings[i].function;
+                value = func(client, textclient, user, chan, event, neonspam_settings[i].intparam, neonspam_settings[i].charparam, args, valueBuf);
+                if(value) {
+                    optimized_value = value;
+                    if(neonspam_settings[i].flags & SET_BOOL) {
+                        if(!strcmp(value, "0"))
+                            optimized_value = get_language_string(user, "NS_SET_OFF");
+                        else if(!strcmp(value, "1"))
+                            optimized_value = get_language_string(user, "NS_SET_ON");
+                    }
+                    if(neonspam_settings[i].flags & SET_OPT) {
+                        sprintf(nameBuf, "SS_SET_OPTION_%s_%s", neonspam_settings[i].setting, value);
+                        option_help = get_language_string(user, nameBuf);
+                    } else
+                        option_help = NULL;
+                    reply(textclient, user, "\002%s\002 %s%s%s", neonspam_settings[i].setting, optimized_value, (option_help ? " - " : ""), (option_help ? option_help : ""));
+                    if(neonspam_settings[i].flags & SET_OPT) {
+                        int maxoption = (neonspam_settings[i].flags & SET_OPT_MAX) >> SET_OPT_SHIFT;
+                        for(j = 0; j < maxoption; j++) {
+                            sprintf(nameBuf, "SS_SET_OPTION_%s_%d", neonspam_settings[i].setting, j);
+                            option_help = get_language_string(user, nameBuf);
+                            reply(textclient, user, " \002%d\002 - %s", j, option_help);
+                        }
+                    }
+                    if((neonspam_settings[i].flags & SET_HELP) && argc && !strcmp(argv[0], "help")) {
+                        char *tmp;
+                        sprintf(nameBuf, "SS_SET_HELP_%s", neonspam_settings[i].setting);
+                        tmp = get_language_string(user, nameBuf);
+                        if(tmp) {
+                            reply(textclient, user, "  %s", tmp);
+                        }
+                    }
+                }
+                j = 1;
+                break;
+            }
+            i++;
+        }
+        if(j == 0) {
+            //unknown setting
+            reply(textclient, user, "NS_SET_UNKNOWN_SETTING", argv[0]);
+        }
+    } else {
+        char valueBuf[MAXLEN], nameBuf[MAXLEN];
+        char *value;
+        int namePos, boolflag = 0;
+        MYSQL_RES *res, *defaults_res;
+        struct Table *table;
+        char *content[2];
+        i = 0;
+        while(neonspam_settings[i].setting)
+            i++;
+        table = table_init(2, i, 0);
+        table_set_bold(table, 0, 1);
+        printf_mysql_query("SELECT " SPAMSERV_SETTINGS_QUERY " FROM `channels` WHERE `channel_name` = 'defaults'");
+        defaults_res = mysql_use();
+        neonspam_settings_defaults = mysql_fetch_row(defaults_res);
+        printf_mysql_query("SELECT " SPAMSERV_SETTINGS_QUERY " FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
+        res = mysql_use();
+        neonspam_settings_row = mysql_fetch_row(res);
+        i = -1;
+        reply(textclient, user, "NS_SET_HEADER", chan->name);
+        while(neonspam_settings[++i].setting) {
+            if((chan->spam_settings->flags & (neonspam_settings[i].if_flag & SPAMSETTINGS_FLAGS)) != (neonspam_settings[i].if_flag & SPAMSETTINGS_FLAGS))
+                continue;
+            if((neonspam_settings[i].if_flag & SPAMSETTINGS_ISTIMEBAN) && !boolflag)
+                continue;
+            neonspam_cmd_set_function *func = neonspam_settings[i].function;
+            value = func(client, textclient, user, chan, event, neonspam_settings[i].intparam, neonspam_settings[i].charparam, NULL, valueBuf);
+            if(neonspam_settings[i].if_flag & SPAMSETTINGS_SETTIMEBAN)
+                boolflag = !strcmp(value, "2");
+            //TODO: append option or help info
+            strcpy(valueBuf, value);
+            if(neonspam_settings[i].flags & SET_BOOL) {
+                if(!strcmp(value, "0"))
+                    strcpy(valueBuf, get_language_string(user, "NS_SET_OFF"));
+                else if(!strcmp(value, "1"))
+                    strcpy(valueBuf, get_language_string(user, "NS_SET_ON"));
+            }
+            if(neonspam_settings[i].flags & SET_OPT) {
+                char *tmp;
+                sprintf(nameBuf, "SS_SET_OPTION_%s_%s", neonspam_settings[i].setting, value);
+                tmp = get_language_string(user, nameBuf);
+                if(tmp) {
+                    sprintf(valueBuf + strlen(valueBuf), " - %s", tmp);
+                }
+            }
+            if((neonspam_settings[i].flags & SET_HELP) && argc && !strcmp(argv[0], "help")) {
+                char *tmp;
+                sprintf(nameBuf, "SS_SET_HELP_%s", neonspam_settings[i].setting);
+                tmp = get_language_string(user, nameBuf);
+                if(tmp) {
+                    sprintf(valueBuf + strlen(valueBuf), " - %s", tmp);
+                }
+            }
+            namePos = 0;
+            for(j = 0; j < neonspam_settings[i].indent; j++) {
+                nameBuf[namePos++] = ' ';
+            }
+            sprintf(nameBuf + namePos, "%s", neonspam_settings[i].setting);
+            content[0] = nameBuf;
+            content[1] = valueBuf;
+            table_add(table, content);
+        }
+        char **table_lines = table_end(table);
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        table_free(table);
+    }
+}
+
+static char* neonspam_cmd_set_trigger(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf) {
+    char *trigger;
+    //get current trigger
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botid` = '%d'", chan->channel_id, client->clientid);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    trigger = (row[0] ? row[0] : row[1]);
+    if(argument) {
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess < 500) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_TRIGGER_OWNER", chan->name);
+                return NULL;
+            }
+        }
+        if(strlen(argument) > 15)
+            argument[15] = '\0';
+        printf_mysql_query("UPDATE `bot_channels` SET `trigger` = '%s' WHERE `chanid` = '%d' AND `botid` = '%d'", escape_string(argument), chan->channel_id, client->clientid);
+        trigger = argument;
+        changeChannelTrigger(client->botid, chan, trigger);
+        logEvent(event);
+    }
+    return trigger;
+}
+
+static char* neonspam_cmd_setflags(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int flag, char *charparam, char *argument, char *retBuf) {
+    char *value = ((chan->spam_settings->flags & flag) ? "1" : "0");
+    if(argument) {
+        //binary argument...
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            chan->spam_settings->flags &= ~flag;
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            chan->spam_settings->flags |= flag;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argument);
+            return NULL;
+        }
+        char str_flags[50];
+        convertNeonSpamSettingsToString(chan->spam_settings->flags, str_flags);
+        printf_mysql_query("UPDATE `channels` SET `channel_scanner` = '%s' WHERE `channel_id` = '%d' ", str_flags, chan->channel_id);
+        value = argument;
+    }
+    return value;
+}
+
+static char* neonspam_cmd_setexcept(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int exceptlvl_index, char *field, char *argument, char *retBuf) {
+    unsigned int value = chan->spam_settings->exceptlevel[exceptlvl_index];
+    if(argument) {
+        //numeric argument... (access)
+        int caccess = atoi(argument);
+        if(caccess < 0 || caccess > 501) {
+            reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+            return NULL;
+        }
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess == 500) uaccess++;
+        if(value > uaccess) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_CANNOT_SET");
+                return NULL;
+            }
+        }
+        if(caccess > uaccess) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_BADLEVEL");
+                return NULL;
+            }
+        }
+        value = caccess;
+        chan->spam_settings->exceptlevel[exceptlvl_index] = value;
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%u' WHERE `channel_id` = '%d' ", field, value, chan->channel_id);
+    }
+    sprintf(retBuf, "%u", value);
+    return retBuf;
+}
+
+static char* neonspam_cmd_set_reaction(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int mysqlindex, char *mysqlfield, char *argument, char *retBuf) {
+    if(argument) {
+        /* valid options:
+        * 0/kick - kick
+        * 1/kickban - kickban
+        * 2/ban - timed ban
+        */
+        if(!strcmp(argument, "0") || !stricmp(argument, "kick")) {
+            argument = "0";
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "kickban")) {
+            argument = "1";
+        } else if(!strcmp(argument, "2") || !stricmp(argument, "ban")) {
+            argument = "2";
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_OPTION_STR", argument);
+            return NULL;
+        }
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%s' WHERE `channel_id` = '%d' ", mysqlfield, argument, chan->channel_id);
+        return argument;
+    } else {
+        if(neonspam_settings_row) {
+            return (neonspam_settings_row[mysqlindex] ? neonspam_settings_row[mysqlindex] : neonspam_settings_defaults[mysqlindex]);
+        } else {
+            MYSQL_RES *res;
+            MYSQL_ROW row;
+            printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", mysqlfield, chan->channel_id);
+            res = mysql_use();
+            row = mysql_fetch_row(res);
+            return row[0];
+        }
+    }
+}
+
+static char* neonspam_cmd_set_reaction_time(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int mysqlindex, char *mysqlfield, char *argument, char *retBuf) {
+    int duration;
+    if(argument) {
+        duration = strToTime(user, argument);
+        if(duration < 30) {
+            reply(textclient, user, "NS_TIMEBAN_DURATION_TOO_SHORT", 30);
+            return NULL;
+        }
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%d' WHERE `channel_id` = '%d' ", mysqlfield, duration, chan->channel_id);
+    } else {
+        if(neonspam_settings_row) {
+            duration = atoi(neonspam_settings_row[mysqlindex] ? neonspam_settings_row[mysqlindex] : neonspam_settings_defaults[mysqlindex]);
+        } else {
+            MYSQL_RES *res;
+            MYSQL_ROW row;
+            printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", mysqlfield, chan->channel_id);
+            res = mysql_use();
+            row = mysql_fetch_row(res);
+            duration = atoi(row[0]);
+        }
+    }
+    timeToStr(user, duration, 3, retBuf);
+    return retBuf;
+}
+
+static char* neonspam_cmd_setpercent(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int percent_index, char *mysqlfield, char *argument, char *retBuf) {
+    unsigned int value = chan->spam_settings->percent[percent_index];
+    if(argument) {
+        //numeric argument... (access)
+        value = atoi(argument);
+        if(value < 0 || value > 100) {
+            //invalid percent value
+            reply(textclient, user, "SS_SET_PERCENT", value);
+            return NULL;
+        }
+        chan->spam_settings->percent[percent_index] = value;
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%u' WHERE `channel_id` = '%d' ", mysqlfield, value, chan->channel_id);
+    }
+    sprintf(retBuf, "%u", value);
+    return retBuf;
+}
+
+static char* neonspam_cmd_setsensibility(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int sensibility_index, char *mysqlfield, char *argument, char *retBuf) {
+    if(argument) {
+        //change value
+        char *delimiter = strstr(argument, ":");
+        if(!delimiter) {
+            //invalid format
+            reply(textclient, user, "SS_SET_SENSIBILITY", argument);
+            return NULL;
+        }
+        *delimiter = '\0';
+        delimiter++;
+        int amount = atoi(argument);
+        int timep = atoi(delimiter);
+        if(amount > MAX_FLOOD_AMOUNT || amount < MIN_FLOOD_AMOUNT) {
+            //invalid amount
+            reply(textclient, user, "SS_SET_SENSIBILITY_AMOUNT", amount, MIN_FLOOD_AMOUNT, MAX_FLOOD_AMOUNT);
+            return NULL;
+        }
+        if(timep > MAX_FLOOD_TIME || timep < 0) {
+            //invalid time period
+            reply(textclient, user, "SS_SET_SENSIBILITY_TIME", timep, 0, MAX_FLOOD_TIME);
+            return NULL;
+        }
+        char amountfield[50], timefield[50];
+        sprintf(amountfield, "%s%s", mysqlfield, "limit");
+        sprintf(timefield, "%s%s", mysqlfield, "time");
+        printf_mysql_query("UPDATE `channels` SET `%s` = '%d', `%s` = '%d' WHERE `channel_id` = '%d' ", amountfield, amount, timefield, timep, chan->channel_id);
+        sprintf(retBuf, "%d:%d", amount, timep);
+        chan->spam_settings->sensibility_amount[sensibility_index] = amount;
+        chan->spam_settings->sensibility_time[sensibility_index] = timep;
+    } else {
+        sprintf(retBuf, "%d:%d", chan->spam_settings->sensibility_amount[sensibility_index], chan->spam_settings->sensibility_time[sensibility_index]);
+    }
+    return retBuf;
+}
+
+static char* neonspam_cmd_set_spamlimit(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int intparam, char *charparam, char *argument, char *retBuf) {
+    if(argument) {
+        //change value
+        int amount = atoi(argument);
+        if(amount > 10 || amount < 2) {
+            //invalid amount
+            reply(textclient, user, "SS_SET_SPAMLIMIT", amount, 2, 10);
+            return NULL;
+        }
+        printf_mysql_query("UPDATE `channels` SET `channel_spam_limit` = '%d' WHERE `channel_id` = '%d' ", amount, chan->channel_id);
+        sprintf(retBuf, "%d", amount);
+        chan->spam_settings->spam_amount = amount;
+    } else
+        sprintf(retBuf, "%d", chan->spam_settings->spam_amount);
+    return retBuf;
+}
+
+static char* neonspam_cmd_setscanops(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int flag, char *charparam, char *argument, char *retBuf) {
+    int i = 0;
+    int value = -1;
+    int identical = 1;
+    while(neonspam_settings[i].setting) {
+        if(neonspam_settings[i].flags & SET_SCANOPS) {
+            if(value == -1)
+                value = ((chan->spam_settings->flags & neonspam_settings[i].intparam) ? 1 : 0);
+            else {
+                if(((chan->spam_settings->flags & neonspam_settings[i].intparam) ? 1 : 0) != value) {
+                    identical = 0;
+                    break;
+                }
+            }
+        }
+        i++;
+    }
+    if(argument) {
+        //binary argument...
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            value = 0;
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            value = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argument);
+            return NULL;
+        }
+        i = 0;
+        char valueBuf[MAXLEN];
+        while(neonspam_settings[i].setting) {
+            if(neonspam_settings[i].flags & SET_SCANOPS) {
+                neonspam_cmd_set_function *func = neonspam_settings[i].function;
+                func(client, textclient, user, chan, event, neonspam_settings[i].intparam, neonspam_settings[i].charparam, (value ? "1" : "0"), valueBuf);
+            }
+            i++;
+        }
+        identical = 1;
+    }
+    if(identical && value)
+        return "1";
+    if(identical && !value)
+        return "0";
+    return "?";
+}
+
+static char* neonspam_cmd_setscanvoice(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int flag, char *charparam, char *argument, char *retBuf) {
+    int i = 0;
+    int value = -1;
+    int identical = 1;
+    while(neonspam_settings[i].setting) {
+        if(neonspam_settings[i].flags & SET_SCANVOICE) {
+            if(value == -1)
+                value = ((chan->spam_settings->flags & neonspam_settings[i].intparam) ? 1 : 0);
+            else {
+                if(((chan->spam_settings->flags & neonspam_settings[i].intparam) ? 1 : 0) != value) {
+                    identical = 0;
+                    break;
+                }
+            }
+        }
+        i++;
+    }
+    if(argument) {
+        //binary argument...
+        if(!strcmp(argument, "0") || !stricmp(argument, "off") || !stricmp(argument, get_language_string(user, "NS_SET_OFF"))) {
+            value = 0;
+        } else if(!strcmp(argument, "1") || !stricmp(argument, "on") || !stricmp(argument, get_language_string(user, "NS_SET_ON"))) {
+            value = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argument);
+            return NULL;
+        }
+        i = 0;
+        char valueBuf[MAXLEN];
+        while(neonspam_settings[i].setting) {
+            if(neonspam_settings[i].flags & SET_SCANVOICE) {
+                neonspam_cmd_set_function *func = neonspam_settings[i].function;
+                func(client, textclient, user, chan, event, neonspam_settings[i].intparam, neonspam_settings[i].charparam, (value ? "1" : "0"), valueBuf);
+            }
+            i++;
+        }
+        identical = 1;
+    }
+    if(identical && value)
+        return "1";
+    if(identical && !value)
+        return "0";
+    return "?";
+}
+
+static char* neonspam_cmd_setscanexcept(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, int flag, char *charparam, char *argument, char *retBuf) {
+    int i = 0;
+    int value = -1;
+    int identical = 1;
+    while(neonspam_settings[i].setting) {
+        if(neonspam_settings[i].flags & SET_SCANEXCEPT) {
+            if(value == -1)
+                value = chan->spam_settings->exceptlevel[neonspam_settings[i].intparam];
+            else {
+                if(chan->spam_settings->exceptlevel[neonspam_settings[i].intparam] != value) {
+                    identical = 0;
+                    break;
+                }
+            }
+        }
+        i++;
+    }
+    if(argument) {
+        //numeric argument... (access)
+        int caccess = atoi(argument);
+        if(caccess < 0 || caccess > 501) {
+            reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+            return NULL;
+        }
+        int uaccess = getChannelAccess(user, chan);
+        if(uaccess == 500) uaccess++;
+        if(identical && value > uaccess) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_CANNOT_SET");
+                return NULL;
+            }
+        }
+        if(caccess > uaccess) {
+            if(isGodMode(user)) {
+                event->flags |= CMDFLAG_OPLOG;
+            } else {
+                reply(textclient, user, "NS_SET_BADLEVEL");
+                return NULL;
+            }
+        }
+        i = 0;
+        char valueBuf[MAXLEN];
+        sprintf(retBuf, "%d", caccess);
+        while(neonspam_settings[i].setting) {
+            if(neonspam_settings[i].flags & SET_SCANEXCEPT) {
+                neonspam_cmd_set_function *func = neonspam_settings[i].function;
+                func(client, textclient, user, chan, event, neonspam_settings[i].intparam, neonspam_settings[i].charparam, retBuf, valueBuf);
+            }
+            i++;
+        }
+        identical = 1;
+        value = caccess;
+    }
+    if(identical) {
+        sprintf(retBuf, "%d", value);
+        return retBuf;
+    }
+    return "?";
+}
+
+#undef MAX_QUERY_LEN
diff --git a/src/modules/NeonSpam.mod/event_neonspam_chanmsg.c b/src/modules/NeonSpam.mod/event_neonspam_chanmsg.c
new file mode 100644 (file)
index 0000000..5e7f1ae
--- /dev/null
@@ -0,0 +1,529 @@
+/* event_neonspam_chanmsg.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
+static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser);
+static int neonspam_botnetscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
+static int neonspam_capsscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
+static int neonspam_digitscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message);
+static int neonspam_badwordscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message, int punish_now, int uaccess);
+
+static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup);
+static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, char *message, unsigned int warn, unsigned int punish);
+
+struct neonspam_event_chanmsg_cache {
+    struct ClientSocket *client;
+    struct ChanUser *chanuser;
+    struct NeonSpamSettings *settings;
+    char *message;
+    unsigned int warn;
+    unsigned int punish;
+};
+
+
+
+static void neonspam_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) {
+    struct ClientSocket *client = getChannelBot(chan, BOTID);
+    if(!client || (user->flags & USERFLAG_ISBOT)) return; //we can't "see" this event
+    loadNeonSpamSettings(chan);
+    struct NeonSpamSettings *settings = chan->spam_settings;
+    struct ChanUser *chanuser = getChanUser(user, chan);
+    if(!settings || !chanuser) return;
+    #define NEONSPAM_CHANMSG_DO_SCANOPS(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_OPPED))
+    #define NEONSPAM_CHANMSG_DO_SCANVOICE(FLAG) ((settings->flags & FLAG) || !(chanuser->flags & CHANUSERFLAG_VOICED))
+    #define NEONSPAM_CHANMSG_DO_EXCEPT(INDEX) (settings->exceptlevel[INDEX] != 0)
+    #define NEONSPAM_CHANMSG_NEED_WHO(INDEX) (settings->exceptlevel[INDEX] != 501)
+    //scan the message
+    int result = 0;
+    unsigned int warn = 0;
+    unsigned int punish = 0;
+    int needwho = 0;
+    if((settings->flags & SPAMSETTINGS_SPAMSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_SPAMSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_SPAMSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_SPAMEXCINDEX)) {
+        result = neonspam_spamscan(settings, chanuser, message);
+        switch(result) {
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+            case SPAMSERV_CHECK_WARN:
+                warn |= SPAMSETTINGS_SPAMSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
+                    needwho = 1;
+                break;
+            case SPAMSERV_CHECK_PUNISH:
+                punish |= SPAMSETTINGS_SPAMSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_SPAMEXCINDEX))
+                    needwho = 1;
+                break;
+        }
+    }
+    if((settings->flags & SPAMSETTINGS_FLOODSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_FLOODSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_FLOODSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_FLOODEXCINDEX)) {
+        result = neonspam_floodscan(settings, chanuser);
+        switch(result) {
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+            case SPAMSERV_CHECK_WARN:
+                warn |= SPAMSETTINGS_FLOODSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
+                    needwho = 1;
+                break;
+            case SPAMSERV_CHECK_PUNISH:
+                punish |= SPAMSETTINGS_FLOODSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_FLOODEXCINDEX))
+                    needwho = 1;
+                break;
+        }
+    }
+    if((settings->flags & SPAMSETTINGS_BOTNETSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_BOTNETSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_BOTNETSCAN_VOICE)) {
+        result = neonspam_botnetscan(client, settings, chanuser, message);
+        switch(result) {
+            case SPAMSERV_CHECK_DEAD:
+                return;
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+        }
+    }
+    if((settings->flags & SPAMSETTINGS_CAPSSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_CAPSSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_CAPSSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_CAPSEXCINDEX)) {
+        result = neonspam_capsscan(settings, chanuser, message);
+        switch(result) {
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+            case SPAMSERV_CHECK_WARN:
+                warn |= SPAMSETTINGS_CAPSSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_CAPSEXCINDEX))
+                    needwho = 1;
+                break;
+            case SPAMSERV_CHECK_PUNISH:
+                punish |= SPAMSETTINGS_CAPSSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_CAPSEXCINDEX))
+                    needwho = 1;
+                break;
+        }
+    }
+    if((settings->flags & SPAMSETTINGS_DIGITSCAN) && NEONSPAM_CHANMSG_DO_SCANOPS(SPAMSETTINGS_DIGITSCAN_OPS) && NEONSPAM_CHANMSG_DO_SCANVOICE(SPAMSETTINGS_DIGITSCAN_VOICE) && NEONSPAM_CHANMSG_DO_EXCEPT(SPAMSETTINGS_DIGITEXCINDEX)) {
+        result = neonspam_digitscan(settings, chanuser, message);
+        switch(result) {
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+            case SPAMSERV_CHECK_WARN:
+                warn |= SPAMSETTINGS_DIGITSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_DIGITEXCINDEX))
+                    needwho = 1;
+                break;
+            case SPAMSERV_CHECK_PUNISH:
+                punish |= SPAMSETTINGS_DIGITSCAN;
+                if(NEONSPAM_CHANMSG_NEED_WHO(SPAMSETTINGS_DIGITEXCINDEX))
+                    needwho = 1;
+                break;
+        }
+    }
+    if((settings->flags & SPAMSETTINGS_BADWORDSCAN)) {
+        if(user->flags & USERFLAG_ISAUTHED) {
+            result = neonspam_badwordscan(client, settings, chanuser, message, 1, getChannelAccess(chanuser->user, chanuser->chan));
+        } else
+            result = neonspam_badwordscan(client, settings, chanuser, message, 0, 0);
+        switch(result) {
+            case SPAMSERV_CHECK_DEAD:
+                return;
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+            case SPAMSERV_CHECK_PUNISH:
+                punish |= SPAMSETTINGS_BADWORDSCAN;
+                needwho = 1;
+                break;
+        }
+    }
+    //some other checks?
+    
+    if(warn || punish) {
+        //whois the user to check against exceptlevel
+        if(!needwho || (user->flags & USERFLAG_ISAUTHED)) {
+            neonspam_event_chanmsg_punish(client, chanuser, settings, message, warn, punish);
+        } else {
+            struct neonspam_event_chanmsg_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonspam", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->chanuser = chanuser;
+            cache->settings = settings;
+            cache->message = strdup(message);
+            cache->warn = warn;
+            cache->punish = punish;
+            get_userauth(user, module_id, neonspam_event_chanmsg_nick_lookup, cache);
+        }
+        
+    }
+    #undef NEONSPAM_CHANMSG_DO_SCANOPS
+    #undef NEONSPAM_CHANMSG_DO_SCANVOICE
+    #undef NEONSPAM_CHANMSG_DO_EXCEPT
+    #undef NEONSPAM_CHANMSG_NEED_WHO
+}
+
+static USERAUTH_CALLBACK(neonspam_event_chanmsg_nick_lookup) {
+    struct neonspam_event_chanmsg_cache *cache = data;
+    neonspam_event_chanmsg_punish(cache->client, cache->chanuser, cache->settings, cache->message, cache->warn, cache->punish);
+    free(cache->message);
+    free(cache);
+}
+
+static void neonspam_event_chanmsg_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, char *message, unsigned int warn, unsigned int punish) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults;
+    loadChannelSettings(chanuser->chan);
+    int uaccess = 0;
+    if(chanuser->user->flags & USERFLAG_ISAUTHED)
+        uaccess = getChannelAccess(chanuser->user, chanuser->chan);
+    char reason[MAXLEN];
+    reason[0] = '\0';
+    int punishment = 0;
+    int punish_time = 0;
+    if(!punishment && (punish & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
+        printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_spam_reaction`, `channel_spam_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
+        punishment = atoi((row[0] ? row[0] : defaults[0])) + 1;
+        punish_time = atoi((row[1] ? row[1] : defaults[1]));
+    }
+    if(!punishment && (punish & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
+        printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
+        punishment = atoi((row[0] ? row[0] : defaults[0])) + 1;
+        punish_time = atoi((row[1] ? row[1] : defaults[1]));
+    }
+    if(!punishment && (punish & SPAMSETTINGS_CAPSSCAN) && settings->exceptlevel[SPAMSETTINGS_CAPSEXCINDEX] > uaccess) {
+        printf_mysql_query("SELECT `channel_caps_reaction`, `channel_caps_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_caps_reaction`, `channel_caps_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_CAPS);
+        punishment = atoi((row[0] ? row[0] : defaults[0])) + 1;
+        punish_time = atoi((row[1] ? row[1] : defaults[1]));
+    }
+    if(!punishment && (punish & SPAMSETTINGS_DIGITSCAN) && settings->exceptlevel[SPAMSETTINGS_DIGITEXCINDEX] > uaccess) {
+        printf_mysql_query("SELECT `channel_digit_reaction`, `channel_digit_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_digit_reaction`, `channel_digit_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_DIGIT);
+        punishment = atoi((row[0] ? row[0] : defaults[0])) + 1;
+        punish_time = atoi((row[1] ? row[1] : defaults[1]));
+    }
+    if(!punishment && (punish & SPAMSETTINGS_BADWORDSCAN)) {
+        int result = neonspam_badwordscan(client, settings, chanuser, message, 1, uaccess);
+        switch(result) {
+            case SPAMSERV_CHECK_DEAD:
+                return;
+            case SPAMSERV_CHECK_IGNORE:
+                break;
+        }
+        
+    }
+    if(!punishment && (warn & SPAMSETTINGS_SPAMSCAN) && settings->exceptlevel[SPAMSETTINGS_SPAMEXCINDEX] > uaccess) {
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_SPAM);
+    }
+    if(!punishment && (warn & SPAMSETTINGS_FLOODSCAN) && settings->exceptlevel[SPAMSETTINGS_FLOODEXCINDEX] > uaccess) {
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_FLOOD);
+    }
+    if(!punishment && (warn & SPAMSETTINGS_CAPSSCAN) && settings->exceptlevel[SPAMSETTINGS_CAPSEXCINDEX] > uaccess) {
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_CAPS);
+    }
+    if(!punishment && (warn & SPAMSETTINGS_DIGITSCAN) && settings->exceptlevel[SPAMSETTINGS_DIGITEXCINDEX] > uaccess) {
+        sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_DIGIT);
+    }
+    if(punishment) {
+        char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
+        char *banmask = NULL;
+        switch (punishment) {
+            case 3: //TIMEBAN
+                banmask = generate_banmask(chanuser->user, banmaskBuf);
+                printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chanuser->chan->channel_id, escape_string(banmask), (unsigned long) (punish_time ? (time(0) + punish_time) : 0), 0, escape_string(reason));
+                if(punish_time) {
+                    int banid = (int) mysql_insert_id(get_mysql_conn());
+                    char nameBuf[MAXLEN];
+                    char banidBuf[20];
+                    sprintf(nameBuf, "ban_%d", banid);
+                    sprintf(banidBuf, "%d", banid);
+                    timeq_add_name(nameBuf, punish_time, module_id, channel_ban_timeout, strdup(banidBuf));
+                }
+            case 2: //KICKBAN
+                if(!banmask)
+                    banmask = generate_banmask(chanuser->user, banmaskBuf);
+                putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
+            case 1: //KICK
+                putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason);
+                break;
+        }
+    } else if(*reason)
+        reply(client, chanuser->user, "%s", reason);
+}
+
+static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
+    //crc32 hash of the message
+    unsigned long msghash = crc32(message);
+    if(chanuser->spamnode) {
+        if(chanuser->spamnode->lastmsg == msghash) {
+            chanuser->spamnode->spamcount++;
+            if(chanuser->spamnode->spamcount == settings->spam_amount)
+                return SPAMSERV_CHECK_WARN;
+            else if(chanuser->spamnode->spamcount > settings->spam_amount)
+                return SPAMSERV_CHECK_PUNISH;
+            else
+                return SPAMSERV_CHECK_IGNORE;
+        }
+    } else
+        createSpamNode(chanuser);
+    chanuser->spamnode->lastmsg = msghash;
+    chanuser->spamnode->spamcount = 1;
+    return SPAMSERV_CHECK_IGNORE;
+}
+
+static int neonspam_update_penalty(struct NeonSpamSettings *settings, struct ChanUser *chanuser, int addmsg) {
+    int last_update = time(0) - chanuser->spamnode->last_penalty_update;
+    if(last_update) {
+        if(last_update < MAX_FLOOD_TIME && chanuser->spamnode->floodpenalty) {
+            chanuser->spamnode->floodpenalty -= last_update * (MAX_FLOOD_TIME / settings->sensibility_time[SPAMSETTINGS_FLOODSENINDEX]);
+            if(chanuser->spamnode->floodpenalty < 0)
+                chanuser->spamnode->floodpenalty = 0;
+        } else
+            chanuser->spamnode->floodpenalty = 0;
+        chanuser->spamnode->last_penalty_update = time(0);
+    }
+    chanuser->spamnode->floodpenalty += MAX_FLOOD_TIME * addmsg;
+    return chanuser->spamnode->floodpenalty / MAX_FLOOD_TIME + ((chanuser->spamnode->floodpenalty % MAX_FLOOD_TIME) ? 1 : 0);
+}
+
+static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) {
+    if(!chanuser->spamnode)
+        createSpamNode(chanuser);
+    int messages_pending = neonspam_update_penalty(settings, chanuser, 1);
+    if(messages_pending == settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
+        return SPAMSERV_CHECK_WARN;
+    else if(messages_pending > settings->sensibility_amount[SPAMSETTINGS_FLOODSENINDEX])
+        return SPAMSERV_CHECK_PUNISH;
+    else
+        return SPAMSERV_CHECK_IGNORE;
+}
+
+static int neonspam_botnetscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
+    //crc32 hash of the message
+    unsigned long msghash = crc32(message);
+    if((time(0) - settings->lastmsg_time) > BOTNETSCAN_TIME || settings->lastmsg != msghash) {
+        int i;
+        for(i = 0; i < BOTNETSCAN_USERS; i++) {
+            if(settings->botnicks[i]) {
+                free(settings->botnicks[i]);
+                settings->botnicks[i] = NULL;
+            }
+        }
+        settings->flags &= ~SPAMSETTINGS_KICKEDBOTQUEUE;
+        settings->lastmsg = msghash;
+    } else if(settings->lastmsg == msghash) {
+        int i;
+        for(i = 0; i < BOTNETSCAN_USERS; i++) {
+            if(!settings->botnicks[i]) {
+                settings->botnicks[i] = strdup(chanuser->user->nick);
+                break;
+            } else if(!stricmp(chanuser->user->nick, settings->botnicks[i])) {
+                return SPAMSERV_CHECK_IGNORE;
+            }
+        }
+        if(i == BOTNETSCAN_USERS) {
+            //BOTNETSCAN_USERS exceeded
+            if(!(settings->flags & SPAMSETTINGS_KICKEDBOTQUEUE)) {
+                for(i = 0; i < BOTNETSCAN_USERS; i++) {
+                    putsock(client, "KICK %s %s :%s", chanuser->chan->name, settings->botnicks[i], SPAMSERV_MSG_BOTNET);
+                }
+                settings->flags |= SPAMSETTINGS_KICKEDBOTQUEUE;
+            }
+            putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, SPAMSERV_MSG_BOTNET);
+            return SPAMSERV_CHECK_DEAD;
+        }
+    }
+    settings->lastmsg_time = time(0);
+    return SPAMSERV_CHECK_IGNORE;
+}
+
+static int neonspam_capsscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
+    int caps = 0, msglen = strlen(message);
+    int i;
+    if(msglen <= 4) return SPAMSERV_CHECK_IGNORE;
+    for(i = 0; i < msglen; i++) {
+        if(isupper(message[i])) caps++;
+    }
+    caps = 100*caps/msglen;
+    if(caps >= settings->percent[SPAMSETTINGS_CAPSPERCENTINDEX]) {
+        if(!chanuser->spamnode)
+            createSpamNode(chanuser);
+        if(chanuser->spamnode->flags & NEONSPAMNODE_FLAG_CAPSSCAN_WARNED)
+            return SPAMSERV_CHECK_PUNISH;
+        else {
+            chanuser->spamnode->flags |= NEONSPAMNODE_FLAG_CAPSSCAN_WARNED;
+            return SPAMSERV_CHECK_WARN;
+        }
+    }
+    return SPAMSERV_CHECK_IGNORE;
+}
+
+static int neonspam_digitscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) {
+    int digit = 0, msglen = strlen(message);
+    int i;
+    if(msglen <= 4) return SPAMSERV_CHECK_IGNORE;
+    for(i = 0; i < msglen; i++) {
+        if(isdigit(message[i])) digit++;
+    }
+    digit = 100*digit/msglen;
+    if(digit >= settings->percent[SPAMSETTINGS_DIGITPERCENTINDEX]) {
+        if(!chanuser->spamnode)
+            createSpamNode(chanuser);
+        if(chanuser->spamnode->flags & NEONSPAMNODE_FLAG_DIGITSCAN_WARNED)
+            return SPAMSERV_CHECK_PUNISH;
+        else {
+            chanuser->spamnode->flags |= NEONSPAMNODE_FLAG_DIGITSCAN_WARNED;
+            return SPAMSERV_CHECK_WARN;
+        }
+    }
+    return SPAMSERV_CHECK_IGNORE;
+}
+
+static int neonspam_badwordscan(struct ClientSocket *client, struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message, int punish_now, int uaccess) {
+    struct NeonSpamBadword *badword;
+    int kick_user = 0;
+    int ban_user = 0;
+    int staticban_user = 0;
+    int ban_duration = 0;
+    int checked_defaults = 0;
+    int apply_default_reaction = 0;
+    for(badword = settings->badwords; badword; badword = badword->next) {
+        if(!match(badword->badword, message)) {
+            if(badword->use_defaults) {
+                if(checked_defaults == 2) continue;
+                else if(!checked_defaults) {
+                    if(!(settings->flags & SPAMSETTINGS_BADWORDSCAN_OPS) && (chanuser->flags & CHANUSERFLAG_OPPED)) {
+                        checked_defaults = 2;
+                        continue;
+                    }
+                    if(!(settings->flags & SPAMSETTINGS_BADWORDSCAN_VOICE) && (chanuser->flags & CHANUSERFLAG_VOICED)) {
+                        checked_defaults = 2;
+                        continue;
+                    }
+                    if(settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX] && settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX] < 501) {
+                        if(!punish_now)
+                            return SPAMSERV_CHECK_PUNISH;
+                        if(settings->exceptlevel[SPAMSETTINGS_BADWORDEXCINDEX] <= uaccess) {
+                            checked_defaults = 2;
+                            continue;
+                        }
+                    }
+                    checked_defaults = 1;
+                }
+            } else {
+                if(!badword->scan_ops && (chanuser->flags & CHANUSERFLAG_OPPED)) continue;
+                if(!badword->scan_voice && (chanuser->flags & CHANUSERFLAG_VOICED)) continue;
+                if(badword->exceptlevel && badword->exceptlevel < 501) {
+                    if(!punish_now)
+                        return SPAMSERV_CHECK_PUNISH;
+                    if(badword->exceptlevel <= uaccess) {
+                        checked_defaults = 2;
+                        continue;
+                    }
+                }
+            }
+            if(badword->use_default_reaction) {
+                apply_default_reaction = 1;
+            } else {
+                switch(badword->reaction) {
+                case 2:
+                    staticban_user = 1;
+                    if(badword->reaction_time > ban_duration)
+                        ban_duration = badword->reaction_time;
+                case 1:
+                    ban_user = 1;
+                case 0:
+                    kick_user = 1;
+                }
+            }
+        }
+    }
+    if(apply_default_reaction) {
+        MYSQL_RES *res;
+        MYSQL_ROW row, defaults;
+        loadChannelSettings(chanuser->chan);
+        printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row[0] || !row[1]) {
+            printf_mysql_query("SELECT `channel_badword_reaction`, `channel_badword_reaction_duration` FROM `channels` WHERE `channel_name` = 'defaults'");
+            res = mysql_use();
+            defaults = mysql_fetch_row(res);
+        }
+        int reaction = atoi((row[0] ? row[0] : defaults[0]));
+        int reaction_time = atoi((row[1] ? row[1] : defaults[1]));
+        switch(reaction) {
+        case 2:
+            staticban_user = 1;
+            if(reaction_time > ban_duration)
+                ban_duration = reaction_time;
+        case 1:
+            ban_user = 1;
+        case 0:
+            kick_user = 1;
+        }
+    }
+    if(!kick_user) return SPAMSERV_CHECK_IGNORE;
+    char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
+    char *banmask = NULL;
+    if(staticban_user) {
+        banmask = generate_banmask(chanuser->user, banmaskBuf);
+        printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chanuser->chan->channel_id, escape_string(banmask), (unsigned long) (ban_duration ? (time(0) + ban_duration) : 0), 0, escape_string(SPAMSERV_MSG_BADWORD));
+        if(ban_duration) {
+            int banid = (int) mysql_insert_id(get_mysql_conn());
+            char nameBuf[MAXLEN];
+            char banidBuf[20];
+            sprintf(nameBuf, "ban_%d", banid);
+            sprintf(banidBuf, "%d", banid);
+            timeq_add_name(nameBuf, ban_duration, module_id, channel_ban_timeout, strdup(banidBuf));
+        }
+    }
+    if(ban_user) {
+        if(!banmask)
+            banmask = generate_banmask(chanuser->user, banmaskBuf);
+        putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
+    }
+    putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, SPAMSERV_MSG_BADWORD);
+    return SPAMSERV_CHECK_DEAD;
+}
diff --git a/src/modules/NeonSpam.mod/event_neonspam_join.c b/src/modules/NeonSpam.mod/event_neonspam_join.c
new file mode 100644 (file)
index 0000000..f34b1a3
--- /dev/null
@@ -0,0 +1,144 @@
+/* event_neonspam_join.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static USERAUTH_CALLBACK(neonspam_event_join_nick_lookup);
+static void neonspam_event_join_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, int action);
+static int neonspam_joinfloodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser);
+
+struct neonspam_event_join_cache {
+    struct ClientSocket *client;
+    struct ChanUser *chanuser;
+    struct NeonSpamSettings *settings;
+    int action;
+};
+
+static void neonspam_event_join(struct ChanUser *chanuser) {
+    struct ClientSocket *client = getChannelBot(chanuser->chan, BOTID);
+    if(!client) return; //we can't "see" this event
+    if(chanuser->user == client->user) {
+        requestOp(client->user, chanuser->chan);
+        return;
+    }
+    if(chanuser->user->flags & USERFLAG_ISBOT) return;
+    loadNeonSpamSettings(chanuser->chan);
+    struct NeonSpamSettings *settings = chanuser->chan->spam_settings;
+    if(!settings || !(settings->flags & SPAMSETTINGS_JOINSCAN)) return;
+    if(settings->exceptlevel[SPAMSETTINGS_JOINEXCINDEX] == 0) return;
+    int result = neonspam_joinfloodscan(settings, chanuser);
+    if(result != SPAMSERV_CHECK_IGNORE) {
+        //whois the user to check against exceptlevel
+        if((chanuser->user->flags & USERFLAG_ISAUTHED) || settings->exceptlevel[SPAMSETTINGS_JOINEXCINDEX] == 501) {
+            neonspam_event_join_punish(client, chanuser, settings, result);
+        } else {
+            struct neonspam_event_join_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("neonspam", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->chanuser = chanuser;
+            cache->settings = settings;
+            cache->action = result;
+            get_userauth(chanuser->user, module_id, neonspam_event_join_nick_lookup, cache);
+        }
+    }
+}
+
+static USERAUTH_CALLBACK(neonspam_event_join_nick_lookup) {
+    struct neonspam_event_join_cache *cache = data;
+    neonspam_event_join_punish(cache->client, cache->chanuser, cache->settings, cache->action);
+    free(cache);
+}
+
+static void neonspam_event_join_punish(struct ClientSocket *client, struct ChanUser *chanuser, struct NeonSpamSettings *settings, int action) {
+    int uaccess = 0;
+    if(chanuser->user->flags & USERFLAG_ISAUTHED)
+        uaccess = getChannelAccess(chanuser->user, chanuser->chan);
+    if(uaccess >= settings->exceptlevel[SPAMSETTINGS_JOINEXCINDEX]) return;
+    //scanops / scanvoiced
+    MYSQL_RES *res;
+    MYSQL_ROW row, defaults = NULL;
+    loadChannelSettings(chanuser->chan);
+    printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration`, `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_id` = '%d'", chanuser->chan->channel_id);
+    res = mysql_use();
+    row = mysql_fetch_row(res);
+    if(!row[0] || !row[1] || !row[2] || !row[3]) {
+        printf_mysql_query("SELECT `channel_flood_reaction`, `channel_flood_reaction_duration`, `channel_getop`, `channel_getvoice` FROM `channels` WHERE `channel_name` = 'defaults'");
+        res = mysql_use();
+        defaults = mysql_fetch_row(res);
+    }
+    if(!(settings->flags & SPAMSETTINGS_JOINSCAN_OPS) && uaccess >= atoi((row[2] ? row[2] : defaults[2]))) return;
+    if(!(settings->flags & SPAMSETTINGS_JOINSCAN_VOICE) && uaccess >= atoi((row[3] ? row[3] : defaults[3]))) return;
+    char reason[MAXLEN];
+    sprintf(reason, SPAMSERV_MSG_WARNING, SPAMSERV_MSG_JOINFLOOD);
+    if(action == SPAMSERV_CHECK_WARN) {
+        reply(client, chanuser->user, "%s", reason);
+    } else if(action == SPAMSERV_CHECK_PUNISH) {
+        int duration = atoi((row[1] ? row[1] : defaults[1]));
+        char banmaskBuf[NICKLEN+USERLEN+HOSTLEN+3];
+        char *banmask = NULL;
+        switch (atoi((row[0] ? row[0] : defaults[0]))) {
+            case 2: //TIMEBAN
+                banmask = generate_banmask(chanuser->user, banmaskBuf);
+                printf_mysql_query("INSERT INTO `bans` (`ban_channel`, `ban_mask`, `ban_triggered`, `ban_timeout`, `ban_owner`, `ban_reason`) VALUES ('%d', '%s', UNIX_TIMESTAMP(), '%lu', '%d', '%s')", chanuser->chan->channel_id, escape_string(banmask), (unsigned long) (duration ? (time(0) + duration) : 0), 0, escape_string(reason));
+                if(duration) {
+                    int banid = (int) mysql_insert_id(get_mysql_conn());
+                    char nameBuf[MAXLEN];
+                    char banidBuf[20];
+                    sprintf(nameBuf, "ban_%d", banid);
+                    sprintf(banidBuf, "%d", banid);
+                    timeq_add_name(nameBuf, duration, module_id, channel_ban_timeout, strdup(banidBuf));
+                }
+            case 1: //KICKBAN
+                if(!banmask)
+                    banmask = generate_banmask(chanuser->user, banmaskBuf);
+                putsock(client, "MODE %s +b %s", chanuser->chan->name, banmask);
+            case 0: //KICK
+                putsock(client, "KICK %s %s :%s", chanuser->chan->name, chanuser->user->nick, reason);
+                break;
+        }
+    }
+}
+
+static int neonspam_update_join_penalty(struct NeonSpamSettings *settings, struct NeonSpamJoinNode *joinnode, int addjoin) {
+    int last_update = time(0) - joinnode->last_penalty_update;
+    if(last_update) {
+        if(last_update < MAX_JOIN_TIME && joinnode->joinpenalty) {
+            joinnode->joinpenalty -= last_update * (MAX_JOIN_TIME / settings->sensibility_time[SPAMSETTINGS_JOINSENINDEX]);
+            if(joinnode->joinpenalty < 0)
+                joinnode->joinpenalty = 0;
+        } else
+            joinnode->joinpenalty = 0;
+        joinnode->last_penalty_update = time(0);
+    }
+    joinnode->joinpenalty += MAX_JOIN_TIME * addjoin;
+    return joinnode->joinpenalty / MAX_JOIN_TIME + ((joinnode->joinpenalty % MAX_JOIN_TIME) ? 1 : 0);
+}
+
+static int neonspam_joinfloodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) {
+    if(!chanuser->spamnode)
+        createSpamNode(chanuser);
+    int joins_pending = neonspam_update_join_penalty(settings, getNeonSpamJoinNode(chanuser), 1);
+    if(joins_pending == settings->sensibility_amount[SPAMSETTINGS_JOINSENINDEX])
+        return SPAMSERV_CHECK_WARN;
+    else if(joins_pending > settings->sensibility_amount[SPAMSETTINGS_JOINSENINDEX])
+        return SPAMSERV_CHECK_PUNISH;
+    else
+        return SPAMSERV_CHECK_IGNORE;
+}
+
+
diff --git a/src/modules/NeonSpam.mod/module.c b/src/modules/NeonSpam.mod/module.c
new file mode 100644 (file)
index 0000000..c731d74
--- /dev/null
@@ -0,0 +1,34 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "bot_NeonSpam.h"
+#include "cmd_neonspam.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    init_NeonSpam(type);
+}
+
+static void module_stop(int type) {
+    free_NeonSpam(type);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/botid.h b/src/modules/botid.h
new file mode 100644 (file)
index 0000000..319f7cf
--- /dev/null
@@ -0,0 +1,28 @@
+/* botid.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _botid_h
+#define _botid_h
+
+#define NEONSERV_BOTID 1
+#define NEONSPAM_BOTID 2
+#define DUMMYSERV_BOTID 3
+#define NEONHELP_BOTID 4
+#define NEONFUN_BOTID 5
+#define NEONBACKUP_BOTID 6
+#define NEONKICK_BOTID 7
+
+#endif
\ No newline at end of file
diff --git a/src/modules/funcmd.mod/cmd_funcmds.c b/src/modules/funcmd.mod/cmd_funcmds.c
new file mode 100644 (file)
index 0000000..eda55cc
--- /dev/null
@@ -0,0 +1,306 @@
+/* cmd_funcmds.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_funcmds.h"
+#include "../../modcmd.h"
+#include "../../mysqlConn.h"
+#include "../../IRCParser.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../lang.h"
+#include "../../tools.h"
+#include "../../DBHelper.h"
+#include "../../IRCEvents.h"
+#include "../../timeq.h"
+#include "../../WHOHandler.h"
+#include "../../EventLogger.h"
+
+static const struct default_language_entry msgtab[] = {
+    {"FUN_DICE", "$b%s$b: A $b%d$b shows on the %d-sided die."}, /* {ARGS: "TestUser", 5, 6} */
+    {"FUN_DICE_NUM", "I do not understand $b%s$b. Please use a single number above 1."}, /* {ARGS: "bla"} */
+    {"FUN_8BALL", "$b%s$b: %s"}, /* {ARGS: "TestUser", "Not a chance."} */
+    {"FUN_8BALL_REPLIES", "Not a chance.|In your dreams.|Absolutely!|Could be, could be.|No!"},
+    {"FUN_COOKIE", "gives %1$s a very big chocolate cookie. %1$s has got %2$d cookies until now (%3$d in this channel)."}, /* {ARGS: "TestUser", 20, 50} */
+    {"FUN_BOMB_MENU", "There are the following commands:"},
+    {"FUN_BOMB_MENU_PLANT",  "$b%s plant <user> [wires] $b Plant a bomb in front of another users feet ([wires] is the number of wires - default: %d)"}, /* {ARGS: "bomb", 3} */
+    {"FUN_BOMB_MENU_KPLANT", "$b%s kplant <user> [wires]$b Plant a kick-bomb in front of another users feet ([wires] is the number of wires - default: %d) (OP only)"}, /* {ARGS: "bomb", 3} */
+    {"FUN_BOMB_MENU_DEFUSE", "$b%s defuse <wire>        $b Defuses a bomb (maybe :D)"}, /* {ARGS: "bomb"} */
+    {"FUN_BOMB_MENU_RETURN", "$b%s return               $b Pushes a bomb planted infront of you back to the owner ($k4WARNING$k: There is a highly chance that the bomb detonates)"}, /* {ARGS: "bomb"} */
+    {"FUN_BOMB_SELF", "You can not plant a bomb in front of yourself..."},
+    {"FUN_BOMB_TARGET_ACTIVE", "%s has already an active bomb..."},
+    {"FUN_BOMB_OWNER_ACTIVE", "You have already planted another bomb..."},
+    {"FUN_BOMB_PLANTED", "%1$s planted a bomb in front of %2$s's feet. %2$s, you have $b%3$d seconds$b to defuse the bomb by cutting one of the following wires: %4$s ($uuse:$u %5$s defuse <wire>)"}, /* {ARGS: "TestUser", "TestTarget", 30, "blue, red, green", "bomb"} */
+    {"FUN_BOMB_KPLANTED", "%1$s planted a kick-bomb in front of %2$s's feet. %2$s, you have $b%3$d seconds$b to defuse the bomb by cutting one of the following wires: %4$s ($uuse:$u %5$s defuse <wire>)"}, /* {ARGS: "TestUser", "TestTarget", 30, "blue, red, green", "bomb"} */
+    {"FUN_BOMB_PLANT_SERVICE", "You can't plant a bomb in front of this user (bot)..."},
+    {"FUN_BOMB_PLANT_PROTECTED", "You can't plant a bomb in front of this user (protect)..."},
+    {"FUN_BOMB_PLANT_MAXWIRES", "A bomb can only have %d-%d wires!"}, /* {ARGS: 2, 8} */
+    {"FUN_BOMB_WIRES", "$k4red$k|$k3green$k|$k8yellow$k|$k12blue$k|$k1black$k|$k6purple$k|$k7orange$k|$k11aqua$k"},
+    {"FUN_BOMB_NOBOMB", "There is no bomb you could defuse..."},
+    {"FUN_BOMB_UNKNOWN_WIRE", "%s is an unknown wire..."}, /* {ARGS: "white"} */
+    {"FUN_BOMB_DEFUSED", "%1$s has successfully defused the bomb! %1$s got %2$d bombs (%3$d in this channel) and has defused %4$d of them."}, /* {ARGS: "TestUser", 20, 10, 5} */
+    {"FUN_BOMB_DETONATED", "*BOOOOOOM* The bomb explodes in front of %1$s!!! %2$s was the right wire. %1$s got %3$d bombs (%4$d in this channel)"}, /* {ARGS: "TestUser", "white", 20, 10} */
+    {NULL, NULL}
+};
+
+#define FUNCMD_BOMB_WIRES_DEFAULT 3
+#define FUNCMD_BOMB_WIRES_MIN     2
+#define FUNCMD_BOMB_WIRES_MAX     8
+#define FUNCMD_BOMB_TIME          30
+
+static int funcmd_bomb_freeuser(struct UserNode *user);
+static int funcmd_bomb_freechan(struct ChanNode *chan);
+static void funcmd_bomb_freeclient(struct ClientSocket *client);
+
+void init_funcmds() {
+    register_default_language_table(msgtab);
+    srand(time(NULL));
+    bind_freeuser(funcmd_bomb_freeuser, module_id);
+    bind_freechan(funcmd_bomb_freechan, module_id);
+    bind_freeclient(funcmd_bomb_freeclient, module_id);
+}
+
+void register_commands() {
+    //Fun Commands
+    register_command_alias(3, "FunCMD");
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,FLAGS) register_command(3, NAME, module_id, FUNCTION, PARAMCOUNT, NULL, 0, FLAGS)
+    //               NAME              FUNCTION        PARAMS   FLAGS
+    //USER_COMMAND("extscript",    neonserv_cmd_extscript, 0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_EMPTY_ARGS | CMDFLAG_CHAN_PARAM | CMDFLAG_FUNCMD);
+    USER_COMMAND("ping",         funcmd_ping,            0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("pong",         funcmd_pong,            0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("dice",         funcmd_dice,            1,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("8ball",        funcmd_8ball,           1,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("cookie",       funcmd_cookie,          0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("bomb",         funcmd_bomb,            0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    USER_COMMAND("ship",         funcmd_ship,            0,  CMDFLAG_REQUIRE_CHAN | CMDFLAG_REGISTERED_CHAN | CMDFLAG_FUNCMD);
+    #undef USER_COMMAND
+}
+
+struct funcmd_header_info {
+    struct ClientSocket *client;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    char send_notice;
+    char null_language;
+};
+
+#define FUNCMD_HEADER \
+struct funcmd_header_info *header = malloc(sizeof(*header)); \
+header->client = textclient; \
+header->user = user; \
+header->chan = chan; \
+header->null_language = 0; \
+{\
+    MYSQL_RES *res; \
+    MYSQL_ROW row; \
+    printf_mysql_query("SELECT `channel_toys` FROM `channels` WHERE `channel_name` = '%s'", escape_string(chan->name)); \
+    res = mysql_use(); \
+    row = mysql_fetch_row(res); \
+    if(!row || !strcmp(row[0], "0")) { \
+        reply(textclient, user, "NS_FUN_DISABLED", chan->name); \
+        return; \
+    } else if(!strcmp(row[0], "1")) \
+        header->send_notice = 1; \
+    else \
+        header->send_notice = 0; \
+}
+
+#define FUNCMD_FOOTER \
+free(header);
+
+#define REPLYTYPE_NORMAL 0x01
+#define REPLYTYPE_ACTION 0x02
+#define REPLYTYPE_NOTICE 0x04
+static void funcmd_reply(struct funcmd_header_info *header, const char *text, int type, ...) {
+    if (!(header->client->flags & SOCKET_FLAG_CONNECTED)) return;
+    struct ClientSocket *client = header->client;
+    const char *reply_format = get_language_string((header->null_language ? NULL : header->user), text);
+    if(reply_format)
+        text = reply_format;
+    char formatBuf[MAXLEN];
+    if(header->send_notice || (type & REPLYTYPE_NOTICE)) {
+        if(type & REPLYTYPE_ACTION)
+            sprintf(formatBuf, "NOTICE %s :%s %s", header->user->nick, header->client->user->nick, text);
+        else
+            sprintf(formatBuf, "NOTICE %s :%s", header->user->nick, text);
+    } else {
+        if(type & REPLYTYPE_ACTION)
+            sprintf(formatBuf, "PRIVMSG %s :\001ACTION %s\001", header->chan->name, text);
+        else
+            sprintf(formatBuf, "PRIVMSG %s :%s", header->chan->name, text);
+        if(!isUserOnChan(client->user, header->chan) && isModeSet(header->chan->modes, 'n'))
+            client = getChannelBot(header->chan, 0);
+    }
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    sendBuf[0] = '\0';
+    va_start(arg_list, type);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, formatBuf, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    write_socket(client, sendBuf, pos+1);
+}
+
+static char* getSetting(struct UserNode *user, struct ChanNode *chan, const char *setting) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        return row[0];
+    } else
+        return NULL;
+}
+
+static void setSetting(struct UserNode *user, struct ChanNode *chan, const char *setting, const char *value) {
+    char *uname = "";
+    int cid = 0;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(user) {
+        uname = ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "*");
+    }
+    if(chan) {
+        loadChannelSettings(chan);
+        if(chan->flags & CHANFLAG_CHAN_REGISTERED)
+            cid = chan->channel_id;
+    }
+    printf_mysql_query("SELECT `id`, `value` FROM `fundata` WHERE `user` = '%s' AND `cid` = '%d' AND `name` = '%s'", escape_string(uname), cid, escape_string(setting));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(strcmp(row[1], value))
+            printf_mysql_query("UPDATE `fundata` SET `value` = '%s' WHERE `id` = '%s'", escape_string(value), row[0]);
+    } else
+        printf_mysql_query("INSERT INTO `fundata` (`user`, `cid`, `name`, `value`) VALUES ('%s', '%d', '%s', '%s')", escape_string(uname), cid, escape_string(setting), escape_string(value));
+}
+
+#include "cmd_funcmds_bomb.c"
+
+
+CMD_BIND(funcmd_ping) {
+    FUNCMD_HEADER;
+    funcmd_reply(header, "\002%s\002: Pong!", REPLYTYPE_NORMAL, user->nick);
+    FUNCMD_FOOTER;
+}
+
+CMD_BIND(funcmd_pong) {
+    FUNCMD_HEADER;
+    funcmd_reply(header, "\002%s\002: Ping!", REPLYTYPE_NORMAL, user->nick);
+    FUNCMD_FOOTER;
+}
+
+CMD_BIND(funcmd_dice) {
+    FUNCMD_HEADER;
+    int max = atoi(argv[0]);
+    if(max > 1) {
+        int val = (rand() % max) + 1;
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "     ____");
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "    /\\' .\\    _____");
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "   /: \\___\\  / .  /\\");
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "   \\' / . / /____/..\\");
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "    \\/___/  \\'  '\\  /");
+        funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "             \\'__'\\/ ");
+        funcmd_reply(header, "FUN_DICE", REPLYTYPE_NORMAL, user->nick, val, max);
+    } else
+        funcmd_reply(header, "FUN_DICE_NUM", REPLYTYPE_NORMAL, argv[0]);
+    FUNCMD_FOOTER;
+}
+
+CMD_BIND(funcmd_8ball) {
+    FUNCMD_HEADER;
+    char *message = merge_argv(argv, 0, argc);
+    const char *const_replies = get_language_string(header->user, "FUN_8BALL_REPLIES");
+    char replies[MAXLEN];
+    int i, reply_count = 1;
+    for(i = 0; const_replies[i]; i++) {
+        if(const_replies[i] == '|')
+            reply_count++;
+        replies[i] = const_replies[i];
+    }
+    replies[i] = '\0';
+    unsigned int crc32_val = (crc32(message)) % reply_count;
+    char *creply = (crc32_val == 0 ? replies : NULL);
+    reply_count = 0;
+    for(i = 0; replies[i]; i++) {
+        if(replies[i] == '|') {
+            if(creply) {
+                replies[i] = '\0';
+                break;
+            } else {
+                reply_count++;
+                if(reply_count == crc32_val) {
+                    creply = &replies[i+1];
+                }
+            }
+        }
+    }
+    if(creply) {
+        funcmd_reply(header, "FUN_8BALL", REPLYTYPE_NORMAL, user->nick, creply);
+    }
+    FUNCMD_FOOTER;
+}
+
+CMD_BIND(funcmd_cookie) {
+    FUNCMD_HEADER;
+    if(argc) {
+        if(!(user = getUserByNick(argv[0]))) {
+            reply(header->client, header->user, "NS_USER_UNKNOWN", argv[0]);
+            FUNCMD_FOOTER;
+            return;
+        }
+    }
+    char *tmp;
+    int user_count = ((tmp = getSetting(user, chan, "cookies")) ? atoi(tmp) : 0);
+    int total_count = ((tmp = getSetting(user, NULL, "cookies")) ? atoi(tmp) : 0);
+    user_count++;
+    total_count++;
+    char buf[10];
+    sprintf(buf, "%d", user_count);
+    setSetting(user, chan, "cookies", buf);
+    sprintf(buf, "%d", total_count);
+    setSetting(user, NULL, "cookies", buf);
+    funcmd_reply(header, "FUN_COOKIE", REPLYTYPE_ACTION, user->nick, total_count, user_count);
+    FUNCMD_FOOTER;
+}
+
+CMD_BIND(funcmd_ship) {
+    FUNCMD_HEADER;
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0------------------------\0031|");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0----------------------\00314)_)_)");
+    funcmd_reply(header, "%s%c%s", REPLYTYPE_NORMAL, "\0030,0--------------------\0031,4\002=", header->client->user->nick[0], "=\002\00314,0|)___)");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0-------------------\00314)__) )___) )");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0------------------\00314)___) )____))_)");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0----------------\00314)____)_____))__)\\");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0---------------\00314)\0035__\00314|\0035____\00314/|\0035___\00314|\0035___\00314-\\\\---");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0-------\00312^^^^^^\0035~~~\\ \002oo oo oo oo\002 /~~\00312^^^^^^");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0------------\00312~^^^^ ~~~~^^~~~~^^~~^^~~~~~");
+    funcmd_reply(header, "%s", REPLYTYPE_NORMAL, "\0030,0-----------------\00312~~^^ ~^^~ ~^~ ~^");
+    FUNCMD_FOOTER;
+}
diff --git a/src/modules/funcmd.mod/cmd_funcmds.h b/src/modules/funcmd.mod/cmd_funcmds.h
new file mode 100644 (file)
index 0000000..7d9e6f4
--- /dev/null
@@ -0,0 +1,34 @@
+/* cmd_funcmds.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_funcmds_h
+#define _cmd_funcmds_h
+#include "../module.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+
+void init_funcmds();
+void register_commands();
+
+CMD_BIND(funcmd_ping);
+CMD_BIND(funcmd_pong);
+CMD_BIND(funcmd_dice);
+CMD_BIND(funcmd_8ball);
+CMD_BIND(funcmd_cookie);
+CMD_BIND(funcmd_bomb);
+CMD_BIND(funcmd_ship);
+
+#endif
\ No newline at end of file
diff --git a/src/modules/funcmd.mod/cmd_funcmds_bomb.c b/src/modules/funcmd.mod/cmd_funcmds_bomb.c
new file mode 100644 (file)
index 0000000..db48bad
--- /dev/null
@@ -0,0 +1,449 @@
+/* cmd_funcmds_bomb.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+static void funcmd_bomb_plant(struct funcmd_header_info *header, struct Event *event, char **argv, int argc, int kick_bomb);
+static USERAUTH_CALLBACK(funcmd_bomb_plant_nick_lookup);
+static void funcmd_bomb_plant_async1(struct funcmd_header_info *header, struct Event *event, struct UserNode *user, int wires, int kick_bomb);
+static void funcmd_bomb_plant_async2(struct funcmd_header_info *header, struct Event *event, struct UserNode *user, int wires, int kick_bomb);
+static void funcmd_bomb_defuse(struct funcmd_header_info *header, struct Event *event, char **argv, int argc);
+static void funcmd_bomb_return(struct funcmd_header_info *header, struct Event *event, char **argv, int argc);
+static TIMEQ_CALLBACK(funcmd_bomb_timeout);
+
+struct funcmd_bomb_plant_cache {
+    struct funcmd_header_info header;
+    struct Event *event;
+    int wires : 4;
+    int kick_bomb : 4;
+};
+
+struct funcmd_bomb_bomb {
+    struct funcmd_header_info header;
+    struct UserNode *owner, *target;
+    struct timeq_entry *timer;
+    char wires[FUNCMD_BOMB_WIRES_MAX];
+    int right_wire : 4;
+    int kick_bomb : 4;
+    struct funcmd_bomb_bomb *next;
+};
+
+static struct funcmd_bomb_bomb *funcmd_bomb_bombs = NULL;
+
+static int funcmd_bomb_freeuser(struct UserNode *user) {
+    struct funcmd_bomb_bomb *bomb, *next_bomb, *prev_bomb = NULL;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = next_bomb) {
+        next_bomb = bomb->next;
+        if(bomb->owner == user)
+            bomb->owner = NULL;
+        if(bomb->target == user) {
+            timeq_del(bomb->timer);
+            free(bomb);
+            if(prev_bomb)
+                prev_bomb->next = next_bomb;
+            else
+                funcmd_bomb_bombs = next_bomb;
+        } else
+            prev_bomb = bomb;
+    }
+    return 1;
+}
+
+static int funcmd_bomb_freechan(struct ChanNode *chan) {
+    struct funcmd_bomb_bomb *bomb, *next_bomb, *prev_bomb = NULL;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = next_bomb) {
+        next_bomb = bomb->next;
+        if(bomb->header.chan == chan) {
+            timeq_del(bomb->timer);
+            free(bomb);
+            if(prev_bomb)
+                prev_bomb->next = next_bomb;
+            else
+                funcmd_bomb_bombs = next_bomb;
+        } else
+            prev_bomb = bomb;
+    }
+    return 1;
+}
+
+static void funcmd_bomb_freeclient(struct ClientSocket *client) {
+    struct funcmd_bomb_bomb *bomb, *next_bomb, *prev_bomb = NULL;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = next_bomb) {
+        next_bomb = bomb->next;
+        if(bomb->header.client == client) {
+            timeq_del(bomb->timer);
+            free(bomb);
+            if(prev_bomb)
+                prev_bomb->next = next_bomb;
+            else
+                funcmd_bomb_bombs = next_bomb;
+        } else
+            prev_bomb = bomb;
+    }
+}
+
+CMD_BIND(funcmd_bomb) {
+    FUNCMD_HEADER;
+    if(argc) {
+        char *subcmd = argv[0];
+        argv++;
+        argc--;
+        if(!stricmp(subcmd, "plant"))
+            funcmd_bomb_plant(header, event, argv, argc, 0);
+        else if(!stricmp(subcmd, "kplant"))
+            funcmd_bomb_plant(header, event, argv, argc, 1);
+        else if(!stricmp(subcmd, "defuse"))
+            funcmd_bomb_defuse(header, event, argv, argc);
+        else if(!stricmp(subcmd, "return"))
+            funcmd_bomb_return(header, event, argv, argc);
+        else {
+            char tmp[MAXLEN];
+            sprintf(tmp, "%s %s", event->command->cmd, subcmd);
+            reply(header->client, header->user, "MODCMD_UNKNOWN", tmp);
+        }
+    } else {
+        reply(header->client, header->user, "FUN_BOMB_MENU");
+        reply(header->client, header->user, "FUN_BOMB_MENU_PLANT", event->command->cmd, FUNCMD_BOMB_WIRES_DEFAULT);
+        reply(header->client, header->user, "FUN_BOMB_MENU_KPLANT", event->command->cmd, FUNCMD_BOMB_WIRES_DEFAULT);
+        reply(header->client, header->user, "FUN_BOMB_MENU_DEFUSE", event->command->cmd);
+        //reply(header->client, header->user, "FUN_BOMB_MENU_RETURN", event->command->cmd);
+    }
+    FUNCMD_FOOTER;
+}
+
+static void funcmd_bomb_plant(struct funcmd_header_info *header, struct Event *event, char **argv, int argc, int kick_bomb) {
+    if(!argc) {
+        reply(header->client, header->user, "MODCMD_LESS_PARAM_COUNT");
+        return;
+    }
+    struct UserNode *user;
+    int wires = FUNCMD_BOMB_WIRES_DEFAULT;
+    if(!(user = getUserByNick(argv[0]))) {
+        reply(header->client, header->user, "NS_USER_UNKNOWN", argv[0]);
+        return;
+    }
+    if(isNetworkService(user)) {
+        reply(header->client, header->user, "FUN_BOMB_PLANT_SERVICE");
+        return;
+    }
+    if(user == header->user) {
+        reply(header->client, header->user, "FUN_BOMB_SELF");
+        return;
+    }
+    if(argc > 1) {
+        wires = atoi(argv[1]);
+        if(wires < FUNCMD_BOMB_WIRES_MIN || wires > FUNCMD_BOMB_WIRES_MAX) {
+            reply(header->client, header->user, "FUN_BOMB_PLANT_MAXWIRES", FUNCMD_BOMB_WIRES_MIN, FUNCMD_BOMB_WIRES_MAX);
+            return;
+        }
+    }
+    if(kick_bomb == 1) { /* protect shit... */
+        if(user->flags & USERFLAG_ISAUTHED) {
+            funcmd_bomb_plant_async1(header, event, user, wires, kick_bomb);
+        } else {
+            struct funcmd_bomb_plant_cache *cache = malloc(sizeof(*cache));
+            memcpy(&cache->header, header, sizeof(*header));
+            cache->event = event;
+            cache->wires = wires;
+            cache->kick_bomb = kick_bomb;
+            get_userauth(user, module_id, funcmd_bomb_plant_nick_lookup, cache);
+        }
+    } else
+        funcmd_bomb_plant_async2(header, event, user, wires, kick_bomb);
+}
+
+static USERAUTH_CALLBACK(funcmd_bomb_plant_nick_lookup) {
+    struct funcmd_bomb_plant_cache *cache = data;
+    if(!user) {
+        reply(cache->header.client, cache->header.user, "NS_USER_UNKNOWN", "*");
+    } else {
+        funcmd_bomb_plant_async1(&cache->header, cache->event, user, cache->wires, cache->kick_bomb);
+    }
+    free(cache);
+}
+
+static void funcmd_bomb_plant_async1(struct funcmd_header_info *header, struct Event *event, struct UserNode *user, int wires, int kick_bomb) {
+    if(kick_bomb == 1) { /* protect shit... */
+        if(isUserProtected(header->chan, user, header->user)) {
+            reply(header->client, header->user, "FUN_BOMB_PLANT_PROTECTED");
+            return;
+        }
+    }
+    funcmd_bomb_plant_async2(header, event, user, wires, kick_bomb);
+}
+
+static void funcmd_bomb_defuse_strip_color(char *buffer) {
+    int i, j;
+    j = 0;
+    for(i = 0; buffer[i]; i++) {
+        if(buffer[i] == '\003') {
+            i++;
+            if(!buffer[i]) break;
+            if(isdigit(buffer[i])) {
+                i++;
+                if(!buffer[i]) break;
+            }
+            if(isdigit(buffer[i])) {
+                i++;
+                if(!buffer[i]) break;
+            }
+        }
+        buffer[j++] = buffer[i];
+    }
+    buffer[j] = 0;
+}
+
+static int funcmd_bomb_defuse_get_wire_id(char *wire, struct UserNode *user) {
+    char *wires = get_language_string(user, "FUN_BOMB_WIRES");
+    char *cwire = wires;
+    char tmp[MAXLEN];
+    int found = 0, cindex = 0;
+    do {
+        wires = strchr(cwire, '|');
+        if(wires) {
+            *wires = '\0';
+        }
+        cindex++;
+        strcpy(tmp, cwire);
+        funcmd_bomb_defuse_strip_color(tmp);
+        if(!stricmp(wire, tmp))
+            found = cindex;
+        if(wires) {
+            *wires = '|';
+            cwire = wires + 1;
+        } else
+            cwire = NULL;
+    } while(!found && cwire);
+    return found;
+}
+
+static int funcmd_bomb_defuse_get_wire_name(int wire_id, char *buffer, char *wires, struct UserNode *user) {
+    if(!wires)
+        wires = get_language_string(user, "FUN_BOMB_WIRES");
+    char *cwire = wires;
+    int cindex = 0, chars = 0;
+    buffer[0] = 0;
+    do {
+        wires = strchr(cwire, '|');
+        if(wires) {
+            *wires = '\0';
+        }
+        cindex++;
+        if(cindex == wire_id)
+            chars = sprintf(buffer, "%s", cwire);
+        if(wires) {
+            *wires = '|';
+            cwire = wires + 1;
+        }
+    } while(!chars && cwire);
+    return chars;
+}
+
+static void funcmd_bomb_plant_async2(struct funcmd_header_info *header, struct Event *event, struct UserNode *user, int wires, int kick_bomb) {
+    //everything should be checked now... plant the bomb :)
+    struct funcmd_bomb_bomb *bomb;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = bomb->next) {
+        if(bomb->target == user) {
+            reply(header->client, header->user, "FUN_BOMB_TARGET_ACTIVE", user->nick);
+            return;
+        }
+        if(bomb->owner == header->user) {
+            reply(header->client, header->user, "FUN_BOMB_OWNER_ACTIVE");
+            return;
+        }
+    }
+    bomb = malloc(sizeof(*bomb));
+    memcpy(&bomb->header, header, sizeof(*header));
+    bomb->target = user;
+    bomb->owner = header->user;
+    bomb->kick_bomb = kick_bomb;
+    bomb->timer = timeq_add(FUNCMD_BOMB_TIME, module_id, funcmd_bomb_timeout, bomb);
+    bomb->next = funcmd_bomb_bombs;
+    funcmd_bomb_bombs = bomb;
+    int i, j, k, l;
+    int pool[FUNCMD_BOMB_WIRES_MAX+1];
+    for(i = 0; i < FUNCMD_BOMB_WIRES_MAX; i++) {
+        pool[i] = i+1;
+    }
+    pool[i] = 0;
+    for(i = 0; i < FUNCMD_BOMB_WIRES_MAX; i++) {
+        if(i < wires) {
+            j = (rand() % (FUNCMD_BOMB_WIRES_MAX - i));
+            bomb->wires[i] = pool[j];
+            l = 0;
+            for(k = 0; k < (FUNCMD_BOMB_WIRES_MAX - i); k++) {
+                if(k == j)
+                    l = 1;
+                pool[k] = pool[k+l];
+            }
+        } else
+            bomb->wires[i] = 0;
+    }
+    bomb->right_wire = bomb->wires[(rand() % wires)];
+    char *wires_lang_str_a = get_language_string(header->user, "FUN_BOMB_WIRES");
+    char *wires_lang_str_b = get_language_string(user, "FUN_BOMB_WIRES");
+    if(!header->send_notice && wires_lang_str_a != wires_lang_str_b) {
+        wires_lang_str_a = get_language_string(NULL, "FUN_BOMB_WIRES");
+        header->null_language = 1;
+        bomb->header.null_language = 1;
+    }
+    char wires_str[MAXLEN];
+    j = 0;
+    for(i = 0; i < wires; i++) {
+        if(i) {
+            wires_str[j++] = ',';
+            wires_str[j++] = ' ';
+        }
+        j += funcmd_bomb_defuse_get_wire_name(bomb->wires[i], wires_str+j, wires_lang_str_a, NULL);
+    }
+    wires_str[j] = '\0';
+    if(header->send_notice) {
+        reply(header->client, header->user, (kick_bomb ? "FUN_BOMB_KPLANTED" : "FUN_BOMB_PLANTED"), header->user->nick, user->nick, FUNCMD_BOMB_TIME, wires_str, event->command->cmd);
+        j = 0;
+        for(i = 0; i < wires; i++) {
+            if(i) {
+                wires_str[j++] = ',';
+                wires_str[j++] = ' ';
+            }
+            j += funcmd_bomb_defuse_get_wire_name(bomb->wires[i], wires_str+j, wires_lang_str_b, NULL);
+        }
+        wires_str[j] = '\0';
+        reply(header->client, user, (kick_bomb ? "FUN_BOMB_KPLANTED" : "FUN_BOMB_PLANTED"), header->user->nick, user->nick, FUNCMD_BOMB_TIME, wires_str, event->command->cmd);
+    } else
+        funcmd_reply(header, (kick_bomb ? "FUN_BOMB_KPLANTED" : "FUN_BOMB_PLANTED"), REPLYTYPE_NORMAL, header->user->nick, user->nick, FUNCMD_BOMB_TIME, wires_str, event->command->cmd);
+}
+
+static void funcmd_bomb_detonate(struct funcmd_bomb_bomb *bomb) {
+    char *ptr;
+    int user_count = ((ptr = getSetting(bomb->target, bomb->header.chan, "bombs")) ? atoi(ptr) : 0);
+    int total_count = ((ptr = getSetting(bomb->target, NULL, "bombs")) ? atoi(ptr) : 0);
+    int detonated_count = ((ptr = getSetting(bomb->target, NULL, "bombs_detonated")) ? atoi(ptr) : 0);
+    user_count++;
+    total_count++;
+    detonated_count++;
+    char buf[10];
+    sprintf(buf, "%d", user_count);
+    setSetting(bomb->target, bomb->header.chan, "bombs", buf);
+    sprintf(buf, "%d", total_count);
+    setSetting(bomb->target, NULL, "bombs", buf);
+    sprintf(buf, "%d", detonated_count);
+    setSetting(bomb->target, NULL, "bombs_detonated", buf);
+    char *wires = get_language_string(NULL, "FUN_BOMB_WIRES");
+    char *cwire = wires;
+    char tmp[MAXLEN];
+    int cindex = 0;
+    tmp[0] = 0;
+    do {
+        wires = strchr(cwire, '|');
+        if(wires) {
+            *wires = '\0';
+        }
+        cindex++;
+        if(cindex == bomb->right_wire)
+            strcpy(tmp, cwire);
+        if(wires) {
+            *wires = '|';
+            cwire = wires + 1;
+        } else
+            cwire = NULL;
+    } while(!tmp[0] && cwire);
+    if(bomb->header.send_notice)
+        reply(bomb->header.client, bomb->owner, "FUN_BOMB_DETONATED", REPLYTYPE_NORMAL, bomb->target->nick, tmp, total_count, user_count);
+    funcmd_reply(&bomb->header, "FUN_BOMB_DETONATED", REPLYTYPE_NORMAL, bomb->target->nick, tmp, total_count, user_count);
+    if(bomb->kick_bomb) {
+        putsock(bomb->header.client, "KICK %s %s :[BOMB] *BOOOOOOM*", bomb->header.chan->name, bomb->target->nick);
+    }
+}
+
+static void funcmd_bomb_defuse(struct funcmd_header_info *header, struct Event *event, char **argv, int argc) {
+    if(!argc) {
+        reply(header->client, header->user, "MODCMD_LESS_PARAM_COUNT");
+        return;
+    }
+    struct funcmd_bomb_bomb *bomb, *prev_bomb = NULL;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = bomb->next) {
+        if(bomb->target == header->user)
+            break;
+        else
+            prev_bomb = bomb;
+    }
+    if(!bomb) {
+        reply(header->client, header->user, "FUN_BOMB_NOBOMB");
+        return;
+    }
+    funcmd_bomb_defuse_strip_color(argv[0]);
+    int cut_wire = funcmd_bomb_defuse_get_wire_id(argv[0], header->user);
+    if(!cut_wire)
+        cut_wire = funcmd_bomb_defuse_get_wire_id(argv[0], NULL);
+    if(!cut_wire) {
+        reply(header->client, header->user, "FUN_BOMB_UNKNOWN_WIRE", argv[0]);
+        return;
+    }
+    if(bomb->right_wire == cut_wire) {
+        char *tmp;
+        int user_count = ((tmp = getSetting(header->user, header->chan, "bombs")) ? atoi(tmp) : 0);
+        int total_count = ((tmp = getSetting(header->user, NULL, "bombs")) ? atoi(tmp) : 0);
+        int defused_count = ((tmp = getSetting(header->user, NULL, "bombs_defused")) ? atoi(tmp) : 0);
+        user_count++;
+        total_count++;
+        defused_count++;
+        char buf[10];
+        sprintf(buf, "%d", user_count);
+        setSetting(header->user, header->chan, "bombs", buf);
+        sprintf(buf, "%d", total_count);
+        setSetting(header->user, NULL, "bombs", buf);
+        sprintf(buf, "%d", defused_count);
+        setSetting(header->user, NULL, "bombs_defused", buf);
+        
+        if(header->send_notice)
+            reply(header->client, bomb->owner, "FUN_BOMB_DEFUSED", REPLYTYPE_NORMAL, header->user->nick, total_count, user_count, defused_count);
+        funcmd_reply(header, "FUN_BOMB_DEFUSED", REPLYTYPE_NORMAL, header->user->nick, total_count, user_count, defused_count);
+    } else {
+        funcmd_bomb_detonate(bomb);
+    }
+    timeq_del(bomb->timer);
+    if(prev_bomb)
+        prev_bomb->next = bomb->next;
+    else
+        funcmd_bomb_bombs = bomb->next;
+    free(bomb);
+}
+
+static void funcmd_bomb_return(struct funcmd_header_info *header, struct Event *event, char **argv, int argc) {
+    if(!argc) {
+        reply(header->client, header->user, "MODCMD_LESS_PARAM_COUNT");
+        return;
+    }
+    //following ;)
+}
+
+static TIMEQ_CALLBACK(funcmd_bomb_timeout) {
+    struct funcmd_bomb_bomb *cbomb = data;
+    struct funcmd_bomb_bomb *bomb, *prev_bomb = NULL;
+    for(bomb = funcmd_bomb_bombs; bomb; bomb = bomb->next) {
+        if(cbomb == bomb) {
+            cbomb = NULL;
+            break;
+        } else
+            prev_bomb = bomb;
+    }
+    if(cbomb) return;
+    funcmd_bomb_detonate(bomb);
+    if(prev_bomb)
+        prev_bomb->next = bomb->next;
+    else
+        funcmd_bomb_bombs = bomb->next;
+    free(bomb);
+}
diff --git a/src/modules/funcmd.mod/module.c b/src/modules/funcmd.mod/module.c
new file mode 100644 (file)
index 0000000..ae7d574
--- /dev/null
@@ -0,0 +1,34 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_funcmds.h"
+
+static int module_initialize() {
+    init_funcmds();
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    
+}
+
+static void module_stop(int type) {
+    
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/global.mod/cmd_global.c b/src/modules/global.mod/cmd_global.c
new file mode 100644 (file)
index 0000000..05d79fc
--- /dev/null
@@ -0,0 +1,62 @@
+/* cmd_global.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_global.h"
+#include "../../modcmd.h"
+#include "../../ConfigParser.h"
+
+void register_commands() {
+    
+    //Global Commands
+    #define USER_COMMAND(NAME,FUNCTION,PARAMCOUNT,PRIVS,FLAGS) register_command(0, NAME, module_id, FUNCTION, PARAMCOUNT, PRIVS, 0, FLAGS)
+    //               NAME            FUNCTION        PARAMS     PRIVS                FLAGS
+    USER_COMMAND("version",      global_cmd_version,   0, NULL,                   0);
+    USER_COMMAND("netinfo",      global_cmd_netinfo,   0, NULL,                   0);
+    USER_COMMAND("commands",     global_cmd_commands,  0, NULL,                   0);
+    USER_COMMAND("command",      global_cmd_command,   1, NULL,                   CMDFLAG_ESCAPE_ARGS);
+    USER_COMMAND("staff",        global_cmd_staff,     0, NULL,                   0);
+    USER_COMMAND("motd",         global_cmd_motd,      0, NULL,                   0);
+    USER_COMMAND("extscript",    global_cmd_extscript, 0, NULL,                   CMDFLAG_EMPTY_ARGS | CMDFLAG_CHAN_PARAM);
+    #undef USER_COMMAND
+    
+    #define OPER_COMMAND(NAME,FUNCTION,PARAMCOUNT,GACCESS,FLAGS) register_command(0, NAME, module_id, FUNCTION, PARAMCOUNT, NULL, GACCESS, FLAGS)
+    //            NAME            FUNCTION            PARAMS  ACCS  FLAGS
+    OPER_COMMAND("register",     global_cmd_register,  1,     200,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("unregister",   global_cmd_unregister,0,     200,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("move",         global_cmd_move,      2,     300,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("say",          global_cmd_say,       2,     600,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("emote",        global_cmd_emote,     2,     600,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("notice",       global_cmd_notice,    2,     600,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_CHAN_PARAM | CMDFLAG_OPLOG);
+    OPER_COMMAND("raw",          global_cmd_raw,       1,     800,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("god",          global_cmd_god,       0,     1,    CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("reloadlang",   global_cmd_reloadlang,1,     500,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("bind",         global_cmd_bind,      2,     900,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG | CMDFLAG_REQUIRED | CMDFLAG_ESCAPE_ARGS);
+    OPER_COMMAND("unbind",       global_cmd_unbind,    1,     900,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG | CMDFLAG_REQUIRED | CMDFLAG_ESCAPE_ARGS);
+    OPER_COMMAND("setaccess",    global_cmd_setaccess, 2,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("bots",         global_cmd_bots,      0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    OPER_COMMAND("reload",       global_cmd_reload,    0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("restart",      global_cmd_restart,   0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("die",          global_cmd_die,       0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("setbot",       global_cmd_setbot,    1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("addbot",       global_cmd_addbot,    2,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("delbot",       global_cmd_delbot,    1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("reconnect",    global_cmd_reconnect, 0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    OPER_COMMAND("modcmd",       global_cmd_modcmd,    1,     900,  CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG | CMDFLAG_REQUIRED | CMDFLAG_ESCAPE_ARGS);
+    OPER_COMMAND("meminfo",      global_cmd_meminfo,   0,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH);
+    OPER_COMMAND("global",       global_cmd_global,    1,     1000, CMDFLAG_REQUIRE_AUTH | CMDFLAG_CHECK_AUTH | CMDFLAG_OPLOG);
+    #undef OPER_COMMAND
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global.h b/src/modules/global.mod/cmd_global.h
new file mode 100644 (file)
index 0000000..38421a9
--- /dev/null
@@ -0,0 +1,76 @@
+/* cmd_global.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _cmd_global_h
+#define _cmd_global_h
+#include "../module.h"
+#include "../botid.h"
+#include "../../main.h"
+#include "../../modcmd.h"
+#include "../../IRCParser.h"
+#include "../../IRCEvents.h"
+#include "../../UserNode.h"
+#include "../../ChanNode.h"
+#include "../../ChanUser.h"
+#include "../../ModeNode.h"
+#include "../../BanNode.h"
+#include "../../ClientSocket.h"
+#include "../../mysqlConn.h"
+#include "../../lang.h"
+#include "../../HandleInfoHandler.h"
+#include "../../WHOHandler.h"
+#include "../../DBHelper.h"
+#include "../../tools.h"
+#include "../../timeq.h"
+#include "../../version.h"
+#include "../../EventLogger.h"
+#include "../../bots.h"
+
+void register_commands();
+
+CMD_BIND(global_cmd_addbot);
+CMD_BIND(global_cmd_bind);
+CMD_BIND(global_cmd_bots);
+CMD_BIND(global_cmd_command);
+CMD_BIND(global_cmd_commands);
+CMD_BIND(global_cmd_delbot);
+CMD_BIND(global_cmd_die);
+CMD_BIND(global_cmd_emote);
+CMD_BIND(global_cmd_extscript);
+CMD_BIND(global_cmd_global);
+CMD_BIND(global_cmd_god);
+CMD_BIND(global_cmd_meminfo);
+CMD_BIND(global_cmd_modcmd);
+CMD_BIND(global_cmd_motd);
+CMD_BIND(global_cmd_move);
+CMD_BIND(global_cmd_netinfo);
+CMD_BIND(global_cmd_notice);
+CMD_BIND(global_cmd_raw);
+CMD_BIND(global_cmd_reconnect);
+CMD_BIND(global_cmd_register);
+CMD_BIND(global_cmd_reload);
+CMD_BIND(global_cmd_restart);
+CMD_BIND(global_cmd_reloadlang);
+CMD_BIND(global_cmd_say);
+CMD_BIND(global_cmd_setaccess);
+CMD_BIND(global_cmd_setbot);
+CMD_BIND(global_cmd_staff);
+CMD_BIND(global_cmd_unbind);
+CMD_BIND(global_cmd_unregister);
+CMD_BIND(global_cmd_version);
+
+
+#endif
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_addbot.c b/src/modules/global.mod/cmd_global_addbot.c
new file mode 100644 (file)
index 0000000..626d895
--- /dev/null
@@ -0,0 +1,43 @@
+/* cmd_global_addbot.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  nick
+* argv[1]  class
+*/
+
+CMD_BIND(global_cmd_addbot) {
+    MYSQL_RES *res;
+    int botid;
+    if((botid = resolve_botalias(argv[1])) == -1) {
+        reply(textclient, user, "NS_SETBOT_INVALID_CLASS", argv[1]);
+        return;
+    }
+    printf_mysql_query("SELECT `id` FROM `bots` WHERE `nick` = '%s'", escape_string(argv[0]));
+    res = mysql_use();
+    if(mysql_fetch_row(res)) {
+        reply(textclient, user, "NS_ADDBOT_EXISTING", argv[0]);
+        return;
+    }
+    printf_mysql_query("INSERT INTO `bots` (`nick`, `botclass`) VALUES ('%s', '%d')", escape_string(argv[0]), botid);
+    botid = (int) mysql_insert_id(get_mysql_conn());
+    reply(textclient, user, "NS_ADDBOT_DONE", argv[0], botid);
+    logEvent(event);
+}
+
diff --git a/src/modules/global.mod/cmd_global_bind.c b/src/modules/global.mod/cmd_global_bind.c
new file mode 100644 (file)
index 0000000..4ee47a2
--- /dev/null
@@ -0,0 +1,54 @@
+/* cmd_global_bind.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]   command name
+* argv[1]   command function
+* argv[2-*] parameters (optional)
+*/
+
+CMD_BIND(global_cmd_bind) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    if(client->botid == 0)
+        printf_mysql_query("SELECT `function` FROM `bot_binds` WHERE `botclass` = '%d' AND `botid` = '%d' AND `command` = '%s'", client->botid, client->clientid, escape_string(argv[0]));
+    else
+        printf_mysql_query("SELECT `function` FROM `bot_binds` WHERE `botclass` = '%d' AND `command` = '%s'", client->botid, escape_string(argv[0]));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        reply(textclient, user, "NS_BIND_ALREADY", argv[0], row[0]);
+        return;
+    }
+    char *params;
+    if(argc > 2)
+        params = merge_argv(argv, 2, argc);
+    else
+        params = "";
+    struct cmd_function *function = find_cmd_function(client->botid, argv[1]);
+    if(!function) {
+        reply(textclient, user, "NS_BIND_UNKNOWN", argv[1]);
+        return;
+    }
+    bind_botwise_cmd_to_function(client->botid, client->clientid, argv[0], function);
+    printf_mysql_query("INSERT INTO `bot_binds` (`botclass`, `botid`, `command`, `function`, `parameters`) VALUES ('%d', '%d', '%s', '%s', '%s')", client->botid, (client->botid == 0 ? client->clientid : 0), escape_string(argv[0]), escape_string(argv[1]), params);
+    if(*params)
+        bind_botwise_set_parameters(client->botid, client->clientid, argv[0], params);
+    reply(textclient, user, "NS_BIND_DONE", argv[0], function->name);
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_bots.c b/src/modules/global.mod/cmd_global_bots.c
new file mode 100644 (file)
index 0000000..9887b7c
--- /dev/null
@@ -0,0 +1,75 @@
+/* cmd_global_bots.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    (optional) class name
+*/
+
+CMD_BIND(global_cmd_bots) {
+    struct Table *table;
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT `active`, `nick`, `server`, `port`, `pass`, `botclass`, `textbot`, `queue`, `defaulttrigger`, `max_channels`, `register_priority`, `id`, `secret` FROM `bots`");
+    res = mysql_use();
+    table = table_init(7, mysql_num_rows(res) + 1, 0);
+    char *content[7];
+    content[0] = get_language_string(user, "NS_BOTS_ID");
+    content[1] = get_language_string(user, "NS_BOTS_NICK");
+    content[2] = get_language_string(user, "NS_BOTS_SERVER");
+    content[3] = get_language_string(user, "NS_BOTS_CLASS");
+    content[4] = get_language_string(user, "NS_BOTS_FLAGS");
+    content[5] = get_language_string(user, "NS_BOTS_CHANNELS");
+    content[6] = get_language_string(user, "NS_BOTS_TRIGGER");
+    table_add(table, content);
+    char botnick[NICKLEN + 3];
+    char botserver[MAXLEN];
+    char botflags[10];
+    int flagspos;
+    char botchans[20];
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        content[0] = row[11];
+        sprintf(botnick, (strcmp(row[0], "0") ? "%s" : "!%s"), row[1]);
+        content[1] = botnick;
+        sprintf(botserver, (strcmp(row[4], "") ? "%s:%s:*" : "%s:%s"), row[2], row[3]);
+        content[2] = botserver;
+        content[3] = (char *) resolve_botid(atoi(row[5]));
+        flagspos = 0;
+        if(!strcmp(row[6], "1"))
+            botflags[flagspos++] = 't';
+        if(!strcmp(row[7], "1"))
+            botflags[flagspos++] = 'q';
+        if(!strcmp(row[12], "1"))
+            botflags[flagspos++] = 's';
+        botflags[flagspos] = '\0';
+        content[4] = botflags;
+        printf_mysql_query("SELECT COUNT(*) FROM `bot_channels` WHERE `botid` = '%s'", row[11]);
+        res2 = mysql_use();
+        row2 = mysql_fetch_row(res2);
+        sprintf(botchans, "%s/%s", row2[0], row[9]);
+        content[5] = botchans;
+        content[6] = row[8];
+        table_add(table, content);
+    }
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    table_free(table);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_command.c b/src/modules/global.mod/cmd_global_command.c
new file mode 100644 (file)
index 0000000..81beee9
--- /dev/null
@@ -0,0 +1,179 @@
+/* cmd_global_command.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0-1]     command
+*/
+static int global_cmd_command_chanaccess(struct cmd_binding *cbind, struct ChanNode *chan);
+static int global_cmd_command_operaccess(struct cmd_binding *cbind);
+
+CMD_BIND(global_cmd_command) {
+    char *ident;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct cmd_binding *cbind = find_botwise_cmd_binding(client->botid, client->clientid, argv[0]);
+    if (!cbind) {
+        reply(textclient, user, "NS_UNBIND_NOT_FOUND", argv[0]);
+        return;
+    }
+    ident = argv[0];
+    char parameters[MAXLEN];
+    if(cbind->paramcount) {
+        int i, parampos = 0;
+        for(i = 0; i < cbind->paramcount; i++) {
+            parampos += sprintf(parameters + parampos, (i ? " %s" : "%s"), cbind->parameters[i]);
+        }
+    } else
+        parameters[0] = '\0';
+    reply(textclient, user, "NS_COMMAND_BINDING", cbind->cmd, cbind->func->name, parameters);
+    if(chan)
+        reply(textclient, user, "NS_COMMAND_ACCESS", global_cmd_command_chanaccess(cbind, chan), global_cmd_command_operaccess(cbind));
+    printf_mysql_query("SELECT `user_lang` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    char *lang;
+    if ((row = mysql_fetch_row(res)) != NULL)
+        lang = row[0];
+    else
+        lang = "en";
+    printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = '%s' AND `ident` = '%s'", escape_string(lang), escape_string(ident));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        if(stricmp(lang, "en")) {
+            printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = 'en' AND `ident` = '%s'", escape_string(ident));
+            res = mysql_use();
+        }
+        if ((row = mysql_fetch_row(res)) == NULL) {
+            printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = '%s' AND `ident` = '%s'", escape_string(lang), escape_string(cbind->func->name));
+            res = mysql_use();
+            if ((row = mysql_fetch_row(res)) == NULL) {
+                if(stricmp(lang, "en")) {
+                    printf_mysql_query("SELECT `text` FROM `help` WHERE `lang` = 'en' AND `ident` = '%s'", escape_string(cbind->func->name));
+                    res = mysql_use();
+                }
+                if ((row = mysql_fetch_row(res)) == NULL) {
+                    return;
+                }
+            }
+        }
+    }
+    char sendBuf[MAXLEN];
+    int sendBufPos = 0;
+    int i;
+    for(i = 0; i < strlen(row[0]); i++) {
+        switch(row[0][i]) {
+            case '\n':
+                if(sendBufPos) {
+                    sendBuf[sendBufPos] = '\0';
+                    reply(textclient, user, "%s", sendBuf);
+                    sendBufPos = 0;
+                }
+                break;
+            case '$':
+                switch(row[0][i+1]) {
+                    case 'b':
+                        sendBuf[sendBufPos++] = '\002';
+                        i++;
+                        break;
+                    case 'k':
+                        sendBuf[sendBufPos++] = '\003';
+                        i++;
+                        break;
+                    case 'u':
+                        sendBuf[sendBufPos++] = '\031';
+                        i++;
+                        break;
+                    case 'C':
+                    case 'S':
+                        sendBufPos += sprintf(sendBuf + sendBufPos, "%s", client->user->nick);
+                        i++;
+                        break;
+                    default:
+                        sendBuf[sendBufPos++] = '$';
+                        break;
+                }
+                break;
+            default:
+                sendBuf[sendBufPos++] = row[0][i];
+                break;
+        }
+    }
+    if(sendBufPos) {
+        sendBuf[sendBufPos] = '\0';
+        reply(textclient, user, "%s", sendBuf);
+        sendBufPos = 0;
+    }
+}
+
+static int global_cmd_command_chanaccess(struct cmd_binding *cbind, struct ChanNode *chan) {
+    char access_list[256];
+    int access_pos = 0;
+    int access_count = 0;
+    int minaccess = 0;
+    char *str_a, *str_b = cbind->func->channel_access, *str_c;
+    if(cbind->flags & CMDFLAG_OVERRIDE_CHANNEL_ACCESS)
+        str_b = cbind->channel_access;
+    access_list[0] = '\0';
+    if(str_b) {
+        str_c = strdup(str_b);
+        str_b = str_c;
+        while((str_a = str_b)) {
+            str_b = strstr(str_a, ",");
+            if(str_b) {
+                *str_b = '\0';
+                str_b++;
+            }
+            if(*str_a == '@' || *str_a == '+') {
+                //privs can override this access requirement
+                str_a++;
+            }
+            if(*str_a == '#') {
+                str_a++;
+                access_pos += sprintf(access_list+access_pos, (access_pos ? ", `%s`" : "`%s`"), str_a);
+                access_count++;
+            } else {
+               if(atoi(str_a) > minaccess)
+                     minaccess = atoi(str_a);
+            }
+        }
+        free(str_c);
+    }
+    if(access_count) {
+        MYSQL_RES *res;
+        MYSQL_ROW row, defaults = NULL;
+        printf_mysql_query("SELECT %s FROM `channels` WHERE `channel_name` = '%s'", access_list, escape_string(chan->name));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            int i, caccess;
+            for(i = 0; i < access_count; i++) {
+                if(!row[i] && !defaults) {
+                    printf_mysql_query("SELECT %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
+                    defaults = mysql_fetch_row(mysql_use());
+                }
+                caccess = (row[i] ? atoi(row[i]) : atoi(defaults[i]));
+                if(caccess > minaccess)
+                     minaccess = caccess;
+            }
+        }
+    }
+    return minaccess;
+}
+
+static int global_cmd_command_operaccess(struct cmd_binding *cbind) {
+    return ((cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS) ? cbind->global_access : cbind->func->global_access);
+}
diff --git a/src/modules/global.mod/cmd_global_commands.c b/src/modules/global.mod/cmd_global_commands.c
new file mode 100644 (file)
index 0000000..fa5f26d
--- /dev/null
@@ -0,0 +1,153 @@
+/* cmd_global_commands.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    mask
+*/
+
+static int global_cmd_commands_sort(const void *a, const void *b);
+static int global_cmd_commands_chanaccess(struct cmd_binding *cbind, struct ChanNode *chan);
+static int global_cmd_commands_operaccess(struct cmd_binding *cbind);
+
+CMD_BIND(global_cmd_commands) {
+    struct cmd_binding *cbind;
+    int bindcount = 0;
+    for(cbind = getAllBinds(NULL); cbind; cbind = getAllBinds(cbind)) {
+        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && !(cbind->func->flags & CMDFLAG_FUNCMD))
+            bindcount++;
+    }
+    struct cmd_binding *binds[bindcount];
+    bindcount = 0;
+    for(cbind = getAllBinds(NULL); cbind; cbind = getAllBinds(cbind)) {
+        if(cbind->botid == client->botid && (cbind->botid || cbind->clientid == client->clientid) && !(cbind->func->flags & CMDFLAG_FUNCMD))
+            binds[bindcount++] = cbind;
+    }
+    qsort(binds, bindcount, sizeof(struct cmd_binding *), global_cmd_commands_sort);
+    int i;
+    struct Table *table;
+    table = table_init(5, bindcount + 1, 0);
+    char *content[5];
+    content[0] = get_language_string(user, "NS_COMMANDS_NAME");
+    content[1] = get_language_string(user, "NS_COMMANDS_ACCESS");
+    content[2] = get_language_string(user, "NS_COMMANDS_GACCESS");
+    content[3] = get_language_string(user, "NS_COMMANDS_TRIGGERED");
+    content[4] = get_language_string(user, "NS_COMMANDS_FUNCTION");
+    table_add(table, content);
+    char caccess[5];
+    char gaccess[5];
+    char triggered[10];
+    char funcname[MAXLEN];
+    int funcpos;
+    for(i = 0; i < bindcount; i++) {
+        cbind = binds[i];
+        content[0] = cbind->cmd;
+        sprintf(caccess, "%d", global_cmd_commands_chanaccess(cbind, chan));
+        content[1] = caccess;
+        sprintf(gaccess, "%d", global_cmd_commands_operaccess(cbind));
+        content[2] = gaccess;
+        sprintf(triggered, "%d", cbind->triggered);
+        content[3] = triggered;
+        funcpos = sprintf(funcname, "%s", cbind->func->name);
+        if(cbind->paramcount) {
+            int j;
+            for(j = 0; j < cbind->paramcount; j++) {
+                funcpos += sprintf(funcname + funcpos, " %s", cbind->parameters[j]);
+            }
+        }
+        content[4] = funcname;
+        table_add(table, content);
+    }
+    //send the table
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    table_free(table);
+}
+
+static int global_cmd_commands_sort(const void *a, const void *b) {
+    const struct cmd_binding *bind_a = *((struct cmd_binding * const *) a);
+    const struct cmd_binding *bind_b = *((struct cmd_binding * const *) b); 
+    int i = stricmp(bind_a->func->name, bind_b->func->name);
+    if(i == 0) {
+        return stricmp(bind_a->cmd, bind_b->cmd);
+    } else
+        return i;
+}
+
+static int global_cmd_commands_chanaccess(struct cmd_binding *cbind, struct ChanNode *chan) {
+    char access_list[256];
+    int access_pos = 0;
+    int access_count = 0;
+    int minaccess = 0;
+    char *str_a, *str_b = cbind->func->channel_access, *str_c;
+    if(cbind->flags & CMDFLAG_OVERRIDE_CHANNEL_ACCESS)
+        str_b = cbind->channel_access;
+    access_list[0] = '\0';
+    if(str_b) {
+        str_c = strdup(str_b);
+        str_b = str_c;
+        while((str_a = str_b)) {
+            str_b = strstr(str_a, ",");
+            if(str_b) {
+                *str_b = '\0';
+                str_b++;
+            }
+            if(*str_a == '@' || *str_a == '+') {
+                //privs can override this access requirement
+                str_a++;
+            }
+            if(*str_a == '#') {
+                str_a++;
+                access_pos += sprintf(access_list+access_pos, (access_pos ? ", `%s`" : "`%s`"), str_a);
+                access_count++;
+            } else {
+               if(atoi(str_a) > minaccess)
+                     minaccess = atoi(str_a);
+            }
+        }
+        free(str_c);
+    }
+    if(access_count) {
+        if(!chan) {
+            return -1;
+        }
+        MYSQL_RES *res;
+        MYSQL_ROW row, defaults = NULL;
+        printf_mysql_query("SELECT %s FROM `channels` WHERE `channel_name` = '%s'", access_list, escape_string(chan->name));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            int i, caccess;
+            for(i = 0; i < access_count; i++) {
+                if(!row[i] && !defaults) {
+                    printf_mysql_query("SELECT %s FROM `channels` WHERE `channel_name` = 'defaults'", access_list);
+                    defaults = mysql_fetch_row(mysql_use());
+                }
+                caccess = (row[i] ? atoi(row[i]) : atoi(defaults[i]));
+                if(caccess > minaccess)
+                     minaccess = caccess;
+            }
+        }
+    }
+    return minaccess;
+}
+
+static int global_cmd_commands_operaccess(struct cmd_binding *cbind) {
+    return ((cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS) ? cbind->global_access : cbind->func->global_access);
+}
diff --git a/src/modules/global.mod/cmd_global_delbot.c b/src/modules/global.mod/cmd_global_delbot.c
new file mode 100644 (file)
index 0000000..101c599
--- /dev/null
@@ -0,0 +1,45 @@
+/* cmd_global_delbot.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  nick/botid
+*/
+
+CMD_BIND(global_cmd_delbot) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `id` FROM `bots` WHERE `nick` = '%s' OR `id` = '%s'", escape_string(argv[0]), escape_string(argv[0]));
+    res = mysql_use();
+    if((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_DELBOT_NOT_FOUND", argv[0]);
+        return;
+    }
+    int botid = atoi(row[0]);
+    printf_mysql_query("DELETE FROM `bots` WHERE `id` = '%s'", row[0]);
+    printf_mysql_query("DELETE FROM `bot_binds` WHERE `botid` = '%s'", row[0]);
+    printf_mysql_query("DELETE FROM `bot_channels` WHERE `botid` = '%s'", row[0]);
+    for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+        if(client->clientid == botid) {
+            close_socket(client);
+            break;
+        }
+    }
+    reply(textclient, user, "NS_DELBOT_DONE");
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_die.c b/src/modules/global.mod/cmd_global_die.c
new file mode 100644 (file)
index 0000000..c8da2d1
--- /dev/null
@@ -0,0 +1,27 @@
+/* cmd_global_die.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* no args
+*/
+
+CMD_BIND(global_cmd_die) {
+    //hard work! :D
+    stop_bot(); 
+}
diff --git a/src/modules/global.mod/cmd_global_emote.c b/src/modules/global.mod/cmd_global_emote.c
new file mode 100644 (file)
index 0000000..a0c5c90
--- /dev/null
@@ -0,0 +1,28 @@
+/* cmd_global_emote.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    target
+* argv[1-*]  message
+*/
+
+CMD_BIND(global_cmd_emote) {
+    char *message = merge_argv(argv, 1, argc);
+    putsock(client, "PRIVMSG %s :\001ACTION %s\001", argv[0], message);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_extscript.c b/src/modules/global.mod/cmd_global_extscript.c
new file mode 100644 (file)
index 0000000..74cde9d
--- /dev/null
@@ -0,0 +1,170 @@
+/* cmd_global_extscript.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+#include <fcntl.h>
+
+/*
+* argv[0]      'toys' if it's a toy command (check if toys are enabled)
+* argv[0-*]    script name & parameter patterns
+* argv[argc-1] all arguments passed to the command
+*/
+
+static TIMEQ_CALLBACK(global_cmd_extscript_callback);
+
+struct global_cmd_extscript_cache {
+    struct ClientSocket *client, *textclient;
+    struct Event *event;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    int answere_channel;
+    FILE *pipe;
+};
+
+CMD_BIND(global_cmd_extscript) {
+    int i, j;
+    char *args[MAXNUMPARAMS];
+    int argpos = 0;
+    char *next, *curr;
+    char command[1024];
+    int commandpos = 0;
+    char part[MAXLEN];
+    int partpos;
+    int escape_param;
+    int answere_channel = 0;
+    //check first arg
+    if(argc && !stricmp(argv[0], "toys")) {
+        if(!chan) return; //toys are not allowed via query
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        printf_mysql_query("SELECT `channel_toys` FROM `channels` WHERE `channel_name` = '%s'", escape_string(chan->name));
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row || !strcmp(row[0], "0")) {
+            //disabled
+            reply(textclient, user, "NS_FUN_DISABLED", chan->name);
+            return;
+        } else if(!strcmp(row[0], "2"))
+            answere_channel = 1;
+        argc--;
+        argv++;
+    }
+    //parse arguments
+    if(argc < 2) return;
+    curr = argv[argc-1];
+    while(curr) {
+        next = strstr(curr, " ");
+        args[argpos++] = curr;
+        if(next) {
+            *next = '\0';
+            curr = next+1;
+        } else
+            curr = NULL;
+    }
+    //parse command pattern and build command
+    commandpos = sprintf(command, "%s", argv[0]);
+    for(i = 1; i < argc-1; i++) {
+        partpos = 0;
+        escape_param = 1;
+        if(argv[i][0] == '$') {
+            argv[i]++;
+            if(argv[i][strlen(argv[i])-1] == '-') {
+                argv[i][strlen(argv[i])-1] = '\0';
+                j = atoi(argv[i]);
+                if(j <= argpos)
+                    partpos = sprintf(part, "%s", merge_argv(args, j-1, argpos));
+            } else if((j = atoi(argv[i])) > 0) {
+                if(j <= argpos)
+                    partpos = sprintf(part, "%s", args[j-1]);
+            } else if(!strcmp(argv[i], "c")) {
+                partpos = sprintf(part, "%s", (chan ? chan->name : ""));
+            } else if(!strcmp(argv[i], "n")) {
+                partpos = sprintf(part, "%s", user->nick);
+            } else if(!strcmp(argv[i], "a")) {
+                partpos = sprintf(part, "%s", ((user->flags & USERFLAG_ISAUTHED) ? user->auth : ""));
+            } else if(!strcmp(argv[i], "access")) {
+                if(chan)
+                    partpos = sprintf(part, "%d", getChannelAccess(user, chan));
+            }
+        } else {
+            partpos = sprintf(part, "%s", argv[i]);
+            escape_param = 0;
+        }
+        //escape shell argument
+        command[commandpos++] = ' ';
+        if(escape_param) {
+            command[commandpos++] = '\'';
+            for(j = 0; j < partpos; j++) {
+                if(part[j] == '\'') {
+                    command[commandpos++] = '\'';
+                    command[commandpos++] = '\\';
+                    command[commandpos++] = '\'';
+                    command[commandpos++] = '\'';
+                } else
+                    command[commandpos++] = part[j];
+            }
+            command[commandpos++] = '\'';
+        } else
+            commandpos += sprintf(command + commandpos, " %s", part);
+    }
+    command[commandpos] = '\0';
+    //we should now have a valid command
+    
+    struct global_cmd_extscript_cache *cache = malloc(sizeof(*cache));
+    if (!cache) {
+        printf_log("global", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return;
+    }
+    cache->client = client;
+    cache->textclient = textclient;
+    cache->event = event;
+    cache->user = user;
+    cache->chan = chan;
+    cache->answere_channel = answere_channel;
+    cache->pipe = popen(command, "r");
+    #ifndef WIN32
+    fcntl(fileno(cache->pipe), F_SETFL, O_NONBLOCK);
+    #endif
+    timeq_uadd(200, module_id, global_cmd_extscript_callback, cache);
+}
+
+static TIMEQ_CALLBACK(global_cmd_extscript_callback) {
+    struct global_cmd_extscript_cache *cache = data;
+    char command[512];
+    char *a;
+    if(feof(cache->pipe)) {
+        pclose(cache->pipe);
+        free(cache);
+        return;
+    }
+    while (fgets(command, 512, cache->pipe) != NULL) {
+        if((a = strchr(command, '\n'))) 
+            *a = '\0';
+        if(!stricmp(command, "/log")) {
+            logEvent(cache->event);
+            continue;
+        }
+        if(cache->answere_channel)
+            putsock(cache->client, "PRIVMSG %s :%s", cache->chan->name, command);
+        else
+            reply(cache->textclient, cache->user, "%s", command);
+    }
+    timeq_uadd(200, module_id, global_cmd_extscript_callback, cache);
+}
+
+
+
diff --git a/src/modules/global.mod/cmd_global_global.c b/src/modules/global.mod/cmd_global_global.c
new file mode 100644 (file)
index 0000000..1401d29
--- /dev/null
@@ -0,0 +1,36 @@
+/* cmd_global_global.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0-*]  message
+*/
+
+CMD_BIND(global_cmd_global) {
+    char *message = merge_argv(argv, 0, argc);
+    //send message to all channels
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->botid == client->botid && (bot->botid || bot->clientid == client->clientid)) {
+            struct ChanUser *chanuser;
+            for(chanuser = getUserChannels(bot->user, NULL); chanuser; chanuser = getUserChannels(bot->user, chanuser)) {
+                putsock(client, "PRIVMSG %s :%s", chanuser->chan->name, message);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_god.c b/src/modules/global.mod/cmd_global_god.c
new file mode 100644 (file)
index 0000000..bab895c
--- /dev/null
@@ -0,0 +1,53 @@
+/* cmd_global_god.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    (optional) on/off
+*/
+
+CMD_BIND(global_cmd_god) {
+    if(argc > 0) {
+        if(!strcmp(argv[0], "0") || !stricmp(argv[0], "off") || !stricmp(argv[0], get_language_string(user, "NS_SET_OFF"))) {
+            if(isGodMode(user)) {
+                printf_mysql_query("UPDATE `users` SET `user_god` = '0' WHERE `user_user` = '%s'", escape_string(user->auth));
+                user->flags &= ~USERFLAG_GOD_MODE;
+            }
+            reply(textclient, user, "NS_GOD_OFF");
+        } else if(!strcmp(argv[0], "1") || !stricmp(argv[0], "on") || !stricmp(argv[0], get_language_string(user, "NS_SET_ON"))) {
+            if(!isGodMode(user)) {
+                printf_mysql_query("UPDATE `users` SET `user_god` = '1' WHERE `user_user` = '%s'", escape_string(user->auth));
+                user->flags |= USERFLAG_GOD_MODE;
+            }
+            reply(textclient, user, "NS_GOD_ON");
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", argv[0]);
+            return;
+        }
+    } else {
+        if(isGodMode(user)) {
+            printf_mysql_query("UPDATE `users` SET `user_god` = '0' WHERE `user_user` = '%s'", escape_string(user->auth));
+            user->flags &= ~USERFLAG_GOD_MODE;
+            reply(textclient, user, "NS_GOD_OFF");
+        } else {
+            printf_mysql_query("UPDATE `users` SET `user_god` = '1' WHERE `user_user` = '%s'", escape_string(user->auth));
+            user->flags |= USERFLAG_GOD_MODE;
+            reply(textclient, user, "NS_GOD_ON");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_meminfo.c b/src/modules/global.mod/cmd_global_meminfo.c
new file mode 100644 (file)
index 0000000..2685798
--- /dev/null
@@ -0,0 +1,118 @@
+/* cmd_global_meminfo.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+#include "../../memoryInfo.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(global_cmd_meminfo) {
+    #ifndef ENABLE_MEMORY_DEBUG
+    reply(textclient, user, "NS_MEMINFO_DISABLED");
+    #else
+    if(argc > 0) {
+        struct Table *table;
+        int elementcount = 0;
+        struct memoryInfoLines *element, *elements;
+        elements = getMemoryInfoLines(argv[0]);
+        for(element = elements; element; element = element->next) {
+            elementcount++;
+        }
+        table = table_init(4, elementcount+2, 0);
+        char *content[4];
+        content[0] = get_language_string(user, "NS_MEMINFO_LINE");
+        content[1] = get_language_string(user, "NS_MEMINFO_COUNT");
+        content[2] = get_language_string(user, "NS_MEMINFO_SIZE");
+        content[3] = get_language_string(user, "NS_MEMINFO_TOTAL");
+        table_add(table, content);
+        char lineBuf[20];
+        char countBuf[20];
+        char sizeBuf[20];
+        char totalBuf[50];
+        unsigned int total_allocations = 0;
+        unsigned int total_allocated = 0;
+        for(element = elements; element; element = element->next) {
+            sprintf(lineBuf, "%u", element->line);
+            content[0] = lineBuf;
+            sprintf(countBuf, "%u", element->allocations);
+            total_allocations += element->allocations;
+            content[1] = countBuf;
+            sprintf(sizeBuf, "%uB", element->allocate);
+            content[2] = sizeBuf;
+            sprintf(totalBuf, "%u (%.2f kB)", (element->allocate * element->allocations), ((float)(element->allocate * element->allocations) / 1024));
+            total_allocated += (element->allocate * element->allocations);
+            content[3] = totalBuf;
+            table_add(table, content);
+        }
+        content[0] = "Total";
+        sprintf(countBuf, "%u", total_allocations);
+        content[1] = countBuf;
+        content[2] = "*";
+        sprintf(sizeBuf, "%u (%.2f kB)", total_allocated, ((float)total_allocated / 1024));
+        content[3] = sizeBuf;
+        table_add(table, content);
+        char **table_lines = table_end(table);
+        int i;
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        table_free(table);
+    } else {
+        struct Table *table;
+        int elementcount = 0;
+        struct memoryInfoFiles *element, *elements;
+        elements = getMemoryInfoFiles();
+        for(element = elements; element; element = element->next) {
+            elementcount++;
+        }
+        table = table_init(3, elementcount+2, 0);
+        char *content[3];
+        content[0] = get_language_string(user, "NS_MEMINFO_NAME");
+        content[1] = get_language_string(user, "NS_MEMINFO_COUNT");
+        content[2] = get_language_string(user, "NS_MEMINFO_SIZE");
+        table_add(table, content);
+        char countBuf[20];
+        char sizeBuf[50];
+        unsigned int total_allocations = 0;
+        unsigned int total_allocated = 0;
+        for(element = elements; element; element = element->next) {
+            content[0] = element->filename;
+            sprintf(countBuf, "%u", element->allocations);
+            total_allocations += element->allocations;
+            content[1] = countBuf;
+            sprintf(sizeBuf, "%u (%.2f kB)", element->allocated, ((float)element->allocated / 1024));
+            total_allocated += element->allocated;
+            content[2] = sizeBuf;
+            table_add(table, content);
+        }
+        content[0] = "Total";
+        sprintf(countBuf, "%u", total_allocations);
+        content[1] = countBuf;
+        sprintf(sizeBuf, "%u (%.2f kB)", total_allocated, ((float)total_allocated / 1024));
+        content[2] = sizeBuf;
+        table_add(table, content);
+        char **table_lines = table_end(table);
+        int i;
+        for(i = 0; i < table->entrys; i++) {
+            reply(textclient, user, table_lines[i]);
+        }
+        table_free(table);
+    }
+    #endif
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_modcmd.c b/src/modules/global.mod/cmd_global_modcmd.c
new file mode 100644 (file)
index 0000000..c1066ec
--- /dev/null
@@ -0,0 +1,223 @@
+/* cmd_global_modcmd.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]     command
+* argv[1]     setting
+* argv[2]     value
+*/
+
+static int global_cmd_modcmd_params(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value);
+static int global_cmd_modcmd_flags(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value);
+static int global_cmd_modcmd_caccess(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value);
+static int global_cmd_modcmd_oaccess(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value);
+
+CMD_BIND(global_cmd_modcmd) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct cmd_binding *cbind = find_botwise_cmd_binding(client->botid, client->clientid, argv[0]);
+    if (!cbind) {
+        reply(textclient, user, "NS_UNBIND_NOT_FOUND", argv[0]);
+        return;
+    }
+    int uaccess = 0;
+    printf_mysql_query("SELECT `user_access` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        uaccess = atoi(row[0]);
+    }
+    int gaccess = ((cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS) ? cbind->global_access : cbind->func->global_access);
+    if(gaccess > uaccess) {
+        reply(textclient, user, "NS_MODCMD_OUTRANKED", cbind->cmd, gaccess);
+        return;
+    }
+    if(argc > 1) {
+        char *value;
+        if(argc > 2) {
+            value = merge_argv(argv, 2, argc);
+        } else
+            value = NULL;
+        int log_event = 0;
+        if(!stricmp(argv[1], "caccess")) log_event = global_cmd_modcmd_caccess(textclient, user, cbind, value);
+        else if(!stricmp(argv[1], "oaccess")) log_event = global_cmd_modcmd_oaccess(textclient, user, cbind, value);
+        else if(!stricmp(argv[1], "parameters")) log_event = global_cmd_modcmd_params(textclient, user, cbind, value);
+        else if(!stricmp(argv[1], "flags")) log_event = global_cmd_modcmd_flags(textclient, user, cbind, value);
+        else {
+            reply(textclient, user, "NS_MODCMD_SETTING", argv[1]);
+        }
+        if(log_event) {
+            logEvent(event);
+        }
+    } else {
+        reply(textclient, user, "NS_MODCMD_HEADER", cbind->cmd);
+        global_cmd_modcmd_params(textclient, user, cbind, NULL);
+        global_cmd_modcmd_caccess(textclient, user, cbind, NULL);
+        global_cmd_modcmd_oaccess(textclient, user, cbind, NULL);
+        global_cmd_modcmd_flags(textclient, user, cbind, NULL);
+    }
+}
+
+static int global_cmd_modcmd_params(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value) {
+    char parameters[MAXLEN];
+    int ret = 0;
+    if(cbind->paramcount) {
+        int i, parampos = 0;
+        for(i = 0; i < cbind->paramcount; i++) {
+            parampos += sprintf(parameters + parampos, (i ? " %s" : "%s"), cbind->parameters[i]);
+        }
+    } else
+        parameters[0] = '\0';
+    if(value) {
+        if(!strcmp(value, "*")) 
+            value = NULL;
+        bind_botwise_set_parameters(cbind->botid, cbind->clientid, cbind->cmd, value);
+        if(cbind->botid == 0)
+            printf_mysql_query("UPDATE `bot_binds` SET `parameters` = '%s' WHERE `botclass` = '0' AND `botid` = '%d' AND `command` = '%s'", (value ? escape_string(value) : ""), cbind->clientid, escape_string(cbind->cmd));
+        else
+            printf_mysql_query("UPDATE `bot_binds` SET `parameters` = '%s' WHERE `botclass` = '%d' AND `command` = '%s'", (value ? escape_string(value) : ""), cbind->botid, escape_string(cbind->cmd));
+        strcpy(parameters, (value ? value : ""));
+        ret = 1;
+    }
+    reply(textclient, user, "\002PARAMETERS \002 %s", parameters);
+    return ret;
+}
+
+static const struct {
+    const char *name;
+    unsigned int flag;
+} global_cmd_modcmd_show_flags[] = {
+    {"REQUIRE_CHAN",        CMDFLAG_REQUIRE_CHAN},      //The command requires a valid channel (at least one bot in it)
+    {"REQUIRE_AUTH",        CMDFLAG_REQUIRE_AUTH},      //The command requires the user to be authenticated
+    {"REQUIRE_GOD",         CMDFLAG_REQUIRE_GOD},       //The command requires the user to have security override enabled
+    {"REQUIRE_REGISTERED",  CMDFLAG_REGISTERED_CHAN},   //The command requires the channel to be registered (database entry)
+    {"CHECK_AUTH",          CMDFLAG_CHECK_AUTH},        //WHO the user if no auth is known, yet
+    {"CHANNEL_ARGS",        CMDFLAG_CHAN_PARAM},        //don't interpret channel arguments as channel - just pass them as a string
+    {"LOG",                 CMDFLAG_LOG},
+    {"OPLOG",               CMDFLAG_OPLOG},
+    {"FUNCMD",              CMDFLAG_FUNCMD},
+    {"ESCAPED_ARGS",        CMDFLAG_ESCAPE_ARGS},       //allows arguments to be escaped ("a\ b" = "a b" as one argument)
+    {"NO_CROSSCHAN",        CMDFLAG_NO_CROSSCHAN},
+    {"SUB_LINKER",          CMDFLAG_SUB_LINKER},        //adds a "quiet" subcommand linker with the binding function as default
+    {NULL, 0}
+};
+
+static int global_cmd_modcmd_flags(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value) {
+    char flags[MAXLEN];
+    int flagpos = 0;
+    int ret = 0;
+    unsigned int visible_flags = 0;
+    int i = 0;
+    while(global_cmd_modcmd_show_flags[i].name) {
+        if(cbind->func->flags & global_cmd_modcmd_show_flags[i].flag) {
+            flagpos += sprintf(flags + flagpos, (flagpos ? " %s" : "\00314%s"), global_cmd_modcmd_show_flags[i].name);
+        }
+        visible_flags |= global_cmd_modcmd_show_flags[i].flag;
+        i++;
+    }
+    if(flagpos) 
+        flags[flagpos++] = '\003';
+    if(value) {
+        int add = 1;
+        unsigned int current_flag = 0;
+        if(value[0] == '+') {
+            value++;
+        } else if(value[0] == '-') {
+            add = 0;
+            value++;
+        }
+        if(*value) {
+            i = 0;
+            while(global_cmd_modcmd_show_flags[i].name) {
+                if(!stricmp(global_cmd_modcmd_show_flags[i].name, value)) {
+                    current_flag = global_cmd_modcmd_show_flags[i].flag;
+                    break;
+                }
+                i++;
+            }
+        }
+        if(cbind->func->flags & current_flag) {
+            reply(textclient, user, "NS_MODCMD_STATIC_FLAG");
+            return 0;
+        }
+        if(add)
+            cbind->flags |= current_flag;
+        else
+            cbind->flags &= ~current_flag;
+        if(cbind->botid == 0)
+            printf_mysql_query("UPDATE `bot_binds` SET `flags` = '%u' WHERE `botclass` = '0' AND `botid` = '%d' AND `command` = '%s'", (cbind->flags & visible_flags), cbind->clientid, escape_string(cbind->cmd));
+        else
+            printf_mysql_query("UPDATE `bot_binds` SET `flags` = '%u' WHERE `botclass` = '%d' AND `command` = '%s'", (cbind->flags & visible_flags), cbind->botid, escape_string(cbind->cmd));
+        ret = 1;
+    }
+    i = 0;
+    while(global_cmd_modcmd_show_flags[i].name) {
+        if(cbind->flags & global_cmd_modcmd_show_flags[i].flag) {
+            flagpos += sprintf(flags + flagpos, (flagpos ? " %s" : "%s"), global_cmd_modcmd_show_flags[i].name);
+        }
+        i++;
+    }
+    flags[flagpos] = '\0';
+    reply(textclient, user, "\002FLAGS      \002 %s", flags);
+    return ret;
+}
+
+static int global_cmd_modcmd_caccess(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value) {
+    char caccess[MAXLEN];
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "*")) 
+            value = NULL;
+        bind_botwise_set_channel_access(cbind->botid, cbind->clientid, cbind->cmd, value);
+        if(cbind->botid == 0)
+            printf_mysql_query("UPDATE `bot_binds` SET `chan_access` = %s%s%s WHERE `botclass` = '0' AND `botid` = '%d' AND `command` = '%s'", (value ? "'" : ""), (value ? escape_string(value) : "NULL"), (value ? "'" : ""), cbind->clientid, escape_string(cbind->cmd));
+        else
+            printf_mysql_query("UPDATE `bot_binds` SET `chan_access` = %s%s%s WHERE `botclass` = '%d' AND `command` = '%s'", (value ? "'" : ""), (value ? escape_string(value) : "NULL"), (value ? "'" : ""), cbind->botid, escape_string(cbind->cmd));
+        
+        ret = 1;
+    }
+    if(cbind->flags & CMDFLAG_OVERRIDE_CHANNEL_ACCESS)
+        sprintf(caccess, "%s", cbind->channel_access);
+    else
+        sprintf(caccess, "\00314%s\003", (cbind->func->channel_access ? cbind->func->channel_access : "0"));
+    reply(textclient, user, "\002CACCESS    \002 %s", caccess);
+    return ret;
+}
+
+static int global_cmd_modcmd_oaccess(struct ClientSocket *textclient, struct UserNode *user, struct cmd_binding *cbind, char *value) {
+    char oaccess[MAXLEN];
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "*")) 
+            value = NULL;
+        bind_botwise_set_global_access(cbind->botid, cbind->clientid, cbind->cmd, atoi(value));
+        if(cbind->botid == 0)
+            printf_mysql_query("UPDATE `bot_binds` SET `global_access` = %s%s%s WHERE `botclass` = '0' AND `botid` = '%d' AND `command` = '%s'", (value ? "'" : ""), (value ? escape_string(value) : "NULL"), (value ? "'" : ""), cbind->clientid, escape_string(cbind->cmd));
+        else
+            printf_mysql_query("UPDATE `bot_binds` SET `global_access` = %s%s%s WHERE `botclass` = '%d' AND `command` = '%s'", (value ? "'" : ""), (value ? escape_string(value) : "NULL"), (value ? "'" : ""), cbind->botid, escape_string(cbind->cmd));
+        
+        ret = 1;
+    }
+    if(cbind->flags & CMDFLAG_OVERRIDE_GLOBAL_ACCESS)
+        sprintf(oaccess, "%d", cbind->global_access);
+    else
+        sprintf(oaccess, "\00314%d\003", (cbind->func->global_access ? cbind->func->global_access : 0));
+    reply(textclient, user, "\002OACCESS    \002 %s", oaccess);
+    return ret;
+}
+
diff --git a/src/modules/global.mod/cmd_global_motd.c b/src/modules/global.mod/cmd_global_motd.c
new file mode 100644 (file)
index 0000000..6e39f9a
--- /dev/null
@@ -0,0 +1,36 @@
+/* cmd_global_motd.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* no args
+*/
+
+CMD_BIND(global_cmd_motd) {
+    FILE *f;
+    f = fopen("motd.txt", "rb");
+    if(!f) return;
+    char line[MAXLEN];
+    char *a;
+    while (fgets(line, MAXLEN, f) != NULL) {
+        if((a = strchr(line, '\n'))) 
+            *a = '\0';
+        reply(textclient, user, "%s", line);
+    }
+    fclose(f);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_move.c b/src/modules/global.mod/cmd_global_move.c
new file mode 100644 (file)
index 0000000..9cf53a4
--- /dev/null
@@ -0,0 +1,97 @@
+/* cmd_global_move.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0] - channel
+* argv[1] - new channel
+*/
+CMD_BIND(global_cmd_move) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *channel = argv[0];
+    char *new_channel = argv[1];
+    if(!is_valid_chan(new_channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", new_channel);
+        return;
+    }
+    if(!stricmp(channel, new_channel)) {
+        reply(textclient, user, "NS_MOVE_SELF");
+        return;
+    }
+    printf_mysql_query("SELECT `channel_id` FROM `bot_channels` LEFT JOIN `channels` ON `channel_id` = `chanid` WHERE `channel_name` = '%s'", escape_string(new_channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        reply(textclient, user, "NS_REGISTER_ALREADY", new_channel, client->user->nick);
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", argv[0], client->user->nick);
+        return;
+    }
+    if(!strcmp(row[2], "1")) {
+        reply(textclient, user, "NS_MOVE_SUSPENDED");
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    struct ChanNode *channode = getChanByName(channel);
+    if(channode) {
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(isUserOnChan(bot->user, channode)) {
+                putsock(bot, "PART %s", channel);
+                putsock(bot, "JOIN %s", new_channel);
+            }
+        }
+    } else {
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->clientid == botid)
+                putsock(bot, "JOIN %s", new_channel);
+        }
+    }
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(new_channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+        printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_cid` = '%d'", chanid);
+        printf_mysql_query("DELETE FROM `bans` WHERE `ban_channel` = '%d'", chanid);
+        printf_mysql_query("DELETE FROM `events` WHERE `cid` = '%d'", chanid);
+        printf_mysql_query("DELETE FROM `noinvite` WHERE `cid` = '%d'", chanid);
+        printf_mysql_query("DELETE FROM `owner_history` WHERE `owner_history_cid` = '%d'", chanid);
+        printf_mysql_query("DELETE FROM `channels` WHERE `channel_id` = '%d'", chanid);
+    }
+    printf_mysql_query("UPDATE `channels` SET `channel_name` = '%s' WHERE `channel_name` = '%s'", escape_string(new_channel), escape_string(channel));
+    
+    if(channode && channode->flags & CHANFLAG_REQUESTED_CHANINFO) {
+        channode->flags &= ~CHANFLAG_CHAN_REGISTERED;
+        channode->channel_id = 0;
+    }
+    reply(textclient, user, "NS_MOVE_DONE", channel, new_channel);
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_netinfo.c b/src/modules/global.mod/cmd_global_netinfo.c
new file mode 100644 (file)
index 0000000..fa149de
--- /dev/null
@@ -0,0 +1,177 @@
+/* cmd_global_netinfo.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* no args
+*/
+
+CMD_BIND(global_cmd_netinfo) {
+    reply(textclient, user, "NS_NETINFO_HEADER");
+    char tmp[MAXLEN];
+    struct Table *table;
+    table = table_init(2, 19, 0);
+    char *content[2];
+    
+    content[0] = get_language_string(user, "NS_NETINFO_UPTIME");
+    content[1] = timeToStr(user, (time(0) - getStartTime()), 3, tmp);
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_BOTS");
+    struct ClientSocket *cclient;
+    int bot_count = 0, connected_bot_count = 0;
+    float traffic_in = 0, traffic_out = 0;
+    for(cclient = getBots(0, NULL); cclient; cclient = getBots(0, cclient)) {
+        bot_count++;
+        if(cclient->flags & SOCKET_FLAG_READY)
+            connected_bot_count++;
+        traffic_in += cclient->traffic_in;
+        traffic_out += cclient->traffic_out;
+    }
+    sprintf(tmp, "%d (%d connected)", bot_count, connected_bot_count);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_TRAFFIC");
+    sprintf(tmp, "in: %.2f kb  out: %.2f kb", traffic_in / 1024, traffic_out / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    int channel_count = getChannelCount();
+    float channel_memory = channel_count * sizeof(struct ChanNode);
+    int channel_ban_count = getChanBanCount();
+    float channel_ban_memory = channel_ban_count * sizeof(struct BanNode);
+    int user_count = getUserCount();
+    float user_memory = user_count * sizeof(struct UserNode);
+    int chanuser_count = getChanUserCount();
+    float chanuser_memory = chanuser_count * sizeof(struct ChanUser);
+    float total_memory = channel_memory + channel_ban_memory + user_memory + chanuser_memory;
+    content[0] = get_language_string(user, "NS_NETINFO_CACHE");
+    sprintf(tmp, "%.2f kB (%.2f MB)", total_memory / 1024, total_memory / 1024 / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_NETINFO_CHANNEL");
+    sprintf(tmp, "%d    %.2f kB (%d * %u B = %.2f kB)", channel_count, channel_memory / 1024, channel_count, (unsigned int) sizeof(struct ChanNode), channel_memory / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_NETINFO_CHANNEL_BAN");
+    sprintf(tmp, "%d    %.2f kB (%d * %u B = %.2f kB)", channel_ban_count, channel_ban_memory / 1024, channel_ban_count, (unsigned int) sizeof(struct BanNode), channel_ban_memory / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_NETINFO_USER");
+    sprintf(tmp, "%d    %.2f kB (%d * %u B = %.2f kB)", user_count, user_memory / 1024, user_count, (unsigned int) sizeof(struct UserNode), user_memory / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    content[0] = get_language_string(user, "NS_NETINFO_CHANUSER");
+    sprintf(tmp, "%d    %.2f kB (%d * %u B = %.2f kB)", chanuser_count, chanuser_memory / 1024, chanuser_count, (unsigned int) sizeof(struct ChanUser), chanuser_memory / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SHOW TABLE STATUS");
+    res = mysql_use();
+    int mysql_entrys[4];
+    float mysql_length[5];
+    total_memory = 0;
+    mysql_entrys[0] = 0; mysql_entrys[1] = 0; mysql_entrys[2] = 0; mysql_entrys[3] = 0;
+    mysql_length[0] = 0; mysql_length[1] = 0; mysql_length[2] = 0; mysql_length[3] = 0; mysql_length[4] = 0;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(!stricmp(row[0], "channels")) {
+            mysql_entrys[0] = atoi(row[4]);
+            mysql_length[0] = atof(row[6]);
+            total_memory += atof(row[6]);
+        } else if(!stricmp(row[0], "bans")) {
+            mysql_entrys[1] = atoi(row[4]);
+            mysql_length[1] = atof(row[6]);
+            total_memory += atof(row[6]);
+        } else if(!stricmp(row[0], "users")) {
+            mysql_entrys[2] = atoi(row[4]);
+            mysql_length[2] = atof(row[6]);
+            total_memory += atof(row[6]);
+        } else if(!stricmp(row[0], "chanusers")) {
+            mysql_entrys[3] = atoi(row[4]);
+            mysql_length[3] = atof(row[6]);
+            total_memory += atof(row[6]);
+        } else {
+            mysql_length[4] += atof(row[6]);
+            total_memory += atof(row[6]);
+        }
+    }
+    
+    content[0] = get_language_string(user, "NS_NETINFO_DATABASE");
+    sprintf(tmp, "%.2f kB (%.2f MB)", total_memory / 1024, total_memory / 1024 / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_CHANNEL");
+    sprintf(tmp, "%d    %.2f kB", mysql_entrys[0], mysql_length[0] / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_CHANNEL_BAN");
+    sprintf(tmp, "%d    %.2f kB", mysql_entrys[1], mysql_length[1] / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_USER");
+    sprintf(tmp, "%d    %.2f kB", mysql_entrys[2], mysql_length[2] / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_CHANUSER");
+    sprintf(tmp, "%d    %.2f kB", mysql_entrys[3], mysql_length[3] / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_OTHER");
+    sprintf(tmp, "*     %.2f kB", mysql_length[4] / 1024);
+    content[1] = tmp;
+    table_add(table, content);
+    
+    #ifdef HAVE_THREADS
+    content[0] = get_language_string(user, "NS_NETINFO_THREADS");
+    sprintf(tmp, "%d (current thread: %i)", getRunningThreads(), getCurrentThreadID());
+    content[1] = tmp;
+    table_add(table, content);
+    #endif
+    
+    if(strcmp(get_revision(), ""))
+        sprintf(tmp, "%s.%d (%s: %s)", NEONSERV_VERSION, get_patchlevel(), (is_stable_revision() ? "stable" : "dev"), (is_stable_revision() ? get_revision() : get_dev_revision()));
+    else 
+        sprintf(tmp, "%s.%d", NEONSERV_VERSION, get_patchlevel());
+    content[0] = get_language_string(user, "NS_NETINFO_VERSION");
+    content[1] = tmp;
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_COMPILER");
+    content[1] = build_language_string(user, tmp, "NS_NETINFO_COMPILER_VALUE", COMPILER, get_creation());
+    table_add(table, content);
+    
+    content[0] = get_language_string(user, "NS_NETINFO_CODE");
+    content[1] = build_language_string(user, tmp, "NS_NETINFO_CODE_VALUE", get_codelines());
+    table_add(table, content);
+    
+    char **table_lines = table_end(table);
+    int i;
+    for(i = 0; i < table->entrys; i++) {
+        reply(textclient, user, table_lines[i]);
+    }
+    table_free(table);
+}
+
diff --git a/src/modules/global.mod/cmd_global_notice.c b/src/modules/global.mod/cmd_global_notice.c
new file mode 100644 (file)
index 0000000..2768a6d
--- /dev/null
@@ -0,0 +1,28 @@
+/* cmd_global_notice.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    target
+* argv[1-*]  message
+*/
+
+CMD_BIND(global_cmd_notice) {
+    char *message = merge_argv(argv, 1, argc);
+    putsock(client, "NOTICE %s :%s", argv[0], message);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_raw.c b/src/modules/global.mod/cmd_global_raw.c
new file mode 100644 (file)
index 0000000..98b9d10
--- /dev/null
@@ -0,0 +1,27 @@
+/* cmd_global_raw.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0-*]    raw
+*/
+
+CMD_BIND(global_cmd_raw) {
+    char *raw = merge_argv(argv, 0, argc);
+    putsock(client, "%s", raw);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_reconnect.c b/src/modules/global.mod/cmd_global_reconnect.c
new file mode 100644 (file)
index 0000000..1ac409a
--- /dev/null
@@ -0,0 +1,49 @@
+/* cmd_global_reconnect.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  nick/botid
+*/
+
+CMD_BIND(global_cmd_reconnect) {
+    int botid;
+    if(argc) {
+        MYSQL_RES *res;
+        MYSQL_ROW row;
+        printf_mysql_query("SELECT `id` FROM `bots` WHERE `nick` = '%s' OR `id` = '%s'", escape_string(argv[0]), escape_string(argv[0]));
+        res = mysql_use();
+        if((row = mysql_fetch_row(res)) == NULL) {
+            reply(textclient, user, "NS_DELBOT_NOT_FOUND", argv[0]);
+            return;
+        }
+        botid = atoi(row[0]);
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == botid) {
+                close_socket(client);
+                connect_socket(client);
+                break;
+            }
+        }
+    } else {
+        close_socket(client);
+        connect_socket(client);
+    }
+    reply(textclient, user, "NS_RECONNECT_DONE");
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_register.c b/src/modules/global.mod/cmd_global_register.c
new file mode 100644 (file)
index 0000000..03fa932
--- /dev/null
@@ -0,0 +1,313 @@
+/* cmd_global_register.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0] - channel
+* argv[1] - nick / *auth
+* argv[2] - (optional) bot nick
+*/
+static AUTHLOOKUP_CALLBACK(global_cmd_register_auth_lookup);
+static USERAUTH_CALLBACK(global_cmd_register_nick_lookup);
+static void global_cmd_register_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *channel, char *auth, int multibot, char *botname);
+
+struct global_cmd_register_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct ChanNode *chan;
+    struct Event *event;
+    char *nick;
+    char *channel;
+    char *botname;
+    int multibot;
+};
+
+CMD_BIND(global_cmd_register) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *channel = argv[0];
+    char *botname = (argc > 2 ? argv[2] : NULL);
+    int multibot = 0;
+    if(!is_valid_chan(channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", argv[0]);
+        return;
+    }
+    printf_mysql_query("SELECT `botid`, `botclass` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `bot_channels`.`chanid` = `channels`.`channel_id` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        if(atoi(row[1]) == client->botid && (client->botid || client->clientid == atoi(row[0]))) {
+            reply(textclient, user, "NS_REGISTER_ALREADY", argv[0], client->user->nick);
+            return;
+        } else
+            multibot = 1;
+    }
+    printf_mysql_query("SELECT `user_user`, `dnr_timeout`, `dnr_reason`, `dnr_id` FROM `donotregister` LEFT JOIN `users` ON `dnr_user` = `user_id` WHERE `dnr_target` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if((row = mysql_fetch_row(res)) != NULL) {
+        int expire_time = atoi(row[1]);
+        if(expire_time) {
+            if(expire_time - time(0) <= 0) {
+                printf_mysql_query("DELETE FROM `donotregister` WHERE `dnr_id` = '%s'", row[3]);
+            } else {
+                char expireBuf[MAXLEN];
+                reply(textclient, user, "NS_DNR_SET_EXPIRES", channel, row[0], timeToStr(user, (expire_time - time(0)), 2, expireBuf), row[2]);
+                return;
+            }
+        } else {
+            reply(textclient, user, "NS_DNR_SET", channel, row[0], row[2]);
+            return;
+        }
+    }
+    //if theres already another bot in the channel we don't need a owner parameter...
+    if(multibot && argc < 2) {
+        //skip all these owner check lines
+        multibot = 2;
+        global_cmd_register_async1(client, textclient, user, chan, event, channel, NULL, multibot, botname);
+        return;
+    } else if(argc < 2) {
+        global_cmd_register_async1(client, textclient, user, chan, event, channel, user->auth, multibot, botname);
+        return;
+    }
+    //check own access
+    if(argv[1][0] == '*') {
+        //we've got an auth
+        argv[1]++;
+        printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_user` = '%s'", escape_string(argv[1]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            global_cmd_register_async1(client, textclient, user, chan, event, channel, row[0], multibot, botname);
+        } else {
+            //we need to create a new user...
+            //but first lookup the auth to check if it really exists
+            struct global_cmd_register_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("global", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[1]);
+            cache->channel = strdup(channel);
+            cache->multibot = multibot;
+            cache->botname = (botname ? strdup(botname) : NULL);
+            lookup_authname(argv[1], module_id, global_cmd_register_auth_lookup, cache);
+        }
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[1]);
+        if(!cuser) {
+            cuser = createTempUser(argv[1]);
+            if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[1]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            global_cmd_register_async1(client, textclient, user, chan, event, channel, cuser->auth, multibot, botname);
+        } else {
+            struct global_cmd_register_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("global", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->chan = chan;
+            cache->event = event;
+            cache->nick = strdup(argv[1]);
+            cache->channel = strdup(channel);
+            cache->multibot = multibot;
+            cache->botname = (botname ? strdup(botname) : NULL);
+            get_userauth(cuser, module_id, global_cmd_register_nick_lookup, cache);
+        }
+    }
+}
+
+static AUTHLOOKUP_CALLBACK(global_cmd_register_auth_lookup) {
+    struct global_cmd_register_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->nick);
+    } else
+        global_cmd_register_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, cache->channel, auth, cache->multibot, cache->botname);
+    if(cache->botname)
+        free(cache->botname);
+    free(cache->channel);
+    free(cache->nick);
+    free(cache);
+}
+
+static USERAUTH_CALLBACK(global_cmd_register_nick_lookup) {
+    struct global_cmd_register_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        global_cmd_register_async1(cache->client, cache->textclient, cache->user, cache->chan, cache->event, cache->channel, user->auth, cache->multibot, cache->botname);
+    if(cache->botname)
+        free(cache->botname);
+    free(cache->channel);
+    free(cache->nick);
+    free(cache);
+}
+
+static void global_cmd_register_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct ChanNode *chan, struct Event *event, char *channel, char *auth, int multibot, char *botname) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    int userid = 0, adminid;
+    printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL)
+        adminid = atoi(row[0]);
+    else
+        adminid = 0;
+    if(multibot != 2) {
+        printf_mysql_query("SELECT `user_user`, `dnr_timeout`, `dnr_reason`, `dnr_id` FROM `donotregister` LEFT JOIN `users` ON `dnr_user` = `user_id` WHERE `dnr_target` = '%s'", escape_string(auth));
+        res = mysql_use();
+        if((row = mysql_fetch_row(res)) != NULL) {
+            int expire_time = atoi(row[1]);
+            if(expire_time) {
+                if(expire_time - time(0) <= 0) {
+                    printf_mysql_query("DELETE FROM `donotregister` WHERE `dnr_id` = '%s'", row[3]);
+                } else {
+                    char expireBuf[MAXLEN];
+                    reply(textclient, user, "NS_DNR_SET_EXPIRES", auth, row[0], timeToStr(user, (expire_time - time(0)), 2, expireBuf), row[2]);
+                    return;
+                }
+            } else {
+                reply(textclient, user, "NS_DNR_SET", auth, row[0], row[2]);
+                return;
+            }
+        }
+        printf_mysql_query("SELECT `user_id` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            userid = atoi(row[0]);
+        } else {
+            printf_mysql_query("INSERT INTO `users` (`user_user`) VALUES ('%s')", escape_string(auth));
+            userid = (int) mysql_insert_id(get_mysql_conn());
+        }
+    }
+    if(client->botid)
+        printf_mysql_query("SELECT `id`, `max_channels`, `defaulttrigger`, `nick` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1' ORDER BY `register_priority` DESC", client->botid);
+    else
+        printf_mysql_query("SELECT `id`, `max_channels`, `defaulttrigger`, `nick` FROM `bots` WHERE `id` = '%d' AND `active` = '1'", client->clientid);
+    res = mysql_use();
+    int botid = 0;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        //check channel count
+        printf_mysql_query("SELECT COUNT(*) FROM `bot_channels` WHERE `botid` = '%s'", row[0]);
+        row2 = mysql_fetch_row(mysql_use());
+        if(atoi(row2[0]) < atoi(row[1]) && (!botname || !stricmp(botname, row[3]))) {
+            botid = atoi(row[0]);
+            break;
+        }
+    }
+    if(!botid) {
+        reply(textclient, user, "NS_REGISTER_FULL");
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+        printf_mysql_query("UPDATE `channels` SET `channel_registered` = UNIX_TIMESTAMP(), `channel_registrator` = '%d' WHERE `channel_id` = '%d'", adminid, chanid);
+    } else {
+        printf_mysql_query("INSERT INTO `channels` (`channel_name`, `channel_registered`, `channel_registrator`) VALUES ('%s', UNIX_TIMESTAMP(), '%d')", escape_string(channel), adminid);
+        chanid = (int) mysql_insert_id(get_mysql_conn());
+    }
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        putsock(bot, "JOIN %s", channel);
+    } else
+        reply(textclient, user, "NS_REGISTER_DISCONNECTED", channel);
+    printf_mysql_query("INSERT INTO `bot_channels` (`botid`, `chanid`, `trigger`) VALUES ('%d', '%d', NULL)", botid, chanid);
+    if(multibot != 2) {
+        if(multibot) {
+            printf_mysql_query("UPDATE `chanusers` SET `chanuser_access` = 499 WHERE `chanuser_cid` = '%d' AND `chanuser_access` = '500'", chanid);
+            printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_cid` = '%d' AND `chanuser_uid` = '%d'", chanid, userid);
+        } else
+            printf_mysql_query("DELETE FROM `chanusers` WHERE `chanuser_cid` = '%d'", chanid);
+        printf_mysql_query("INSERT INTO `chanusers` (`chanuser_cid`, `chanuser_uid`, `chanuser_access`) VALUES ('%d', '%d', '%d')", chanid, userid, 500);
+        reply(textclient, user, "NS_REGISTER_DONE", channel, auth);
+    } else
+        reply(textclient, user, "NS_REGISTER_DONE_NOAUTH", channel);
+    // NeonBackup SubBlock
+    if(client->botid == NEONSERV_BOTID) {
+        char setting[128];
+        sprintf(setting, "modules.%s.auto_backup_register", get_module_name(module_id));
+        if(get_int_field(setting))
+            module_global_cmd_register_neonbackup(channel);
+    }
+    logEvent(event);
+}
+
+void global_cmd_register_neonbackup(char *channel) {
+    MYSQL_RES *res;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT `botid` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `bot_channels`.`chanid` = `channels`.`channel_id` WHERE `channel_name` = '%s' AND botclass = '%d'", escape_string(channel), NEONBACKUP_BOTID);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL)
+        return;
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else
+        return;
+    printf_mysql_query("SELECT `id`, `max_channels`, `defaulttrigger`, `nick` FROM `bots` WHERE `botclass` = '%d' AND `active` = '1' ORDER BY `register_priority` DESC", NEONBACKUP_BOTID);
+    res = mysql_use();
+    int botid = 0;
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        //check channel count
+        printf_mysql_query("SELECT COUNT(*) FROM `bot_channels` WHERE `botid` = '%s'", row[0]);
+        row2 = mysql_fetch_row(mysql_use());
+        if(atoi(row2[0]) < atoi(row[1])) {
+            botid = atoi(row[0]);
+            break;
+        }
+    }
+    if(!botid)
+        return;
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    if(bot) {
+        putsock(bot, "JOIN %s", channel);
+    }
+    printf_mysql_query("INSERT INTO `bot_channels` (`botid`, `chanid`, `trigger`) VALUES ('%d', '%d', NULL)", botid, chanid);
+}
+
diff --git a/src/modules/global.mod/cmd_global_reload.c b/src/modules/global.mod/cmd_global_reload.c
new file mode 100644 (file)
index 0000000..ca77ac9
--- /dev/null
@@ -0,0 +1,27 @@
+/* cmd_global_reload.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  soft  (optional)
+*/
+
+CMD_BIND(global_cmd_reload) {
+    reload_config();
+    reply(client, user, "reloaded.");
+}
diff --git a/src/modules/global.mod/cmd_global_reloadlang.c b/src/modules/global.mod/cmd_global_reloadlang.c
new file mode 100644 (file)
index 0000000..8e25890
--- /dev/null
@@ -0,0 +1,35 @@
+/* cmd_global_reloadlang.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    language tag
+*/
+
+CMD_BIND(global_cmd_reloadlang) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `text`, `lang` FROM `language` WHERE `ident` = 'name' AND `lang` = '%s'", escape_string(argv[0]));
+    res = mysql_use();
+    if((row = mysql_fetch_row(res)) != NULL) {
+        load_language(row[1], row[0]);
+        reply(textclient, user, "NS_RELOADLANG_DONE", row[0], row[1]);
+    } else {
+        reply(textclient, user, "NS_RELOADLANG_UNKNOWN", argv[0]);
+    }
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_restart.c b/src/modules/global.mod/cmd_global_restart.c
new file mode 100644 (file)
index 0000000..80f4403
--- /dev/null
@@ -0,0 +1,26 @@
+/* cmd_global_restart.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  soft  (optional)
+*/
+
+CMD_BIND(global_cmd_restart) {
+    restart_bot(0);
+}
diff --git a/src/modules/global.mod/cmd_global_say.c b/src/modules/global.mod/cmd_global_say.c
new file mode 100644 (file)
index 0000000..1af7672
--- /dev/null
@@ -0,0 +1,28 @@
+/* cmd_global_say.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]    target
+* argv[1-*]  message
+*/
+
+CMD_BIND(global_cmd_say) {
+    char *message = merge_argv(argv, 1, argc);
+    putsock(client, "PRIVMSG %s :%s", argv[0], message);
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/cmd_global_setaccess.c b/src/modules/global.mod/cmd_global_setaccess.c
new file mode 100644 (file)
index 0000000..8753cc3
--- /dev/null
@@ -0,0 +1,144 @@
+/* cmd_global_setaccess.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0] - nick / *auth
+* argv[1] - global access
+*/
+static AUTHLOOKUP_CALLBACK(global_cmd_setaccess_auth_lookup);
+static USERAUTH_CALLBACK(global_cmd_setaccess_nick_lookup);
+static void global_cmd_setaccess_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *nick, char *auth, int access);
+
+struct global_cmd_setaccess_cache {
+    struct ClientSocket *client, *textclient;
+    struct UserNode *user;
+    struct Event *event;
+    int access;
+    char *nick;
+};
+
+CMD_BIND(global_cmd_setaccess) {
+    int caccess;
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    caccess = atoi(argv[1]);
+    if(caccess < 0 || caccess > 1000) {
+        reply(textclient, user, "NS_INVALID_ACCESS", caccess);
+        return;
+    }
+    printf_mysql_query("SELECT `user_access` FROM `users` WHERE `user_user` = '%s'", escape_string(user->auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL || atoi(row[0]) < caccess) {
+        reply(textclient, user, "NS_ACCESS_OUTRANKED");
+        return;
+    }
+    if(argv[0][0] == '*') {
+        //we've got an auth
+        argv[0]++;
+        printf_mysql_query("SELECT `user_user` FROM `users` WHERE `user_user` = '%s'", escape_string(argv[0]));
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res)) != NULL) {
+            global_cmd_setaccess_async1(client, textclient, user, event, argv[0], row[0], caccess);
+        } else {
+            //we need to create a new user...
+            //but first lookup the auth to check if it really exists
+            struct global_cmd_setaccess_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("global", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->event = event;
+            cache->access = caccess;
+            cache->nick = strdup(argv[0]);
+            lookup_authname(argv[0], module_id, global_cmd_setaccess_auth_lookup, cache);
+        }
+    } else {
+        struct UserNode *cuser = getUserByNick(argv[0]);
+        if(!cuser) {
+            cuser = createTempUser(argv[0]);
+                       if(!cuser) {
+                reply(textclient, user, "NS_USER_UNKNOWN", argv[0]);
+                return;
+            }
+            cuser->flags |= USERFLAG_ISTMPUSER;
+        }
+        if(cuser->flags & USERFLAG_ISAUTHED) {
+            global_cmd_setaccess_async1(client, textclient, user, event, argv[0], cuser->auth, caccess);
+        } else {
+            struct global_cmd_setaccess_cache *cache = malloc(sizeof(*cache));
+            if (!cache) {
+                printf_log("global", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+                return;
+            }
+            cache->client = client;
+            cache->textclient = textclient;
+            cache->user = user;
+            cache->event = event;
+            cache->access = caccess;
+            cache->nick = strdup(argv[0]);
+            get_userauth(cuser, module_id, global_cmd_setaccess_nick_lookup, cache);
+        }
+    }
+}
+
+static AUTHLOOKUP_CALLBACK(global_cmd_setaccess_auth_lookup) {
+    struct global_cmd_setaccess_cache *cache = data;
+    if(!exists) {
+        //AUTH_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_AUTH_UNKNOWN", cache->nick);
+    } else
+        global_cmd_setaccess_async1(cache->client, cache->textclient, cache->user, cache->event, cache->nick, auth, cache->access);
+    free(cache->nick);
+    free(cache);
+}
+
+static USERAUTH_CALLBACK(global_cmd_setaccess_nick_lookup) {
+    struct global_cmd_setaccess_cache *cache = data;
+    if(!user) {
+        //USER_DOES_NOT_EXIST
+        reply(cache->textclient, cache->user, "NS_USER_UNKNOWN", cache->nick);
+    }
+    else if(!(user->flags & USERFLAG_ISAUTHED)) {
+        //USER_NOT_AUTHED
+        reply(cache->textclient, cache->user, "NS_USER_NEED_AUTH", cache->nick);
+    }
+    else
+        global_cmd_setaccess_async1(cache->client, cache->textclient, cache->user, cache->event, user->nick, user->auth, cache->access);
+    free(cache->nick);
+    free(cache);
+}
+
+static void global_cmd_setaccess_async1(struct ClientSocket *client, struct ClientSocket *textclient, struct UserNode *user, struct Event *event, char *nick, char *auth, int caccess) {
+    //we've got a valid auth now...
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `user_id`, `user_access` FROM `users` WHERE `user_user` = '%s'", escape_string(auth));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        if(atoi(row[1]) != caccess)
+            printf_mysql_query("UPDATE `users` SET `user_access` = '%d' WHERE `user_id` = '%s'", caccess, row[0]);
+    } else {
+        printf_mysql_query("INSERT INTO `users` (`user_user`, `user_access`) VALUES ('%s', '%d')", escape_string(auth), caccess);
+    }
+    reply(textclient, user, "NS_SETACCESS_DONE", auth, caccess);
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_setbot.c b/src/modules/global.mod/cmd_global_setbot.c
new file mode 100644 (file)
index 0000000..c81fd7f
--- /dev/null
@@ -0,0 +1,559 @@
+/* cmd_global_setbot.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]  botid
+* argv[1]  setting
+* argv[2]  value
+*/
+
+static int global_cmd_setbot_active(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_nick(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_ident(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_realname(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_server(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_port(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_bind(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_ssl(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_serverpass(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_class(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_queue(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_prefered(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_secret(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_maxchan(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_priority(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+static int global_cmd_setbot_trigger(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value);
+
+CMD_BIND(global_cmd_setbot) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    int botid = atoi(argv[0]);
+    printf_mysql_query("SELECT `active`, `nick`, `server`, `port`, `pass`, `botclass`, `textbot`, `queue`, `defaulttrigger`, `max_channels`, `register_priority`, `bind`, `ident`, `realname`, `ssl`, `id`, `secret` FROM `bots` WHERE `id` = '%d'", botid);
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        reply(textclient, user, "NS_SETBOT_UNKNOWN", botid);
+        return;
+    }
+    if(argc > 1) {
+        char *value;
+        if(argc > 2) {
+            value = merge_argv(argv, 2, argc);
+        } else
+            value = NULL;
+        int log_event = 0;
+        if(!stricmp(argv[1], "active")) log_event = global_cmd_setbot_active(textclient, user, row, value);
+        else if(!stricmp(argv[1], "nick")) log_event = global_cmd_setbot_nick(textclient, user, row, value);
+        else if(!stricmp(argv[1], "ident")) log_event = global_cmd_setbot_ident(textclient, user, row, value);
+        else if(!stricmp(argv[1], "realname")) log_event = global_cmd_setbot_realname(textclient, user, row, value);
+        else if(!stricmp(argv[1], "server")) log_event = global_cmd_setbot_server(textclient, user, row, value);
+        else if(!stricmp(argv[1], "port")) log_event = global_cmd_setbot_port(textclient, user, row, value);
+        else if(!stricmp(argv[1], "bind")) log_event = global_cmd_setbot_bind(textclient, user, row, value);
+        else if(!stricmp(argv[1], "ssl")) log_event = global_cmd_setbot_ssl(textclient, user, row, value);
+        else if(!stricmp(argv[1], "serverpass")) log_event = global_cmd_setbot_serverpass(textclient, user, row, value);
+        else if(!stricmp(argv[1], "botclass")) log_event = global_cmd_setbot_class(textclient, user, row, value);
+        else if(!stricmp(argv[1], "queue")) log_event = global_cmd_setbot_queue(textclient, user, row, value);
+        else if(!stricmp(argv[1], "prefered")) log_event = global_cmd_setbot_prefered(textclient, user, row, value);
+        else if(!stricmp(argv[1], "secret")) log_event = global_cmd_setbot_secret(textclient, user, row, value);
+        else if(!stricmp(argv[1], "maxchan")) log_event = global_cmd_setbot_maxchan(textclient, user, row, value);
+        else if(!stricmp(argv[1], "priority")) log_event = global_cmd_setbot_priority(textclient, user, row, value);
+        else if(!stricmp(argv[1], "trigger")) log_event = global_cmd_setbot_trigger(textclient, user, row, value);
+        else {
+            reply(textclient, user, "NS_SETBOT_SETTING", argv[1]);
+        }
+        if(log_event) {
+            if(!stricmp(argv[1], "serverpass") && value) { //censor server password 
+                char cmd_args[MAXLEN];
+                sprintf(cmd_args, "%d SERVERPASS ***", botid);
+                free(event->arguments);
+                event->arguments = strdup(cmd_args);
+            }
+            logEvent(event);
+        }
+    } else {
+        reply(textclient, user, "NS_SETBOT_HEADER", botid);
+        global_cmd_setbot_active(textclient, user, row, NULL);
+        global_cmd_setbot_nick(textclient, user, row, NULL);
+        global_cmd_setbot_ident(textclient, user, row, NULL);
+        global_cmd_setbot_realname(textclient, user, row, NULL);
+        global_cmd_setbot_server(textclient, user, row, NULL);
+        global_cmd_setbot_port(textclient, user, row, NULL);
+        global_cmd_setbot_bind(textclient, user, row, NULL);
+        global_cmd_setbot_ssl(textclient, user, row, NULL);
+        global_cmd_setbot_serverpass(textclient, user, row, NULL);
+        global_cmd_setbot_class(textclient, user, row, NULL);
+        global_cmd_setbot_queue(textclient, user, row, NULL);
+        global_cmd_setbot_prefered(textclient, user, row, NULL);
+        global_cmd_setbot_secret(textclient, user, row, NULL);
+        global_cmd_setbot_maxchan(textclient, user, row, NULL);
+        global_cmd_setbot_priority(textclient, user, row, NULL);
+        global_cmd_setbot_trigger(textclient, user, row, NULL);
+    }
+}
+
+static int global_cmd_setbot_active(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = ((bot[0] && !strcmp(bot[0], "1")) ? 1 : 0);
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "0") || !stricmp(value, "off") || !stricmp(value, get_language_string(user, "NS_SET_OFF"))) {
+            val = 0;
+        } else if(!strcmp(value, "1") || !stricmp(value, "on") || !stricmp(value, get_language_string(user, "NS_SET_ON"))) {
+            val = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", value);
+            return 0;
+        }
+        if(val != ((bot[0] && !strcmp(bot[0], "1")) ? 1 : 0)) {
+            if(val) {
+                //add the bot
+                struct ClientSocket *client;
+                client = create_socket(bot[2], atoi(bot[3]), bot[11], bot[4], bot[1], bot[12], bot[13]);
+                client->flags |= (strcmp(bot[6], "0") ? SOCKET_FLAG_PREFERRED : 0);
+                client->flags |= (strcmp(bot[7], "0") ? SOCKET_FLAG_USE_QUEUE : 0);
+                client->flags |= (strcmp(bot[14], "0") ? SOCKET_FLAG_SSL : 0);
+                client->botid = atoi(bot[5]);
+                client->clientid = atoi(bot[15]);
+                connect_socket(client);
+                if(client->botid == 0) {
+                    MYSQL_RES *res;
+                    MYSQL_ROW row;
+                    printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d'", client->clientid);
+                    res = mysql_use();
+                    while ((row = mysql_fetch_row(res)) != NULL) {
+                        if(bind_botwise_cmd_to_command(0, client->clientid, row[0], row[1])) {
+                            if(row[2] && strcmp(row[2], "")) {
+                                bind_botwise_set_parameters(0, client->clientid, row[0], row[2]);
+                            }
+                            if(row[3]) {
+                                bind_botwise_set_global_access(0, client->clientid, row[0], atoi(row[3]));
+                            }
+                            if(row[4]) {
+                                bind_botwise_set_channel_access(0, client->clientid, row[0], row[4]);
+                            }
+                        }
+                    }
+                    bind_botwise_unbound_required_functions(0, client->clientid);
+                }
+            } else {
+                //remove the bot
+                struct ClientSocket *client;
+                for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+                    if(client->clientid == atoi(bot[15])) {
+                        unbind_botwise_allcmd(0, client->clientid);
+                        close_socket(client);
+                        break;
+                    }
+                }
+            }
+            printf_mysql_query("UPDATE `bots` SET `active` = '%d' WHERE `id` = '%s'", val, bot[15]);
+            ret = 1;
+        }
+    }
+    reply(textclient, user, "\002ACTIVE     \002 %s", get_language_string(user, (val ? "NS_SET_ON" : "NS_SET_OFF")));
+    return ret;
+}
+
+static int global_cmd_setbot_nick(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[1];
+    int ret = 0;
+    if(value) {
+        if(!is_valid_nick(value)) {
+            reply(textclient, user, "NS_SETBOT_NICK_INVALID", value);
+            return 0;
+        }
+        //rename the bot
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->nick)
+                    free(client->nick);
+                client->nick = strdup(value);
+                if(client->flags & SOCKET_FLAG_READY)
+                    putsock(client, "NICK %s", value);
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `nick` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002NICK       \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_ident(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[12];
+    int ret = 0;
+    if(value) {
+        if(strlen(value) > 12)
+            value[12] = '\0';
+        //rename the bot
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->ident)
+                    free(client->ident);
+                client->ident = strdup(value);
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `ident` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002IDENT      \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_realname(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[13];
+    int ret = 0;
+    if(value) {
+        if(strlen(value) > 255)
+            value[255] = '\0';
+        //rename the bot
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->ident)
+                    free(client->ident);
+                client->ident = strdup(value);
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `realname` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002REALNAME   \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_server(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[2];
+    int ret = 0;
+    if(value) {
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->host)
+                    free(client->host);
+                client->host = strdup(value);
+                if(client->flags & SOCKET_FLAG_READY)
+                    reply(textclient, user, "NS_SETBOT_NEED_RESTART");
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `server` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002SERVER     \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_port(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = atoi(bot[3]);
+    int ret = 0;
+    if(value) {
+        val = atoi(value);
+        if(val <= 0 || val > 65534) {
+            reply(textclient, user, "NS_SETBOT_PORT_INVALID", value);
+            return 0;
+        }
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                client->port = val;
+                if(client->flags & SOCKET_FLAG_READY)
+                    reply(textclient, user, "NS_SETBOT_NEED_RESTART");
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `port` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002PORT       \002 %d", val);
+    return ret;
+}
+
+static int global_cmd_setbot_bind(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[11];
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "*")) 
+            value = NULL;
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->bind)
+                    free(client->bind);
+                client->bind = (value ? strdup(value) : NULL);
+                if(client->flags & SOCKET_FLAG_READY)
+                    reply(textclient, user, "NS_SETBOT_NEED_RESTART");
+                break;
+            }
+        }
+        if(value)
+            printf_mysql_query("UPDATE `bots` SET `bind` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        else
+            printf_mysql_query("UPDATE `bots` SET `bind` = NULL WHERE `id` = '%s'", bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002BIND       \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_ssl(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = (strcmp(bot[14], "0") ? 1 : 0);
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "0") || !stricmp(value, "off") || !stricmp(value, get_language_string(user, "NS_SET_OFF"))) {
+            val = 0;
+        } else if(!strcmp(value, "1") || !stricmp(value, "on") || !stricmp(value, get_language_string(user, "NS_SET_ON"))) {
+            val = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", value);
+            return 0;
+        }
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(val)
+                    client->flags |= SOCKET_FLAG_SSL;
+                else
+                    client->flags &= ~SOCKET_FLAG_SSL;
+                if(client->flags & SOCKET_FLAG_READY)
+                    reply(textclient, user, "NS_SETBOT_NEED_RESTART");
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `ssl` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002SSL        \002 %s", get_language_string(user, (val ? "NS_SET_ON" : "NS_SET_OFF")));
+    return ret;
+}
+
+static int global_cmd_setbot_serverpass(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[4];
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "*")) 
+            value = "";
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(client->pass)
+                    free(client->pass);
+                client->pass = (value ? strdup(value) : NULL);
+                if(client->flags & SOCKET_FLAG_READY)
+                    reply(textclient, user, "NS_SETBOT_NEED_RESTART");
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `pass` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002SERVERPASS \002 %s", val);
+    return ret;
+}
+
+static int global_cmd_setbot_class(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = atoi(bot[5]);
+    int ret = 0;
+    if(value) {
+        if((val = resolve_botalias(value)) == -1) {
+            reply(textclient, user, "NS_SETBOT_INVALID_CLASS", value);
+            return 0;
+        }
+        if(val != atoi(bot[5])) {
+            struct ClientSocket *client;
+            for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+                if(client->clientid == atoi(bot[15])) {
+                    unbind_botwise_allcmd(0, client->clientid);
+                    client->botid = val;
+                    if(client->botid == 0) {
+                        MYSQL_RES *res;
+                        MYSQL_ROW row;
+                        printf_mysql_query("SELECT `command`, `function`, `parameters`, `global_access`, `chan_access` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d'", client->clientid);
+                        res = mysql_use();
+                        while ((row = mysql_fetch_row(res)) != NULL) {
+                            if(bind_botwise_cmd_to_command(client->botid, client->clientid, row[0], row[1])) {
+                                if(row[2] && strcmp(row[2], "")) {
+                                    bind_botwise_set_parameters(client->botid, client->clientid, row[0], row[2]);
+                                }
+                                if(row[3]) {
+                                    bind_botwise_set_global_access(client->botid, client->clientid, row[0], atoi(row[3]));
+                                }
+                                if(row[4]) {
+                                    bind_botwise_set_channel_access(client->botid, client->clientid, row[0], row[4]);
+                                }
+                            }
+                        }
+                        bind_botwise_unbound_required_functions(client->botid, client->clientid);
+                    }
+                    break;
+                }
+            }
+            printf_mysql_query("UPDATE `bots` SET `botclass` = '%d' WHERE `id` = '%s'", val, bot[15]);
+            ret = 1;
+        }
+    }
+    reply(textclient, user, "\002BOTCLASS   \002 %s", resolve_botid(val));
+    return ret;
+}
+
+static int global_cmd_setbot_queue(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = (strcmp(bot[7], "0") ? 1 : 0);
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "0") || !stricmp(value, "off") || !stricmp(value, get_language_string(user, "NS_SET_OFF"))) {
+            val = 0;
+        } else if(!strcmp(value, "1") || !stricmp(value, "on") || !stricmp(value, get_language_string(user, "NS_SET_ON"))) {
+            val = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", value);
+            return 0;
+        }
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(val)
+                    client->flags |= SOCKET_FLAG_USE_QUEUE;
+                else
+                    client->flags &= ~SOCKET_FLAG_USE_QUEUE;
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `queue` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002QUEUE      \002 %s", get_language_string(user, (val ? "NS_SET_ON" : "NS_SET_OFF")));
+    return ret;
+}
+
+static int global_cmd_setbot_prefered(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = (strcmp(bot[6], "0") ? 1 : 0);
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "0") || !stricmp(value, "off") || !stricmp(value, get_language_string(user, "NS_SET_OFF"))) {
+            val = 0;
+        } else if(!strcmp(value, "1") || !stricmp(value, "on") || !stricmp(value, get_language_string(user, "NS_SET_ON"))) {
+            val = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", value);
+            return 0;
+        }
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(val)
+                    client->flags |= SOCKET_FLAG_PREFERRED;
+                else
+                    client->flags &= ~SOCKET_FLAG_PREFERRED;
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `textbot` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002PREFERED   \002 %s", get_language_string(user, (val ? "NS_SET_ON" : "NS_SET_OFF")));
+    return ret;
+}
+
+static int global_cmd_setbot_secret(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = (strcmp(bot[16], "0") ? 1 : 0);
+    int ret = 0;
+    if(value) {
+        if(!strcmp(value, "0") || !stricmp(value, "off") || !stricmp(value, get_language_string(user, "NS_SET_OFF"))) {
+            val = 0;
+        } else if(!strcmp(value, "1") || !stricmp(value, "on") || !stricmp(value, get_language_string(user, "NS_SET_ON"))) {
+            val = 1;
+        } else {
+            reply(textclient, user, "NS_SET_INVALID_BOOLEAN", value);
+            return 0;
+        }
+        struct ClientSocket *client;
+        for(client = getBots(0, NULL); client; client = getBots(0, client)) {
+            if(client->clientid == atoi(bot[15])) {
+                if(val)
+                    client->flags |= SOCKET_FLAG_SECRET_BOT;
+                else
+                    client->flags &= ~SOCKET_FLAG_SECRET_BOT;
+                break;
+            }
+        }
+        printf_mysql_query("UPDATE `bots` SET `secret` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002SECRET     \002 %s", get_language_string(user, (val ? "NS_SET_ON" : "NS_SET_OFF")));
+    return ret;
+}
+
+static int global_cmd_setbot_maxchan(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = atoi(bot[9]);
+    int ret = 0;
+    if(value) {
+        val = atoi(value);
+        if(val < 0 || val > 99999) {
+            reply(textclient, user, "NS_SETBOT_MAXCHAN_INVALID", value);
+            return 0;
+        }
+        printf_mysql_query("UPDATE `bots` SET `max_channels` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002MAXCHAN    \002 %d", val);
+    return ret;
+}
+
+static int global_cmd_setbot_priority(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    int val = atoi(bot[10]);
+    int ret = 0;
+    if(value) {
+        val = atoi(value);
+        if(val < 0 || val > 99) {
+            reply(textclient, user, "NS_SETBOT_PRIORITY_INVALID", value);
+            return 0;
+        }
+        printf_mysql_query("UPDATE `bots` SET `register_priority` = '%d' WHERE `id` = '%s'", val, bot[15]);
+        ret = 1;
+    }
+    reply(textclient, user, "\002PRIORITY   \002 %d", val);
+    return ret;
+}
+
+static int global_cmd_setbot_trigger(struct ClientSocket *textclient, struct UserNode *user, MYSQL_ROW bot, char *value) {
+    char *val = bot[8];
+    int ret = 0;
+    if(value) {
+        if(!*value || strlen(value) > 10) {
+            reply(textclient, user, "NS_SETBOT_TRIGGER_INVALID", value);
+            return 0;
+        }
+        printf_mysql_query("UPDATE `bots` SET `defaulttrigger` = '%s' WHERE `id` = '%s'", escape_string(value), bot[15]);
+        flush_trigger_cache(atoi(bot[5]), atoi(bot[15]));
+        reply(textclient, user, "NS_SETBOT_TRIGGER_NOTE");
+        val = value;
+        ret = 1;
+    }
+    reply(textclient, user, "\002TRIGGER    \002 %s", val);
+    return ret;
+}
diff --git a/src/modules/global.mod/cmd_global_staff.c b/src/modules/global.mod/cmd_global_staff.c
new file mode 100644 (file)
index 0000000..73c7b3a
--- /dev/null
@@ -0,0 +1,52 @@
+/* cmd_global_staff.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* no arguments
+*/
+
+CMD_BIND(global_cmd_staff) {
+    MYSQL_RES *res, *res2;
+    MYSQL_ROW row, row2;
+    printf_mysql_query("SELECT `rank_id`, `rank_name` FROM `support_ranks` ORDER BY `rank_order` ASC");
+    res = mysql_use();
+    while ((row = mysql_fetch_row(res)) != NULL) {
+        printf_mysql_query("SELECT `user_user`, `user_god` FROM `users` WHERE `user_rank` = '%s'", row[0]);
+        res2 = mysql_use();
+        if(mysql_num_rows(res2)) {
+            reply(textclient, user, "\002%s\002", row[1]);
+            while ((row2 = mysql_fetch_row(res2)) != NULL) {
+                if(strcmp(row2[1], "0")) {
+                    //god enabled - show nicks
+                    char loggedinBuf[MAXLEN];
+                    int loggedinPos = 0;
+                    struct UserNode *cuser;
+                    for(cuser = getUsersWithAuth(row2[0], NULL); cuser; cuser = getUsersWithAuth(row2[0], cuser)) {
+                        loggedinPos += sprintf(loggedinBuf+loggedinPos, (loggedinPos ? ", %s" : "%s"), cuser->nick);
+                    }
+                    if(loggedinPos)
+                        reply(textclient, user, "  %s (%s: %s)", row2[0], get_language_string(user, "NS_STAFF_LOGGEDIN"), loggedinBuf);
+                    else
+                        reply(textclient, user, "  %s", row2[0]);
+                } else
+                    reply(textclient, user, "  %s", row2[0]);
+            }
+        }
+    }
+}
diff --git a/src/modules/global.mod/cmd_global_unbind.c b/src/modules/global.mod/cmd_global_unbind.c
new file mode 100644 (file)
index 0000000..14e724a
--- /dev/null
@@ -0,0 +1,54 @@
+/* cmd_global_unbind.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* argv[0]   command name
+*/
+
+CMD_BIND(global_cmd_unbind) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    struct cmd_binding *cbind = find_botwise_cmd_binding(client->botid, client->clientid, argv[0]);
+    if(client->botid == 0)
+        printf_mysql_query("SELECT `id`, `function` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d' AND `command` = '%s'", client->clientid, escape_string(argv[0]));
+    else
+        printf_mysql_query("SELECT `id`, `function` FROM `bot_binds` WHERE `botclass` = '%d' AND `command` = '%s'", client->botid, escape_string(argv[0]));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL && (!cbind || !(cbind->flags & CMDFLAG_TEMPONARY_BIND))) {
+        reply(textclient, user, "NS_UNBIND_NOT_FOUND", argv[0]);
+        return;
+    }
+    struct cmd_function *function = find_cmd_function(client->botid, cbind->func->name);
+    if(function && (function->flags & CMDFLAG_REQUIRED)) {
+        if(client->botid == 0)
+            printf_mysql_query("SELECT `id` FROM `bot_binds` WHERE `botclass` = '0' AND `botid` = '%d' AND `function` = '%s'", client->clientid, escape_string(function->name));
+        else
+            printf_mysql_query("SELECT `id` FROM `bot_binds` WHERE `botclass` = '%d' AND `function` = '%s'", client->botid, escape_string(function->name));
+        res = mysql_use();
+        if (mysql_num_rows(res) <= 1) {
+            reply(textclient, user, "NS_UNBIND_REQUIRED", function->name);
+            return;
+        }
+    }
+    unbind_botwise_cmd(client->botid, client->clientid, argv[0]);
+    if(!cbind || !(cbind->flags & CMDFLAG_TEMPONARY_BIND))
+        printf_mysql_query("DELETE FROM `bot_binds` WHERE `id` = '%s'", row[0]);
+    reply(textclient, user, "NS_UNBIND_DONE", argv[0]);
+    logEvent(event);
+}
diff --git a/src/modules/global.mod/cmd_global_unregister.c b/src/modules/global.mod/cmd_global_unregister.c
new file mode 100644 (file)
index 0000000..df7b1b5
--- /dev/null
@@ -0,0 +1,118 @@
+/* cmd_global_unregister.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+#include "../botid.h"
+
+/*
+* argv[0] - channel
+*/
+CMD_BIND(global_cmd_unregister) {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    char *channel;
+    if(argc)
+        channel = argv[0];
+    else
+        channel = (chan ? chan->name : "");
+    if(!is_valid_chan(channel)) {
+        reply(textclient, user, "NS_INVALID_CHANNEL_NAME", channel);
+        return;
+    }
+    int chanid;
+    printf_mysql_query("SELECT `channel_id`, `channel_nodelete` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", channel, client->user->nick);
+        return;
+    }
+    if(client->botid == NEONSERV_BOTID && !strcmp(row[1], "1")) {
+        reply(textclient, user, "NS_UNREGISTER_NODELETE", channel);
+        return;
+    }
+    int sync_neonspam_unreg = get_int_field("General.sync_neonspam_unreg");
+    if(client->botid == 0)
+        printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '0' AND `botid` = '%d'", chanid, client->clientid);
+    else
+        printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, client->botid);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL) {
+        reply(textclient, user, "NS_UNREGISTER_NOT_REGISTERED", channel, client->user->nick);
+        return;
+    }
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
+    reply(textclient, user, "NS_UNREGISTER_DONE", channel);
+    if(bot && strcmp(row[2], "1")) {
+        putsock(bot, "PART %s :Channel unregistered.", channel);
+    }
+    if(client->botid == NEONSERV_BOTID && sync_neonspam_unreg) {
+        printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` LEFT JOIN `channels` ON `chanid` = `channel_id` WHERE `channel_name` = '%s' AND `botclass` = '%d'", escape_string(channel), NEONSPAM_BOTID);
+        res = mysql_use();
+        if ((row = mysql_fetch_row(res))) {
+            botid = atoi(row[0]);
+            for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                if(bot->clientid == botid)
+                    break;
+            }
+            printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
+            if(bot && strcmp(row[2], "1"))
+                putsock(bot, "PART %s :Channel unregistered.", channel);
+        }
+    }
+    // NeonBackup SubBlock
+    if(client->botid == NEONSERV_BOTID) {
+        char setting[128];
+        sprintf(setting, "modules.%s.auto_backup_unregister", get_module_name(module_id));
+        if(get_int_field(setting))
+            module_global_cmd_unregister_neonbackup(channel);
+    }
+    logEvent(event);
+}
+
+void global_cmd_unregister_neonbackup(char *channel) {
+       MYSQL_RES *res;
+    MYSQL_ROW row;
+    int chanid;
+    printf_mysql_query("SELECT `channel_id` FROM `channels` WHERE `channel_name` = '%s'", escape_string(channel));
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) != NULL) {
+        chanid = atoi(row[0]);
+    } else 
+        return;
+    printf_mysql_query("SELECT `botid`, `bot_channels`.`id`, `suspended` FROM `bot_channels` LEFT JOIN `bots` ON `bot_channels`.`botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botclass` = '%d'", chanid, NEONBACKUP_BOTID);
+    res = mysql_use();
+    if ((row = mysql_fetch_row(res)) == NULL)
+        return;
+    int botid = atoi(row[0]);
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if(bot->clientid == botid)
+            break;
+    }
+    printf_mysql_query("DELETE FROM `bot_channels` WHERE `id` = '%s'", row[1]);
+    if(bot && strcmp(row[2], "1")) {
+        putsock(bot, "PART %s :Channel unregistered.", channel);
+    }
+}
diff --git a/src/modules/global.mod/cmd_global_version.c b/src/modules/global.mod/cmd_global_version.c
new file mode 100644 (file)
index 0000000..9cc3392
--- /dev/null
@@ -0,0 +1,40 @@
+/* cmd_global_version.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "cmd_global.h"
+
+/*
+* no args
+*/
+
+CMD_BIND(global_cmd_version) {
+    reply(textclient, user, "\002NeonServ %s.%d %s\002 (%s), written by pk910", NEONSERV_VERSION, get_patchlevel(), (is_stable_revision() ? "(stable)" : "(dev)"), (is_stable_revision() ? (strcmp(get_revision(), "") ? get_revision() : "-") : get_dev_revision()));
+    reply(textclient, user, "Build (#%s) %s (%s lines, " COMPILER ")", get_compilation(), get_creation(), get_codelines());
+    reply(textclient, user, "NeonServ can be found on: http://dev.pk910.de/NeonServ");
+    //helpers :D
+    reply(textclient, user, "special thanks to:");
+    reply(textclient, user, "  Testing and Ideas:");
+    reply(textclient, user, "    Zer0n, TeaTow, Phil, Stricted");
+    reply(textclient, user, "  Translations:");
+    reply(textclient, user, "    Buschman (de), Zer0n (de), Neon (de), TheFlie (nl), GodsWarriors (pt)");
+    reply(textclient, user, "  Others:");
+    reply(textclient, user, "    Patschi95, DerGrinch, Darkfly, Zer0n, Buschman  (testing and ideas older versions)");
+    reply(textclient, user, "    Buschman, Georg, richard  (translating older versions)");
+    reply(textclient, user, "and all the other users that reported all these nasty bugs :D");
+    reply(textclient, user, "\002If you found a bug or if you have a good idea report it on http://dev.pk910.de/BugTrack\002");
+    
+}
\ No newline at end of file
diff --git a/src/modules/global.mod/module.c b/src/modules/global.mod/module.c
new file mode 100644 (file)
index 0000000..d000c63
--- /dev/null
@@ -0,0 +1,33 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "cmd_global.h"
+
+static int module_initialize() {
+    register_commands();
+    return 0;
+}
+
+static void module_start(int type) {
+    
+}
+
+static void module_stop(int type) {
+    
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/modules/module.h b/src/modules/module.h
new file mode 100644 (file)
index 0000000..4c55c40
--- /dev/null
@@ -0,0 +1,253 @@
+/* module.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _module_h
+#define _module_h
+#define DND_FUNCTIONS 1 /* DoNotDefine_Functions */
+#include "../overall.h"
+#include "../version.h"
+#include "../mysqlConn.h"
+#include "../log.h"
+
+extern void **global;
+extern int module_id;
+
+/**** global function list ****/
+/* 000 */ #define getStartTime ((time_t (*)(void))global[0])
+/* 001 */ #define getRunningThreads ((int (*)(void))global[1])
+/* 002 */ #define getCurrentSecondsOfDay ((int (*)(void))global[2])
+/* 003 */ #define stricmp ((int (*)(const char *, const char *))global[3])
+/* 004 */ #define stricmplen ((int (*)(const char *, const char *, int))global[4])
+#ifdef ENABLE_MUTEX_DEBUG
+/* 005 */ #define xmutex ((void (*)(int, pthread_mutex_t *, const char *, unsigned int))global[005])
+/* 006 */ #define mutex_debug ((void (*)(pthread_mutex_t *))global[006])
+#endif
+/* 007 */ #define restart_bot ((void (*)(int))global[7])
+/* 008 */ #define stop_bot ((void (*)(void))global[8])
+/* 009 */ #define reload_config ((void (*)(void))global[9])
+/* 010 */ #define printf_log ((void (*)(char *, int, const char *, ...))global[10])
+#ifdef HAVE_THREADS
+/* 011 */ #define getCurrentThreadID ((int (*)(void))global[11])
+#endif
+/* 012 */ #define getMatchingChannelBan ((struct BanNode* (*)(struct ChanNode *, char *))global[12])
+/* 013 */ #define getChannelBot ((struct ClientSocket* (*)(struct ChanNode *, int))global[13])
+/* 014 */ #define requestOp ((void (*)(struct UserNode *, struct ChanNode *))global[14])
+/* 015 */ #define channel_ban_timeout ((void (*)(void *))global[15])
+/* 016 */ #define general_event_privctcp ((void (*)(struct UserNode *, struct UserNode *, char *, char *))global[16])
+/* 017 */ #define set_bot_alias ((void (*)(int, char *))global[17])
+/* 018 */ #define resolve_botid ((const char * (*)(int))global[18])
+/* 019 */ #define resolve_botalias ((int (*)(const char *))global[19])
+/* 020 */ #define is_valid_chan ((int (*)(const char *))global[20])
+/* 021 */ #define getAllChans ((struct ChanNode* (*)(struct ChanNode *))global[21])
+/* 022 */ #define getChanByName ((struct ChanNode* (*)(const char *))global[22])
+/* 023 */ #define getChannelCount ((int (*)(void))global[23])
+/* 024 */ #define getChanUserCount ((int (*)(void))global[24])
+/* 025 */ #define getChanBanCount ((int (*)(void))global[25])
+/* 026 */ #define isUserOnChan ((int (*)(struct UserNode *, struct ChanNode *))global[26])
+/* 027 */ #define getChanUser ((struct ChanUser* (*)(struct UserNode *, struct ChanNode *))global[27])
+/* 028 */ #define getChannelUsers ((struct ChanUser* (*)(struct ChanNode *, struct ChanUser *))global[28])
+/* 029 */ #define getUserChannels ((struct ChanUser* (*)(struct UserNode *, struct ChanUser *))global[29])
+/* 030 */ #define create_socket ((struct ClientSocket* (*)(char *, int, char *, char *, char *, char *, char *))global[30])
+/* 031 */ #define connect_socket ((void (*)(struct ClientSocket *))global[31])
+/* 032 */ #define close_socket ((int (*)(struct ClientSocket *))global[32])
+/* 033 */ #define destroy_socket ((int (*)(struct ClientSocket *))global[33])
+/* 034 */ #define write_socket ((int (*)(struct ClientSocket *, char*, int))global[34])
+/* 035 */ #define putsock ((void (*)(struct ClientSocket *, const char *, ...))global[35])
+/* 036 */ #define getBots ((struct ClientSocket* (*)(int, struct ClientSocket *))global[36])
+/* 037 */ #define get_int_field ((int (*)(char *))global[37])
+/* 038 */ #define get_string_field ((char * (*)(char *))global[38])
+/* 039 */ #define _loadUserSettings ((void (*)(struct UserNode *))global[39])
+/* 040 */ #define isGodMode ((int (*)(struct UserNode *))global[40])
+/* 041 */ #define getChanDefault ((char * (*)(char *))global[41])
+/* 042 */ #define getChannelAccess ((int (*)(struct UserNode *, struct ChanNode *))global[42])
+/* 043 */ #define checkChannelAccess ((int (*)(struct UserNode *, struct ChanNode *, char *, int))global[43])
+/* 044 */ #define _loadChannelSettings ((void (*)(struct ChanNode *))global[44])
+/* 045 */ #define isUserProtected ((int (*)(struct ChanNode *, struct UserNode *, struct UserNode *))global[45])
+/* 046 */ #define getBanAffectingMask ((char * (*)(struct ChanNode *, char *))global[46])
+/* 047 */ #define renameAccount ((int (*)(char *, char *))global[47])
+/* 048 */ #define deleteUser ((void (*)(int))global[48])
+/* 049 */ #define logEvent ((void (*)(struct Event *))global[49])
+/* 050 */ #define lookup_authname ((void (*)(char *, int, authlookup_callback_t, void *))global[50])
+/* 051 */ #define bind_join ((int (*)(join_func_t *, int))global[51])
+/* 052 */ #define unbind_join ((void (*)(join_func_t *))global[52])
+/* 053 */ #define bind_nick ((int (*)(nick_func_t *, int))global[53])
+/* 054 */ #define unbind_nick ((void (*)(nick_func_t *))global[54])
+/* 055 */ #define bind_part ((int (*)(part_func_t *, int))global[55])
+/* 056 */ #define unbind_part ((void (*)(part_func_t *))global[56])
+/* 057 */ #define bind_reload ((int (*)(reload_func_t *, int))global[57])
+/* 058 */ #define unbind_reload ((void (*)(reload_func_t *))global[58])
+/* 059 */ #define bind_kick ((int (*)(kick_func_t *, int))global[59])
+/* 060 */ #define unbind_kick ((void (*)(kick_func_t *))global[60])
+/* 061 */ #define bind_topic ((int (*)(topic_func_t *, int))global[61])
+/* 062 */ #define unbind_topic ((void (*)(topic_func_t *))global[62])
+/* 063 */ #define bind_mode ((int (*)(mode_func_t *, int))global[63])
+/* 064 */ #define unbind_mode ((void (*)(mode_func_t *))global[64])
+/* 065 */ #define bind_chanmsg ((int (*)(chanmsg_func_t *, int))global[65])
+/* 066 */ #define unbind_chanmsg ((void (*)(chanmsg_func_t *))global[66])
+/* 067 */ #define bind_privmsg ((int (*)(privmsg_func_t *, int))global[67])
+/* 068 */ #define unbind_privmsg ((void (*)(privmsg_func_t *))global[68])
+/* 069 */ #define bind_channotice ((int (*)(channotice_func_t *, int))global[69])
+/* 070 */ #define unbind_channotice ((void (*)(channotice_func_t *))global[70])
+/* 071 */ #define bind_privnotice ((int (*)(privnotice_func_t *, int))global[71])
+/* 072 */ #define unbind_privnotice ((void (*)(privnotice_func_t *))global[72])
+/* 073 */ #define bind_chanctcp ((int (*)(chanctcp_func_t *, int))global[73])
+/* 074 */ #define unbind_chanctcp ((void (*)(chanctcp_func_t *))global[74])
+/* 075 */ #define bind_privctcp ((int (*)(privctcp_func_t *, int))global[75])
+/* 076 */ #define unbind_privctcp ((void (*)(privctcp_func_t *))global[76])
+/* 077 */ #define bind_invite ((int (*)(invite_func_t *, int))global[77])
+/* 078 */ #define unbind_invite ((void (*)(invite_func_t *))global[78])
+/* 079 */ #define bind_raw ((int (*)(raw_func_t *, int))global[79])
+/* 080 */ #define unbind_raw ((void (*)(raw_func_t *))global[80])
+/* 081 */ #define bind_bot_ready ((int (*)(bot_ready_func_t *, int))global[81])
+/* 082 */ #define unbind_bot_ready ((void (*)(bot_ready_func_t *))global[82])
+/* 083 */ #define bind_registered ((int (*)(registered_func_t *, int))global[83])
+/* 084 */ #define unbind_registered ((void (*)(registered_func_t *))global[84])
+/* 085 */ #define bind_freeuser ((int (*)(freeuser_func_t *, int))global[85])
+/* 086 */ #define unbind_freeuser ((void (*)(freeuser_func_t *))global[86])
+/* 087 */ #define bind_freechan ((int (*)(freechan_func_t *, int))global[87])
+/* 088 */ #define unbind_freechan ((void (*)(freechan_func_t *))global[88])
+/* 089 */ #define reply ((void (*)(struct ClientSocket *, struct UserNode *, const char *, ...))global[89])
+/* 090 */ #define merge_argv ((char * (*)(char **, int, int))global[90])
+/* 091 */ #define merge_argv_char ((char * (*)(char **, int, int, char))global[91])
+/* 092 */ #define get_language_by_tag ((struct language * (*)(char *))global[92])
+/* 093 */ #define get_language_by_name ((struct language * (*)(char *))global[93])
+/* 094 */ #define get_default_language ((struct language * (*)(void))global[94])
+/* 095 */ #define load_language ((void (*)(char *, char *))global[95])
+/* 096 */ #define register_default_language_table ((void (*)(const struct default_language_entry *))global[96])
+/* 097 */ #define get_language_string ((char * (*)(struct UserNode *, const char *))global[97])
+/* 098 */ #define build_language_string ((char * (*)(struct UserNode *, char *, const char *, ...))global[98])
+#ifdef ENABLE_MEMORY_DEBUG
+/* 099 */ #define xmalloc ((void * (*)(unsigned int, const char *, unsigned int))global[99])
+/* 100 */ #define xcalloc ((void * (*)(unsigned int, unsigned int, const char *, unsigned int))global[100])
+/* 101 */ #define xstrdup ((char * (*)(const char *, const char *, unsigned int))global[101])
+/* 102 */ #define xfree ((void (*)(void *))global[102])
+#endif
+/* 103 */ #define getMemoryInfoFiles ((struct memoryInfoFiles * (*)(void))global[103])
+/* 104 */ #define freeMemoryInfoFiles ((void (*)(struct memoryInfoFiles *))global[104])
+/* 105 */ #define getMemoryInfoLines ((struct memoryInfoLines * (*)(const char *))global[105])
+/* 106 */ #define freeMemoryInfoLines ((void (*)(struct memoryInfoLines *))global[106])
+/* 107 */ #define get_botwise_prefered_bot ((struct ClientSocket * (*)(int, int))global[107])
+/* 108 */ #define register_command ((int (*)(int, char *, int, cmd_bind_t *, int, char *, int, unsigned int))global[108])
+/* 109 */ #define set_trigger_callback ((int (*)(int, int, trigger_callback_t *))global[109])
+/* 110 */ #define flush_trigger_cache ((int (*)(int, int))global[110])
+/* 111 */ #define changeBotwiseChannelTrigger ((int (*)(int, int, struct ChanNode *, char *))global[111])
+/* 112 */ #define bind_botwise_cmd_to_function ((int (*)(int, int, char *, struct cmd_function *))global[112])
+/* 113 */ #define bind_botwise_cmd_to_command ((int (*)(int, int, char *, char *))global[113])
+/* 114 */ #define unbind_botwise_cmd ((int (*)(int, int, char *))global[114])
+/* 115 */ #define unbind_botwise_allcmd ((int (*)(int, int))global[115])
+/* 116 */ #define bind_botwise_set_parameters ((void (*)(int, int, char *, char *))global[116])
+/* 117 */ #define bind_botwise_set_global_access ((void (*)(int, int, char *, int))global[117])
+/* 118 */ #define bind_botwise_set_channel_access ((void (*)(int, int, char *, char *))global[118])
+/* 119 */ #define bind_botwise_set_bind_flags ((void (*)(int, int, char *, unsigned int))global[119])
+/* 120 */ #define find_botwise_cmd_binding ((struct cmd_binding * (*)(int, int, char *))global[120])
+/* 121 */ #define bind_botwise_unbound_required_functions ((void (*)(int, int))global[121])
+/* 122 */ #define find_cmd_function ((struct cmd_function * (*)(int , char *))global[122])
+/* 123 */ /* deprecated */
+/* 124 */ #define register_command_alias ((void (*)(int, char *))global[124])
+/* 125 */ #define getAllBinds ((struct cmd_binding * (*)(struct cmd_binding *))global[125])
+/* 126 */ #define createModeNode ((struct ModeNode * (*)(struct ChanNode *))global[126])
+/* 127 */ #define freeModeNode ((void (*)(struct ModeNode *))global[127])
+/* 128 */ #define isModeSet ((int (*)(struct ModeNode *, char))global[128])
+/* 129 */ #define isModeAffected ((int (*)(struct ModeNode *, char))global[129])
+/* 130 */ #define getModeValue ((void * (*)(struct ModeNode *, char))global[130])
+/* 131 */ #define getModeType ((unsigned int (*)(struct ModeNode *, char))global[131])
+/* 132 */ #define parseModes ((void (*)(struct ModeNode *, char *, char **, int))global[132])
+/* 133 */ #define parseModeString ((void (*)(struct ModeNode *, char *))global[133])
+/* 134 */ #define parseMode ((int (*)(struct ModeNode *, int, char, char *))global[134])
+/* 135 */ #define getModeString ((void (*)(struct ModeNode *, char *))global[135])
+/* 136 */ #define getFullModeString ((void (*)(struct ModeNode *, char *))global[136])
+/* 137 */ #define mysql_use ((MYSQL_RES * (*)(void))global[137])
+/* 138 */ #define mysql_free ((void (*)(void))global[138])
+/* 139 */ #define printf_mysql_query ((void (*)(const char *, ...))global[139])
+/* 140 */ #define printf_long_mysql_query ((void (*)(int, const char *, ...))global[140])
+/* 141 */ #define escape_string ((char * (*)(const char *))global[141])
+/* 142 */ #define get_mysql_conn ((MYSQL * (*)(void))global[142])
+/* 143 */ #define timeq_add ((struct timeq_entry * (*)(int, int, timeq_callback_t *, void *))global[143])
+/* 144 */ #define timeq_uadd ((struct timeq_entry * (*)(int, int, timeq_callback_t *, void *))global[144])
+/* 145 */ #define timeq_add_name ((struct timeq_entry * (*)(char *, int, int, timeq_callback_t *, void *))global[145])
+/* 146 */ #define timeq_uadd_name ((struct timeq_entry * (*)(char *, int, int, timeq_callback_t *, void *))global[146])
+/* 147 */ #define timeq_del ((int (*)(struct timeq_entry *))global[147])
+/* 148 */ #define timeq_del_name ((int (*)(char *))global[148])
+/* 149 */ #define timeq_name_exists ((int (*)(char *))global[149])
+/* 150 */ #define match ((int (*)(const char *, const char *))global[150])
+/* 151 */ #define table_init ((struct Table * (*)(int, int, int))global[151])
+/* 152 */ #define table_add ((int (*)(struct Table *, char **))global[152])
+/* 153 */ #define table_change ((int (*)(struct Table *, int, char **))global[153])
+/* 154 */ #define table_change_field ((int (*)(struct Table *, int, int, char *))global[154])
+/* 155 */ #define table_set_bold ((int (*)(struct Table *, int, int))global[155])
+/* 156 */ #define table_end ((char ** (*)(struct Table *))global[156])
+/* 157 */ #define table_free ((void (*)(struct Table *))global[157])
+/* 158 */ #define timeToStr ((char * (*)(struct UserNode *, int, int, char *))global[158])
+/* 159 */ #define strToTime ((int (*)(struct UserNode *, char *))global[159])
+/* 160 */ #define initModeBuffer ((struct ModeBuffer * (*)(struct ClientSocket *, struct ChanNode *))global[160])
+/* 161 */ #define modeBufferSet ((void (*)(struct ModeBuffer *, int, char, char *))global[161])
+/* 162 */ #define flushModeBuffer ((void (*)(struct ModeBuffer *))global[162])
+/* 163 */ #define freeModeBuffer ((void (*)(struct ModeBuffer *))global[163])
+/* 164 */ #define is_ircmask ((int (*)(const char *))global[164])
+/* 165 */ #define generate_banmask ((char * (*)(struct UserNode *, char *))global[165])
+/* 166 */ #define make_banmask ((char * (*)(char *, char *))global[166])
+/* 167 */ #define isFakeHost ((int (*)(char *))global[167])
+/* 168 */ #define mask_match ((int (*)(char *, struct UserNode *))global[168])
+/* 169 */ #define crc32 ((unsigned long (*)(const char *))global[169])
+/* 170 */ #define is_valid_nick ((int (*)(const char *))global[170])
+/* 171 */ #define getUserByNick ((struct UserNode * (*)(const char *))global[171])
+/* 172 */ #define getUserByMask ((struct UserNode * (*)(const char *))global[172])
+/* 173 */ #define countUsersWithHost ((int (*)(char *))global[173])
+/* 174 */ #define getAuthFakehost ((char * (*)(char *))global[174])
+/* 175 */ #define searchUserByNick ((struct UserNode * (*)(const char *))global[175])
+/* 176 */ #define getAllUsers ((struct UserNode * (*)(struct UserNode *))global[176])
+/* 177 */ #define getUsersWithAuth ((struct UserNode * (*)(const char *, struct UserNode *))global[177])
+/* 178 */ #define getUserCount ((int (*)(void))global[178])
+/* 179 */ #define createTempUser ((struct UserNode * (*)(const char *))global[179])
+/* 180 */ #define createTempUserMask ((struct UserNode * (*)(const char *))global[180])
+/* 181 */ #define get_userlist ((void (*)(struct ChanNode *, int, userlist_callback_t, void *))global[181])
+/* 182 */ #define _get_userlist_with_invisible ((void (*)(struct ChanNode *, int, userlist_callback_t, void *, int))global[182])
+/* 183 */ #define get_userauth ((void (*)(struct UserNode *, int, userauth_callback_t, void *))global[183])
+/* 184 */ #define get_compilation ((const char * (*)(void))global[184])
+/* 185 */ #define get_creation ((const char * (*)(void))global[185])
+/* 186 */ #define get_revision ((const char * (*)(void))global[186])
+/* 187 */ #define get_codelines ((const char * (*)(void))global[187])
+/* 188 */ #define get_patchlevel ((const int (*)(void))global[188])
+/* 189 */ #define get_module_name ((char * (*)(int))global[189])
+/* 190 */ #define isUserModeSet ((int (*)(struct UserNode *, char))global[190])
+/* 191 */ #define module_global_cmd_register_neonbackup ((void (*)(char *))global[191])
+/* 192 */ #define module_global_cmd_unregister_neonbackup ((void (*)(char *))global[192])
+/* 193 */ #define module_neonbackup_recover_chan ((void (*)(struct ChanNode *))global[193])
+/* 194 */ #define requestInvite ((void (*)(struct UserNode *, struct ChanNode *))global[194])
+/* 195 */ #define is_stable_revision ((const int (*)(void))global[195])
+/* 196 */ #define get_dev_revision ((const char * (*)(void))global[196])
+/* 197 */ #define bind_freeclient ((int (*)(freeclient_func_t *, int))global[197])
+/* 198 */ #define unbind_freeclient ((void (*)(freeclient_func_t *))global[198])
+
+#define MODULE_HEADER(initfunc,startfunc,stopfunc) \
+    void **global = NULL; \
+    int module_id = 0; \
+    int init_module(void **functions, int modid) { \
+        global = functions; \
+        module_id = modid; \
+        return initfunc(); \
+    } \
+    void start_module(int type) { \
+        startfunc(type); \
+    } \
+    void stop_module(int type) { \
+        stopfunc(type); \
+    } \
+    int modversion() { \
+        return MODULE_VERSION; \
+    }
+
+#endif
\ No newline at end of file
diff --git a/src/modules/stats.mod/module.c b/src/modules/stats.mod/module.c
new file mode 100644 (file)
index 0000000..4780077
--- /dev/null
@@ -0,0 +1,171 @@
+/* module.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "../../timeq.h"
+#include "../../ConfigParser.h"
+#include "../../mysqlConn.h"
+#include "../../ClientSocket.h"
+#include "../../UserNode.h"
+
+#define STATS_UPDATE_SECONDS  1800
+#define STATS_UPDATE_HOST     "neonserv.krypton-bouncer.de"
+#define STATS_UPDATE_PORT     1675
+#define STATS_IDENTIFIER_LEN  10
+#define STATS_IDENTIFIER_POOL "abcdefghijklmnopqrstuvwxyz0123456789#*+-_.:;,!§$%&/()=?[]{}<>|@"
+#define STATS_IDENTIFIER_POOL_SIZE 63
+
+static TIMEQ_CALLBACK(stats_timer_callback);
+
+static int module_initialize() {
+    return 0;
+}
+
+static void module_start(int type) {
+    if(!timeq_name_exists("stats"))
+        timeq_add_name("stats", 60, module_id, stats_timer_callback, NULL);
+}
+
+static void module_stop(int type) {
+    timeq_del_name("stats");
+}
+
+static char *get_identifier() {
+    MYSQL_RES *res;
+    MYSQL_ROW row;
+    printf_mysql_query("SELECT `value` FROM `settings` WHERE `name` = 'identifier'");
+    res = mysql_use();
+    if(!(row = mysql_fetch_row(res))) {
+        srand(time(NULL));
+        char identifier[STATS_IDENTIFIER_LEN+1];
+        int i;
+        char *pool = STATS_IDENTIFIER_POOL;
+        for(i = 0; i < STATS_IDENTIFIER_LEN; i++) {
+            identifier[i] = pool[(rand() % STATS_IDENTIFIER_POOL_SIZE)];
+        }
+        identifier[i] = 0;
+        printf_mysql_query("INSERT INTO `settings` (`name`, `value`) VALUES ('identifier', '%s')", escape_string(identifier));
+        printf_mysql_query("SELECT `value` FROM `settings` WHERE `name` = 'identifier'");
+        res = mysql_use();
+        row = mysql_fetch_row(res);
+        if(!row) return NULL;
+    }
+    if(strlen(row[0]) < 10) return NULL;
+    return row[0];
+}
+
+static TIMEQ_CALLBACK(stats_timer_callback) {
+    timeq_add_name("stats", STATS_UPDATE_SECONDS, module_id, stats_timer_callback, NULL);
+    char tmp[200];
+    char pkgbuf[1024];
+    int pkgpos = 0;
+    char *modname = get_module_name(module_id);
+    char *bot_identifier = get_identifier();
+    if(!bot_identifier) return;
+    //build update package
+    pkgpos += sprintf(pkgbuf + pkgpos, "nsupdate\n%s\n%s.%d %s\n", bot_identifier, NEONSERV_VERSION, get_patchlevel(), (strcmp(get_revision(), "") ? get_revision() : "-"));
+    sprintf(tmp, "modules/%s/hide_networkname", modname);
+    if(get_int_field(tmp))
+        pkgpos += sprintf(pkgbuf + pkgpos, "*\n");
+    else {
+        struct ClientSocket *bot;
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->network_name) break;
+        }
+        if(bot)
+            pkgpos += sprintf(pkgbuf + pkgpos, "%s\n", bot->network_name);
+        else
+            pkgpos += sprintf(pkgbuf + pkgpos, "?\n");
+    }
+    sprintf(tmp, "modules/%s/hide_botnick", modname);
+    if(get_int_field(tmp))
+        pkgpos += sprintf(pkgbuf + pkgpos, "*\n");
+    else {
+        sprintf(tmp, "modules/%s/use_bot", modname);
+        char *botname = get_string_field(tmp);
+        struct ClientSocket *bot, *bot1 = NULL, *bot2 = NULL, *bot3 = NULL, *bot4 = NULL;
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(botname && !stricmp(bot->nick, botname))
+                bot1 = bot;
+            else if(bot->botid == 1 && (bot->flags & SOCKET_FLAG_PREFERRED))
+                bot2 = bot;
+            else if((bot->flags & SOCKET_FLAG_PREFERRED))
+                bot3 = bot;
+            else
+                bot4 = bot;
+        }
+        if(bot1)
+            bot = bot1;
+        else if(bot2)
+            bot = bot2;
+        else if(bot3)
+            bot = bot3;
+        else
+            bot = bot4;
+        if(bot) {
+            pkgpos += sprintf(pkgbuf + pkgpos, "%s!%s@%s %d\n", (bot->user ? bot->user->nick : "*"), (bot->user ? bot->user->ident : "*"), (bot->host ? bot->host : "*"), bot->port);
+        } else
+            pkgpos += sprintf(pkgbuf + pkgpos, "?\n");
+    }
+    sprintf(tmp, "modules/%s/hide_chancount", modname);
+    if(get_int_field(tmp))
+        pkgpos += sprintf(pkgbuf + pkgpos, "*\n");
+    else {
+        int channel_count = getChannelCount();
+        pkgpos += sprintf(pkgbuf + pkgpos, "%d\n", channel_count);
+    }
+    sprintf(tmp, "modules/%s/hide_usercount", modname);
+    if(get_int_field(tmp))
+        pkgpos += sprintf(pkgbuf + pkgpos, "*\n");
+    else {
+        int user_count = getUserCount();
+        int chanuser_count = getChanUserCount();
+        pkgpos += sprintf(pkgbuf + pkgpos, "%d %d\n", user_count, chanuser_count);
+    }
+    pkgpos += sprintf(pkgbuf + pkgpos, "%lu\n", getStartTime());
+    pkgbuf[pkgpos] = 0;
+    //send package
+    #ifndef WIN32
+    struct sockaddr_in si_other;
+    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock == -1) return;
+    memset((char *) &si_other, 0, sizeof(si_other));
+    si_other.sin_family = AF_INET;
+    si_other.sin_port = htons(STATS_UPDATE_PORT);
+    if (!inet_aton(STATS_UPDATE_HOST, &si_other.sin_addr)) {
+        struct hostent *host = gethostbyname(STATS_UPDATE_HOST);
+        if (!host) return;
+        si_other.sin_addr = *(struct in_addr*)host->h_addr;
+    }
+    sendto(sock, pkgbuf, pkgpos, 0, &si_other, sizeof(si_other));
+    #else
+    struct sockaddr_in si_other;
+    int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock == -1) return;
+    si_other.sin_family = AF_INET;
+    si_other.sin_port = htons(STATS_UPDATE_PORT);
+    si_other.sin_addr.s_addr = inet_addr(STATS_UPDATE_HOST);
+    if (si_other.sin_addr.s_addr == INADDR_NONE) {
+        struct hostent *host = gethostbyname(STATS_UPDATE_HOST);
+        if(!host) return;
+        memcpy(&(si_other.sin_addr), host->h_addr_list[0], 4);
+    }
+    sendto(sock, pkgbuf, pkgpos, 0, (struct sockaddr *) &si_other, sizeof(si_other));
+    #endif
+    close(sock);
+}
+
+MODULE_HEADER(module_initialize, module_start, module_stop);
diff --git a/src/mutexDebug.c b/src/mutexDebug.c
new file mode 100644 (file)
index 0000000..1b1e784
--- /dev/null
@@ -0,0 +1,190 @@
+/* mutexDebug.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "main.h"
+#include "mutexDebug.h"
+
+#ifdef ENABLE_MUTEX_DEBUG
+
+struct MutexLockEvent {
+    int locked;
+    const char *file;
+    unsigned int line;
+    struct MutexLockEvent *next;
+};
+
+struct MutexLock {
+    int thread;
+    int count;
+    struct MutexLockEvent *first_event, *last_event;
+    struct MutexLock *prev, *next;
+};
+
+struct MutexNode {
+    pthread_mutex_t *mutex;
+    struct MutexLock *first_lock, *last_lock;
+    struct MutexNode *next;
+};
+
+static pthread_mutex_t synchronized;
+static struct MutexNode *mutex_nodes = NULL;
+
+static struct MutexNode *getMutexNode(pthread_mutex_t *mutex, int create);
+static void lockMutex(struct MutexNode *node, const char *file, unsigned int line);
+static void unlockMutex(struct MutexNode *node, const char *file, unsigned int line);
+static void mutex_replay(struct MutexNode *node);
+
+void xmutex(int lock, pthread_mutex_t *mutex, const char *file, unsigned int line) {
+    pthread_mutex_lock(&synchronized);
+    struct MutexNode *node = getMutexNode(mutex, 1);
+    if(lock)
+        lockMutex(node, file, line);
+    else
+        unlockMutex(node, file, line);
+    pthread_mutex_unlock(&synchronized);
+}
+
+void initMutexDebug() {
+    THREAD_MUTEX_INIT(synchronized);
+}
+
+static struct MutexNode *getMutexNode(pthread_mutex_t *mutex, int create) {
+    struct MutexNode *node;
+    for(node = mutex_nodes; node; node = node->next) {
+        if(node->mutex == mutex)
+            return node;
+    }
+    if(!create)
+        return NULL;
+    node = malloc(sizeof(*node));
+    node->first_lock = NULL;
+    node->last_lock = NULL;
+    node->mutex = mutex;
+    node->next = mutex_nodes;
+    mutex_nodes = node;
+    return node;
+}
+
+static void lockMutex(struct MutexNode *node, const char *file, unsigned int line) {
+    struct MutexLock *lock;
+    int thread = getCurrentThreadID();
+    for(lock = node->first_lock; lock; lock = lock->next) {
+        if(lock->thread == thread)
+            break;
+    }
+    if(!lock) {
+        lock = malloc(sizeof(*lock));
+        lock->thread = thread;
+        lock->count = 0;
+        lock->first_event = NULL;
+        lock->last_event = NULL;
+        lock->prev = node->last_lock;
+        lock->next = NULL;
+        node->last_lock = lock;
+        if(!node->first_lock)
+            node->first_lock = lock;
+    }
+    lock->count++;
+    //add event
+    struct MutexLockEvent *event = malloc(sizeof(*event));
+    event->locked = 1;
+    event->file = file;
+    event->line = line;
+    event->next = NULL;
+    if(lock->last_event) {
+        lock->last_event->next = event;
+        lock->last_event = event;
+    } else {
+        lock->first_event = event;
+        lock->last_event = event;
+    }
+}
+
+static void unlockMutex(struct MutexNode *node, const char *file, unsigned int line) {
+    struct MutexLock *lock;
+    int thread = getCurrentThreadID();
+    for(lock = node->first_lock; lock; lock = lock->next) {
+        if(lock->thread == thread)
+            break;
+    }
+    if(!lock)
+        return;
+    lock->count--;
+    if(lock->count <= 0) {
+        //remove lock
+        if(lock->prev)
+            lock->prev->next = lock->next;
+        else
+            node->first_lock = lock->next;
+        if(lock->next)
+            lock->next->prev = lock->prev;
+        else
+            node->last_lock = lock->prev;
+        //recursive free all events
+        struct MutexLockEvent *event, *next_event;
+        for(event = lock->first_event; event; event = next_event) {
+            next_event = event->next;
+            free(event);
+        }
+        free(lock);
+    } else {
+        //add event
+        struct MutexLockEvent *event = malloc(sizeof(*event));
+        event->locked = 0;
+        event->file = file;
+        event->line = line;
+        event->next = NULL;
+        if(lock->last_event) {
+            lock->last_event->next = event;
+            lock->last_event = event;
+        } else {
+            lock->first_event = event;
+            lock->last_event = event;
+        }
+    }
+}
+
+void mutex_debug(pthread_mutex_t *mutex) {
+    //replay mutex events to stdout
+    struct MutexNode *node;
+    if(mutex) {
+        node = getMutexNode(mutex, 0);
+        if(!node) {
+            printf("[MUTEX_DEBUG] unknown mutex!\n");
+            return;
+        }
+        mutex_replay(node);
+    } else {
+        for(node = mutex_nodes; node; node = node->next) {
+            mutex_replay(node);
+        }
+    }
+    printf("[MUTEX_DEBUG] end of mutex replay.\n");
+}
+
+static void mutex_replay(struct MutexNode *node) {
+    printf("[MUTEX_DEBUG] mutex replay:\n");
+    struct MutexLock *lock;
+    struct MutexLockEvent *event;
+    for(lock = node->first_lock; lock; lock = lock->next) {
+        printf("[MUTEX_DEBUG]  THREAD %d (%d locks):\n", lock->thread, lock->count);
+        for(event = lock->first_event; event; event = event->next) {
+            printf("[MUTEX_DEBUG]   %s in %s:%d\n", (event->locked ? "lock  " : "unlock"), event->file, event->line);
+        }
+    }
+}
+
+#endif
diff --git a/src/mutexDebug.h b/src/mutexDebug.h
new file mode 100644 (file)
index 0000000..2b2a995
--- /dev/null
@@ -0,0 +1,29 @@
+/* mutexDebug.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _mutexdebug_h
+#define _mutexdebug_h
+
+#ifndef DND_FUNCTIONS
+#ifdef ENABLE_MUTEX_DEBUG
+/* MODULAR ACCESSIBLE */ void xmutex(int lock, pthread_mutex_t *mutex, const char *file, unsigned int line);
+/* MODULAR ACCESSIBLE */ void mutex_debug(pthread_mutex_t *mutex);
+
+void initMutexDebug();
+
+#endif
+#endif
+#endif
diff --git a/src/mysqlConn.c b/src/mysqlConn.c
new file mode 100644 (file)
index 0000000..45ab193
--- /dev/null
@@ -0,0 +1,328 @@
+/* mysqlConn.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "mysqlConn.h"
+#include "ConfigParser.h"
+#include "tools.h"
+#include "log.h"
+#define DATABASE_VERSION "20"
+
+static void show_mysql_error();
+
+struct mysql_conn_struct {
+    unsigned int tid;
+    MYSQL *mysql_conn;
+    struct used_result *used_results;
+    struct escaped_string *escaped_strings;
+    struct mysql_conn_struct *next;
+};
+
+struct used_result {
+    MYSQL_RES *result;
+    struct used_result *next;
+};
+
+struct escaped_string {
+    char *string;
+    struct escaped_string *next;
+};
+
+struct mysql_conn_struct *get_mysql_conn_struct();
+
+struct mysql_conn_struct *mysql_conns = NULL;
+static int mysql_serverport;
+static char *mysql_host, *mysql_user, *mysql_pass, *mysql_base;
+
+#ifdef HAVE_THREADS
+static pthread_mutex_t synchronized;
+#endif
+
+static void check_mysql() {
+    MYSQL *mysql_conn = get_mysql_conn();
+    int errid;
+    if((errid = mysql_ping(mysql_conn))) {
+        if(mysql_errno(mysql_conn) == CR_SERVER_GONE_ERROR) {
+            if(!mysql_real_connect(mysql_conn, mysql_host, mysql_user, mysql_pass, mysql_base, mysql_serverport, NULL, 0)) {
+                show_mysql_error();
+            }
+        } else {
+            //mysql error
+            show_mysql_error();
+        }
+    }
+}
+
+MYSQL_RES *mysql_use() {
+    struct mysql_conn_struct *mysql_conn = get_mysql_conn_struct();
+    MYSQL_RES *res = mysql_store_result(mysql_conn->mysql_conn);
+    struct used_result *result = malloc(sizeof(*result));
+    if (!result) {
+        mysql_free_result(res);
+        return NULL;
+    }
+    result->result = res;
+    result->next = mysql_conn->used_results;
+    mysql_conn->used_results = result;
+    return res;
+}
+
+void mysql_free() {
+    struct mysql_conn_struct *mysql_conn = get_mysql_conn_struct();
+    if(!mysql_conn) return;
+    struct used_result *result, *next_result;
+    for(result = mysql_conn->used_results; result; result = next_result) {
+        next_result = result->next;
+        mysql_free_result(result->result);
+        free(result);
+    }
+    mysql_conn->used_results = NULL;
+    struct escaped_string *escaped, *next_escaped;
+    for(escaped = mysql_conn->escaped_strings; escaped; escaped = next_escaped) {
+        next_escaped = escaped->next;
+        free(escaped->string);
+        free(escaped);
+    }
+    mysql_conn->escaped_strings = NULL;
+}
+
+int reload_mysql() {
+    char *new_mysql_host = get_string_field("MySQL.host");
+    char *new_mysql_user = get_string_field("MySQL.user");
+    char *new_mysql_pass = get_string_field("MySQL.pass");
+    char *new_mysql_base = get_string_field("MySQL.base");
+    if(!(new_mysql_host && new_mysql_user && new_mysql_pass && new_mysql_base))
+        return 0;
+    
+    //replace login data
+    if(mysql_host)
+        free(mysql_host);
+    mysql_host = strdup(new_mysql_host);
+    
+    if(mysql_user)
+        free(mysql_user);
+    mysql_user = strdup(new_mysql_user);
+    
+    if(mysql_pass)
+        free(mysql_pass);
+    mysql_pass = strdup(new_mysql_pass);
+    
+    if(mysql_base)
+        free(mysql_base);
+    mysql_base = strdup(new_mysql_base);
+    
+    mysql_serverport = get_int_field("MySQL.port");
+    if(!mysql_serverport)
+        mysql_serverport = 3306;
+    return 1;
+}
+
+void init_mysql() {
+    THREAD_MUTEX_INIT(synchronized);
+    
+    MYSQL *mysql_conn = get_mysql_conn();
+    
+    //check database version...
+    int version = 0;
+    if(!mysql_query(mysql_conn, "SELECT `database_version` FROM `version`")) {
+        MYSQL_RES *res = mysql_use();
+        MYSQL_ROW row;
+        if((row = mysql_fetch_row(res))) {
+            version = atoi(row[0]);
+        }
+    }
+    if(!version) {
+        //CREATE DATABASE
+        FILE *f = fopen("database.sql", "r");
+        mysql_set_server_option(mysql_conn, MYSQL_OPTION_MULTI_STATEMENTS_ON);
+        if (f) {
+            char line[512];
+            char query_buffer[8192];
+            int query_buffer_pos = 0;
+            while (fgets(line, sizeof(line), f)) {
+                query_buffer_pos += sprintf(query_buffer + query_buffer_pos, " %s", line);
+                if(line[(strlen(line) - 2)] == ';') {
+                    if(mysql_query(mysql_conn, query_buffer))
+                        show_mysql_error();
+                    query_buffer_pos = 0;
+                }
+            }
+            fclose(f);
+        } else
+            printf_log("main", LOG_ERROR | LOG_MYSQL, "File not found: database.sql");
+        f = fopen("database.defaults.sql", "r");
+        if (f) {
+            char line[4096];
+            char query_buffer[131072];
+            int query_buffer_pos = 0;
+            while (fgets(line, sizeof(line), f)) {
+                query_buffer_pos += sprintf(query_buffer + query_buffer_pos, " %s", line);
+                if(line[(strlen(line) - 2)] == ';') {
+                    if(mysql_query(mysql_conn, query_buffer))
+                        show_mysql_error();
+                    query_buffer_pos = 0;
+                }
+            }
+            fclose(f);
+        } else
+            printf_log("main", LOG_ERROR | LOG_MYSQL, "File not found: database.defaults.sql");
+        do { 
+            MYSQL_RES *res = mysql_store_result(mysql_conn); 
+            mysql_free_result(res); 
+        } while(!mysql_next_result(mysql_conn));
+        mysql_set_server_option(mysql_conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF);
+        mysql_query(mysql_conn, "INSERT INTO `version` (`database_version`) VALUES ('" DATABASE_VERSION "')");
+    }
+    else if(version < atoi(DATABASE_VERSION)) {
+        //UPDATE DATABASE
+        FILE *f = fopen("database.upgrade.sql", "r");
+        mysql_set_server_option(mysql_conn, MYSQL_OPTION_MULTI_STATEMENTS_ON);
+        if (f) {
+            char line[512];
+            char query_buffer[8192];
+            int query_buffer_pos = 0, use_querys = 0;
+            sprintf(query_buffer, "-- version: %d", version);
+            while (fgets(line, sizeof(line), f)) {
+                if(use_querys) {
+                    query_buffer_pos += sprintf(query_buffer + query_buffer_pos, " %s", line);
+                    if(line[strlen(line) - 1] == ';') {
+                        mysql_query(mysql_conn, query_buffer);
+                        query_buffer_pos = 0;
+                    }
+                } else if(!stricmplen(query_buffer, line, strlen(query_buffer))) {
+                    use_querys = 1;
+                }
+            }
+            if(query_buffer_pos) {
+                if(mysql_query(mysql_conn, query_buffer))
+                    show_mysql_error();
+            }
+            fclose(f);
+        } else
+            printf_log("main", LOG_ERROR | LOG_MYSQL, "File not found: database.upgrade.sql");
+        do { 
+            MYSQL_RES *res = mysql_store_result(mysql_conn); 
+            mysql_free_result(res); 
+        } while(!mysql_next_result(mysql_conn));
+        mysql_set_server_option(mysql_conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF);
+        mysql_query(mysql_conn, "UPDATE `version` SET `database_version` = '" DATABASE_VERSION "'");
+    }
+}
+
+void free_mysql() {
+    struct mysql_conn_struct *mysql_conn, *next;
+    for(mysql_conn = mysql_conns; mysql_conn; mysql_conn = next) {
+        next = mysql_conn->next;
+        mysql_close(mysql_conn->mysql_conn);
+        free(mysql_conn);
+    }
+    mysql_conns = NULL;
+}
+
+static void show_mysql_error() {
+    MYSQL *mysql_conn = get_mysql_conn();
+    //show mysql_error()
+    printf_log("main", LOG_ERROR | LOG_MYSQL, "Error: %s\n", mysql_error(mysql_conn));
+}
+
+void printf_mysql_query(const char *text, ...) {
+    MYSQL *mysql_conn = get_mysql_conn();
+    va_list arg_list;
+    char queryBuf[MYSQLMAXLEN];
+    int pos;
+    queryBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(queryBuf, MYSQLMAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MYSQLMAXLEN - 2)) pos = MYSQLMAXLEN - 2;
+    queryBuf[pos] = '\0';
+    printf_log("main", LOG_MYSQL, "%s\n", queryBuf);
+    if(mysql_query(mysql_conn, queryBuf)) {
+        check_mysql();
+        if(mysql_query(mysql_conn, queryBuf)) {
+            show_mysql_error();
+        }
+    }
+}
+
+void printf_long_mysql_query(int len, const char *text, ...) {
+    MYSQL *mysql_conn = get_mysql_conn();
+    va_list arg_list;
+    char queryBuf[len];
+    int pos;
+    queryBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(queryBuf, len - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (len - 2)) pos = len - 2;
+    queryBuf[pos] = '\0';
+    printf_log("main", LOG_MYSQL, "%s\n", queryBuf);
+    if(mysql_query(mysql_conn, queryBuf)) {
+        check_mysql();
+        if(mysql_query(mysql_conn, queryBuf)) {
+            show_mysql_error();
+        }
+    }
+}
+
+char* escape_string(const char *str) {
+    struct mysql_conn_struct *mysql_conn = get_mysql_conn_struct();
+    struct escaped_string *escapedstr = malloc(sizeof(*escapedstr));
+    if (!escapedstr) {
+        return NULL;
+    }
+    char escaped[strlen(str)*2+1];
+    mysql_real_escape_string(mysql_conn->mysql_conn, escaped, str, strlen(str));
+    escapedstr->string = strdup(escaped);
+    escapedstr->next = mysql_conn->escaped_strings;
+    mysql_conn->escaped_strings = escapedstr;
+    return escapedstr->string;
+}
+
+struct mysql_conn_struct *get_mysql_conn_struct() {
+    SYNCHRONIZE(synchronized);
+    struct mysql_conn_struct *mysql_conn;
+    unsigned int tid;
+    #ifdef HAVE_THREADS
+    tid = (unsigned int) pthread_self_tid();
+    #else
+    tid = 1;
+    #endif
+    for(mysql_conn = mysql_conns; mysql_conn; mysql_conn = mysql_conn->next) {
+        if(mysql_conn->tid == tid) {
+            DESYNCHRONIZE(synchronized);
+            return mysql_conn;
+        }
+    }
+    mysql_conn = malloc(sizeof(*mysql_conn));
+    mysql_conn->mysql_conn = mysql_init(NULL);
+    mysql_conn->tid = tid;
+    mysql_conn->used_results = NULL;
+    mysql_conn->escaped_strings = NULL;
+    mysql_conn->next = mysql_conns;
+    mysql_conns = mysql_conn;
+    if (!mysql_real_connect(mysql_conn->mysql_conn, mysql_host, mysql_user, mysql_pass, mysql_base, mysql_serverport, NULL, 0)) {
+        //error
+        show_mysql_error();
+    }
+    DESYNCHRONIZE(synchronized);
+    return mysql_conn;
+}
+
+MYSQL *get_mysql_conn() {
+    struct mysql_conn_struct *mysql_conn = get_mysql_conn_struct();
+    return mysql_conn->mysql_conn;
+}
diff --git a/src/mysqlConn.h b/src/mysqlConn.h
new file mode 100644 (file)
index 0000000..e85832f
--- /dev/null
@@ -0,0 +1,41 @@
+/* mysqlConn.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _MySQLConn_h
+#define _MySQLConn_h
+
+#include "main.h"
+#include <mysql.h>
+#ifdef HAVE_MYSQL_ERRMSG_H
+#include <mysql/errmsg.h>
+#elif defined(HAVE_ERRMSG_H)
+#include <errmsg.h>
+#endif
+
+#define MYSQLMAXLEN 1024
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ MYSQL_RES *mysql_use();
+/* MODULAR ACCESSIBLE */ void mysql_free();
+int reload_mysql();
+void init_mysql();
+void free_mysql();
+/* MODULAR ACCESSIBLE */ void printf_mysql_query(const char *text, ...) PRINTF_LIKE(1, 2);
+/* MODULAR ACCESSIBLE */ void printf_long_mysql_query(int len, const char *text, ...) PRINTF_LIKE(2, 3);
+/* MODULAR ACCESSIBLE */ char* escape_string(const char *str);
+/* MODULAR ACCESSIBLE */ MYSQL *get_mysql_conn();
+#endif
+#endif
diff --git a/src/overall.h b/src/overall.h
new file mode 100644 (file)
index 0000000..ff6f408
--- /dev/null
@@ -0,0 +1,174 @@
+/* overall.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#ifndef _overall_h
+#define _overall_h
+#include "../config.h"
+
+#define NEONSERV_VERSION "5.6"
+#define VERSION_PATCHLEVEL 736
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef WIN32
+#include <windows.h>
+#include <winsock2.h>
+#include <malloc.h>
+#else
+#ifdef HAVE_FEATURES_H
+#include <features.h>
+#endif
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/wait.h>
+#include <errno.h>
+#endif
+#include <unistd.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#ifdef SYNCHRONIZE
+//some winnt machines do already have a SYNCHRONIZE macro defined...
+#undef SYNCHRONIZE
+#endif
+#ifdef HAVE_THREADS
+#include <pthread.h>
+#ifdef WIN32
+#define pthread_self_tid() pthread_self().p
+#else
+#define pthread_self_tid() pthread_self()
+#endif
+#ifdef PTHREAD_MUTEX_RECURSIVE_NP
+#define PTHREAD_MUTEX_RECURSIVE_VAL PTHREAD_MUTEX_RECURSIVE_NP
+#else
+#define PTHREAD_MUTEX_RECURSIVE_VAL PTHREAD_MUTEX_RECURSIVE
+#endif
+#define THREAD_MUTEX_INIT(var) { \
+    pthread_mutexattr_t mutex_attr; \
+    pthread_mutexattr_init(&mutex_attr);\
+    pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE_VAL);\
+    pthread_mutex_init(&var, &mutex_attr); \
+}
+#define THREAD_MUTEX_INIT_TYPE(var, type) { \
+    pthread_mutexattr_t mutex_attr; \
+    pthread_mutexattr_init(&mutex_attr);\
+    pthread_mutexattr_settype(&mutex_attr, type);\
+    pthread_mutex_init(&var, &mutex_attr); \
+}
+#ifdef ENABLE_MUTEX_DEBUG
+#include "mutexDebug.h"
+#define SYNCHRONIZE(var) xmutex(1, &var, __FILE__, __LINE__); pthread_mutex_lock(&var)
+#define DESYNCHRONIZE(var) xmutex(0, &var, __FILE__, __LINE__); pthread_mutex_unlock(&var)
+#else
+#define SYNCHRONIZE(var) pthread_mutex_lock(&var)
+#define DESYNCHRONIZE(var) pthread_mutex_unlock(&var)
+#endif
+#else
+#define THREAD_MUTEX_INIT(var)
+#define SYNCHRONIZE(var)
+#define DESYNCHRONIZE(var)
+#ifdef ENABLE_MUTEX_DEBUG
+#undef ENABLE_MUTEX_DEBUG
+#endif
+#endif
+
+#if __GNUC__
+#define PRINTF_LIKE(M,N) __attribute__((format (printf, M, N)))
+#else
+#define PRINTF_LIKE(M,N)
+#endif
+
+#if __GNUC__ >= 2
+#define UNUSED_ARG(ARG) ARG __attribute__((unused))
+#elif defined(S_SPLINT_S)
+#define UNUSED_ARG(ARG) /*@unused@*/ ARG
+#define const /*@observer@*/ /*@temp@*/
+#else
+#define UNUSED_ARG(ARG) ARG
+#endif
+
+#define STRINGIFY_(x) #x
+#define STRINGIFY(x) STRINGIFY_(x)
+#if defined(__GNUC__)
+#if defined(__GNUC_PATCHLEVEL__)
+#define COMPILER "GCC" " " STRINGIFY(__GNUC__) "." STRINGIFY(__GNUC_MINOR__) "." STRINGIFY(__GNUC_PATCHLEVEL__)
+#else
+#define COMPILER "GCC" " " STRINGIFY(__GNUC__) "." STRINGIFY(__GNUC_MINOR__)
+#endif
+#elif defined (__IMAGECRAFT__)
+#define COMPILER "ICCAVR"
+#else
+#define COMPILER "Unknown"
+#endif
+
+#ifdef ENABLE_MEMORY_DEBUG
+#include "memoryDebug.h"
+#endif
+
+#define SOCKET_SELECT_TIME    1
+#define SOCKET_RECONNECT_TIME 20
+
+#define NICKLEN         30
+#define USERLEN         10
+#define AUTHLEN         32
+#define HOSTLEN         63
+#define REALLEN         50
+#define TOPICLEN        500
+#define CHANNELLEN      200
+#define MAXLEN          512
+#define MAXLOGLEN       1024
+#define TRIGGERLEN      50
+#define MAXNUMPARAMS    200 /* maximum number of parameters in one line */
+#define MAXLANGUAGES    5
+#define MAXMODES        6
+#define INVITE_TIMEOUT  30
+#define BOTWAR_DETECTION_TIME 7
+#define BOTWAR_DETECTION_EVENTS 6
+#define REWHO_TIMEOUT   10 /* wait 10 seconds before WHO an unauthed user again */
+#define CLEAR_CACHE_INTERVAL 10
+
+//valid nick chars
+#define VALID_NICK_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890{|}~[\\]^-_`"
+//the first char is a little bit different
+//                              0        1         2         3         4         5          6
+//                              1234567890123456789012345678901234567890123456789012345678 9012   62
+#define VALID_NICK_CHARS_FIRST "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~[\\]^_`"
+#define VALID_NICK_CHARS_FIRST_LEN 62
+
+#define TEMPUSER_LIST_INDEX VALID_NICK_CHARS_FIRST_LEN
+
+#define LOGLEVEL_INFO  0x01
+#define LOGLEVEL_ERROR 0x02
+#define LOGLEVEL_RAW   0x04
+#define LOGLEVEL_MYSQL 0x08
+
+#define timeval_is_bigger(x,y) ((x.tv_sec > y.tv_sec) || (x.tv_sec == y.tv_sec && x.tv_usec > y.tv_usec))
+
+#define MODSTATE_RELOAD    0x01
+#define MODSTATE_STARTSTOP 0x02
+#define MODSTATE_REBIND    0x03
+
+#endif
diff --git a/src/signal.c b/src/signal.c
new file mode 100644 (file)
index 0000000..4e8f9a5
--- /dev/null
@@ -0,0 +1,96 @@
+/* signal.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "signal.h"
+#include "bots.h"
+#include "ChanNode.h"
+#include "ClientSocket.h"
+#include "IOHandler.h"
+#include "ConfigParser.h"
+#include "log.h"
+
+static void sigcrash();
+static void sigexit();
+
+void sighandler(int signum) {
+    printf_log("main", LOG_INFO, "Received Signal %d\n", signum);
+    signal(signum, SIG_DFL);
+    switch(signum) {
+        case SIGABRT:
+        case SIGTERM:
+        case SIGINT:
+            //normal shutdown
+            sigexit(signum);
+            break;
+        case SIGSEGV:
+        case SIGFPE:
+        case SIGILL:
+            //crash
+            sigcrash(signum);
+            break;
+    }
+    #ifdef WIN32
+    exit(signum);
+    #else
+    kill(getpid(), signum);
+    #endif
+}
+
+static void sigcrash(int signum) {
+    char coregen[MAXLEN];
+       coregen[0] = 0;
+    char *signame;
+    switch(signum) {
+        case SIGSEGV:
+            signame = "SIGSEGV";
+            break;
+        case SIGFPE:
+            signame = "SIGFPE";
+            break;
+        case SIGILL:
+            signame = "SIGILL";
+            break;
+        default:
+            signame = "SIGUNKNOWN";
+            break;
+    }
+    printf_log("main", LOG_ERROR, "NeonServ process crashed (%s)\n", signame);
+    #ifndef WIN32
+    char gcore[50];
+    sprintf(gcore, "gcore %u", getpid());
+    int sysretn = system(gcore); //generate core file
+    sprintf(coregen, "core file generated. (%d)", sysretn);
+    printf_log("main", LOG_ERROR | LOG_INFO, "generated core file.\n");
+    #endif
+    //close all bots
+    struct ClientSocket *bot;
+    for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+        if((bot->flags & SOCKET_FLAG_CONNECTED)) {
+            iohandler_close(bot->iofd);
+            bot->flags &= ~(SOCKET_FLAG_CONNECTED | SOCKET_FLAG_READY);
+            bot->iofd = NULL;
+        }
+    }
+    printf_log("main", LOG_INFO, "hard shutdown...\n");
+    usleep(2000000);
+    //hard restart
+    restart_bot(1);
+    exit(0);
+}
+
+static void sigexit(int signum) {
+    stop_bot();
+}
diff --git a/src/signal.h b/src/signal.h
new file mode 100644 (file)
index 0000000..b59620a
--- /dev/null
@@ -0,0 +1,24 @@
+/* signal.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _signal_h
+#define _signal_h
+
+#include "main.h"
+
+void sighandler(int signum);
+
+#endif
\ No newline at end of file
diff --git a/src/statistics.c b/src/statistics.c
new file mode 100644 (file)
index 0000000..5d102ca
--- /dev/null
@@ -0,0 +1,78 @@
+/* statistics.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "statistics.h"
+#include "timeq.h"
+#include "ConfigParser.h"
+#include "ClientSocket.h"
+#include "bots.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ChanUser.h"
+#include "IRCParser.h"
+#include "modcmd.h"
+
+int statistics_requested_lusers = 0;
+
+static TIMEQ_CALLBACK(main_statistics) {
+    int update_minutes = get_int_field("statistics.frequency");
+    if(!update_minutes) update_minutes = 2;
+    timeq_add(update_minutes * 60, 0, main_statistics, NULL);
+    if(get_int_field("statistics.enable")) {
+        statistics_requested_lusers = 1;
+        if(get_int_field("statistics.include_lusers")) {
+            struct ClientSocket *bot, *lusersbot = NULL;
+            for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+                if(bot->flags & SOCKET_FLAG_PREFERRED)
+                    lusersbot = bot;
+            }
+            bot = lusersbot;
+            if(bot == NULL) bot = getBots(SOCKET_FLAG_READY, NULL);
+            putsock(bot, "LUSERS");
+        } else
+            statistics_update();
+    } else {
+        statistics_privmsg = 0;
+        statistics_commands = 0;
+    }
+}
+
+int statistics_update() {
+    if(get_int_field("statistics.enable") && statistics_requested_lusers && get_string_field("statistics.execute")) {
+        statistics_requested_lusers = 0;
+        char command[MAXLEN];
+        /* parameters:
+         - visible users
+         - visible chanusers
+         - visible channels
+         - privmsg per minute
+         - commands per minute
+         - network users
+         - network channels
+        */
+        sprintf(command, "%s %d %d %d %d %d %d %d", get_string_field("statistics.execute"), getUserCount(), getChanUserCount(), getChannelCount(), statistics_privmsg, statistics_commands, statistics_network_users, statistics_network_channels);
+        statistics_privmsg = 0;
+        statistics_commands = 0;
+        return system(command);
+    }
+       return -1;
+}
+
+void init_statistics() {
+    int update_minutes = get_int_field("statistics.frequency");
+    if(!update_minutes) update_minutes = 2;
+    timeq_add(update_minutes * 60 + 10, 0, main_statistics, NULL);
+}
diff --git a/src/statistics.h b/src/statistics.h
new file mode 100644 (file)
index 0000000..79f7f4d
--- /dev/null
@@ -0,0 +1,25 @@
+/* statistics.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _statistics_h
+#define _statistics_h
+
+#include "main.h"
+
+int statistics_update();
+void init_statistics();
+
+#endif
diff --git a/src/timeq.c b/src/timeq.c
new file mode 100644 (file)
index 0000000..ec9ea3e
--- /dev/null
@@ -0,0 +1,149 @@
+/* timeq.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "timeq.h"
+#include "IOHandler.h"
+#include "tools.h"
+#include "log.h"
+
+static struct timeq_entry *timeq_events;
+#ifdef HAVE_THREADS
+static int pthread_mutex_initialized = 0;
+static pthread_mutex_t synchronized;
+#endif
+
+static IOHANDLER_CALLBACK(timeq_callback) {
+    struct timeq_entry *entry = event->iofd->data;
+    switch(event->type) {
+    case IOEVENT_TIMEOUT:
+        if(entry->name) {
+            free(entry->name);
+            entry->name = NULL;
+        }
+        entry->callback(entry->data);
+        entry->iofd = NULL;
+        timeq_del(entry);
+        break;
+    default:
+        break;
+    }
+}
+
+struct timeq_entry* timeq_add(int seconds, int module_id, timeq_callback_t *callback, void *data) {
+    return timeq_uadd(seconds * 1000, module_id, callback, data);
+}
+
+struct timeq_entry* timeq_uadd(int useconds, int module_id, timeq_callback_t *callback, void *data) {
+    struct timeval timeout;
+    struct timeq_entry *entry = malloc(sizeof(*entry));
+    if (!entry)
+    {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    #ifdef HAVE_THREADS
+    if(!pthread_mutex_initialized) {
+        THREAD_MUTEX_INIT(synchronized);
+        pthread_mutex_initialized = 1;
+    }
+    #endif
+    gettimeofday(&timeout, NULL);
+    SYNCHRONIZE(synchronized);
+    timeout.tv_usec += (useconds % 1000);
+    timeout.tv_sec += (useconds / 1000);
+    entry->iofd = iohandler_timer(timeout, timeq_callback);
+    entry->iofd->data = entry;
+    entry->module_id = module_id;
+    entry->callback = callback;
+    entry->data = data;
+    entry->name = NULL;
+    entry->next = timeq_events;
+    entry->prev = NULL;
+    if(timeq_events)
+        timeq_events->prev = entry;
+    timeq_events = entry;
+    DESYNCHRONIZE(synchronized);
+    return entry;
+}
+
+struct timeq_entry* timeq_add_name(char *name, int seconds, int module_id, timeq_callback_t *callback, void *data) {
+    return timeq_uadd_name(name, seconds * 1000, module_id, callback, data);
+}
+
+struct timeq_entry* timeq_uadd_name(char *name, int useconds, int module_id, timeq_callback_t *callback, void *data) {
+    struct timeq_entry *entry = timeq_uadd(useconds, module_id, callback, data);
+    entry->name = strdup(name);
+    return entry;
+}
+
+int timeq_del(struct timeq_entry* entry) {
+    #ifdef HAVE_THREADS
+    if(!pthread_mutex_initialized) return 0;
+    #endif
+    SYNCHRONIZE(synchronized);
+    if(entry->next)
+        entry->next->prev = entry->prev;
+    if(entry->prev)
+        entry->prev->next = entry->next;
+    else
+        timeq_events = entry->next;
+    if(entry->name)
+        free(entry->name);
+    if(entry->iofd)
+        iohandler_close(entry->iofd);
+    free(entry);
+    DESYNCHRONIZE(synchronized);
+    return 1;
+}
+
+int timeq_del_name(char *name) {
+    SYNCHRONIZE(synchronized);
+    struct timeq_entry *entry;
+    int removed = 0;
+    for(entry = timeq_events; entry; entry = entry->next) {
+        if(entry->name && !stricmp(entry->name, name)) {
+            removed = timeq_del(entry);
+            break;
+        }
+    }
+    DESYNCHRONIZE(synchronized);
+    return removed;
+}
+
+int timeq_name_exists(char *name) {
+    SYNCHRONIZE(synchronized);
+    struct timeq_entry *centry;
+    for(centry = timeq_events; centry; centry = centry->next) {
+        if(centry->name && !stricmp(centry->name, name)) {
+            DESYNCHRONIZE(synchronized);
+            return 1;
+        }
+    }
+    DESYNCHRONIZE(synchronized);
+    return 0;
+}
+
+void unregister_module_timers(int module_id) {
+    SYNCHRONIZE(synchronized);
+    struct timeq_entry *entry, *next_entry;
+    for(entry = timeq_events; entry; entry = next_entry) {
+        next_entry = entry->next;
+        if(entry->module_id == module_id)
+            timeq_del(entry);
+    }
+    DESYNCHRONIZE(synchronized);
+}
diff --git a/src/timeq.h b/src/timeq.h
new file mode 100644 (file)
index 0000000..6f5546e
--- /dev/null
@@ -0,0 +1,47 @@
+/* timeq.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _timeq_h
+#define _timeq_h
+
+#include "main.h"
+
+#define TIMEQ_CALLBACK(NAME) void NAME(UNUSED_ARG(void *data))
+typedef TIMEQ_CALLBACK(timeq_callback_t);
+
+struct IODescriptor;
+
+struct timeq_entry {
+    struct IODescriptor *iofd;
+    char *name;
+    int module_id;
+    timeq_callback_t *callback;
+    void *data;
+    
+    struct timeq_entry *prev, *next;
+};
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ struct timeq_entry* timeq_add(int seconds, int module_id, timeq_callback_t *callback, void *data);
+/* MODULAR ACCESSIBLE */ struct timeq_entry* timeq_uadd(int useconds, int module_id, timeq_callback_t *callback, void *data);
+/* MODULAR ACCESSIBLE */ struct timeq_entry* timeq_add_name(char *name, int seconds, int module_id, timeq_callback_t *callback, void *data);
+/* MODULAR ACCESSIBLE */ struct timeq_entry* timeq_uadd_name(char *name, int useconds, int module_id, timeq_callback_t *callback, void *data);
+/* MODULAR ACCESSIBLE */ int timeq_del(struct timeq_entry* entry);
+/* MODULAR ACCESSIBLE */ int timeq_del_name(char *name);
+/* MODULAR ACCESSIBLE */ int timeq_name_exists(char *name);
+void unregister_module_timers(int module_id);
+#endif
+#endif
diff --git a/src/tools.c b/src/tools.c
new file mode 100644 (file)
index 0000000..0232671
--- /dev/null
@@ -0,0 +1,615 @@
+/* tools.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "tools.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "lang.h"
+#include "ClientSocket.h"
+#include "IPNode.h"
+#include "log.h"
+
+static const struct default_language_entry msgtab[] = {
+    {"TIME_MASK_2_ITEMS", "%s and %s"}, /* {ARGS: "2 days", "1 hour"} */
+    {"TIME_MASK_3_ITEMS", "%s, %s and %s"}, /* {ARGS: "2 days", "1 hour", "20 minutes"} */
+    {"TIME_YEAR", "year"},
+    {"TIME_YEARS", "years"},
+    {"TIME_MONTH", "month"},
+    {"TIME_MONTHS", "months"},
+    {"TIME_WEEK", "week"},
+    {"TIME_WEEKS", "weeks"},
+    {"TIME_DAY", "day"},
+    {"TIME_DAYS", "days"},
+    {"TIME_HOUR", "hour"},
+    {"TIME_HOURS", "hours"},
+    {"TIME_MINUTE", "minute"},
+    {"TIME_MINUTES", "minutes"},
+    {"TIME_SECOND", "second"},
+    {"TIME_SECONDS", "seconds"},
+    {NULL, NULL}
+};
+
+/* copied from IRCU 2.10.12 match.c */
+/*
+ * Compare if a given string (name) matches the given
+ * mask (which can contain wild cards: '*' - match any
+ * number of chars, '?' - match any single character.
+ *
+ * return  0, if match
+ *         1, if no match
+ *
+ *  Originally by Douglas A Lewis (dalewis@acsu.buffalo.edu)
+ *  Rewritten by Timothy Vogelsang (netski), net@astrolink.org
+ */
+int match(const char *mask, const char *name)
+{
+  const char *m = mask, *n = name;
+  const char *m_tmp = mask, *n_tmp = name;
+  int star_p;
+
+  for (;;) switch (*m) {
+  case '\0':
+    if (!*n)
+      return 0;
+  backtrack:
+    if (m_tmp == mask)
+      return 1;
+    m = m_tmp;
+    n = ++n_tmp;
+    if (*n == '\0')
+      return 1;
+    break;
+  case '\\':
+    m++;
+    /* allow escaping to force capitalization */
+    if (*m++ != *n++)
+      goto backtrack;
+    break;
+  case '*': case '?':
+    for (star_p = 0; ; m++) {
+      if (*m == '*')
+        star_p = 1;
+      else if (*m == '?') {
+        if (!*n++)
+          goto backtrack;
+      } else break;
+    }
+    if (star_p) {
+      if (!*m)
+        return 0;
+      else if (*m == '\\') {
+        m_tmp = ++m;
+        if (!*m)
+          return 1;
+        for (n_tmp = n; *n && *n != *m; n++) ;
+      } else {
+        m_tmp = m;
+        for (n_tmp = n; *n && tolower(*n) != tolower(*m); n++) ;
+      }
+    }
+    /* and fall through */
+  default:
+    if (!*n)
+      return *m != '\0';
+    if (tolower(*m) != tolower(*n))
+      goto backtrack;
+    m++;
+    n++;
+    break;
+  }
+}
+
+
+//TABLES
+struct Table *table_init(int width, int length, int flags) {
+    int row;
+    struct Table *table = malloc(sizeof(*table));
+    table->contents = malloc(length * sizeof(*table->contents));
+    for(row = 0; row < length; row++) {
+        table->contents[row] = calloc(width, sizeof(*table->contents[row]));
+    }
+    table->length = length;
+    table->width = width;
+    table->flags = flags;
+    table->col_flags = calloc(width, sizeof(int));
+    table->entrys = 0;
+    table->maxwidth = calloc(width, sizeof(int));
+    table->table_lines = NULL;
+    return table;
+}
+
+int table_add(struct Table *table, char **entry) {
+    int col;
+    if(table->entrys == table->length) return 0;
+    for(col = 0; col < table->width; col++) {
+        table->contents[table->entrys][col] = ((table->flags & TABLE_FLAG_USE_POINTER) || !entry[col] ? entry[col] : strdup(entry[col]));
+        if(table->contents[table->entrys][col])
+            table->col_flags[col] |= TABLE_FLAG_COL_CONTENTS;
+        if(entry[col] && strlen(entry[col]) > table->maxwidth[col])
+            table->maxwidth[col] = strlen(entry[col]);
+    }
+    table->entrys++;
+    return 1;
+}
+
+int table_change(struct Table *table, int row, char **entry) {
+    int col;
+    if(row >= table->length) return 0;
+    for(col = 0; col < table->width; col++) {
+        if(table->contents[row][col] && !(table->flags & TABLE_FLAG_USE_POINTER))
+            free(table->contents[row][col]);
+        table->contents[row][col] = ((table->flags & TABLE_FLAG_USE_POINTER) || !entry[col] ? entry[col] : strdup(entry[col]));
+        if(table->contents[row][col])
+            table->col_flags[col] |= TABLE_FLAG_COL_CONTENTS;
+        if(entry[col] && strlen(entry[col]) > table->maxwidth[col])
+            table->maxwidth[col] = strlen(entry[col]);
+    }
+    return 1;
+}
+
+int table_change_field(struct Table *table, int row, int col, char *entry) {
+    if(row >= table->length) return 0;
+    if(col >= table->width) return 0;
+    if(table->contents[row][col] && !(table->flags & TABLE_FLAG_USE_POINTER))
+        free(table->contents[row][col]);
+    table->contents[row][col] = (((table->flags & TABLE_FLAG_USE_POINTER) || !entry) ? entry : strdup(entry));
+    if(table->contents[row][col])
+        table->col_flags[col] |= TABLE_FLAG_COL_CONTENTS;
+    if(entry && strlen(entry) > table->maxwidth[col])
+        table->maxwidth[col] = strlen(entry);
+    return 1;
+}
+
+int table_set_bold(struct Table *table, int collum, int bold) {
+    if(bold)
+        table->col_flags[collum] |= TABLE_FLAG_COL_BOLD;
+    else
+        table->col_flags[collum] &= ~TABLE_FLAG_COL_BOLD;
+    return 1;
+}
+
+char **table_end(struct Table *table) {
+    int row, col, tablewidth = 0, pos,i,j,k;
+    if(!table->entrys) return NULL;
+    for(col = 0; col < table->width; col++) {
+        tablewidth += table->maxwidth[col]+1;
+        if(table->col_flags[col] & TABLE_FLAG_COL_BOLD)
+            tablewidth += 2;
+    }
+    table->table_lines = malloc(table->entrys * sizeof(table->table_lines));
+    for(row = 0; row < table->entrys; row++) {
+        table->table_lines[row] = malloc(tablewidth * sizeof(*table->table_lines[row]));
+        pos = 0;
+        for(col = 0; col < table->width; col++) {
+            if(!(table->col_flags[col] & TABLE_FLAG_COL_CONTENTS)) continue;
+            if(table->col_flags[col] & TABLE_FLAG_COL_BOLD)
+                table->table_lines[row][pos++] = '\002';
+            i = 0;
+            j = 0;
+            if(table->contents[row][col]) {
+                for(; i < strlen(table->contents[row][col]); i++) {
+                    table->table_lines[row][pos++] = table->contents[row][col][i];
+                    if(table->contents[row][col][i] == '\002') j++;
+                    else if(table->contents[row][col][i] == '\003') {
+                        j++;
+                        for(k = 1; k <= 2; k++) {
+                            if(isdigit(table->contents[row][col][i+k]))
+                                j++;
+                            else 
+                                break;
+                        }
+                    }
+                }
+            } else if(table->col_flags[col] & TABLE_FLAG_COL_SKIP_NULL)
+                continue;
+            i -= j;
+            if(col < table->width-1) {
+                for(;i < table->maxwidth[col]; i++) {
+                    table->table_lines[row][pos++] = ' ';
+                }
+                table->table_lines[row][pos++] = ' ';
+            } else
+                table->table_lines[row][pos++] = '\0';
+            
+            if(table->col_flags[col] & TABLE_FLAG_COL_BOLD)
+                table->table_lines[row][pos++] = '\002';
+        }
+    }
+    return table->table_lines;
+}
+
+void table_free(struct Table *table) {
+    int row, col;
+    for(row = 0; row < table->length; row++) {
+        if(!(table->flags & TABLE_FLAG_USE_POINTER) && table->entrys > row) {
+            for(col = 0; col < table->width; col++) {
+                if(table->contents[row][col])
+                    free(table->contents[row][col]);
+            }
+        }
+        free(table->contents[row]);
+    }
+    free(table->contents);
+    free(table->col_flags);
+    free(table->maxwidth);
+    if(table->table_lines) {
+        for(row = 0; row < table->entrys; row++) {
+            free(table->table_lines[row]);
+        }
+        free(table->table_lines);
+    }
+    free(table);
+}
+
+char* timeToStr(struct UserNode *user, int seconds, int items, char *buf) {
+    char item[items][MAXLEN];
+    int tmp, citem = 0;
+    if(citem != items && seconds >= 31536000) { //60*60*24*365 = 31536000
+        
+        tmp = seconds / 31536000;
+        sprintf(item[citem++], "%d %s", tmp, get_language_string(user, tmp == 1 ? "TIME_YEAR" : "TIME_YEARS"));
+        seconds -= tmp * 31536000;
+    }
+    if(citem != items && seconds >= 86400) { //60*60*24 = 86400
+        tmp = seconds / 86400;
+        sprintf(item[citem++], "%d %s", tmp, get_language_string(user, tmp == 1 ? "TIME_DAY" : "TIME_DAYS"));
+        seconds -= tmp * 86400;
+    }
+    if(citem != items && seconds >= 3600) { //60*60 = 3600
+        tmp = seconds / 3600;
+        sprintf(item[citem++], "%d %s", tmp, get_language_string(user, tmp == 1 ? "TIME_HOUR" : "TIME_HOURS"));
+        seconds -= tmp * 3600;
+    }
+    if(citem != items && seconds >= 60) {
+        tmp = seconds / 60;
+        sprintf(item[citem++], "%d %s", tmp, get_language_string(user, tmp == 1 ? "TIME_MINUTE" : "TIME_MINUTES"));
+        seconds -= tmp * 60;
+    }
+    if(citem != items && seconds >= 1) {
+        sprintf(item[citem++], "%d %s", seconds, get_language_string(user, seconds == 1 ? "TIME_SECOND" : "TIME_SECONDS"));
+    }
+    if(citem == 2) {
+        build_language_string(user, buf, "TIME_MASK_2_ITEMS", item[0], item[1]);
+    } else if(citem == 3) {
+        build_language_string(user, buf, "TIME_MASK_3_ITEMS", item[0], item[1], item[2]);
+    } else {
+        int i, ii, p = 0;
+        for(i = 0; i < citem; i++) {
+            for(ii = 0; ii < strlen(item[i]); ii++) {
+                buf[p++] = item[i][ii];
+            }
+            buf[p++] = ' ';
+        }
+        buf[(p ? p-1 : 0)] = '\0';
+    }
+    return buf;
+}
+
+int strToTime(struct UserNode *user, char *str) {
+    /*
+    * y = year = 365 days
+    * M = month = 30 days
+    * w = week = 7 days
+    * d = day
+    * h = hour
+    * m = minute
+    * (s) = second
+    */
+    int total_time = 0, cvalue;
+    char *p, tmpchar;
+    int unit_multiplikator;
+    while(*str) {
+        p = str;
+        while(*p && !isdigit(*p)) //skip leading chars
+            p++;
+        str = p;
+        while(*p && isdigit(*p)) //get the value
+            p++;
+        tmpchar = *p;
+        *p = '\0';
+        cvalue = isdigit(*str) ? atoi(str) : 0;
+        *p = tmpchar;
+        while(*p == ' ') //skip spaces
+            p++;
+        str = p;
+        while(*p && !isdigit(*p)) //get the unit
+            p++;
+        tmpchar = *p;
+        *p = '\0';
+        if(p - str > 1) { //unit has more than one char
+            if(!stricmp(str, "year") || !stricmp(str, "year") || !stricmp(str, get_language_string(user, "TIME_YEAR")) || !stricmp(str, get_language_string(user, "TIME_YEARS")))
+                unit_multiplikator = 31536000; //60*60*24*365 = 31536000
+            else if(!stricmp(str, "month") || !stricmp(str, "months") || !stricmp(str, get_language_string(user, "TIME_MONTH")) || !stricmp(str, get_language_string(user, "TIME_MONTHS")))
+                unit_multiplikator = 2592000; //60*60*24*30 = 2592000
+            else if(!stricmp(str, "week") || !stricmp(str, "weeks") || !stricmp(str, get_language_string(user, "TIME_WEEK")) || !stricmp(str, get_language_string(user, "TIME_WEEKS")))
+                unit_multiplikator = 604800; //60*60*24*7 = 604800
+            else if(!stricmp(str, "day") || !stricmp(str, "days") || !stricmp(str, get_language_string(user, "TIME_DAY")) || !stricmp(str, get_language_string(user, "TIME_DAYS")))
+                unit_multiplikator = 86400; //60*60*24 = 86400
+            else if(!stricmp(str, "hour") || !stricmp(str, "hours") || !stricmp(str, get_language_string(user, "TIME_HOUR")) || !stricmp(str, get_language_string(user, "TIME_HOURS")))
+                unit_multiplikator = 3600; //60*60 = 3600
+            else if(!stricmp(str, "minute") || !stricmp(str, "minutes") || !stricmp(str, "min") || !stricmp(str, "mins") || !stricmp(str, get_language_string(user, "TIME_MINUTE")) || !stricmp(str, get_language_string(user, "TIME_MINUTES")))
+                unit_multiplikator = 60;
+            else
+                unit_multiplikator = 1;
+        } else {
+            switch(*str) {
+                case 'y':
+                    unit_multiplikator = 31536000; //60*60*24*365 = 31536000
+                    break;
+                case 'M':
+                    unit_multiplikator = 2592000; //60*60*24*30 = 2592000
+                    break;
+                case 'w':
+                    unit_multiplikator = 604800; //60*60*24*7 = 604800
+                    break;
+                case 'd':
+                    unit_multiplikator = 86400; //60*60*24 = 86400
+                    break;
+                case 'h':
+                    unit_multiplikator = 3600; //60*60 = 3600
+                    break;
+                case 'm':
+                    unit_multiplikator = 60;
+                    break;
+                default:
+                    unit_multiplikator = 1;
+                    break;
+            }
+        }
+        total_time += (cvalue * unit_multiplikator);
+        *p = tmpchar;
+        str = p;
+    }
+    return total_time;
+}
+
+int getCurrentSecondsOfDay() {
+    time_t now = time(0);
+    struct tm *timeofday = localtime(&now);
+    int seconds = 0;
+    seconds += timeofday->tm_hour * 3600;
+    seconds += timeofday->tm_min * 60;
+    seconds += timeofday->tm_sec;
+    return seconds;
+}
+
+struct ModeBuffer* initModeBuffer(struct ClientSocket *client, struct ChanNode *chan) {
+    struct ModeBuffer *modeBuf = malloc(sizeof(*modeBuf));
+    if(!modeBuf) {
+        printf_log("main", LOG_ERROR, "%s:%d malloc() failed", __FILE__, __LINE__);
+        return NULL;
+    }
+    modeBuf->client = client;
+    modeBuf->chan = chan;
+    modeBuf->addCount = 0;
+    modeBuf->delCount = 0;
+    return modeBuf;
+}
+
+void modeBufferSet(struct ModeBuffer *modeBuf, int add, char mode, char *param) {
+    if(add) {
+        modeBuf->addModes[modeBuf->addCount] = mode;
+        modeBuf->addModesParams[modeBuf->addCount] = (param ? strdup(param) : NULL);
+        modeBuf->addCount++;
+        modeBuf->addModes[modeBuf->addCount] = '\0';
+    } else {
+        modeBuf->delModes[modeBuf->delCount] = mode;
+        modeBuf->delModesParams[modeBuf->delCount] = (param ? strdup(param) : NULL);
+        modeBuf->delCount++;
+        modeBuf->delModes[modeBuf->delCount] = '\0';
+    }
+    if(modeBuf->addCount + modeBuf->delCount == MAXMODES)
+        flushModeBuffer(modeBuf);
+}
+
+void flushModeBuffer(struct ModeBuffer *modeBuf) {
+    char modeStr[MAXMODES+3];
+    int modePos = 0;
+    char paramStr[MAXLEN];
+    *paramStr = '\0';
+    int paramPos = 0;
+    int i;
+    if(modeBuf->addCount) {
+        modeStr[modePos++] = '+';
+        for(i = 0; i < modeBuf->addCount; i++) {
+            modeStr[modePos++] = modeBuf->addModes[i];
+            if(modeBuf->addModesParams[i]) {
+                paramPos += sprintf(paramStr + paramPos, " %s", modeBuf->addModesParams[i]);
+                free(modeBuf->addModesParams[i]);
+                modeBuf->addModesParams[i] = NULL;
+            }
+        }
+        modeBuf->addCount = 0;
+    }
+    if(modeBuf->delCount) {
+        modeStr[modePos++] = '-';
+        for(i = 0; i < modeBuf->delCount; i++) {
+            modeStr[modePos++] = modeBuf->delModes[i];
+            if(modeBuf->delModesParams[i]) {
+                paramPos += sprintf(paramStr + paramPos, " %s", modeBuf->delModesParams[i]);
+                free(modeBuf->delModesParams[i]);
+                modeBuf->delModesParams[i] = NULL;
+            }
+        }
+        modeBuf->delCount = 0;
+    }
+    modeStr[modePos++] = '\0';
+    putsock(modeBuf->client, "MODE %s %s%s", modeBuf->chan->name, modeStr, paramStr);
+}
+
+void freeModeBuffer(struct ModeBuffer *modeBuf) {
+    if(modeBuf->addCount + modeBuf->delCount)
+        flushModeBuffer(modeBuf);
+    free(modeBuf);
+}
+
+int is_ircmask(const char *text) {
+    while (*text && (isalnum((char)*text) || strchr("-_[]|\\`^{}?*", *text)))
+        text++;
+    if (*text++ != '!')
+        return 0;
+    while (*text && *text != '@' && !isspace((char)*text))
+        text++;
+    if (*text++ != '@')
+        return 0;
+    while (*text && !isspace((char)*text))
+        text++;
+    return !*text;
+}
+
+char* generate_banmask(struct UserNode *user, char *buffer) {
+    char *userhost = user->host;
+    
+    if(isFakeHost(user->host)) {
+        sprintf(buffer, "*!*@%s", userhost);
+        return buffer;
+    }
+    
+    //check if the hostname has more than 4 connections (trusted host)
+    if(countUsersWithHost(userhost) > 4) {
+        sprintf(buffer, "*!%s@%s", user->ident, userhost);
+        return buffer;
+    } else {
+        sprintf(buffer, "*!*@%s", userhost);
+        return buffer;
+    }
+}
+
+char* make_banmask(char *input, char* buffer) {
+    char *nick = NULL, *ident = NULL, *host = NULL;
+    char tmp[HOSTLEN];
+    char *p;
+    if((p = strstr(input, "!"))) {
+        nick = input;
+        *p = '\0';
+        ident = p+1;
+        if((p = strstr(ident, "@"))) {
+            *p = '\0';
+            host = p+1;
+        }
+    } else if((p = strstr(input, "@"))) {
+        ident = input;
+        *p = '\0';
+        host = p+1;
+    } else if((p = strstr(input, ".")) || (p = strstr(input, ":"))) {
+        host = input;
+    } else if(*input == '*' && input[1] != '\0' && !strstr(input+1, "*")) {
+        //AUTH MASK
+        p = getAuthFakehost(input+1);
+        if(p)
+            host = p;
+        else {
+            sprintf(tmp, "%s.*", input+1);
+            host = tmp;
+        }
+    } else {
+        struct UserNode *user = searchUserByNick(input);
+        if(user)
+            return generate_banmask(user, buffer);
+        else
+            nick = input;
+    }
+    if(nick && *nick == '\0') nick = NULL;
+    if(ident && *ident == '\0') ident = NULL;
+    if(host && *host == '\0') host = NULL;
+    sprintf(buffer, "%s!%s@%s", (nick ? nick : "*"), (ident ? ident : "*"), (host ? host : "*"));
+    return buffer;
+}
+
+int isFakeHost(char *host) {
+    char *p1, *p2 = host;
+    
+    //find the last dot to identify if the hostmask is a fake host
+    while((p1 = strchr(p2, '.'))) {
+        p2 = p1 + 1;
+    }
+    //TLD database: http://www.iana.org/domains/root/db/
+    //the longest TLD i found was 6 chars long (ignoring the stange exotic ones :D)
+    //but we even ignore '.museum' and '.travel' so we can say that the TLD of our mask needs to be less than 4 chars to be a real domain
+    return (strlen(p2+1) > 4);
+}
+
+int mask_match(char *mask, struct UserNode *user) {
+    char usermask[NICKLEN+USERLEN+HOSTLEN+3];
+    char matchmask[strlen(mask)+3];
+    strcpy(matchmask, mask);
+    char *host = strchr(mask, '@');
+    if(host) {
+        struct IPNode *ip = createIPNode(host);
+        int bits = (ip->flags & IPNODE_IS_IPV6 ? 128 : 32);
+        if((host = strchr(host, '/'))) {
+            bits = atoi(host+1);
+        }
+        if(ip && user->ip&& !ipmatch(user->ip, ip, bits)) {
+            host[1] = '*';
+            host[2] = '\0';
+        }
+    }
+    sprintf(usermask, "%s!%s@%s", user->nick, user->ident, user->host);
+    return match(matchmask, usermask);
+}
+
+static unsigned long crc_table[256];
+
+static void crc32_init() {
+    unsigned long crc;
+    int i, j;
+    for(i = 0; i < 256; i++) {
+        crc = i;
+        for(j = 8; j > 0; j--) {
+            if(crc & 1)
+                               crc = (crc >> 1) ^ 0xEDB88320L;
+            else
+                crc >>= 1;
+        }
+        crc_table[i] = crc;
+    }
+}
+
+unsigned long crc32(const char *text) {
+    register unsigned long crc = 0xFFFFFFFF;
+    unsigned int c, i = 0;
+    while((c = (unsigned int)text[i++]) != 0)
+        crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF];
+    return (crc^0xFFFFFFFF);
+}
+
+int stricmp (const char *s1, const char *s2) {
+    return stricmplen(s1, s2, -1);
+}
+
+int stricmplen(const char *s1, const char *s2, int len) {
+    if (s1 == NULL) 
+        return (s2 == NULL ? 0 : -(*s2));
+    if (s2 == NULL) 
+        return *s1;
+    char c1, c2;
+    int i = 0;
+    while ((c1 = tolower(*s1)) == (c2 = tolower(*s2))) {
+        if (*s1 == '\0') 
+            break;
+        i++;
+        s1++; 
+        s2++;
+        if(len != -1 && i == len) break;
+    }
+    return c1 - c2;
+}
+
+void init_tools() {
+    register_default_language_table(msgtab);
+    crc32_init();
+}
diff --git a/src/tools.h b/src/tools.h
new file mode 100644 (file)
index 0000000..bab14cb
--- /dev/null
@@ -0,0 +1,101 @@
+/* tools.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _tools_h
+#define _tools_h
+
+#include "main.h"
+
+#define TABLE_FLAG_USE_POINTER   0x01
+#define TABLE_FLAG_COL_BOLD      0x02
+#define TABLE_FLAG_COL_CONTENTS  0x04
+#define TABLE_FLAG_COL_SKIP_NULL 0x08
+
+struct ClientSocket;
+struct UserNode;
+struct ChanNode;
+
+struct Table {
+    char ***contents;
+    int length;
+    int width;
+    int flags;
+    int *col_flags;
+    
+    int entrys;
+    int *maxwidth;
+    
+    char **table_lines; //we just store this to free it in table_free
+};
+
+struct ModeBuffer {
+    struct ClientSocket *client;
+    struct ChanNode *chan;
+    char addModes[MAXMODES];
+    char delModes[MAXMODES];
+    char *addModesParams[MAXMODES];
+    char *delModesParams[MAXMODES];
+    int addCount;
+    int delCount;
+};
+
+#define modeBufferSimpleMode(MODEBUF,ADD,MODE) modeBufferSet(MODEBUF, ADD, MODE, NULL) 
+#define modeBufferOp(MODEBUF,USER) modeBufferSet(MODEBUF, 1, 'o', USER)
+#define modeBufferDeop(MODEBUF,USER) modeBufferSet(MODEBUF, 0, 'o', USER) 
+#define modeBufferHalfop(MODEBUF,USER) modeBufferSet(MODEBUF, 1, 'h', USER)
+#define modeBufferDehalfop(MODEBUF,USER) modeBufferSet(MODEBUF, 0, 'h', USER) 
+#define modeBufferVoice(MODEBUF,USER) modeBufferSet(MODEBUF, 1, 'v', USER)
+#define modeBufferDevoice(MODEBUF,USER) modeBufferSet(MODEBUF, 0, 'v', USER) 
+#define modeBufferBan(MODEBUF,MASK) modeBufferSet(MODEBUF, 1, 'b', MASK) 
+#define modeBufferUnban(MODEBUF,MASK) modeBufferSet(MODEBUF, 0, 'b', MASK) 
+
+#ifndef DND_FUNCTIONS
+/* MODULAR ACCESSIBLE */ int match(const char *mask, const char *name);
+
+/* MODULAR ACCESSIBLE */ struct Table *table_init(int width, int length, int flags);
+/* MODULAR ACCESSIBLE */ int table_add(struct Table *table, char **entry);
+/* MODULAR ACCESSIBLE */ int table_change(struct Table *table, int row, char **entry);
+/* MODULAR ACCESSIBLE */ int table_change_field(struct Table *table, int row, int col, char *entry);
+/* MODULAR ACCESSIBLE */ int table_set_bold(struct Table *table, int collum, int bold);
+/* MODULAR ACCESSIBLE */ char **table_end(struct Table *table);
+/* MODULAR ACCESSIBLE */ void table_free(struct Table *table);
+
+/* MODULAR ACCESSIBLE */ char* timeToStr(struct UserNode *user, int seconds, int items, char *buf);
+/* MODULAR ACCESSIBLE */ int strToTime(struct UserNode *user, char *str);
+/* MODULAR ACCESSIBLE */ int getCurrentSecondsOfDay();
+
+/* MODULAR ACCESSIBLE */ struct ModeBuffer* initModeBuffer(struct ClientSocket *client, struct ChanNode *chan);
+/* MODULAR ACCESSIBLE */ void modeBufferSet(struct ModeBuffer *modeBuf, int add, char mode, char *param);
+/* MODULAR ACCESSIBLE */ void flushModeBuffer(struct ModeBuffer *modeBuf);
+/* MODULAR ACCESSIBLE */ void freeModeBuffer(struct ModeBuffer *modeBuf);
+
+/* MODULAR ACCESSIBLE */ int is_ircmask(const char *text);
+
+/* MODULAR ACCESSIBLE */ char* generate_banmask(struct UserNode *user, char *buffer); 
+/* MODULAR ACCESSIBLE */ char* make_banmask(char *input, char* buffer);
+/* MODULAR ACCESSIBLE */ int isFakeHost(char *host);
+
+/* MODULAR ACCESSIBLE */ int mask_match(char *mask, struct UserNode *user);
+
+/* MODULAR ACCESSIBLE */ unsigned long crc32(const char *text);
+
+/* MODULAR ACCESSIBLE */ int stricmp (const char *s1, const char *s2);
+/* MODULAR ACCESSIBLE */ int stricmplen (const char *s1, const char *s2, int len);
+
+void init_tools();
+
+#endif
+#endif
diff --git a/src/version.h b/src/version.h
new file mode 100644 (file)
index 0000000..ec8c207
--- /dev/null
@@ -0,0 +1,41 @@
+/* version.h - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#ifndef _version_h
+#define _version_h
+
+#include "main.h"
+
+#define MODULE_VERSION 7
+
+#ifndef DND_FUNCTIONS
+extern const char *compilation;
+extern const char *creation;
+extern const char *revision;
+extern const char *codelines;
+extern const int revision_master;
+extern const char *dev_revision;
+extern const int patchlevel;
+
+const char *get_compilation();
+const char *get_creation();
+const char *get_revision();
+const int is_stable_revision();
+const char *get_dev_revision();
+const char *get_codelines();
+const int get_patchlevel();
+#endif
+#endif
diff --git a/src/version.sh b/src/version.sh
new file mode 100755 (executable)
index 0000000..1e64d41
--- /dev/null
@@ -0,0 +1,103 @@
+#! /bin/sh
+echo "Extracting version.c ..."
+
+if test -r version.c
+then
+   compilation=`sed -n 's/^const char \*compilation = \"\(.*\)\";/\1/p' < version.c`
+   if test ! "$compilation" ; then compilation=0; fi
+else
+   compilation=0
+fi
+
+compilation=`expr $compilation + 1`
+
+creation=`date | \
+awk '{if (NF == 6) \
+        { print $1 " " $2 " " $3 " " $6 " at " $4 " " $5 } \
+else \
+        { print $1 " " $2 " " $3 " " $7 " at " $4 " " $5 " " $6 }}'`
+
+codelines=`find . -type f -regex '\./.*\.h' -or -regex '\./.*\.c' |xargs cat|wc -l`
+
+
+git_revision_id=`git rev-list -n 1 --pretty="format:%h" --header refs/heads/master | grep '^[0-9a-f]*$'`
+if test "x$git_revision_id" = "x" ; then
+  git_revision="0"
+  git_commitcount="0"
+  git_is_stable="1"
+  git_dev_rev=""
+else
+  git_commitcount=`git rev-list --oneline --first-parent refs/heads/master | wc -l | sed "s/[ \t]//g"`
+  git_revision="git-$git_revision_id"
+  
+  git_real_revision_id=`git rev-list -n 1 --pretty="format:%h" --header HEAD | grep '^[0-9a-f]*$'`
+  if test "$git_revision_id" = "$git_real_revision_id" ; then
+    git_is_stable="1"
+    git_dev_rev=""
+  else
+    git_is_stable="0"
+    git_dev_rev="git-$git_real_revision_id"
+  fi
+fi
+
+
+/bin/cat > version.c <<!SUB!THIS!
+/* version.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+//Auto generated file!
+
+#include "version.h"
+
+const char *compilation = "$compilation";
+const char *creation = "$creation";
+const char *revision = "$git_revision";
+const char *codelines = "$codelines";
+const int revision_master = $git_is_stable;
+const char *dev_revision = "$git_dev_rev";
+
+const int patchlevel = ($git_commitcount ? ($git_commitcount - VERSION_PATCHLEVEL) : 0);
+
+const char *get_compilation() {
+    return compilation;
+}
+
+const char *get_creation() {
+    return creation;
+}
+
+const char *get_revision() {
+    return revision;
+}
+
+const char *get_codelines() {
+    return codelines;
+}
+
+const int is_stable_revision() {
+    return revision_master;
+}
+
+const char *get_dev_revision() {
+    return dev_revision;
+}
+
+const int get_patchlevel() {
+    return patchlevel;
+}
+
+!SUB!THIS!
+